String. То, чего ты мог и не знать - Примеры - Delphi - Каталог статей - Вирусология, взгляд из Delphi
Понедельник, 05.12.2016, 13:28 Приветствую вас Гость | Группа "Гости" 


Меню сайта

Категории раздела
Вирусология [39]
Статьи о вирусах
Системные [0]
Работа с системой
Примеры [44]
Приёмы, функции, процедуры
Ceти [1]
Работа с интернет
Приколы [5]
Пишем шуточные програмки
Остальное [5]
Всё что не вошло

Администрация
000000 dolphin

Помощь проекту

R106276538945
Z160640024212

Яндекс деньги
410011190732605

Недавние темы

Опрос
Пользуетесь ли Вы пиратской продукцией?
Всего ответов: 192

Главная » Статьи » Delphi » Примеры

String. То, чего ты мог и не знать
Ты наверное думаешь, что про строки уже и писать нечего. Тогда почитай, чего я для себя наконспектировал и наверняка что-нибудь новое для себя узнаешь. Думаю статейка будет полезна не только начинающим программистам. 

Прежде всего давай четко определимся, что такое тип String в Delphi. В зависимости от директив компилятора тип String может интерпретироваться как ShortString или AnsiString. 



- {$H+} или {$LongStrings On} String = AnsiString. По умолчанию. 
- {$H-} или {$LongStrings Off} String = ShortString. 
- Можно управлять из окна настроек проекта – “Compiler” -> “Huge strings” 
- Если при определении типа String указана длина строки: String[32], то вне зависимости от установок компилятора это будет означать объявление ShortString соответствующего размера. 



ShortString - короткая строка 
- sstr: ShortString; - это обычная паскалевская строка, то есть массив (цепочка) символов с зарезервированным в начале байтом для длины строки. 
- Соответственно максимальная длина короткой строки = 0..255. 
- Может объявляться так: sstr: String[макс.длина строки];. 
- Если сослаться на нулевой элемент: sstr [0], то получишь длину строки в виде Char, но лучше использовать для этого Length(sstr)(она делает тоже самое). Ord(sstr[0]) = Length(sstr) 
- При определении адреса короткой строки (с помощью @sstr или Addr(sstr)) возвращается указатель на байт длины. 
- Индексы символов (sstr [ i]) начинается с 1. 
- Значение в байте длины может быть меньше, чем размер строковой переменной : Byte(sstr[0]) ‹= SizeOf(sstr). То есть, хотя длина строки может и меняться, память, занимаемая ShortString, всегда равна 256 байтам. 
- Короткие строки удобны для записи текстовой информации в файл (т.к. они фиксированной длины). 

Чтобы преобразовать ShortString в PChar надо ручками добавить в конец строки терминальный нуль #0 и вернуть адрес первого символа : 

function ShortStringToPChar (sstr: ShortString): PChar; 
begin  
sstr := sstr + #0;  
Result := @sstr[1]; 
end; 



AnsiString - длинная строка 
- astr: AnsiString; - это длинная строка состоящая из символов AnsiChar (тоже, что и Char, пока). Этот тип принят по умолчанию, то есть если сделать определение: var astr: String; - то astr определится как AnsiString. 

AnsiString можно представить в виде записи: 
type // Это описательное определение, 
TAnsiString = record // не предназначенное для компиляции !!!  
RefCount: LongWord; //Счетчик ссылок  
Length: LongWord; // Длина строки  
Data: array[1..Length+1] of AnsiChar; //Массив символов, нумерация с единицы 
end; 



Так что AnsiString - это указатель на запись, только ссылается он не на начало записи, а на начало поля Data. Ты можешь это проверить сам (Приложение 1). Объяви заголовок строки вот так: 

type 
 PStrRec = ^StrRec; 
 StrRec = packed record // Заголовок строки 8 байт 
  refCnt: Longint; // 4 байта – счетчик ссылок 
  length: Longint; // 4 байта – длина строки 
 end; 

В коде объяви строку и указатель на структуру заголовка: 
var 
 S: String; 
 P: PStrRec; 

Получи указатель на заголовок строки S: 

P := Pointer(Integer(S) - ; 

Здесь ты получил указатель на строку S и отступил от начала строки на 8 байт (длину заголовка StrRec). Теперь ты можешь смотреть значение счетчика, получать длину строки и даже менять их (но это не рекомендуется) 

Memo1.Lines.Add('refCnt = ' + IntToStr(P.refCnt)); // Счетчик ссылок 
Memo1.Lines.Add('length = ' + IntToStr(P.length)); // Длина строки 

- Получить символ по его номеру (индексу) можно, обращаясь со строкой, как с динамическим массивом: astr[ i]. 

Delphi проверяет: попадает ли индекс в границы диапазона, как и с динамическими массивами (если включена проверка диапазона {$R+}). Но пустая длинная строка представлена нулевым указателем. Поэтому проверка границ пустой строки (при обращении к символу строки по индексу) приводит к ошибке доступа вместо ошибки выхода за границы диапазона. 




По умолчанию проверка диапазона выключена ({$R-} или {$RangeChecks Off}), но лучше всегда ее включать, т.к. она помогает отловить многие ошибки, а в релизе сделает прогу менее чувствительной к BufferOverflow-атаке (т.н. строковое и массивное переполнение). По этой же причине всегда включай {$O+} или {$OverflowCheks On}. Выключай их только при серьезной проблеме с производительностью и только в критичных участках кода. 



- Длина строки (Length) может изменяться с помощью функции SetLength. На настоящий момент максимальная длина длинной строки = 2 Гб (т.к. размер длины строки - 4 байта). Минимальная длина – 4 байта (пустая строка) Функция SizeOf(astr) возвратит 4 байта при любой длине строки, т.е. возвращается размер указателя. 
- В конец строки автоматически записывается терминальный нуль #0 (но он не включается в общую длину строки). Поэтому строку легко преобразовать в тип PChar: PChar(astr). С короткой строкой такое преобразование не получится, потому что у нее в конце терминального нуля нет ! 
- AnsiString поддерживает многобайтовые строки. В отличие от PChar в AnsiString могут быть любые символы, даже несколько терминальных нулей! Но некоторые строковые фукции думают, что терминальный нуль в строке только один и что он в конце (например SysUtils.AnsiPos). Учитывай это! 
- Счетчик ссылок RefCount используется в операциях присваивания и управляет жизненным циклом строки. Придуман для экономии памяти. Если мы копируем строку в другую переменную ( somestr := astr, то настоящего копирования памяти не происходит, а просто копируется указатель (AnsiString ведь указатель) и увеличивается на 1 счетчик ссылок. А вот если исходная строка изменяется (somestr := astr + 'A', то тогда создается новая уникальная запись со своим счетчиком ссылок. 
- Если тебе очень нужно создать именно уникальную строку, а не увеличить счетчик ссылок, то используй функцию: procedure UniqueString (var str: string). Она гарантирует, что строка str до этого больше нигде не использовалась и, изменяя эту строку, ты больше нигде не напортачишь. Например может потребоваться указатель на строку PChar при работе с API-функциями. Тогда создай уникальную строку, преобразуй ее в PChar и спокойно передавай в функцию не опасаясь побочных эффектов. 

Пример. Вывод русского текста в консольное окно. Это почему-то у многих вызывает трудности. 

procedure WriteRussianText(Msg :String); 
// вывод строки в консольное окно в OEM кодировке 
begin  
UniqueString(Msg); // получим уникальный экземпляр строки  
Windows.CharToOem(PChar(Msg),PChar(Msg)); // преобразуем его  
Write(Msg); // выведем текст на консоль 
end; 



- Компилятор сам управляет длинными строками, не доверяя это программисту, и вставляет вместо операций со строками свои процедуры. Память для длинной строки выделяется динамически. Компилятор почти всегда (про почти: см. в третьей статье - Переменная Result) инициализирует длинные строки: (примерно так) Pointer(astr) := nil; При выходе из области видимости (из процедуры, при разрушении объекта) компилятор вставляет процедуру финализации и освобождения динамической памяти примерно так: System._LStrClr(S); 

PChar - нультерминальная строка 
- pstr: PChar; - это нультерминальная строка (zero-terminated). Так называется, потому что представляет собой указатель на цепочку символов, заканчивающуюся терминальным нулем #0. Ее еще называют сишной строкой (из языка С, там она определяется как char*). 
- type PChar = ^Char; 
- Используется для вызова ANSI-версий API-функций (типа CreateFileA). VCL использует только ANSI-версии API-функций для совместимости со всеми версиями Windows, поэтому вызов CreateFile идентичен CreateFileA. В модуле SysUtils сделаны функции-оболочки для многих API-функций, в которые надо передавать String вместо PChar (все-таки PChar не родной паскалевкий тип). 
- Delphi хранит терминальный нуль в конце длинных и широких строк для удобства преобразования в PChar, PAnsiChar и PWideChar. 
- Можно рассматривать PChar, как указатель на array of Char. В этом случае индексы начинаются с нуля. Проверка на выход за границу массива не выполняется! Подпрограмма, сканирующая строку, ищет только #0. 
- При приведении AnsiString к PChar надо помнить, что Delphi автоматически уничтожает строку, когда она больше не нужна (т.е. когда счетчик ссылок равен 0, например при выходе из процедуры) , и тогда в переменной PChar может оказаться некорректный указатель. Поэтому надо быть осторожным при сохранении указателя PChar для дальнейшего использования (pstr := PChar(astr) , а лучше делать это приведение только при передаче параметров в API-функцию. То же относится и к приведению WideString к PWideChar. Прочитай еще про UniqueString выше. 
- Операции с PChar проходят медленнее, чем операции с AnsiString, потому-что сначала Delphi сканирует всю строку PChar, что определить ее длину, а уже потом производит с ней действия. 
- PChar автоматически преобразуется в AnsiString: astr := patr; , но эта операция проходит медленно. 

Чтобы избежать накладных расходов можно использовать: 
procedure SetString(var Str: string; Buffer: PChar; Length: Integer); устанавливает длину Str равной Length и копирует Length символов из Buffer в Str (если Str - короткая строка, то Length должна быть ‹256). Эта процедура используется в исходном коде многих строковых функций Delphi. 

PWideChar 
- PWideChar - скажем так, это "широкая" нультерминальная строка. 
- Для хранения символа используется 2 байта. Поддерживает стандарт Unicode. 
- На конце - терминальный нуль #0. 
- Может рассматриваться как указатель на array of WideChar. Нумерация начинается с нуля. Так же как и PChar - не контролирует выход за границы массива! 
- Используется для передачи параметров в Unicode-версии API-функций (типа CreateFileW), подпрограммы OLE и COM. 
- Создать строку PWideChar из String можно с помощью функции StringToOleStr. Только помни, что строка создается динамически и потом надо освободить память с помощью API-функции SysFreeString. 

procedure SomeProc(S: String); 
var  
OleStr: PWideChar; 
begin  
OleStr := StringToOleStr(S); // создаешь широкую строку  
try  
 CallSomeOLEProc(OleStr); // че-то там вызываешь  
finally  
 SysFreeString (OleStr); // Освобождаешь память  
end; 
end; 

WideString - широкая строка 
- wstr: WideString; - широкая строка. Хранит строку в формате Unicode, то есть использует для хранения символа 2 байта (16-битовые символы WideChar). 
- Первые 256 символов в Unicode (WideChar) и AnsiChar (Char) совпадают. 
- Также, как и AnsiString, WideString отслеживает свою длину, дописывает в конец #0 (может быть преобразована в PWideChar), но не содержит счетчика ссылок, поэтому любое присваивание приводит к копированию строки в памяти. 
- Delphi автоматически по мере надобности расширяет "узкие" строки и сужает "широкие". 
- При приведении WideString к AnsiString используется кодовая страница ANSI. Преобразование не-ANSI-символов (с индексом больше 255) происходит, как принято в Windows по умолчанию (то есть зависит от национальных настроек). При этом приведении в строке могут оказаться многобайтовые символы. Чтобы управлять процессом преобразования надо напрямую вызывать API-функцию WideCharToMultiByte. 

Многобайтовые строки - для сведения 
- Многобайтовая строка - это строка, в которой символ может занимать более 1 байта (в Windows используются 2 байта). Не надо путать многобайтовые строки с Unicode - это разные вещи, хотя они приводятся друг к другу. В Unicode символ всегда занимает 2 байта, а многобайтовой строке он может занимать 1 или 2 байта (во как!). 
- Нужны такие строки для некоторых национальных языков (японского, китайского), где используется больше 256 символов. 
- Байт в многобайтовой строке может быть: одинарным символом, ведущим байтом (первым байтом символа) и завершающим байтом (т.е. вторым байтом). При обработке таких строк надо учитывать этот момент, т.к. символ, выглядящий как 'A' может оказаться вторым байтом многобайтового символа. 
- Delphi не всегда корректно работает с многобайтовыми строками, и в этом случае нас опять спасает модуль SysUtils, где есть специальные функции. Для определения типа байта (по его индексу) в многобайтовой строке применяются функции SysUtils: ByteType и StrByteType: 

type TMbcsByteType = ( 
mbSingleByte,// Одиночный однобайтовый символ  
mbLeadByte, // Первый байт многобайтового символа  
mbTrailByte);// Второй байт многобайтового символа 

function ByteType (const S: String; Index: Integer): TMbcsByteType; 

- В реальной работе многобайтовая строка может получиться при приведении WideString (Unicode) к AnsiString (String): wstr := astr; 
- Если планируется программу (или компонент) продавать (да еще за бугром), то при встрече с многобайтовыми строками надо хотя бы корректно завершить работу (конечно лучше, чтобы прога их поддерживала). Встает вопрос: Как определить нужна ли поддержка многобайтовых строк? Ответ: В переменной var SysLocale: TSysLocale; хранится информация о региональных установках Windows по умолчанию и, если поддержка многобайтовых срок нужна, то SysLocale.FarEast = True. 
- Правильно обрабатывать такие строки особенно важно для имен файлов. Ты ведь собираешься со своей программой на мировой рынок выходить . 

На сладкое: resourcestring 

- Что ты делаешь, когда тебе надо в программу включить строковые ресурсы? Наверное создаешь файл с расширением mystringresource.rc, пишешь в него по специальным правилам свои строки, компилишь файл в res c помощью brcc32.exe, включаешь в свой экзешник директивой компилятора {$R mystringresource.res} и потом загружаешь из него строки с помошью API-функций. Скажи мне, а нужна тебе такая морока? Разработчики Delphi, как я постоянно убеждаюсь, далеко не дураки и всё уже для тебя придумали. 
- Всё что требуется от тебя - это объявить с своем модуле строковую константу вот так: 

resourcestring  
MyResString = 'Hello World'; 

- ВСЁ! Теперь твоя строка будет сохранена в строковом табличном ресурсе, под уникальным номером. Можешь обращаться с ней, как с обычной строковой константой. После компиляции проги можешь открыть ее ResHacker'ом или Restorator'ом и среди других строк увидишь свою. Учти, что номер(идентификатор) ресурса присваивается автоматически и может меняться от компиляции к компиляции. Так что особо на него не полагайся. 
- Компилятор заменяет строковую константу на вызов LoadResSring для загрузки ресурса во время выполнения программы. 
- Эти ресурсные строки очень полезны, если потом надо будет локализовать программу для другого языка. Поэтому как resourcestring надо объявлять все строковые констаты в программе: сообщения об ошибках и не только, хинты-подсказки и т.п. Тоже самое и даже в большей степени относится к разработке компонентов. 
- Delphi сам назначает номера строковым ресурсам, так что можешь не беспокоиться насчет конфликтов идентификаторов resourcestring из разных модулей. 
- Если ресурсная строка используется в качестве строки формата (например для функции SysUtils.Format), то обязательно включай в нее спецификаторы позиций (потом удобнее переводить будет, т.к. в другом языке и порядок слов другой): 

resourcestring  
ResErrorMsg = 'Ошибка: невозможно сделать %0:s для %1:s , потому-что %2:s'; 

- Адрес resourcestring - указатель типа PResStringRec, который ты можешь использовать для получения идентификатора ресурса во время работы программы: 

type  
PResStringRec = ^TResStringRec;  
TResStringRec = packed 
record  
 Module: ^Cardinal; // Модуль из которого загружен ресурс (чаще всего твой экзешник)  
 Identifier: Integer; // Идентификатор строкового ресурса  
end; 

- Получить номер строкового ресурса можно так: 

var 
ResID: Integer; 
ResID := PResStringRec(@MyResString).Indentifier; 

Успехов! Орехов Роман aka tripsin 


Приложение 1. 

unit Unit1; 
// Модуль для изучения AnsiString и его счетчика ссылок 
interface 

uses 
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
 Dialogs, StdCtrls; 

type 
 TForm1 = class(TForm) 
  Button1: TButton; 
  Memo1: TMemo; 
  procedure Button1Click(Sender: TObject); 
  // 1. Сначала попробуй процедуру без передачи параметра, потом 
  // раскомментируй параметр везде и проверь, как он передается, 
  // 2. при этом не забудь закомментировать строки обозначенные //*** 
  // 3. Напоследок попробуй передать параметр как (var S: String) 
  procedure GetStringInfo{(S: String)}; 
 end; 

var 
 Form1: TForm1; 
 S: String; 
implementation 

{$R *.dfm} 
procedure TForm1.GetStringInfo{(S: String)}; 
type 
 PStrRec = ^StrRec; 
 StrRec = packed record // Заголовок строки 8 байт 
  refCnt: Longint; // 4 байта 
  length: Longint; // 4 байта 
 end; 
var 
 S: String; //*** 
 P: PStrRec; 
 pp: ^Byte; 
begin 
 S := 'AAAAAAAAAA'; // Так S будет ссылаться на литерал //*** 
// S := S + 'a'; // Раскомментируешь - S будет в динамической памяти 
 P := Pointer(Integer(s) - ; // Смещаемся к началу заголовка 

 // Проверяем размер переменной S 
 Memo1.Lines.Add('SizeOf(S) = ' + IntToStr(SizeOf(S))); 
 // Проверяем размер заголовка 
 Memo1.Lines.Add('Sizeof(StrRec) = ' + IntToStr(Sizeof(StrRec))); 
 Memo1.Lines.Add('refCnt = ' + IntToStr(P.refCnt)); // Счетчик ссылок 
 Memo1.Lines.Add('length = ' + IntToStr(P.length)); // Длина строки 
 pp := Pointer(S); // Получаем указатель на строку 
 inc(pp, P.length); // Передвигаемся в конец строки ... 
 // ... и смотрим, что там 
 Memo1.Lines.Add('Байт в конце строки = ' + IntToStr(Integer(pp^))); 
 Memo1.Lines.Add('S = ' + s); // Выводим строку 
end; 

procedure TForm1.Button1Click(Sender: TObject); 
begin 
S := 'AAAAAAAAAAAAAAAA'; 
GetStringInfo{( S)}; 
end; 

end.

Категория: Примеры | Добавил: dolphin (11.07.2009)
Просмотров: 3693 | Рейтинг: 5.0/1

Всего комментариев: 0
avatar
Профиль


Логин:
Пароль:

Поиск

Наша кнопка

Вирусология, взгляд из Delphi



Статистика
HSDN :: Рейтинг сайтов WOlist.ru - каталог качественных сайтов Рунета Яндекс.Метрика Счетчик тИЦ и PR
Статистика материалов
Файлов: 364
Форум: 1128/7979
Коментариев: 759
Новостей: 27

Статистика пользователей
Всего: 409
За неделю: 2
Вчера: 0
Сегодня: 0
Всего онлайн: 3
Гостей: 3
Пользователей: 0

delfcode.ru © 2008 - 2016 Хостинг от uCoz