P: PChar;
begin
S:= '';
P:= PChar(S);
if Pointer(S) = P then Label1.Caption: = 'Равно'
else Label1.Caption:= 'He равно';
end;
От предыдущего он отличается только тем, что строка S
AnsiString
к типу PChar
на самом деле не является приведением типов. Это скрытый вызов функции _LStrToPChar
, и сделано так для того, чтобы правильно обрабатывать пустые строки.Значение ''
AnsiString
означает, что память для нее вообще не выделена, а указатель имеет значение nil
. Для типа PChar
пустая строка — это ненулевой указатель на символ #0
. Нулевой указатель также может рассматриваться как пустая строка, но не всегда — иногда это рассматривается как отсутствие какого бы то ни было значения, даже пустого (аналог NULL в базах данных). Чтобы решить это противоречие, функция _LStrToPChar
проверяет, пустая ли строка хранится в переменной, и, если не пустая, возвращает этот указатель, а если пустая, то возвращает не nil
, а указатель на символ #0
, который специально для этого размещен в сегменте кода. Таким образом, для пустой строки PChar(S) <> Pointer(S)
, потому что приведение строки AnsiString
к указателю другого типа — это нормальное приведение типов без дополнительной обработки значения.3.3.5. Побочное изменение
Из-за того, что две одинаковые строки AnsiString
S2
изменении
S1procedure TForm1.Button1Click(Sender: TObject);
var
S1, S2: string;
P: PChar;
begin
S1:= 'Test';
UniqueString(S1);
S2:= S1;
P:= PChar(S1);
P[0]:= 'F';
Label1.Caption:= S2;
end;
В этом примере требует комментариев процедура UniqueString
S1
хранилась в динамической памяти, а не в сегменте кода, иначе мы получили бы Access violation, как и во втором случае рассмотренного ранее примера Constants (см. листинг 2.17).В результате работы этого примера на экран будет выведено не Test
, a Fest, хотя значениеS2
, казалось бы, не должно меняться, потому что изменения, которые мы делаем, касаются только S1
. Но более внимательный анализ подсказывает объяснение: после присваивания S2:= S1
счетчик ссылок строки становится равным двум, а сама строка разделяется двумя указателями: S1
и S2
. Если бы мы попытались изменить непосредственно S2
, то сначала была бы создана копия этой строки, а потом сделаны изменения в этой копии, а оригинал, на который указывала бы S2
, остался без изменений. Но, использовав PChar
, мы обошли механизм копирования, поэтому строка осталась в единственном экземпляре, и изменения затронули не только S1
, но и S2
.В данном примере все достаточно очевидно, но в более сложных случаях разработчик программы может и не подозревать, что строка, с которой он работает, разделяется несколькими переменными. Справка Delphi советует сначала обеспечить уникальность копии строки с помощью UniqueString
PChar
, если в этом есть необходимость.Рассмотрим еще один пример, практически не отличающийся от предыдущего (листинг 3.31).
S2
S1
procedure TForm1.Button2Click(Sender: TObject);
var
S1, S2: string;
P: PChar;
begin
S1:= 'Test';
UniqueString(S1);
S2:= S1;
P:= @S1[1];
P[0]:= 'F';
Label1.Caption:= S2;
end;
В этом случае на экран будет выведено Test
, т. е. побочного изменения переменной не произойдёт, хотя переменнаяS1
по прежнему изменяется в обход стандартных механизмов Delphi.