Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Delphi.doc
Скачиваний:
15
Добавлен:
19.03.2015
Размер:
176.13 Кб
Скачать

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

Здесь, все как обычно, и просто и сложно.

Преобразование между "настоящими" строковыми типами 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 <> p3

s = ''

p1 <> p2 = p3

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

pChar(s)

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

Pointer(s)

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

@(s[1])

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

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

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

procedure X1;

var

s :AnsiString;

p :PChar;

begin

s := 'abcd';

p := PChar(s);

p^ := 'X'; // <-

ShowMessage(s);

end;

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

procedure X1;

var

s :AnsiString;

p :PChar;

begin

s := 'abcd';

p := @(s[1]);

p^ := 'X';

ShowMessage(s);

end;

будет выполнен без ошибок, выведя строку 'Xabcd'. Также как и код:

procedure X1;

var

s :AnsiString;

p :PChar;

begin

s := 'abcd';

s[2] := 'b';

p := PChar(s);

p^ := 'X';

ShowMessage(s);

end;

Рассматривая преобразование AnsiString в pChar (получение адреса строки) нельзя не упомянуть ещё одну серьезную проблему – область действия для полученного таким путем указателя.

Как вы, наверное, еще помните, Delphi ведет учет всех ссылок на каждый экземпляр динамически распределенной строки. И на основании этого принимает решение об освобождении памяти занимаемой экземпляром строки. Но, это касается только ссылок хранящихся в переменных AnsiString. Ссылки, полученные преобразованием в pChar, не учитываются. Вот демонстрация того, как это может сказаться на поведении программы. Пусть есть следующая функция:

function IntToPChar (n :Integer) :pChar;

var s :AnsiString;

begin

s := IntToStr(n);

Result := PChar(s);

end;

Казалось бы, всё написано правильно. Однако если выполнить такой оператор:

ShowMessage(IntToPChar(100));

То, вместо ожидаемого окна со строкой '100', мы либо получим абракадабру, либо и того хуже – ошибку AV. А все почему? Да, просто, единственным учтённым указателем на экземпляр строки, полученный от IntToStr, будет s. Поэтому, когда его область действия прекращается по выходу из процедуры, экземпляр строки будет уничтожен. А не учтённый указатель, возвращаемый функцией IntToPChar, после этого станет указывать "куда бог пошлёт". Точнее, на то место в памяти, где недавно была строка. Если же переменная s будет объявлена на глобальном уровне, то будет нормально, но всё равно возможны ошибки. Например:

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

...

function IntToPChar (n :Integer) :pChar;

begin

s := IntToStr(n);

Result := PChar(s);

end;

...

var p100, p200 :pChar;

begin

p100 := IntToPChar(100);

p200 := IntToPChar(200);

...

После второго выполнения функции IntToPChar, указатель p100, опять будет указывать "куда бог пошлет", Почему, разберитесь сами :).

В завершении этого раздела, я хочу "напугать" Вас:

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

Знание того, как это все работает, думаю, Вам поможет.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]