страничка tripsin'а
Главная |
Статьи |
Заметки |
Файлы|
Ссылки |
я
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) - 8);
Здесь ты получил указатель на строку 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 - нультерминальная строка
PWideChar
Многобайтовые строки - для сведения
- Многобайтовая строка - это строка, в которой символ может занимать более 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) - 8); // Смещаемся к началу заголовка
// Проверяем размер переменной 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.
Опубликована на
VR-online в 2006 г.