Избежать этой ошибки поможет либо явное сравнение длины перед сравнением строк, либо приведение одной из сравниваемых строк к типу AnsiString
(второй аргумент при этом также будет приведен к этому типу). Следующий пример (листинг 3.27) дает правильный результат Не равно.
Листинг 3.27. Правильное сравнение переменных типа ShortString
и PChar
procedure TForm1.Button9Click(Sender: TObject);
var
P: PChar;
S: ShortString;
begin
P := StrAlloc(300);
FillChar(P^, 299, 'A');
P[299] := #0;
S[0] := #255;
FillChar(S[1], 255, 'A');
if string(S) = P then Label1.Caption := 'Равно'
else Label1.Caption := 'He равно';
StrDispose(P);
end;
Учтите, что конвертирование в AnsiString
— операция дорогостоящая в смысле процессорного времени (в этом примере будут выделены, а потом освобождены два блока памяти), поэтому там, где нужна производительность, целесообразнее вручную сравнить длину, а еще лучше вообще по возможности избегать сравнения строк разных типов, т.к. без конвертирования это в любом случае не обходится.
Теперь зададимся глупым, на первый взгляд, вопросом: если мы приведем строку AnsiString
к PChar
, будут ли равны указатели? Проверим это (листинг 3.28).
Листинг 3.28. Равенство указателей после приведения AnsiString
к PChar
procedure TForm1.Button10Click(Sender: TObject);
var
S: string;
P: PChar;
begin
S := 'Test';
P := PChar(S);
if Pointer(S) = P then Label1.Caption := 'Равно'
else Label1.Caption := 'Не равно';
end;
Вполне ожидаемый результат — Равно. Можно, например, перенести строку из сегмента кода в динамическую память с помощью UniqueString
— результат не изменится. Однако выводы делать рано. Рассмотрим следующий пример (листинг 3.29).
Листинг 3.29. Сравнение указателя после приведения пустой строки к PChar
procedure TForm1.Button11Click(Sender: TObject);
var
S: string;
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
разделяют одну область памяти, на неожиданные эффекты можно натолкнуться, если модифицировать содержимое строки в обход стандартных механизмов. Следующий код (листинг 3.30, пример SideChange на компакт-диске) иллюстрирует такую ситуацию.
Листинг 3.30. Побочное изменение переменной S2
при изменении
S1
procedure 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
.
Читать дальше
Конец ознакомительного отрывка
Купить книгу