New Page 1
Строки в Delphi (Pchar, WideString,
TStream, поток)
Скачать исходники с примерами к уроку можно
здесь.
Для начала определим, какова сущность типа
String в Delphi. В зависимости от директив
компилятора это может быть либо ShortString, либо
AnsiString. по умолчанию в компиляторе
Delphi действует директива {$H+}
или
{$LongStrings On}.
В этом случае String У нас равносилен типу
AnsiString. Если действует директива
{$H-}
или
{$LongStrings Off}
то
String у нас становиться
ShortString.
Данной директивой можно управлять путем настроек опций проекта: "Project"
-> "Options"*
Длина строк настраивается на закладке "Compiler",
опция "Huge
strings"*:
Теперь чем отличается ShortString от
AnsiString
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. Функция Szieof, в
применении к строке всегда, независимо от длины строки возвратит 4 байта -
размер указателя. Именно поэтому
сроку в Delphi нельзя просто взять и сериализовать в
поток. в частности, такой код работать не будет:
procedure
TEasyGameContainer.Serialize(AStream:TStream);
var i,cn:integer; lClassName:string;
begin
inherited Serialize(AStream);
cn:=FObjects.Count-1;
AStream.Write(cn,sizeof(cn));
for i:=0 to cn do
begin
lClassName:=TEasyGameObject(FObjects[i]).ClassName;
AStream.Write(lClassName,sizeof(lClassName));
TEasyGameObject(FObjects[i]).Serialize(AStream);
end;
end;procedure
TEasyGameContainer.Unserialize(AStream:TStream);
var i,cn:integer; lClassName:string;
lClass:TPersistentClass;
lObject:TPersistent;
begin
inherited
Unserialize(AStream);
AStream.Read(cn,sizeof(cn));
for i:=0 to cn do
begin
AStream.Read(lClassName,sizeof(lClassName));
lClass:=FindClass(lClassName);
lObject:=lClass.Create;
TEasyGameObject(lObject).Unserialize(AStream);
AddGameObject(TEasyGameObject(lObject));
end;
end; |
так как при чтении
строки из потока будет прочитан "мусор". Как
правильно писать и читать строки из потока описано здесь.
Для того, что бы поэкспериментировать со строкой AnsiString,
создадим небольшой тестовый пример. Для начала в раздел тпиов внесем определение
структуры заголовка AnsiString:
PStrRec = ^StrRec;
StrRec = packed record //
Заголовок строки 8 байт
refCnt: Longint; // 4 байта -
счетчик ссылок
length: Longint; // 4 байта -
длина строки
end; |
Затем добавим строку ввода (TEdit), кнопочку (TButton)
и три метки (TLabel), эти компоненты можно найти на
закладке Standart*:
Теперь напишем
обработчик события OnClick кнопочки:
procedure
TStringsSampleForm.btnAnsiStrDemoClick(Sender: TObject);
var
S: String;
P: PStrRec;
pp: ^char;
begin
s:=edString.Text;
P:=Pointer(Integer(S)-8);
Label1.Caption:='refCnt
= ' + IntToStr(P.refCnt);
Label2.Caption:='length
= ' + IntToStr(P.length);
pp := Pointer(S);
inc(pp,2);
Label3.Caption:='Третий
символ '+pp^;
end; |
Вот что мы увидим,
запустив эту программу:
И так, пойдем
дальше. Обращаться к отдельным символам AnsiString
можно аналогично ShortString, как к элементам массива
- : astr[ i].
Delphi проверяет:
попадает ли индекс в границы диапазона, как и с динамическими массивами (если
включена проверка диапазона {$R+}). Но пустая длинная строка представлена
нулевым указателем. Поэтому проверка границ пустой строки (при обращении к
символу строки по индексу) приводит к ошибке доступа вместо ошибки выхода за
границы диапазона. По умолчанию проверка диапазона выключена ({$R-} или {$RangeChecks
Off}), но лучше всегда ее включать, т.к. она помогает отловить многие ошибки, а
в релизе сделает прогу менее чувствительной к BufferOverflow-атаке (т.н.
строковое и массивное переполнение). По этой же причине всегда следует включать
{$O+} или {$OverflowCheks On}. Выключать их рекомендуется только при серьезной
проблеме с производительностью и только в критичных участках кода.
Длина строки (Length)
может изменяться с помощью функции SetLength. На настоящий момент максимальная
длина длинной строки = 2 Гб (т.к. размер длины строки - 4 байта). Минимальная
длина – 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 и спокойно передавайте в функцию не опасаясь побочных эффектов.
Компилятор сам управляет длинными строками, не доверяя это программисту, и
вставляет вместо операций со строками свои процедуры. Память для длинной строки
выделяется динамически. Компилятор почти всегда инициализирует длинные строки:
(примерно так) Pointer(astr) := nil; При выходе из области видимости (из
процедуры, при разрушении объекта) компилятор вставляет процедуру финализации и
освобождения динамической памяти примерно так: System._LStrClr(S);
PChar - нультерминальная строка
-
pstr: PChar; - это
нультерминальная строка (zero-terminated, иначе еще называется срока с
завершающим нулем). Так называется, потому что представляет собой указатель на
цепочку символов, заканчивающуюся терминальным нулем #0. Ее еще называют сишной
строкой (из языка С, там она определяется как char*).
-
Определяется тип
PChar как 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.
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; |
Скриншоты, помеченные знаком * ,
являются цитатами и иллюстрациями
программного продукта "Delphi", авторское
право на который принадлежит "Borland Software
Corporation".
|