Строки в Delphi, или Жестокие игры со словами

Вначале было слово, А потом слова, слова, слова!

Афоризм

Людям привычнее общаться с помощью слов, а не чисел, поэтому для вы­вода сообщений и другой текстовой информации почти в каждой про­грамме используются строки. О важности этого типа данных говорит и тот факт, что в паскале был только 1 тип строк, а в Delphi их огромное количе­ство!

Очень часто мы будем рассматривать строки как одномерный мас­сив символов типа Char, но при этом следует помнить, что строки это не разновидность массива, а отдельный тип данных. К ним можно применять операции, отсутствующие у массивов, например, конкатенацию («сложение» строк).

Короткие строки

Тот самый, «паскалевский», тип строк в Delphi называется ShortString - короткая строка. Он используется всё реже и оставлен только для совме­стимости с более ранними версиями Delphi. Объявляют короткие строки с помощью зарезервированного слова string, после которого, подобно мас­сивам, в квадратных скобках указывают максимальную длину строки в диапазоне от 1 до 255:

var sPascal: string[10];

Сразу после объявления длина строки sPascal равна нулю, хотя в памяти компьютера она всегда будет занимать 11 байтов. Почему 11, а не 10, как указано в объявлении?

 

Дело в том, что, действительно, для хранения каждого символа в памяти компьютера требуется 1 байт, что для 10 символов и составляет 10 бай­тов. А ещё 1 байт нужен для хранения текущей длины строки. Он имеет нулевой индекс в массиве, а символы строки - от 1 до заданной макси­мальной длины строки. Так как под хранение длины строки отводится 1 байт, то и длина строки не может превышать 255 символов.

Чтобы яснее представить, как хранятся строки в памяти компьютера, об­ратимся к Рис., верхний.

Строки в Delphi, или Жестокие игры со словами

Итак, при запуске программы процессор компьютера выполняет машин­ный код, соответствующий строке исходного кода 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, подобную массиву, после которых Del­phi автоматически добавляет нулевой символ #0, который и является признаком конца строки. Кроме того, в четырёх байтах перед первым символом строки хранится текущая длина строки (и другая информация).

 

При изменении строки Delphi автоматически уменьшает или увеличивает размер памяти под строку так, чтобы она целиком могла там разместить­ся. Это значит, что вам не нужно заранее заботиться и высчитывать мак­симальную длину строки, в то время как при объявлении коротких строк это делать необходимо. Более того, даже пустая короткая строка всегда занимает в памяти заказанное число байтов. Интересный вопрос: сколько памяти занимает пустая длинная строка? Интересный ответ: нисколько, пустые строки в памяти не хранятся. Так что при объявлении длинной строки система выделяет только 4 байта для хранения адреса памяти, начиная с которого будет размещаться сама строка, и для пустой строки там будет константа nil.

 

Строки с нулевым символом в конце используются во многих язы­ках программирования, например в C и C++, а также в Windows API, чем и вызвано появление таких строк в Delphi.

Таким образом, переменная типа AnsiString - это указатель на об­ласть памяти, в которой хранятся символы строки.

 

Как мы уже выяснили, при объявлении длинной строки место в памяти для неё не выделяется. Если же присвоить строке значение (не пустую строку!), например,

sDelphi:='Delphi';,
 

то в памяти выделяется столько байтов, чтобы поместились все символы заданной строки, плюс ещё 1 байт для нулевого символа в конце строки (не нуля, а нулевого символа типа AnsiChar!), плюс 4 байта для хранения длины строки (они занимают в памяти место перед первым символом строки), плюс ещё 4 байта для подсчёта ссылок на строку (нам это значе­ние не потребуется) + 4 байта для хранения кодовой страницы.

В картинках это выглядит так

Delphi

Очень похоже на короткую строку, к которой добавлен нулевой символ. Поэтому доступ к отдельным символам строки осуществляется точно так же, как и в случае коротких строк - по их индексу в массиве символов. За­помните, что индекс первого символа равен 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’

������� ������ ��� dle ������� ��������� ������

Помоги проекту! Расскажи друзьям об этом сайте: