• Что бы вступить в ряды "Принятый кодер" Вам нужно:
    Написать 10 полезных сообщений или тем и Получить 10 симпатий.
    Для того кто не хочет терять время,может пожертвовать средства для поддержки сервеса, и вступить в ряды VIP на месяц, дополнительная информация в лс.

  • Пользаватели которые будут спамить, уходят в бан без предупреждения. Спам сообщения определяется администрацией и модератором.

  • Гость, Что бы Вы хотели увидеть на нашем Форуме? Изложить свои идеи и пожелания по улучшению форума Вы можете поделиться с нами здесь. ----> Перейдите сюда
  • Все пользователи не прошедшие проверку электронной почты будут заблокированы. Все вопросы с разблокировкой обращайтесь по адресу электронной почте : info@guardianelinks.com . Не пришло сообщение о проверке или о сбросе также сообщите нам.

String. То, Чего Ты Мог И Не Знать

Sascha Оффлайн

Sascha

Заместитель Администратора
Команда форума
Администратор
Регистрация
9 Май 2015
Сообщения
1,562
Баллы
155
Ты наверное думаешь, что про строки уже и писать нечего. Тогда почитай, чего я для себя наконспектировал и наверняка что-нибудь новое для себя узнаешь. Думаю статейка будет полезна не только начинающим программистам.

Прежде всего давай четко определимся, что такое тип 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;

Успехов!


Приложение 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.
 
Вверх Снизу