Происходит это потому, что тип литерала зависит не только от его вида, но и оттого, в каком контексте он упомянут. Например, в предыдущем разделе мы видели, что литерал 'Xest'
мог иметь тип string
или PChar
в зависимости от того, какой переменной он присваивался. Там, где явного приведения типов нет, тип литерала однозначно определяется по типу формального параметра, и в обработчиках нажатия первых двух кнопок компилятор создает правильные литералы 'Text'
и 'А'
типа PChar
. Явное приведение литерала к типу PChar
меняет контекст, в котором литерал упомянут, и компилятор может сделать неправильный вывод о его типе. В обработчике третьей кнопки компилятор правильно понимает, что литерал имеет тип PChar
и генерирует код, полностью эквивалентный коду обработчика первой кнопки. А вот в случае приведения к типу PChar
литерала 'А'
компилятор принимает этот литерал не за строковый, а за символьный (т. е. за литерал типа Char
), состоящий из одного символа без всяких добавлений длины, символа #0
и т. п. При приведении выражения типа Char
к любому указателю (в том числе и к PChar
) оно рассматривается как выражение любого порядкового типа, и его численное значение становится численным значением указателя. В нашем случае это символ с кодом 65 ($41 в шестнадцатиричной записи), поэтому в функцию передается указатель $00000041. Такой указатель указывает на ту область виртуальной памяти, которая никогда не отображается на физическую память, поэтому его использование приводит к ошибке Access violation.
Итак, мы увидели, что явное приведение литерала к типу PChar
либо никак не отражается на генерируемом компилятором коде (в случае литералов из нескольких символов), либо приводит к генерированию заведомо некорректного кода (в случае односимвольных литералов). Если еще учесть, что приведение литералов к PChar
загромождает код, легко сделать вывод, что приводить литералы к PChar
не нужно, поскольку это потенциальный источник проблем и признак плохого оформления кода.
3.3.4. Сравнение строк
Для типов PChar
и AnsiString
, которые являются указателями, понятие равенства двух строк может толковаться двояко: либо как равенство указателей, либо как равенство содержимого памяти, на которую эти указатели указывают. Второй вариант предпочтительнее, т. к. он ближе к интуитивному понятию равенства строк. Для типа AnsiString
реализован именно этот вариант, т. е. сравнивать такие строки можно, ни о чем не задумываясь. Более сложные ситуации мы проиллюстрируем примером Companions. В нем одиннадцать кнопок, и обработчик каждой из них иллюстрирует одну из возможных ситуаций.
Начнем со сравнения двух строк типа PChar
(листинг. 3.19).
PChar
procedure TForm1.Button1Click(Sender: TObject);
var
P1, P2: PChar;
begin
P1:= StrNew('Test');
P2:= StrNew('Test');
if P1 = P2 then Label1.Caption:= 'Равно';
else Label1.Caption:= 'Не равно';
StrDispose(P1);
StrDispose(P2);
end;
В данном примере мы увидим надпись Не равно. Это происходит потому, что в этом случае сравниваются указатели, а не содержимое строк, а указатели здесь будут разные. Попытка сравнить строки с помощью оператора сравнения — весьма распространенная ошибка у начинающих. Для сравнения таких строк следует применять специальную функцию — StrComp
. Следующий пример, на первый взгляд, в плане сравнения ничем не отличается от только что рассмотренного (листинг 3.20).
PChar
, заданных одинаковыми литераламиprocedure TForm1.Button2Click(Sender: TObject);
var
P1, P2: PChar;
begin
P1:= 'Test';
P2:= 'Test';
if P1 = P2 then Label1.Caption:= 'Равно'
else Label1.Caption:= 'Не равно';
end;
Разница только в том, что строки хранятся не в динамической памяти, a в сегменте кода. Тем не менее на экране появится надпись Равно. Это происходит, разумеется, не потому, что сравнивается содержимое строк, а потому, что в данном случае два указателя оказываются равными. Компилятор поступает достаточно интеллектуально: видя, что в разных местах указаны литералы с одинаковым значением, он выделяет для такого литерала место только один раз, а потом помещает в разные указатели один адрес. Поэтому сравнение дает правильный (с интуитивной точки зрения) результат.
Такое положение дел только запутывает ситуацию со сравнением PChar
: написав подобный тест, человек может сделать вывод, что строки PChar
сравниваются не по указателю, а по значению, и действовать под руководством этого заблуждения.