String — Тип Delphi

Содержание

Чем отличается тип String в Delphi2 и выше от аналогичного в Delphi1

B D2 и выше на самом деле используется тип LongString вместо String, а стаpый тип тепеpь обзывается ShortString (о чем, кстати, написано в help). Из того же help можно узнать, что указатель LongString указывает на nullterminated string и потому возможно обычное пpиведение типа LongString к PChar (о чем я и написал), котоpое сводится пpосто к смене вывески. Там же можно узнать, что длина стpоки хpанится в dword пеpед указателем. Есть также намек на то, что пpи пpисваивании дpугой стpоке инфоpмация не копиpуется, а увеличивается только счетчик ссылок. Более подpобную инфоpмацию можно почеpпнуть из system.pas:

От себя добавлю:

Сама пеpеменная LongString указывает на байт, непосpедственно следующий за этой пpоцедуpой, там же находится собственно значение стpоки. Значение » (пустая стpока) пpедставляется как указатель nil, кстати, поэтому сpавнение str=» это быстpая опеpация.

Тепеpь подpобнее о счетчике ссылок. Я уже говоpил, что пpи пpисваивании копиpования не пpоисходит, а только увеличивается счетчик. Когда он уменьшается? Hу, очевидно, когда в pезультате опеpации значение стpоки меняется, то для стаpого значения счетчик уменьшается. Это понятно. Более непонятно, когда освобождаются значения, на котоpые ссылаются поля некого класса. Это пpоисходит в System. TObject.FreeInstance пpи вызове _FinalizeRecord, а инфоpмация беpется из vtInitTable (кстати, здесь же очищаются Variant). Ещё более непонятно, когда освобождаются пеpеменые String, котоpые описаны как локальные в пpоцедуpах/функциях/методах. Здесь pаботает компилятоp, котоpые вставляет эти неявные опеpации в код этой функции.

Тепеpь о типе PString. Hа самом деле пеpеменные этого типа указывают на такие же значения, как и LongString, но для пеpеменных этого типа для всех опеpаций по созданию/копиpованию/удалению нужно помнить об этих самых счетчиках ссылок. Иногда без этого типа не обойтись. Вот опеpации для этого типа (sysutils.pas):

Можно отметить, что явно задать использование long strings можно декларацией

Хотелось бы также предупредить наиболее частные ошибки при использовании длинных строк:

  • Если Вы передаёте указатель PChar на буфер, взятый от длинной строки, в функцию, которая может изменить содержание буфера, то убедитесь, что на этот буфер указывает только одна строка. Это верно в случаях сложения строк, вызова UniqueString или SetLength и некоторых других;
  • Если Вы используете длинные строки как аргументы или результаты для функций, располагающихся в DLL, то в DLL надо использовать модуль ShareMem;
  • Не используйте длинные строки как члены структур типа record. Используйте там короткие строки или array[0..n] of char. Также нельзя использовать в структурах типа record динамические массивы. Данные ограничения отсутствуют для классов.

String — Тип Delphi

Строки в Delphi состоят из одоного (тип ShortString ) или двух байтов (тип AnsiString, WideString ), содержащих указание количества символов в строке, и последовательности символов. Таким образом ShortString максимально может содержать до 255 символов и занимать память 2 байта ..256 байт, а AnsiString или WideString — максимально могут содержать примерно 2 x 10 30 символов и занимать память 4 байта .. 2 Гбайта.

В типе AnsiString символы кодируются в коде ANSI, а в типе WideString — в коде Unicode.

Общим типом является тип String , который может соответствовать как типу ShortString , так и типу AnsiString . Это определяется директивой компилятора $H . По умолчанию используется <$H+>, и тип String равен типу AnsiString .

Кроме того имеется тип PChar , представляющий так называемую строку с завершающим нулем. Строки с завершающим нулем не содержат байтов длины. В отличие от обычных строк они состоят из последовательности ненулевых символов, за которым следует символ NULL (#0). Никаких ограничений на длину строк с завершающим нулем не накладывается. Фактически он указывает на символ

Расширенный синтаксис позволяет ставить в соответствие строкам с завершающим нулем символьный массив типа

где X — положительное число типа Integer , определяющее количество символов в строке, не считая завершающего символа с кодом 0. В отличие от типа String , символ с индексом 0 здесь является первым символом строки, а последний символ с индексом X — завершающим символом с кодом 0. (см. Функции работы со строками с завершающим нулем)

Список литературы:

  1. Гофман В.Э., Хомоненко А.Д. Delphi 6. — СПб. БХВ-Петербург, 2002. — 1152 с.: ил.
  2. Турбо Паскаль 7.0 — К. Торгово-издательское бюро BHV, 1996 — 448 с.: ил.
  3. Delphi7 Help

Какие строковые типы существуют в Delphi, и чем они отличаются друг от друга?

В Delphi 1.0 существовал лишь единственный строковый тип String, полностью эквивалентный одноименному типу в Turbo Pascal и Borland Pascal. Однако, этот тип имеет существенные ограничения. Для обхода этих ограничений, в Delphi 2, разработчики из Borland устроили небольшую революцию. Теперь, начиная с Delphi 2, имеются три фундаментальных строковых типа: ShortString, AnsiString, и WideString. Кроме того, тип String теперь стал логическим. Т.е., в зависимости от настройки соответствующего режима компилятора (режим больших строк), он приравнивается либо к типу ShortString (для совместимости со старыми программами), либо к типу AnsiString (по умолчанию). Управлять режимом, можно используя директиву компиляции <$LONGSTRINGS ON/OFF>(короткая форма <$H+/->) или из окна настроек проекта – вкладка «Compiler» -> галочка «Huge strings». Если режим включен, то String приравнивается к AnsiString, иначе String приравнивается ShortString. Из этого правила есть исключение: если в определении типа String указан максимальный размер строки, например String[25], то, вне зависимости от режима компилятора, этот тип будет приравнен к ShortString соответствующего размера.

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

Существуют различия между типами AnsiString и WideString. Эти типы имеют практически одинаковую реализацию, и отличаются лишь тем, что WideString используется для представления строк в кодировке UNICODE использующей 16-ти битное представление каждого символа (WideChar). Эта кодировка используется в тех случаях когда необходима возможность одновременного присутствия в одной строке символов из двух и более языков (помимо английского). Например, строк содержащих одновременно символы английского, русского и европейских языков. За эту возможность приходится платить – размер памяти, занимаемый такими строками в два раза больше размера, занимаемого обычными строками. Использование WideString встречается не часто, поэтому, я буду в основном рассказывать о строках типа AnsiString. Но, поскольку они имеют одинаковую реализацию, почти все сказанное относительно AnsiString будет действительно и для WideString, естественно с учетом разницы в размере каждого символа.

Тоже самое касается и разницы между pChar и pWideChar.

Строковый тип AnsiString, обычно используется для представления строк в кодировке ANSI, или других (например OEM) в которых для кодирования одного символа используется один байт (8 бит). Такой способ кодирования называется single-byte character set, или SBCS. Но, очень многие не знают о существовании еще одного способа кодирования многоязычных строк (помимо UNICODE) используемого в системах Windows и Linux. Этот способ называется multibyte character sets, или MBCS. При этом способе, некоторые символы представляются одним байтом, а некоторые, двумя и более. В отличие от UNICODE, строки, закодированные таким способом, требуют меньше памяти для своего хранения, но требуют более сложной обработки. Так вот, строковый тип AnsiString может использоваться для хранения таких строк.

Также существуют еще типы pChar (pWideChar) и array [. ] of Char, они очень часто используются в сочетании со строковыми типами.

Итак, основные характеристики строковых типов:

Тип Максимальный размер строки Размер переменной Объем памяти, требуемый для хранения строки
String[n] где 0

Есть ещё – оператор @ (получение указателя) для переменной такого типа возвращает значение типа pChar. Это очень удобно, поскольку переменные этого типа очень часто используются как буфер при работе с функциями Windows API. Например:

var a :array[0..20] of Char;. GetModuleFileName(GetModuleFileName(HInstance,@a,SizeOf(a));

Здесь, функция GetModuleFileName возвращает результат в массив a.

PChar

Этот тип широко используется в языках C и C++. В Delphi, это не фундаментальный тип, а производный. Его определение выглядит так:

Т.е. переменные этого типа являются указателем, поэтому и имеют размер 4 байта. Формально, значение pChar может указывать как на один символ, так и на строку символов. Однако, общепринято что значения pChar указывают на строки, завершающиеся символом с кодом 0 (#0). В DOSе, такие строки назывались ASCIIZ, но чаще можно встретить название null-terminated string. Наличие такого «концевика» позволяет легко определить реальный размер строки на которую указывает значение pChar.

Не смотря на то, что формально pChar это указатель на Char (^Char), как это часто бывает в Delphi, тип pChar имеет несколько особенностей по сравнению с другими указателями. Таких особенностей несколько.

Первая, заключается в том, что константы, и глобальные переменные этого типа могут быть проинициализированы строковой константой. Вот пример:

const pc :pChar =’abc’;var pv :pChar =’abc’;

Эти строки, определяют константу pc и переменную pv типа pChar. При этом, и pc и pv указывают на разные области памяти, но содержащие одинаковые значения, состоящие из трех символов: ‘a’, ‘b’, ‘c’, и символа #0. Завершающий символ с кодом 0 компилятор добавил автоматически.

Вторая особенность в том, что к переменным типа pChar применимо обращение как к массиву символов. Например, если есть приведенные выше определения, тогда:

C := pv^; // C будет присвоен символ ‘a’. Это обычное обращениеC := pv[0]; // необычно, но С станет равным ‘a’C := pv[1]; // С станет равным ‘b’C := pv[2]; // С станет равным ‘c’C := pv[3]; // С станет равным #0C := pv[4]; // ОШИБКА!

Символ с индексом 3 отсутствует в строке, однако, там есть завершающий ее символ с кодом 0. Именно он будет результатом pv[3]. О pv[4] тоже стоит сказать особо. Дело в том, что компилятор не даст ошибки при компиляции, поскольку на этапе компиляции он, в общем случае, не известен реальный размер строки, на которую указывает переменная pv. Однако, на этапе выполнения программы, такое обращение может вызвать ошибку нарушения доступа к памяти (Access Violation). А может и не вызвать, но результатом будет неопределённое значение. Все зависит от «расклада» в памяти. Поэтому, при таком способе обращения необходимо быть внимательным, и выполнять все необходимые проверки, исключающие выход за размеры строки.

Третья и последняя особенность типа pChar в том, что к значениям этого типа применима так называемая адресная арифметика. Тем, кто программирует на C и C++ она хорошо знакома. Суть её в том, что значения pChar можно увеличивать, уменьшать, вычитать, и складывать. Для демонстрации использования этой особенности, приведем пример реализации функции подсчитывающей длину строки, указатель на которую передается в качестве параметра.

function StringLength (p :pChar) :Cardinal;begin Result := 0; if p = nil then Exit; while p^ <> #0 do begin Inc(Result); Inc(p); end;end;

Здесь важно обратить внимание на два нюанса.

Первый, это проверка переданного указателя на nil. Такая проверка необходима, поскольку очень часто, для обозначения пустой строки используется значение nil.

Второй, это оператор Inc(p) – он «продвигает» указатель на следующий символ. Можно было бы записать его и так: p := p + 1.

Что бы продемонстрировать вычитание указателей pChar, приведем еще один вариант реализации той же функции:

function StringLength (p :pChar) :Cardinal;var pp :pChar;begin Result := 0; pp := p; if pp <> nil then while pp^ <> #0 do Inc(pp); Result := (pp-p);end;

Здесь, выражение pp-p дает «расстояние» между указателями, т.е. число символов между символом, на который указывает указатель p (начало строки) и символом, на который указывает указатель pp (завершающий строку #0).

ShortString, и String[n]

ShortString является частным случаем String[n], а если быть более точным, он полностью эквивалентен String[255].

Если посмотреть на таблицу, где приведены характеристики переменных этого типа, то мы увидим что их размер на один байт больше чем размер хранимой в них строки. Но в данном случае, это не для завершающего символа, как в pChar. Здесь в дополнительном байте хранится текущий размер строки. Кроме того, этот байт располагается не в конце строки, а наоборот, в ее начале. К примеру, если имеется следующее определение:

Оно означает, что для переменной s будет статически выделена область памяти размером 5 байт.

Теперь, выполнение оператора s := ‘abc’, приведёт к тому, что содержимое этих пяти байт станет следующим: байт 1 = 3, байт 2 = ‘a’, байт 3 = ‘b’, байт 4 = ‘c’, а значение байта 5 будет неопределённо – оно будет зависеть от «расклада» в памяти. Т.е., первый символ строки будет находиться во втором байте. Это неудобно, поэтому к символам строк ShortString принято индексироваться, начиная с 1. Следовательно:

s[1] = ‘a’ – первый символ строкиs[2] = ‘b’ – второй символ строкиs[3] = ‘c’ – третий символ строкиs[4] = все что угодно :).

А как же байт длины? Да все очень просто, к нему можно обратиться как s[0]. Только вот есть маленькая проблемка. Поскольку элементами строки являются символы, то и тип значения s[0] тоже будет символ. Т.е., если Вы хотите получить длину строки в виде целого числа, как это принято у нормальных людей, то надо выполнить соответствующее преобразование типа: Ord(s[0]) = 3 – размер строки.

Почему для типа String[n] существует ограничение 0 0, поэтому то Delphi и не освобождает память, занятую строкой.

Еще, этот счётчик используется и для разрешения проблем, связанных со следующей ситуацией:

procedure ShowInteger;var s1 :AnsiString; s2 : AnsiString; n :Integer;begin n := 123; s1 := ‘abc’+IntToStr(n); s2 := s1; s2[1] := ‘X’;end;

Здесь, как мы уже знаем, после выполнения оператора s2 := s1, обе переменные указывают на один и тот же экземпляр строки ‘abc123’. Однако, что же произойдёт когда выполниться оператор s2[1] := ‘X’? Казалось бы, в единственном имеющимся в нашем распоряжении экземпляре строки первая буква будет заменена на ‘X’. И как следствие, обе строки станут равными ‘Xbc123’. s1 то за что «страдает»? Но, к счастью это не так. Здесь на помощь Delphi вновь приходит счетчик ссылок. Delphi, при выполнении этого оператора понимает, что строка на которую указывает s2 будет изменена, а это может повлиять на других. Поэтому, перед изменением строки, проверяется ее счётчик ссылок. Обнаружив, что на нее ссылается более одной строковой переменной, делается следующее: создается копия этой строки со счётчиком ссылок равным 1, и адрес этой копии, присваивается s2; У исходного экземпляра строки, счетчик ссылок уменьшается на 1 – ведь s2 на неё теперь не ссылается. И лишь после этого, происходит изменение первой буквы, теперь уже собственного экземпляра строки. Т.е., по окончанию выполнения этого оператора, в памяти будут находиться две строки: ‘abc123’ и ‘Xbc123’. Причем, s1 будет ссылаться на первую, а s2 на вторую.

При работе со строками определенными как константы, алгоритм работы несколько отличается.

Пример:

procedure ShowInteger;var s :AnsiString;begin s := ‘Вася’; ShowMessage(s);end;

Казалось бы, при завершении работы процедуры, экземпляр строки ‘Вася’ должен быть уничтожен. Но в данном случае это не так. Ведь, при следующем входе в процедуру, для выполнения присваивания нужно будет вновь где-то взять строку ‘Вася’. Для этого, ещё при компиляции, Delphi размещает экземпляр строки ‘Вася’ в области констант программы, где её даже невозможно изменить, по крайней мере, простыми методами. Но как же при завершении процедуры определить что строка ‘Вася’ – константная строка, и ее нельзя уничтожать? Все очень просто. Для константных строк, счётчик ссылок устанавливается равным -1. Это значение, «выключает» нормальный алгоритм работы со «счётчиком ссылок». Он не увеличивается при присваивании, и не уменьшается при уничтожении переменной. Однако, при попытке изменения переменной (помните s2[1]:=’X’), значение счётчика равное -1 будет всегда считаться признаком того, что на строку ссылается более одной переменной (ведь он не равен 1). Поэтому, в такой ситуации всегда будет создаваться уникальный экземпляр строки, естественно, без декремента счётчика ссылок старой. Это защитит от изменений экземпляр строки-константы.

К сожалению, этот алгоритм срабатывает не всегда.

Где же Delphi хранит «счётчик ссылок»? Причем, для каждой строки свой! Естественно, вместе с самой строкой. Вот что представляет собой эта область памяти, хранящая экземпляр строки ‘abc’:

Байты с 1 по 4 Счётчик ссылок равный -1
Байты с 5 по 8 Длина строки равная 3
Байт 9 Символ ‘a’
Байт 10 Символ ‘b’
Байт 11 Символ ‘c’
Байт 12 Символ с кодом 0 (#0)

Для удобства работы с такой структурой, когда строковой переменной присваивается ссылка на эту строку, в переменную заносится адрес не начала этой структуры, а адрес её девятого байта. Т.е. адрес начала реальной строки (прямо как pChar). Для того, что бы приблизиться к реальной жизни, перепишем приведённую структуру:

Смещение Размер Значение Назначение
-8 -1 Счётчик ссылок
-4 Длина строки
‘a’
‘b’
‘c’
#0

С полем по смещению -8, нам уже должно быть все ясно. Это значение, хранящееся в двойном слове (4 байта), тот самый счетчик, который позволяет оптимизировать хранение одинаковых строк. Значение этого счетчика имеет тип Integer, т.е. может быть отрицательным. На самом деле, используется лишь одно отрицательное значение – «-1», и положительные значения. 0 не используется.

Теперь, обратим внимание на поле, лежащее по смещению -4. Это, четырёхбайтовое значение длинны строки (почти как в ShortString). Думаю, Вы заметили, что размер памяти выделенной под эту строку не имеет избыточности. Т.е. компилятор выделяет под строку минимально необходимое число байт памяти. Это конечно хорошо, но, при попытке «нарастить» строку: s1 := s1 + ‘d’, компилятору, точнее библиотеке времени исполнения (RTL) придется перераспределить память. Ведь теперь строке требуется больше памяти, аж на целый байт. Для перераспределения памяти нужно знать текущий размер строки. Вероятно, именно для того, что бы не приходилось каждый раз сканировать строку, определяя её размер, разработчики Delphi и включили поле длины, строки в эту структуру. Длина строки, хранится как значение Integer, отсюда и ограничение на максимальный размер таких строк – 2 Гбайт. Кстати, именно потому, что память под эти строки выделяется динамически, они и получили ещё одно свое название: динамические строки.

Ещё немного о нескольких особенностях переменных AnsiString. Важнейшей особенностью значений этого типа является возможность приведения их к типу Pointer. Это впрочем, естественно, ведь в «душе» они и есть указатели, как бы они этого не скрывали. Например, если описаны переменные: s :AnsiString и p :Pointer. То выполнение оператора p := Pointer(s) приведет к тому, что переменная p станет указывать на экземпляр строки. Однако, при этом, очень важно знать: счетчик ссылок этой строки не будет увеличен.

Поскольку, переменные этого типа реально являются указателями, то для них и реально такое значение как Nil – указатель в «никуда». Это значение в переменной типа AnsiString по смыслу приравнивается пустой строке. Более того, чтобы не тратить память и время на ведение счётчика ссылок, и поля размера строки всегда равного 0, при присваивании пустой строке переменной этого типа, реально, присваивается значение Nil. Это не очевидно, поскольку обычно не заметно, но как мы увидим позже, очень важная особенность.

Преобразование строк из одного типа в другой

Преобразование между «настоящими» строковыми типами String[n], ShortString, и AnsiString выполняются легко, и прозрачно. Никаких явных действий делать не надо, Delphi все сделает за Вас. Надо лишь понимать, что в маленькое большое не влезает. Например:

var s3 :String[3]; s :AnsiString;. s := ‘abcdef’; s3 := s;

В результате выполнения этого кода, в переменной s3 окажется строка ‘abc’, а не ‘abcdef’. С преобразованием из pChar в String[n], ShortString, и AnsiString, тоже всё очень не плохо. Просто присваивайте, и все будет нормально.

Сложности начинаются тогда, когда мы начинаем преобразовывать «настоящие» строковые типы в pChar. Непосредственное присваивание переменным типа pChar значений строк не допускается компилятором. На оператор p := s где p имеет тип pChar, а s :AnsiString, компилятор выдаст сообщение: «Incompatible types: ‘String’ and ‘PChar'» — несовместимые типы ‘String’ и ‘PChar’. Чтобы избежать такой ошибки, надо применять явное приведение типа: p := pChar(s). Так рекомендуют разработчики Delphi. В общем, они правы. Но, если вспомнить, как хранятся динамические строки — с нулем в конце, как и pChar. А еще и то, что к AnsiString применимо преобразование в тип Pointer. Станет очевидным, что всего, возможно целых три способа преобразования строки в pChar:

var s :AnsiString; p1,p2,p3 :PChar;. p1 := pChar(s); p2 := Pointer(s); p3 := @(s[1]);

Все они, синтаксически правильны. И кажется, что все три указателя (p1, p2 и p3) будут в результате иметь одно и то же значение. Но это не так. Всё зависит от того, что находится в s. Если быть более точным, равно ли значение s пустой строке, или нет:

s <> »p1 = p2 <> p3s = »p1 <> p2 = p3

Чтобы была понятна причина такого явления, опишем, как Delphi выполняет каждое из этих преобразований. Переменные AnsiString представляющие пустые строки, реально имеют значение Nil. Так вот:

Для выполнения преобразования pChar(s), компилятор генерит вызов специальной внутренней функции @LstrToPChar. Эта функция проверяет – если строковая переменная имеет значение Nil, то вместо него, она возвращает указатель на реально размещенную в памяти пустую строку. Т.е. pChar(s) никогда не вернет указатель равный Nil.

Тут все просто, такое преобразование просто возвращает содержимое строковой переменной. Т.е. если она при пустой строке содержит Nil, то и результатом преобразования будет Nil. Если же строка не пуста, то результатом будет адрес экземпляра строки.

Здесь, все совсем по-другому. Перед выполнением такого преобразования, компилятор вставляет код, обеспечивающий ситуацию, когда указатель, хранящийся в s, будет единственным указателем на экземпляр строки в памяти. В нашем примере, если строка не пуста, то будет создана копия исходной строки. Вот ее-то адрес и будет возвращен как результат такого преобразования. Но, если строка пуста, то результатом будет Nil, как и во втором случае.

Теперь, интересно отметить, что если в приведенном примере, преобразование p3 := @(s[1]) выполнить первым, то при не пустой строке в s, все указатели (p1, p2, и p3), будут равны. И содержать они будут адрес «персонального» экземпляра строки.

Вот, теперь, зная, как выполняются те или иные преобразования, Вы сможете всегда выбрать подходящий способ.

Приведем пример. В нем, преобразование, рекомендуемое разработчиками, приводит к «странному» поведению программы:

procedure X1;var s :AnsiString; p :PChar;begin s := ‘abcd’; p := PChar(s); p^ := ‘X’; // 0 then PChar(S)[0] := ‘q’; //.

вызовет ошибку нарушения доступа при передаче в нее строки сrefCnt = -1.

Чтобы получить уникальную ссылку для строки, состоящей из некоторой последовательности символов, можно воспользоваться функцией UniqueString.Это позволяет ускорить вычисления со строками, так как при этом можно будет сравнивать строки, просто сравнивая указатели на них. У таких строк refCntвсегда равен 1.

Примеры

Пример 1.

В символьной строке подсчитать количество цифр, предшествующих первому символу «!»

+
+
Начало
s
K:=0; I:=1;
I ’!’
S(i)>=’0’ and s(i) ’!’) Do

If (S[I]>=’0’) And (S[i] а, тт -> т ).

1.Имеется строка, содержащая буквы латинского алфавита и цифры. Вывести на экран длину наибольшей последовательности цифр, идущих подряд.

2. Дана строка, содержащая текст, заканчивающийся точкой. Вывести на экран слова, содержащие хотя бы одну букву о.

1. Дан набор слов, разделенных точкой с запятой (;). Набор заканчивается двоеточием (:). Определить, сколько в нем слов, заканчивающихся буквой а.

2. Дана строка, заканчивающаяся точкой. Подсчитать, сколько запятых в строке.

Последнее изменение этой страницы: 2020-07-16; Нарушение авторского права страницы

String Types in Delphi (Delphi For Beginners)

As with any programming language, in Delphi, variables are placeholders used to store values; they have names and data types. The data type of a variable determines how the bits representing those values are stored in the computer’s memory.

When we have a variable that will contain some array of characters, we can declare it to be of typeString.
Delphi provides a healthy assortment of string operators, functions and procedures. Before assigning a String data type to a variable, we need to thoroughly understand Delphi’s four string types.

Short String

Simply put, Short String is a counted array of (ANSII) characters, with up to 255 characters in the string. The first byte of this array stores the length of the string. Since this was the main string type in Delphi 1 (16 bit Delphi), the only reason to use Short String is for backward compatibility.
To create a ShortString type variable we use:

The s variable is a Short string variable capable of holding up to 256 characters, its memory is a statically allocated 256 bytes. Since this is usually wasteful — unlikely will your short string spread to the maximum length — second approach to using Short Strings is using subtypes of ShortString, whose maximum length is anywhere from 0 to 255.

This creates a variable called ssmall whose maximum length is 50 characters.

Note: When we assign a value to a Short String variable, the string is truncated if it exceeds the maximum length for the type. When we pass short strings to some Delphi’s string manipulating routine, they are converted to and from long string.

String / Long / Ansi

Delphi 2 brought to Object Pascal Long String type. Long string (in Delphi’s help AnsiString) represents a dynamically allocated string whose maximum length is limited only by available memory. All 32-bit Delphi versions use long strings by default. I recommend using long strings whenever you can.

The s variable can hold from zero to any practical number of characters. The string grows or shrinks as you assign new data to it.

We can use any string variable as an array of characters, the second character in s has the index 2. The following code

assigns T to the second character os the s variable. Now the few of the first characters in s look like: TTe s str. .
Don’t be mislead, you can’t use s[0] to see the length of the string, s is not ShortString.

Reference counting, copy-on-write

Since memory allocation is done by Delphi, we don’t have to worry about garbage collection. When working with Long (Ansi) Strings Delphi uses reference counting. This way string copying is actually faster for long strings than for short strings.
Reference counting, by example:

When we create string s1 variable, and assign some value to it, Delphi allocates enough memory for the string. When we copy s1 to s2, Delphi does not copy the string value in memory, it only increases the reference count and alters the s2 to point to the same memory location as s1.

To minimize copying when we pass strings to routines, Delphi uses copy-on-write technique. Suppose we are to change the value of the s2 string variable; Delphi copies the first string to a new memory location, since the change should affect only s2, not s1, and they are both pointing to the same memory location.

Wide String

Wide strings are also dynamically allocated and managed, but they don’t use reference counting or the copy-on-write semantics. Wide strings consist of 16-bit Unicode characters.

About Unicode character sets

The ANSI character set used by Windows is a single-byte character set. Unicode stores each character in the character set in 2 bytes instead of 1. Some national languages use ideographic characters, which require more than the 256 characters supported by ANSI. With 16-bit notation we can represent 65,536 different characters. Indexing of multibyte strings is not reliable, since s[i] represents the ith byte (not necessarily the i-th character) in s.

If you must use Wide characters, you should declare a string variable to be of the WideString type and your character variable of the WideChar type. If you want to examine a wide string one character at a time, be sure to test for multibite characters. Delphi doesn’t support automatic type conversions betwwen Ansi and Wide string types.

Null terminated

A null or zero terminated string is an array of characters, indexed by an integer starting from zero. Since the array has no length indicator, Delphi uses the ASCII 0 (NULL; #0) character to mark the boundary of the string.
This means there is essentially no difference between a null-terminated string and an array[0..NumberOfChars] of type Char, where the end of the string is marked by #0.

We use null-terminated strings in Delphi when calling Windows API functions. Object Pascal lets us avoid messing arround with pointers to zero-based arrays when handling null-terminated strings by using the PChar type. Think of a PChar as being a pointer to a null-terminated string or to the array that represents one. For more info on pointers, check:Pointers in Delphi.

For example, The GetDriveType API function determines whether a disk drive is a removable, fixed, CD-ROM, RAM disk, or network drive. The following procedure lists all the drives and their types on a users computer. Place one Button and one Memo component on a form and assign an OnClick handler of a Button:

Mixing Delphi’s strings

We can freely mix all four different kinds of strings, Delphi will give it’s best to make sense of what we are trying to do. The assignment s:=p, where s is a string variable and p is a PChar expression, copies a null-terminated string into a long string.

Character types

In addition to four string data types, Delphi has three character types: Char, AnsiChar, and ​WideChar. A string constant of length 1, such as ‘T’, can denote a character value. The generic character type is Char, which is equivalent to AnsiChar. WideChar values are 16-bit characters ordered according to the Unicode character set. The first 256 Unicode characters correspond to the ANSI characters.

Блог GunSmoker-а (переводы)

. when altering one’s mind becomes as easy as programming a computer, what does it mean to be human.

четверг, 3 сентября 2009 г.

PChars: сами строки не включены

The string is a stark data structure and everywhere it is passed there is much duplication of process. It is a perfect vehicle for hiding information. — Alan Perlis

В общедоступных newsgroup-ах на сервере Embarcadero я часто вижу, что по-прежнему есть много проблем с пониманием как типа PChar , так и типа String . В этой статье я хотел бы обсудить общие моменты и различия между обоими типами, а также вещи, которые вы можете и которые вы не должны делать с ними.

Общие принципы, описанные в этой статье, применимы ко всем Win32 версиям Delphi, включая Delphi 2009 и выше. В конце, однако, есть специальный раздел для тех, кто использует Delphi 2009 и выше.
Примечание: поскольку PChar является указателем, а String — ссылочным типом данных, то первым делом вам нужно бы разобраться с понятием указателя.

Содержание

PChar

Trying to outsmart a compiler defeats much of the purpose of using one. — Kernighan and Plauger, The Elements of Programming Style .

Идея типа PChar взята из строк языка C. Большинство функций Windows API имеют интерфейс в стиле C и принимают строки в стиле C. Чтобы можно было использовать эти API функции, Borland пришлось ввести тип, который бы подражал им, ещё в предшественнике Delphi: Turbo Pascal-е.

В C на самом деле не существует специального типа для строк, как это имеет место в Delphi. Строки в C – это просто массивы символов, а конец текста отмечается символом, ASCII код которого равен 0 . Это позволяет строкам быть большими (по сравнению со строками в Turbo Pascal-ле, где они были ограничены 255 символами из-за счётчика длины в виде байта – теперь это тип ShortString в Delphi), но несколько неудобными в использовании. Начало массива просто отмечается указателем на символ, который и стал определением типа в PChar в Delphi. Чтобы пройтись по строке в C, вы должны использовать этот указатель, как указатель на массив символов (вообще, это общее правило для всех указателей в C), и использовать s[20] для указания 21 -го символа (отсчёт начинается с 0 ). Но арифметика указателей в C позволяет делать не только инкременты и декременты, но также допускает сложение указателя с числом или вычисление разницы между двумя указателями. В C, *(s + 20) эквивалентно s[20] ( * в C является оператором разыменования, это аналог ^ в Delphi). Для типа PChar Borland сделала возможным практически тот же самый синтаксис.

Итак, PChar – это просто указатель, ровно как в C. И, снова как и в C, вы можете использовать его, как если бы это был массив (т.е. указатель, указывающий на первый элемент массива). Но на самом деле он им не является! Тип PChar не имеет автоматически управляемого хранилища данных, как это есть у обычных строк в Delphi. Если вы копируете текст в PChar -«строку», вы должны всегда быть уверенными, что этот ваш PChar действительно указывает на допустимый массив символов, и что массив достаточно велик, чтобы вместить весь текст.
Код выше не выделяет никакого хранилища для строки, поэтому он пытается сохранить символы в какое-то случайное место в памяти (адрес S неопределён и содержит какой-то мусор, см. мою статью про указатели). Это вызовет проблемы или даже вылет программы. Это ваша ответственность за гарантию наличия массива (прим. пер.: для типа String в Delphi ответственность за хранилище лежит на библиотеке поддержки языка Delphi). Простейший способ сделать это – использовать локальный массив: Код выше записывает символы в массив. Но если вы попробуете показать строку S на экране, вы, вероятно, увидите много другого мусора или даже вылет программы (*). Это потому что мы не завершили нашу строку символом #0 . OK, тогда мы можем добавить ещё одну строку: и тогда при выводе S вы получите текст » D6 «. Но записывать символы по-одному – это весьма неудобно. Чтобы показать текст через PChar , можно поступить проще: вы просто указываете PChar -ом на уже готовый массив символов с текстом в нём. К счастью, строковые константы типа ‘Delphi’ также являются такими массивами, поэтому они могут быть использованы как PChar : Вам только следует понимать, что код выше просто меняет указатель S . Сам текст никуда не копируется и не перемещается. Текст строковых констант хранится где-то в программе (и имеет терминатор #0 ), а S теперь указывает на его начало – вот и всё. Если вы сделаете: это не скопирует текст ‘Delphi’ в массив A . Первая строка после begin нацеливает указатель S на массив A , но тут же мы, во второй строке, изменяем S так, что он теперь указывает на строковую константу. Если вы хотите скопировать текст в массив, вам нужно указать это явно, используя, например, StrCopy или StrLCopy : или В конкретно этом случае нам очевидно, что строка ‘Delphi’ влезет в массив ( 101 символ как-никак), так что использование StrLCopy выглядит немного перебором, но в других случаях, когда вы не знаете наперёд размера строки, вы должны использовать StrLCopy для избежания переполнения буфера (да, ТОГО самого переполнения буфера – прим.пер.).

Массив типа A полезен как буфер для небольших строк или строк, ограниченных сверху (т.е. для которых известен максимальный потенциальный размер), но часто у вас будут строки, размер которых вам неизвестен во время компиляции. В этом случае вам нужно использовать динамическое выделение буфера для текста. Вы можете использовать, например, StrAlloc или StrNew для создания буфера, или же GetMem (а также динамические массивы или даже строки – прим.пер.), но тогда вы должны не забывать освобождать память, когда она вам станет не нужна, используя StrDispose или FreeMem . Вы также можете использовать тип String в Delphi в качестве буфера, но, прежде чем я опишу, как это сделать, я бы сперва хотел обсудить этот тип.

String

A world without string is chaos — Randolf Smuntz, Mouse Hunt

Позвольте мне вас запутать: String или, более точно, AnsiString (в Delphi 2009 и выше: UnicodeString ) фактически является PChar -ом. Точно так же, как и PChar , строка представляет собой указатель на массив символов, заканчивающихся символом #0 . Но есть одно большое отличие: обычно вам не нужно думать, как работают строки. Их можно использовать не задумываясь, почти как любую другую переменную. Компилятор сам заботится о вызове кода для выделения, копирования и освобождения текста строк. Поэтому вместо ручного вызова подпрограмм типа StrCopy , вы просто позволяете компилятору сделать это за вас.

Но это ещё не всё. Хотя текст, несомненно, всегда заканчивается символом #0 – сделано это только для того, чтобы сделать строки Delphi совместимыми со строками C, сам компилятор не нуждается в терминаторе. Перед текстом строки в памяти, по отрицательному смещению указателя, хранится длина строки, как число Integer . Так что, чтобы узнать длину строки, компилятор просто читает этот Integer , экономя на поиске первого #0 в строке. Это означает, что вы можете хранить и сам символ #0 в середине строки, и это будет работать. Но некоторые подпрограммы, которые работают с терминатором, воспримут только часть строки.

Обычно, каждый раз, когда вы присваиваете одну строку другой, компилятору надо бы выделять память и копировать текст из одной переменной в другую. Поскольку строки в Delphi могут быть очень большими (теоретически до 2 Гб максимум в 32 -х разрядных приложениях), это может быть весьма медленно. Чтобы избежать лишнего копирования, Delphi использует концепцию, которая известна под названием «копирование по требованию» («copy on demand»). Каждая строка имеет, помимо длины, и другое служебное поле: счётчик ссылок (reference count). Он содержит количество строковых переменных, которые ссылаются на конкретно эту строку в памяти. Если счётчик опускается до 0 , то это значит, что на текст строки никто больше не ссылается, и он может быть удалён из памяти.

Компилятор гарантирует вам, что счётчик ссылок всегда будет содержать правильное значение (но вы можете и обмануть компилятор: приведениями типов – подробнее об этом ниже). Если строка объявлена в секции var или как поле класса или записи, она начнёт свою жизнь как nil – внутреннее представление пустой строки ( » ). Когда текст строки только создаётся и присваивается переменной, счётчик ссылок становится равным 1 . Каждое дополнительное присваивание этой строки другой переменной будет увеличивать счётчик ссылок (никаких данных при этом не копируется). Если строковая переменная покидает область видимости (когда заканчивается функция или удаляется объект), или же ей присваивают другую строку, то счётчик ссылок уменьшается.

Простой пример: Теперь S1 указывает на текст ‘123456’ и имеет счётчик ссылок равный 1 . Текст не копируется, S2 просто указывает на тот же адрес, что и S1 , но только счётчик ссылок текста ‘123456’ теперь равен 2 . Теперь выделяется новый, больший буфер, в него копируется текст ‘The number is ‘ и туда же добавляется текст ‘123456’ . Но т.к. S2 более не указывает на текст ‘123456’ , то счётчик ссылок этого текста снова уменьшается до 1 . Result теперь указывает на тот же адрес, что и S2 , а счётчик ссылок текста ‘The number is 123456’ увеличивается до 2 . Теперь S1 и S2 выходят из области видимости. Счётчик ссылок текста ‘123456’ будет уменьшен до 0 , поэтому буфер этого текста освобождается (**). Счётчик ссылок текста ‘The number is 123456’ также уменьшается на единицу, становясь равным 1 . Сам буфер не удаляется, поскольку счётчик ссылок не равен 0 (у нас на него ещё указывает Result ).

Сложно? Да, весьма запутанно. И это становится ещё более запутанным с введением в игру var , const и out параметров. Но, к счастью, обычно вам не нужно заморачиваться этими вопросами. Такие вещи важно понимать только если вы обращаетесь к строкам из ассемблера, напрямую через PChar или с помощью подпрограмм прямого доступа к памяти. Но использование строк с приведением к PChar не является чем-то необычным. Тем не менее, если вы хотите заглянуть чуть дальше под капот языка — вы можете прочитать эту статью.

Самыми важными вещами, которые вам надо запомнить для работы со строками, являются:

  • что текст копируется в новый буфер только при изменении строки;
  • что счётчик ссылок и длина текста не привязаны к строковой переменной, а только к конкретному тексту, на который могут указывать несколько (равноправных) строковых переменных;
  • что счётчик ссылок всегда верен, если только вы не наврёте компилятору, используя приведения типов;
  • что присваивание чего-то строковой переменной уменьшит счётчик ссылок текстового буфера, на который она указывала до присваивания;
  • что, если счётчик ссылок опускается до 0 , то строковый буфер удаляется.

Использования вместе String и PChar

If you can’t be a good example, then you’ll just have to be a horrible warning. — Catherine Aird

PChar и символьные массивы довольно тяжело использовать. В большинстве случаев вы должны выделять память и не забывать её освобождать. Если вы хотите добавить текст, то вы должны сперва посчитать длину получающейся строки, затем увеличить буфер, если он слишком мал, и использовать StrCat или StrLCat , чтобы, наконец, добавить текст. Вы должны использовать StrComp или StrLComp для сравнения строк и т.д. и т.п.

Строки ( String ), с другой стороны, намного проще использовать. Большинство вещей выполняется автоматически, само собой. Но множество API функций Windows (или Linux) требуют нуль-терминированных строк PChar , а не строк Delphi String . К счастью, тип String специально устроен так, что они фактически тоже являются указателями на строки, завершающиеся нулём (этот терминатор никак не используется Delphi и компилятор постоянно таскает его со строками только на этот случай). Так что любую строку String вы можете использовать и как PChar – просто приводя тип: Не забывайте, что переменная AnsiString является указателем на текст, а не самим текстовым буфером. Если текст изменяется, то он будет скопирован в другое место, а адрес в переменной соответствующим образом изменится. Это означает, что вы не должны использовать PChar для указания на строки, а затем изменять строку. Лучше всего избегать таких вещей: Если S изменяется на ‘Something else’ , указатель P не будет изменён и будет продолжать указывать на ‘C:\Test.exe’ . Поскольку P не является строковой (в смысле String ) ссылкой на этот текст, и у нас нет никакой другой переменной, ссылающейся на него, то его счётчик ссылок станет равным 0 , и текст будет удалён из памяти. Это означает, что P теперь указывает на недоступную память (invalid memory).

Будет мудрым решением не путать компилятор смешением переменных PChar и String , только если вы точно не знаете, что вы делаете. Компилятор не воспринимает PChar как String , поэтому он не будет менять счётчик ссылок строковых буферов, если вы нацелите на них PChar . Часто наилучшим решением будет отказ от подобного использования PChar . Просто используйте обычные строки, и делайте приведение типов только в последний момент. Функции, принимающие параметр PChar , должны копировать текст параметра в свои собственные переменные.

Обычно строковые буферы имеют размер достаточный только для того, чтобы вместить лежащий в них (присвоенный им) текст. Но используя SetLength , вы можете установить произвольный размер строки (в символах). Это делает строки полезными для использования в качестве буферов. Например, вызов функции Windows API, которая возвращает текст в символьном массиве, может выглядеть так: Альтернативно, вы можете присвоить PChar -у String , и в результате у вас получится новая строка с копией текста. Поэтому вы можете установить длину строки с помощью такого эквивалентного кода: Последняя строка в функции устанавливает размер строки равный размеру нуль-терминированной C-строке, записанной в ней (длина C-строки всегда меньше или равна длине её String -хранилища – прим.пер.). Если вам нужен результат как PChar -строка для дальнейших действий (например для передачи в другие API-функции), вы могли бы попробовать использовать такой код: Однако, это не будет работать. Потому что Buffer является локальной переменной, хранящейся целиком в локальной памяти (процессорном стеке). Как только вы покидаете эту функцию, память, которая была выделена под Buffer , начинает использоваться другими подпрограммами, так что текст, который лежал в буфере, становится мусором. Вы никогда не должны использовать локальные переменные для возвращаемых значений PChar .

Вы могли бы обойти это, делая динамическое выделения памяти для возвращаемого PChar , с помощью, например, StrAlloc , но тогда вызывающему пришлось бы руками освобождать выделенный вами буфер. Обычно, это не самый лучший подход. Лучше следовать примеру GetWindowsDirectory , и позволить вызывающему указать свой буфер и его размер. Тогда вы просто заполните уже готовый буфер (используя StrLCopy ) своими данными до допустимого размера.

Есть и альтернативная реализация функции WindowsDirectory , которая может использовать локальный буфер. Она основывается на том, что вы можете присваивать PChar строке String напрямую. Чтобы сделать текст именно строкой Delphi (с служебными полями длины и счётчиком ссылок), будет создан строковый буфер требуемой длины, а текст будет скопирован в него. Поэтому, даже если локальный буфер будет удалён, то текст в строковом буфере всё ещё будет с нами: Но как вам написать функцию, например в DLL, которая должна возвращать данные как PChar ? Я думаю, что вы снова можете следовать примеру GetWindowsDirectory . Вот пример простой функции из DLL, возвращающей строку версии: Как вы можете видеть, строка просто копируется в предоставленный вызывающим буфер с помощью StrLCopy . Поскольку вызывающий обязан подготовить буфер, вы избегаете любых проблем с управлением памятью. Если вы предоставляете буфер, то вы и знаете, как его удалять. FreeMem не будет работать через границу DLL (в общем случае). Но даже если бы работала (например, с использованием общего менеджера памяти – прим.пер.), то пользователь вашей DLL, работающий в C или Visual Basic, не знал бы, как освободить буфер в своём языке, т.к. управления памятью индивидуально в каждом языке. Позволяя вызывающему указывать свой буфер, вы делаете его или её независимыми от вашей реализации.

Прим.пер.: а вот ещё обсуждение этого вопроса с несколькими альтернативными решениями.

Delphi 2009 и выше

В Delphi 2009 строки были значительно изменены. До Delphi 2009 (т.е. с Delphi 2 по Delphi 2007) строки были, фактически, типом AnsiString , а каждый символ был однобайтовым AnsiChar . Тип PChar был псевдонимом для PAnsiChar . Но в Delphi 2009 строки стали использовать Unicode, а ещё точнее – UTF-16, что означает, что потребовался новый тип строк: UnicodeString . Этот тип строк состоит уже из двух-байтовых WideChar . Он стал умалчиваемым типом строк, что означает, что String теперь псевдоним для UnicodeString , Char для WideChar , а PChar для PWideChar .

Delphi for Win32 уже имела строковый тип WideString (также состоящий из WideChar ), но это всегда лишь псевдоним для системного типа строк BSTR , используемом в основном в COM. Этот тип управляется ОС (и поэтому является идеальным средством для обмена строками между границами модулей – прим.пер.) и не имеет счётчика ссылок и “копирования по требованию”, так что каждое присваивание означает создание новой уникальной строки с полным копированием текстового буфера. Поэтому тип WideString не отличается особой производительностью – вот почему был введён новый тип UnicodeString .

Кроме длины и счётчика ссылок, каждый строковый тип данных (т.е. AnsiString и UnicodeString ) теперь имеют дополнительные служебные поля: Word , содержащий кодировку (encoding) строки (в основном используется в однобайтовых строках типа AnsiString ), и Word , содержащий размер символа в байтах. Кодировка строки AnsiString управляет интерпретацией и конвертацией символов с кодами от 128 до 255 , а размер символа в основном используется для взаимодействия с кодом на C++.

Кроме того, также было введено несколько вспомогательных типов строк: RawByteString ( = AnsiString($FFFF) ) и UTF8String ( = AnsiString(65001) ) (а также огромного количества любых других пользовательских типов строк на базе AnsiString , например, Win1251String = AnsiString(1251) – прим.пер.). Подразумевается, что строки UTF8Strings используются для хранения данных в формате UTF-8, что означает, что каждый элемент строки является AnsiChar -ом, но каждый «символ» может быть представлен несколькими элементами AnsiChar . Заметьте, что я поставил слово «символ» в кавычки, потому что в контексте Unicode более правильно будет говорить о кодовых позициях (code point) (а также это не приведёт к путанице с символом в смысле один Char — прим.пер.).

Как вы можете видеть из статьи Википедии о UTF-16, также возможно, что некоторые кодовые позиции UTF-16 требуют нескольких WideChar -ов – так называемых «суррогатных пар» (surrogate pairs). Так что длина UnicodeString или UTF8String не обязательно напрямую соответствует числу кодовых позиций в них. Однако если в UnicodeString суррогатные пары относительно редки (суррогатные пары используются только для записи символов вне базовой плоскости: Basic Multilingual Plane (BMP) – именно там находятся все символы для всех современных языков и множество специальных символов – прим.пер.), то в то же время редкая UTF8-строка обходится без мультибайтовых символов.

Ещё одним новым типом является RawByteString . Если вы присвоите AnsiString с одним типом кодировки другой AnsiString с другой кодировкой, то будет выполнена автоматическая конвертация (потенциально: с возможной потерей данных, если символы из одной кодировки не имеют эквивалента в другой кодировке). Собственно AnsiString используют кодировку по-умолчанию, управляемую системой (“Кодировка для не-Unicode приложений” в региональных настройках системы – прим.пер.). Пользовательские типы строк на базе AnsiString всегда имеют фиксированную кодировку (например, UTF8String всегда имеет кодировку 65001 ). А RawByteString – это специальная строка без кодировки, так что вы можете быть уверены, что при присваивании ей другой AnsiString -строки (собственно AnsiString или пользовательской типа UTF8String ), никакой конвертации не будет, а все текстовые буфера будут скопированы “как есть”.

Справка Delphi 2009 так говорит о RawByteString :

RawByteString позволяет передачу строковых данных с любой кодовой страницей без выполнения конвертации данных. Обычно это означает, что параметры подпрограмм, которые обрабатывают строки без учёта кодовой страницы, должны иметь тип RawByteString . Объявление переменных типа RawByteString должно происходить редко, если происходить вообще. Потому что это может приводить к неопределённому поведению и потере данных.

Так что же с этим делать?

Как вы можете видеть из текста статьи, я ни разу не делал ссылок на размер Char . Так что всё, что я написал выше, полностью применимо и к Delphi 2009 и выше без изменений. Большинство кода, использующего эти техники, может быть просто перекомпилировано в Delphi 2009 и выше, и будет работать корректно, но теперь уже используя Unicode, т.е. вместо AnsiString , код будет работать с UnicodeString , WideChar и PWideChar .

Функции Win32 API часто поставляются в двух вариантах: один из которых принимает Ansi (т.е. однобайтовые) символы и (C-) строки, а второй принимает Wide (Unicode, двухбайтовые) символы и (C-) строки. Эти варианты обычно отличаются друг от друга окончаниями A или W в имени функции соответственно. Заголовочные модули Delphi для таких API функций, типа Windows.pas , обычно также определяют третий вариант, без окончаний A или W (точно так же поступает Microsoft в своих заголовочниках C), и отождествляет этот вариант с Ansi вариантом. Вот один пример из Windows.pas до Delphi 2009: Как вы можете видеть, GetShortPathName проецируется на функцию ‘GetShortPathNameA’ . Вы также можете видеть, что -A версия объявляется с PAnsiChar -ами, а -W версия принимает строки PWideChar . Нейтральная же работает с нейтральным типом PChar .

В Delphi 2009 и выше, такие нейтральные функции теперь ассоциируются с -W вариантом, так что вторая часть вышеприведённого кода теперь становится: Это означает, что в Delphi 2009 и выше, даже если вы вызываете функцию из Windows API, а также если вы вызываете функцию RTL или VCL, то в большинстве случаев вам не нужно беспокоиться о размере символов в строке. Строки теперь у нас Unicode, функции API стали тоже Unicode, так что если вы продолжите использовать нейтральные типы данных String , Char и PChar , то вам не придётся изменять свой код. А если у вас есть код, который работает с неверным размером символа (некоторые функции API, типа GetProcAddress , существуют только в ANSI варианте), то вы получите красивое предупреждение или ошибку от компилятора, так что вы тут же сможете решить, как вам реагировать (прим.пер.: вообще-то, у GetProcAddress есть перегруженный Unicode-вариант, который делает преобразование строк к ANSI-варианту).

SizeOf или Length?

Конечно же, вам нужно быть осторожным с кодом, особенно если этот код использует низко-уровневые подпрограммы типа Move или FillChar (которая теперь по-хорошему должна была бы называться FillByte , т.к. работает она с байтами, но старое название было сохранено по соображениям совместимости кода – прим.пер.), которые предполагают, что символы имеют размер в один байт (ну, на самом деле они просто меряют размеры в байтах, а не в символах – прим.пер.). Так, чтобы очистить массив из Char , не делайте так: потому что теперь буфер состоит из WideChar , что означает размер в 2 * (MAX_PATH + 1) байт. Намного проще при этом использовать SizeOf : Заметьте, что SizeOf можно применять только к статическим массивам. Для динамических массивов она всегда возвращает размер указателя (см. статью про указатели). В этом случае вам надо писать: Для ситуаций же, когда важно число символов, используйте просто Length :

Информация для дальнейшего чтения

Тут есть whitepaper от Marco Cantù, который обширно и ясно описывает различные новые строковые типы и расширения. Я рекомендую скачать её и прочитать хотя бы один раз.

Заключение

The open secrets of good design practice include the importance of knowing what to keep whole, what to combine, what to separate, and what to throw away. — Kevlin Henny

Хотя оба типа String и PChar являются строковыми типами, они весьма различны. String проще использовать, а для PChar вам нужно всё делать вручную. Вы можете использовать их вместе и приводить String к PChar , и присваивать PChar строке, но поскольку String меняет свой адрес при изменениях, вы не должны долго держаться за этот адрес при использовании PChar . Присваивание PChar -а строке намного безболезненней.

Как показано в тексте выше, выделение текста в функции и возвращение PChar на новый буфер не является очень хорошей идеей. Это ещё хуже в ситуациях межмодульного обмена данными, поскольку вызывающий может даже не сумеет освободить возвращаемую память – DLL и вызывающий, скорее всего, будут иметь различные менеджеры памяти – каждый со своей кучей. Также не очень хорошей идеей является использование локального буфера для возвращаемого текста.

Если вам обязательно нужно использовать PChar , потому что этого требует функция, вы должны использовать String настолько, насколько это возможно. И приводить строку к PChar только когда вы готовы передать строку параметром. Использование строк намного проще и менее подвержено ошибкам, чем использование функций строк в C-стиле.

A little inaccuracy sometimes saves a ton of explanation. — H. H. Munro (Saki)

Я надеюсь, что я немного приподнял завесу тумана над PChar . Я не рассказал всё, что известно, и даже, возможно, немного переврал правду (например, не любая Delphi-строка типа String управляется счётчиком ссылок – к примеру, строковые константы всегда имеют счётчик ссылок равный –1 (***)), но эти мелкие детали не столь важны для общей, большой картины и не оказывают влияния на использование и взаимодействие между String -ми и PChar -ми.

Дополнительный материал для чтения: тонкости работы со строками.

Примечания переводчика:
(*) Вот ещё интересный пример.
(**) Чтобы гарантировать обязательное выполнение очистки строк (уменьшения счётчика ссылок), компилятор неявно вставляет в подпрограмму скрытый try/finally. Поэтому, фактически, код любой подпрограммы со строковой переменной (а также любой другой авто-финализируемой переменной) выглядит примерно так: (***) См. также пример ситуации, где это имеет значение.

DelphiComponent.ru — бесплатно видеоуроки по Delphi, статьи, исходники

Работа со строками в Delphi

Строковый тип данных — наиболее широко используемый тип данных в стандартных приложениях. При работе со строковыми значениями можно применять один из доступных строковых типов или массив символов. Delphi позволяет использовать операцию присваивания для присвоения строкового значения массиву символов.

Поскольку память для массива символов резервируется статически, размер массива не может изменяться во время выполнения и для него всегда резервируется стек размером 256 байтов, независимо от действительного количества символов в массиве.

Delphi по-прежнему поддерживает старый строковый тип языка Pascal, но в Delphi этот строковый тип называется ShortString. Память для типа ShortString, как и память для массива символов, резервируется статически, и этот тип всегда использует 256 байтов стековой памяти. Тип ShortString может содержать не более 255 символов. Первый символ, индекс которого равен 0. содержит длину строки — действительное количество символов в типе ShortString.

В следующем примере отображается длина и объем памяти, занимаемой переменной типа ShortString. Для выяснения количества байтов, занимаемых переменной, можно использовать функцию SizeOf.

Листинг 6.11. Сведения о переменной типа ShortString

3.3.1. Виды строк в Delphi

3.3.1. Виды строк в Delphi

Для работы с кодировкой ANSI в Delphi существует три вида строк: AnsiString , ShortString и PChar . Различие между ними заключается в способе хранения строки, а также выделения и освобождения памяти для нее. Зарезервированное слово string по умолчанию означает тип AnsiString , но если после нее следует число в квадратных скобках, то это означает тип ShortString , а число — ограничение по длине. Кроме того, существует опция компилятора Huge strings (управляется также директивами компилятора <$H+/->и <$LONGSTRINGS ON/OFF>, которая по умолчанию включена, но если ее выключить, то слово string станет эквивалентно ShortString ; или, что то же самое, string[255] . Эта опция введена для обратной совместимости с Turbo Pascal, в новых программах отключать ее нет нужды. Внутреннее устройство этих типов данных иллюстрирует рис. 3.2.

Рис. 3.2. Устройство различных строковых типов Delphi

Наиболее просто устроен тип ShortString . Это массив символов с индексами от 0 до N, где N — число символов, указанное при объявлении переменной (в случае использования идентификатора ShortString N явно не указывается и равно 255). Нулевой элемент массива хранит текущую длину строки, которая может быть меньше или равна объявленной (эту длину мы будем далее обозначать M), элементы с индексами от 1 до M — это символы, составляющие строку. Значения элементов с индексами M+1..N не определены. Все стандартные функции для работы со строками игнорируют эти символы. В памяти такая переменная всегда занимает N+1 байтов.

Ограничения типа ShortString очевидны: на хранение длины отводится только один байт, поэтому такая строка не может содержать больше 255 символов. Кроме того, такой способ записи длины не совпадает с принятым в Windows, поэтому ShortString несовместим с системными строками.

В системе приняты так называемые нуль-терминированные строки: строка передается указателем на ее первый символ, длина строки отдельно нигде не хранится, признаком конца строки считается встретившийся в цепочке символов #0 . Длина таких строк ограничена только доступной памятью и способом адресации (т. е. в Windows теоретически это 4 294 967 295 символов). Для работы с такими строками предусмотрен тип PChar . Переменная такого типа является указателем на начало строки. В литературе нередко можно встретить утверждение, что PChar = ^Сhar , однако это неверно: тип PChar встроен в компилятор и не выводится из других типов. Это позволяет выполнять с ним операции, недопустимые для других указателей. Во-первых, если P — переменная типа PChar , то допустимо обращение к отдельным символам строки с помощью конструкции P[N] , где N — целочисленное выражение, определяющее номер символа (в отличие от типа ShortString , здесь символы нумеруются с 0, а не с 1). Во-вторых, к указателям типа PChar разрешено добавлять и вычитать целые числа, смещая указатель на соответствующее число байтов вверх или вниз (здесь речь идет только об операторах «+» и «-«; адресная арифметика с помощью процедур Inc и Dec доступна для любых типизированных указателей, а не только для PChar ).

При работе с PChar программист целиком и полностью отвечает за выделение памяти для строки и за ее освобождение. Именно это и служит основным источником ошибок у новичков: они пытаются работать с такими строками так же, как и с AnsiString , надеясь, что операции с памятью будут выполнены автоматически. Это очень грубая ошибка, способная привести к самым непредсказуемым последствиям.

Хотя программист имеет полную свободу выбора в том, как именно выделять и освобождать память для нуль-терминированных строк, в большинстве случаев самыми удобными оказываются специально предназначенные для этого функции StrNew , StrDispose и т. п. Их преимущество заключается в том, что менеджер памяти выделяет чуть больше места, чем требуется для хранения строки, и в эту дополнительную память записывается, сколько байтов было выделено. Благодаря этому функция StrDispose удаляет ровно столько памяти, сколько было выделено, даже если в середину выделенного блока был записан символ #0 , уменьшающий длину строки.

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

Тип AnsiString объединяет достоинства типов ShortString и PChar : строки имеют фактически неограниченную длину, заботиться о выделении памяти для них не нужно, в их конец автоматически добавляется символ #0 , что делает их совместимыми с системными строками (впрочем, эта совместимость не абсолютная; как и когда можно использовать AnsiString в функциях API, мы рассматривали в разд. 1.1.13.).

Переменная типа AnsiString — это указатель на первый символ строки, как и в случае PChar . Разница в том, что перед этой строкой в память записывается дополнительная информация: длина строки и счетчик ссылок. Это позволяет компилятору генерировать код, автоматически выделяющий, перераспределявший и освобождающий память, выделяемую для строки. Работа с памятью происходит совершенно прозрачно для программиста, в большинстве случаев со строками AnsiString можно работать, вообще не задумываясь об их внутреннем устройстве. Символы в таких строках нумеруются с единицы, чтобы облегчить перенос старых программ, использовавших строки типа ShortString .

Счетчик ссылок позволяет реализовать то, что называется copy-on-demand, копирование по необходимости. Если у нас есть две переменные S1 , S2 типа AnsiString , присваивание вида S1:= S2 не приводит к копированию всей строки. Вместо этого в указатель S1 копируется значение указателя S2 , а счетчик ссылок строки увеличивается на единицу. В дальнейшем, если одну из этих строк потребуется модифицировать, она сначала будет скопирована (а счетчик ссылок оригинала, естественно, уменьшен) и только потом изменена, чтобы это не затрагивало остальные переменные.

Далее мы рассмотрим, какие проблемы могут возникнуть при использовании строк разного вида.

String — Тип Delphi

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

Здесь ты получил указатель на строку 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;

— В реальной работе многобайтовая строка может получиться при приведении W >- Если планируется программу (или компонент) продавать (да еще за бугром), то при встрече с многобайтовыми строками надо хотя бы корректно завершить работу (конечно лучше, чтобы прога их поддерживала). Встает вопрос: Как определить нужна ли поддержка многобайтовых строк? Ответ: В переменной 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;
Res >
Успехов! Орехов Роман aka tripsin

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;

String — Тип Delphi

Строки в Delphi состоят из одоного (тип ShortString ) или двух байтов (тип AnsiString, WideString ), содержащих указание количества символов в строке, и последовательности символов. Таким образом ShortString максимально может содержать до 255 символов и занимать память 2 байта ..256 байт, а AnsiString или WideString — максимально могут содержать примерно 2 x 10 30 символов и занимать память 4 байта .. 2 Гбайта.

В типе AnsiString символы кодируются в коде ANSI, а в типе WideString — в коде Unicode.

Общим типом является тип String , который может соответствовать как типу ShortString , так и типу AnsiString . Это определяется директивой компилятора $H . По умолчанию используется <$H+>, и тип String равен типу AnsiString .

Кроме того имеется тип PChar , представляющий так называемую строку с завершающим нулем. Строки с завершающим нулем не содержат байтов длины. В отличие от обычных строк они состоят из последовательности ненулевых символов, за которым следует символ NULL (#0). Никаких ограничений на длину строк с завершающим нулем не накладывается. Фактически он указывает на символ

Расширенный синтаксис позволяет ставить в соответствие строкам с завершающим нулем символьный массив типа

где X — положительное число типа Integer , определяющее количество символов в строке, не считая завершающего символа с кодом 0. В отличие от типа String , символ с индексом 0 здесь является первым символом строки, а последний символ с индексом X — завершающим символом с кодом 0. (см. Функции работы со строками с завершающим нулем)

Список литературы:

  1. Гофман В.Э., Хомоненко А.Д. Delphi 6. — СПб. БХВ-Петербург, 2002. — 1152 с.: ил.
  2. Турбо Паскаль 7.0 — К. Торгово-издательское бюро BHV, 1996 — 448 с.: ил.
  3. Delphi7 Help

Класс TStrings — операции со строковыми данными в Делфи

Класс TStrings является базовым классом для операций со строковыми данными. Этот класс представляет собой контейнер для строк (коллекцию или массив строк). Для операций со строками класс TStrings предоставляет соответствующие свойства и методы. От класса TStrings происходит большое количество производных классов, например, TStringList, которые могут использоваться для задания различных типов строк.

Визуальные компоненты, способные работать со списком строк, имеют свойства, которые являются массивами строк, содержащихся в этих компонентах. Например, для списков ListBox и DBListBox и для групп зависимых переключателей RadioGroup и DBRadioGroup таким свойством является Items, а для многострочных редакторов Memo и DbMemoLines.

Указанные свойства для визуальных компонентов ListBox и Memo доступны при разработке и при выполнении приложения, а для визуальных компонентов DBListBox и DBMemo, связанных с данными, — только при выполнении приложения.

Особенности класса TStrings

Рассмотрим особенности и использование класса TStrings на примере свойства Items списков. Работа с другими объектами типа TStrings происходит аналогично.

Каждый элемент списка является строкой, к которой можно получить доступ по ее номеру в массиве строк Items. Отсчет элементов списка начинается с нуля. Для обращения к первому элементу нужно указать Items[0], ко второму— Items[1], к третьему — Items[2] и т. д. При операциях с отдельными строками программист должен контролировать номера строк в списке и не допускать обращения к несуществующему элементу. Например, если список содержит три строки, то попытка работы с десятой строкой приведет к исключению.

Свойство Count

Свойство Count типа Integer задает число элементов в списке. Поскольку первый элемент списка имеет нулевой номер, то номер последнего элемента равен Count-1.

Например, присваивание элементам списка ListBox1 новых значений может быть реализовано так:

Методы Add и Insert

Методы Add и Insert служат для добавления/вставки строк в список. Функция Add (const S: string): integer добавляет заданную параметром S строку в конец списка, а в качестве результата возвращает положение нового элемента в списке. Процедура Insert (Index: Integer; const S: String) вставляет строку S в позицию с номером, определяемым параметром index. При этом элементы списка, находившиеся до операции вставки в указанной позиции и ниже, смещаются вниз.

В приводимой далее процедуре к комбинированному списку СomboBox1 добавляется строка Нажата кнопка Button1:

Заполнение списка с помощью методов AddStrings и AddObject

Для заполнения списка можно использовать методы AddStrings и AddObject. Метод AddStrings позволяет при вызове увеличить содержимое списка более чем на один элемент.

Процедура AddStrings (strings: TStrings) добавляет в конец списка группу строк, определяемую параметром Strings. Класс TStrings позволяет хранить строки и ссылки на объектыпроизвольного типа.

Функция AddObject (const S: String; AObject: TObject): Integer добавляет в конец списка строку S и связанную с ней ссылку на объект, указываемую параметром AObject.

Рассмотрим следующий пример заполнения списка:

Здесь список TS заполняется перечнем названий страниц блокнота PageControl1 при создании формы Form1. Вместе с названиями запоминаются ссылки на страницы. Страницы блокнота имеют тип TTabSheet. Знание типа необходимо при последующей работе с задаваемыми посредством ссылок объектами, в частности, для корректного выполнения операции, такой как программное переключение страниц управляющего элемента PageControl1. Вместо переменной TS можно использовать другой список подходящего типа, например, список компонента ListBox, доступный через свойство Items.

В процессе создания приложений иногда необходимо, чтобы один список содержал те же данные, что и другой. Такое согласование списков достаточно просто выполняется с помощью методов AddStrings и Assign. Последний из методов также позволяет при вызове увеличить содержимое списка более чем на один элемент. Проверить, требуется операция согласования списков или нет, можно с помощью метода Equals.

Процедура Assign

Процедура Assign (Source: TPersistent) присваивает один объект другому, при этом объекты должны иметь совместимые типы. В результате выполнения процедуры информация копируется из одного списка в другой с заменой содержимого. Если размеры списков (число элементов) не совпадают, то после замены число элементов заменяемого списка становится равным числу элементов копируемого списка.

Функция Equals

Функция Equals (Strings: TStrings): Boolean используется для определения, содержат ли два списка строк одинаковый текст. Если содержимое списков совпадает, то функция возвращает значение True, в противном случае— значение False. Содержимое списков одинаково, если списки равны по длине и совпадают все их соответствующие элементы.

В приведенном далее примере производится согласование двух списков по содержанию:

В случае, если списки не совпадают, содержимое списка ListBox1 копируется в список ListBox2, в результате чего содержимое списков становится одинаковым.

Удаление элементов списка с помощью методов Delete и Сlear

Для удаления элементов списка используются методы Delete и Сlear. Метод Delete (index: integer) удаляет элемент с номером, заданным параметром index. При попытке удаления несуществующей строки сообщение об ошибке не выдается, но метод Delete не срабатывает.

при нажатии кнопки Button2 из комбинированного списка ComboBox1 удаляется пятая строка.

Метод Сlear очищает список, удаляя все его элементы в следующей процедуре:

при нажатии кнопки btnClearPersonalList очищается список lbPersonal.

Процедура Move

Процедура Move (Curindex, NewIndex: integer) перемещает элемент из позиции с номером CurIndex в новую позицию с номером NewIndex. Если указанный номер выходит за пределы списка, то возникает исключение.

Поиск элементов в списке процедурой IndexOf

Поиск элемента в списке можно выполнить с помощью метода IndexOf. Процедура IndexOf (const S: string): integer определяет, содержится ли строка S в списке. В случае успешного поиска процедура возвращает номер позиции найденной строки в списке; если строковый элемент не найден, то возвращается значение −1.

Работа с текстовыми файлами с помощью методов SaveToFile и LoadFromFile

У класса TStrings есть методы SaveToFile и LoadFromFile, позволяющие непосредственно работать с текстовыми файлами. Эти методы предоставляют возможность сохранения строк списка в текстовом фате на диске и последующего чтения строк из этого файла. Символы файла кодируются в системе ANSI.

Процедура SaveToFile (const FileName: String) сохраняет строковые элементы списка в файле FileName. Если заданный файл отсутствует на диске, то он создается. В последующем сохраненные строки можно извлечь из файла, используя метод LoadFromFile. Например:

Здесь содержимое списка ListBox3 записывается в файл names.txt каталога C:\COMPANY.

Процедура LoadFromFile (const FileName: String) заполняет список содержимым указанного текстового файла, при этом предыдущее содержимое списка стирается. Если заданный файл отсутствует на диске, то возникает исключение.

Пример заполнения списка содержимым файла:

Файл personal.txt содержит фамилии сотрудников организации. При запуске приложения содержимое этого файла загружается в комбинированный список ComboВох2.

При конструировании приложения изменение списка строк выполняется с помощью строкового редактора String List Editor.

Его можно вызвать из окна Инспектора объектов двойным щелчком мыши в области значения свойства типа TStrings (например, в области значения свойства Items списка ListBox).

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

Илон Маск рекомендует:  Настройка ide vb
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL