Чтение онлайн

ЖАНРЫ

О чём не пишут в книгах по Delphi

Григорьев Антон Борисович

Шрифт:

Происходит это потому, что тип литерала зависит не только от его вида, но и оттого, в каком контексте он упомянут. Например, в предыдущем разделе мы видели, что литерал

'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).

Листинг 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).

Листинг 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
сравниваются не по указателю, а по значению, и действовать под руководством этого заблуждения.

Раз уж мы столкнулись с такой особенностью компилятора, немного отвлечемся от сравнения строк и "копнем" этот вопрос немного глубже. В частности, выясним, распространяется ли "интеллект" компилятора на литералы типа

AnsiString
(листинг 3.21).

Листинг 3.21. Сравнение переменных типа
AnsiString
как указателей

procedure TForm1.Button3Click(Sender: TObject);

var

 S1, S2: string;

begin

 S1 := 'Test';

 S2 := 'Test';

 if Pointer(S1) = Pointer(S2) then Label1.Caption := 'Равно'

 else Label1.Caption := 'He равно';

end;

В этом примере на экран будет выведено Равно. Как мы видим, указатели равны, т.е. и здесь компилятор проявил "интеллект". 

Рассмотрим чуть более сложный случай (листинг 3.22).

Листинг 3.22. Сравнение переменных
AnsiString
и
PChar
как указателей

procedure TForm1.Button4Click(Sender: TObject);

var

 P: PChar;

 S: string;

var

 S := 'Test';

 P := 'Test';

 if Pointer(S) = P then Label1.Caption := 'Равно'

 else Label1.Caption := 'He равно';

end;

В этом случае указатели окажутся не равны. Действительно, с формальной точки зрения литерал типа

AnsiString
отличается от литерала типа
PChar
: в нем есть счетчик ссылок (равный -1) и длина. Однако если забыть с существовании этой добавки, эти два литерала одинаковы: четыре значащих символа и один
#0
, т.е. компилятор, в принципе, мог бы обойтись одним литералом. Тем не менее на это ему "интеллекта" уже не хватило. Рассмотрим еще один пример: сравнение строк по указателям (листинг 3.23).

Листинг 3.23. Сравнение глобальных переменных типа
AnsiString
как указателей

var

 GS1, GS2: string;

procedure TForm1.Button5Click(Sender: TObject);

begin

 GS1 := 'Test';

 GS2 := 'Test';

 if Pointer(GS1) = Pointer(GS2) then Label1.Caption := 'Равно';

 else Label1.Caption := 'Не равно';

end;

Поделиться с друзьями: