Вначале было слово, А потом слова, слова, слова!
Афоризм
Людям привычнее общаться с помощью слов, а не чисел, поэтому для вывода сообщений и другой текстовой информации почти в каждой программе используются строки. О важности этого типа данных говорит и тот факт, что в паскале был только 1 тип строк, а в Delphi их огромное количество!
Очень часто мы будем рассматривать строки как одномерный массив символов типа Char, но при этом следует помнить, что строки это не разновидность массива, а отдельный тип данных. К ним можно применять операции, отсутствующие у массивов, например, конкатенацию («сложение» строк).
Короткие строки
Тот самый, «паскалевский», тип строк в Delphi называется ShortString - короткая строка. Он используется всё реже и оставлен только для совместимости с более ранними версиями Delphi. Объявляют короткие строки с помощью зарезервированного слова string, после которого, подобно массивам, в квадратных скобках указывают максимальную длину строки в диапазоне от 1 до 255:
var sPascal: string[10];
Сразу после объявления длина строки sPascal равна нулю, хотя в памяти компьютера она всегда будет занимать 11 байтов. Почему 11, а не 10, как указано в объявлении?
Дело в том, что, действительно, для хранения каждого символа в памяти компьютера требуется 1 байт, что для 10 символов и составляет 10 байтов. А ещё 1 байт нужен для хранения текущей длины строки. Он имеет нулевой индекс в массиве, а символы строки - от 1 до заданной максимальной длины строки. Так как под хранение длины строки отводится 1 байт, то и длина строки не может превышать 255 символов.
Чтобы яснее представить, как хранятся строки в памяти компьютера, обратимся к Рис., верхний.

Итак, при запуске программы процессор компьютера выполняет машинный код, соответствующий строке исходного кода var sPascal: string[10]; При этом создаётся переменная sPascal, под неё отводится 11 байтов памяти, начиная, например, с адреса 1000 (конечно, адрес вымышленный, но точный адрес нам и не нужен) и по адресу 1000 заносится число 0 (на самом деле не число, а символ с кодом 0!), соответствующее текущей длине строки, то есть наша строка пустая: sPascal =
Присвоим строке какое-нибудь значение, к примеру, Pascal:
sPascal:='Pascal';
Содержимое памяти изменится (Рис. У10.1, средний). Теперь по адресу 1000 находится число 6 (длина строки), а далее - символы строки, которые имеют тип AnsiChar.
Если присваивать переменной sPascal другие значения, то, соответственно, будет изменяться и содержимое ячеек памяти. Как именно, вы теперь легко можете догадаться. Нас могут заинтересовать только 2 предельных случая. Чтобы уничтожить строку (не саму переменную, а только её значение!), достаточно её сделать пустой:
sPascal := '';
В этом случае строка вернётся в исходное состояние, показанное на рисунке Рис., верхний.
Если переменной присвоить строку, длина которой больше указанной в объявлении максимальной, например,
sPascal:='Pascal123456';
то все «лишние» символы будут просто проигнорированы, и в данном случае длина строки будет равна 10, а её значение - Pascal1234. Эту ситуацию отражает Рис. У10.1, нижний.
Таким образом, Delphi нещадно пресекает все ваши - даже неумышленные - попытки испортить программу. В самом деле, под переменную отводится 10 байтов памяти, а вы желаете втиснуть туда строку, длина которой на 2 символа больше. Значит, остальные символы должны занять следующие 2 байта памяти, в которых уже есть что-то жизненно необходимое для программы, и тем безнадёжно испортить её. А сможете ли вы потом найти ошибку в исходном коде?
Впрочем, если вы ярый последователь экстремального программирования, переходите на язык С и вытворяйте с памятью компьютера самые непотребные вещи, о которых здесь даже стыдно упоминать.
Поскольку короткая строка это почти массив символов, то мы легко можем добраться до каждого из них по его индексу. Снова обратим свой пылающий взор на Рис. У10.1. На нём синим цветом выделены индексы массива, то есть номера символов в строке. Из него ясно видно, что
sPascal[1] ='P', sPascal[2] = 'a', и так далее до
sPascal[10] = '4'.
Но вы никогда не узнаете, что же находится дальше - sPascal[11] :
Delphi сочтёт ваше любопытство необоснованным и выдаст ошибку при первой же попытке запустить программу.
Наверное, вы думаете, что значение sPascal[0] имеет тип byte и вы легко сможете узнать длину строки? - А вот и нет! Оно тоже имеет тип AnsiChar - как и символы строки (это значит, что для Delphi там находится не число, а символ, код которого равен длине строки).
Но, «если нельзя, но очень хочется, то можно», поэтому просто объявите новую переменную типа byte (или integer)
var n: byte;
и получите длину строки
n:= byte(sPascal[0]);
Или так:
n:= ord(sPascal[0]);
Хотя можно было не стараться, а просто расширить кругозор, найти функцию Length и воспользоваться ею:
n:= Length(sPascal);
Результат будет тем же самым, но программа лишится части своей загадочности, которая так же гипнотически действует на неокрепшие умы, как удав на кролика.
Если мы имеем право считать значение элемента sPascal[0], то мы можем и записать туда всякую ерунду.
Например, sPascal[0]: =#255;
(почему не просто 255?) - и длина строки увеличится до 255. Однако попробуйте что-нибудь записать в новую строку:
sPascal[11]:= 'Я';
- всё равно ничего не получится.
Для таких крамольных действий имеется и встроенная процедура SetLength (sPascal, 255);, которая действует аналогично и столь же бесполезна для коротких строк. Разве что вы захотите отсечь хвост у текущей строки. После выполнения процедуры SetLength (sPascal, 2);, останутся только 2 первых символа «Ра». Но это па вы можете станцевать и более красиво!
Если строка неполная, то есть короче максимальной длины, то значения символов за последним символом строки не определены! Обратимся к среднему рисунку. Первые 6 байтов памяти занимает строка Pascal, а дальше находятся ещё 4 «пустые» ячейки памяти. Не обольщайтесь, они не пустые, в них что-нибудь да есть!
Если вы присваивали строке значение Pascal123456, а потом Pascal, то в них останутся символы 1234, хотя длина строки теперь только 6 символов.
В других случаях эти ячейки могут занимать любые символы, поэтому никогда не используйте в своих программах значения символов за пределами присвоенного строке значения!
Если вы создаёте несколько строковых переменных равной длины, то удобнее объявить новые типы строк:
str5 = string[5];
str10 = string[10];
Type
Затем вы можете использовать эти типы для объявления строковых переменных ограниченной длины:
Var
s1, s2: str5; s3, s4, s5: str10;
Длинные ANSI-строки
AnsiString, или длинные строки, - основной тип строк в Delphi до последнего времени. Они могут содержать до 231 символов и занимать в памяти компьютера до 2 гигабайтов.
Для объявления длинных строк также используется зарезервированное слово AnsiString, но максимальная длина строки в скобках не указывается, поскольку длина этих строк изменяется динамически:
var sDelphi: AnsiString;
Но главное отличие длинных строк от коротких вовсе не в длине строки. Символы длинной строки, как и короткой, образуют в памяти последовательность символов типа AnsiChar, подобную массиву, после которых Delphi автоматически добавляет нулевой символ #0, который и является признаком конца строки. Кроме того, в четырёх байтах перед первым символом строки хранится текущая длина строки (и другая информация).
При изменении строки Delphi автоматически уменьшает или увеличивает размер памяти под строку так, чтобы она целиком могла там разместиться. Это значит, что вам не нужно заранее заботиться и высчитывать максимальную длину строки, в то время как при объявлении коротких строк это делать необходимо. Более того, даже пустая короткая строка всегда занимает в памяти заказанное число байтов. Интересный вопрос: сколько памяти занимает пустая длинная строка? Интересный ответ: нисколько, пустые строки в памяти не хранятся. Так что при объявлении длинной строки система выделяет только 4 байта для хранения адреса памяти, начиная с которого будет размещаться сама строка, и для пустой строки там будет константа nil.
Строки с нулевым символом в конце используются во многих языках программирования, например в C и C++, а также в Windows API, чем и вызвано появление таких строк в Delphi.
Таким образом, переменная типа AnsiString - это указатель на область памяти, в которой хранятся символы строки.
Как мы уже выяснили, при объявлении длинной строки место в памяти для неё не выделяется. Если же присвоить строке значение (не пустую строку!), например,
sDelphi:='Delphi';,
то в памяти выделяется столько байтов, чтобы поместились все символы заданной строки, плюс ещё 1 байт для нулевого символа в конце строки (не нуля, а нулевого символа типа AnsiChar!), плюс 4 байта для хранения длины строки (они занимают в памяти место перед первым символом строки), плюс ещё 4 байта для подсчёта ссылок на строку (нам это значение не потребуется) + 4 байта для хранения кодовой страницы.
В картинках это выглядит так

Очень похоже на короткую строку, к которой добавлен нулевой символ. Поэтому доступ к отдельным символам строки осуществляется точно так же, как и в случае коротких строк - по их индексу в массиве символов. Запомните, что индекс первого символа равен 1!
Таким образом, если
Var ch: AnsiChar;,
то после присвоения символьной переменной ch значения первого символа строки sDelphi
ch:= sDelphi[1];
её значение будет равно D, второго символа - е, шестого - i.
Если задать индекс, превышающий длину слова (в нашем случае 7 и больше), то значение переменной ch будет равно нулевому символу.
Длинные строки Юникода
Длинные строки Юникода типа UnicodeString - основной тип строк в последних версиях языка Delphi. Они очень похожи на длинные ANSI-строки, но для каждого символа строки в них отводится 2 байта, поэтому они могут содержать до 230 символов (вдвое меньше, чем строки ANSI) и занимать в памяти компьютера до 2 гигабайтов.
Для объявления длинных строк используются зарезервированные слова UnicodeString и string:
var sDelphi: string;
sDelphi2: UnicodeString;
Таким образом, ключевое слово string - это всего лишь псевдоним более длинного слова UnicodeString. Оба эти слова обозначают один и тот же тип данных.
Длинные строки Юникода имеют ту структуру и те же свойства, что и ANSI-строки, поэтому мы не будем повторяться.
Объявление глобальных строковых переменных и типизированных констант можно совмещать с их инициализацией, то есть присваивать им начальное значение:
var sDelphi: string ='Delphi'; const STR_DELPHI=’Delphi’;
Обратите внимание: нулевой символ после строки указывать не нужно, он будет добавлен автоматически!
Другие типы строк
Строки WideString - основной тип строк платформы .NET - очень похожи на длинные строки Юникода, но символы имеют тип не UnicodeChar, а WideChar (эти типы совместимы с типами string и char). Они занимают 2 байта памяти каждый. Этот тип строк используется исключительно для работы с объектной моделью компонентов для ОС Microsoft (Component Object Model, COM).
Строки типа PAnsiChar, PChar (PUnicodeChar) и PWideChar- также очень похожи на длинные строки Delphi (точнее, наоборот: длинные строки похожи на эти строки), они также заканчиваются нулевым символом, но в строках этих типов отсчёт индексов ведётся от нуля, а не от единицы (вечная головная боль от массивов в языке С).
Строки типа PChar используются не только в языках C и C++ (нам до этого и дела нет), но и в функциях Windows API, без которых обойтись не удастся.
Нам придётся использовать эти невнятные строки только для передачи строковых параметров в чуждые нам функции, поэтому подробно рассматривать их не будем.
Мешанина из строк разных типов
Таким образом, строки типа string сочетают в себе все достоинства коротких строк паскаля и строк PChar языка С, избегая при этом их недостатков. Им можно присваивать значения коротких строк и строк PChar:
var
sPascal: string[6]; sDelphi: string; szDelphi: PChar;
sPascal:= 'Pascal';
sDelphi:= sPascal; // значение переменной sDelphi = „Pascal" szDelphi:= 'Delphi0';
sDelphi:= szDelphi; // значение переменной sDelphi = „DelphiO"
Строки PChar вреднее строк Delphi: они не позволяют присвоить им всуе значения ни коротких, ни длинных строк. Для этого необходимо явно преобразовать их к типу PChar:
szDelphi:= PChar(string(sPascal)); szDelphi:= PChar(sDelphi);
Очень часто в приложениях для вывода сообщений используется функция Windows API Application.MessageBox:
function MessageBox(const Text: PChar;
const Caption: PChar;
Flags: Integer): Integer;
Первые два передаваемых ей параметра - текст сообщения и заголовок окна - имеют тип PChar. Если вы передаёте в функцию строковые константы (выражения), то просто укажите их при вызове функции:
Application.MessageBox('Мешок пустой!', 'Лото', MB_OK);
Если же ваши строки имеют тип string, то следует привести их к типу PChar:
Var
sMessage: string; sTitle: string;
sMessage:= ’Мешок пустой!’; sTitle:= ’ Лото’;
Application.MessageBox(PChar(sMessage), PChar(sTitle), MB_OK);
Впрочем, для таких случаев лучше сразу использовать тип PChar:
Var
sMessage: PChar; sTitle: PChar;
sMessage:= ’Мешок пустой!’; sTitle:= ’ Лото’;
Application.MessageBox(sMessage, sTitle, MB_OK);
Простые операции со строками
Строки типа string можно объединять в одну строку с помощью оператора + (оператор сцепления строк; сама операция называется конкатенацией строк). В качестве операнда можно использовать не только длинную, но и короткую строку, а также символьные и строковые переменные и кон¬станты.
Var
s1, s2, s3: string;
s1:= ’ string1’; s2:= ’ string2’;
s3:= ’ s3 = ’ + s1 + ’ + ’ + s2;
Значением строки s3 будет новая строка s3 = stringl + string2. Длина строки будет равна длине всех строковых операндов, входящих в выражение. В нашем случае - 22.
Эти и многие другие операции со строками можно выполнить с помощью процедур и функций, рассматриваемых на одном из следующих уроков.
Для сравнения строк используются операторы отношения (< > = <> <= >=). Результат сравнения имеет логический тип, то есть значение true или false.
Сравнение строк производится слева направо, начиная с первого символа. Пустая строка всегда меньше строки, имеющей символы:
’’ < ’Delphi’ (то есть значение этого выражения равно true, а выражения ’’ > ’Delphi’ - false).
Если очередные символы двух сравниваемых строк не совпали, то меньше та, код символа которой меньше:
’ab’ < ’ba’
’ab’ < ’ac’
’abc’ < ’abde’
Если строки имеют разную длину и все символы более короткой строки совпали с соответствующими символами более длинной строки, то меньше первая строка:
’abc’ < ’abed’
Если строки имеют одинаковую длину и все символы совпали, то строки равны:
’abc’ = ’abc’
’abcd’ = ’abed’
