ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 18.01.2025

Просмотров: 758

Скачиваний: 2

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

s[1] = 'a' – первый символ строки

s[2] = 'b' – второй символ строки

s[3] = 'c' – третий символ строки

s[4] = все что угодно :).

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

Теперь, я думаю, Вам станет ясно, почему для типа String[n] существует ограничение 0<=n<=255. Это весь набор значений, которые могут быть представлены в одном байте. На всякий случай, отмечу, что размерность переменной (n) определяет размер выделяемой под эту переменную памяти, и он не зависит от размера строки, которая там хранится. Т.е., если, например переменная описана как String[30], то даже если присвоить ей значение 'abcd', то размер этой переменной все равно останется 31 байт. Это иногда бывает очень удобно, например, при записи (чтении) таких переменных в файл (из файла).

В качестве примера работы со структурой хранения такого типа, приведу пример все той же функции возвращающей длину строки:

function StringLength (s :ShortString) :Cardinal;

begin

Result := Ord(s[0]);

end;

Как видите, это код существенно компактнее, и быстрее чем соответствующий код для pChar. Именно поэтому, он и применялся в языке Pascal.

Однако ясно, что ограничения переменных этого типа – максимальный размер строки 255 символов, и всегда максимальный размер занимаемой переменной памяти, вне зависимости от реально помещенной в нее строки, заставили разработчиков Delphi вести новые строковые типы. Вот к ним мы сейчас и перейдем.

AnsiString и WideString

Строки этого типа объединили в себе ряд качеств как строк ShortString и их байтом длины, так и строк завершающихся нулем (pChar). Последнее было необходимо, поскольку к моменту их появления, засилье C-ишников было уже так велико :), что большинство системных функций Windows API оперировало строками именно такого формата. А если серьезно, то такой формат строк хоть и более трудоемок в обработке, зато в принципе лишен ограниченности на максимальный размер строки. Ведь хранение размера строки всегда ограниченно какими-либо рамками: если хранить в байте, то ограничение 255 байт; если хранить в слове, то ограничение 65535 символов; и т.д. Однако, совсем отказываться от хранения текущей длинны строки в Borland не стали. Но об этом позже.


Напомню, что еще одним из недостатков статически размещаемых строк ShortString было "расточительство". Т.е. определив переменную такого типа, мы заранее резервировали под нее 256 байт памяти, поэтому, если мы один раз, во всей программе, присвоили ей значение 'abcd', то 251 байт памяти мы просто «пустили на ветер». Казалось бы, а зачем так определили, написали бы String[4], и ничего не потеряли бы. Но, когда мы пишем программу, мы же чаще всего не знаем что мы будем «класть» в эту переменную. Вот и определяем с запасом. Решением этой проблемы стало использование динамически размещаемых строк.


Так как они устроены?

Если вы обратили внимание, в таблице характеристик строковых типов, размер переменных AnsiString равен четырем байтам, как и у pChar. Это говорит нам о том, что переменные этого типа тоже являются указателями. Но, в отличие от pChar, в данном случае, это скрыто реализацией. Т.е. программист, работает с ними как с обычными строками: не надо выделять под них память, не надо заботиться о наличии завершающего нуля, не надо волноваться об изменении размеров строки и т.п. Всю эту работу берет на себя компилятор Delphi. Более того, он ещё и занимается оптимизациями. В частности, если например выполнить следующий код:

var s1, s2 :AnsiString;

...

s1 := 'abc';

s2 := s1;

то в памяти будет храниться лишь ОДИН экземпляр строки 'abc'! Во как.

Как же это происходит?

При выполнении первого оператора (присваивание s1), указатель, хранящийся в переменной s1 настраивается на область памяти в которой размещена строка 'abc'. При выполнении второго оператора, в s2 попадает тот же адрес! Т.е. в памяти при это присутствует лишь один экземпляр строки 'abc'. Да и зачем нам нужны дублирующие себя строки.

Не нужно программисту заботиться и об освобождении памяти занятой ненужными уже строками. Вот например есть такая процедура:

procedure ShowInteger;

var

s :AnsiString;

n :Integer;

begin

n := 123;

s := 'Значение переменной равно '+IntToStr(n);

ShowMessage(s);

end;

Здесь, при выполнении присваивания, Delphi создаст в памяти экземпляр строки 'Значение переменной равно 123', и присвоит адрес этой строки переменной s. Однако, при завершении процедуры, переменная s перестанет существовать – она же локальная. Значит, и экземпляр строки тоже уже не нужен – на него некому будет указывать. Вот поэтому, Delphi автоматически освободит память, выделенную под строку, как только, выполнение процедуры достигнет строки end. Более того, даже если во время выполнения процедуры возникнет исключительная ситуация, при которой "хвост" процедуры может и не выполниться, Delphi всё равно корректно освободит память для всех строк, распределенных в этой процедуре. Достигается это неявным использованием механизма подобного try - finally.

Казалось бы, всё замечательно. Но попробуем усложнить ситуацию.

var

gs :AnsiString; // глобальная переменная

procedure ShowInteger;

var

s :AnsiString;

n :Integer;

begin

n := 123;

s := 'Значение переменной равно '+IntToStr(n);


gs := s;

ShowMessage(s);

end;

Теперь, к моменту завершения процедуры, на экземпляр строки 'Значение переменной равно 123' уже ссылаются две переменные s и gs. И, несмотря на то, что область существования переменной s заканчивается, освобождать память, выделенную под строку на которую она указывает нельзя! Ведь позже, возможны обращения к переменной gs.

Для того, чтобы корректно обрабатывать такие ситуации, Delphi для каждой динамически распределенной строки ведет так называемый "счётчик ссылок". Т.е., как только он присваивает какой-либо из строковых (AnsiString) переменных ссылку на распределенную в памяти строку, то он увеличивает этот счетчик на единицу. Первоначально, при присваивании динамически распределённой строки, первой переменной (в примере s), значение этого счётчика устанавливается равным единице. В последствии, при прекращении жизни каждой строковой переменной, он уменьшает на 1 этот счетчик для той строки на которую она указывает. Если счётчик становится равным 0, то значит более нет строковых переменных, указывающих на данную строку. Значит, ее можно освобождать. Благодаря такому алгоритму, после присваивания в примере значения переменной gs, у строки 'Значение переменной равно 123' счетчик ссылок становится равным 2. Следовательно, при "умирании" переменной s, он декрементируется, и становится равным 1. Т.е. >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). Для того, что бы приблизиться к реальной жизни, перепишем приведённую структуру: