Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
delphi.pdf
Скачиваний:
191
Добавлен:
24.02.2016
Размер:
6.84 Mб
Скачать

ExtractRelativePath(const BaseName, DestName: string): string —

возвращает относительный путь к файлу DestName, отсчитанный от каталога BaseName. Путь BaseName должен заканчиваться символомразделителем каталогов.

ExtractFileDrive(const FileName: string): string — выделяет имя диска

(или сетевого каталога) из имени файла. Для операционной системы Linux функция возвращает пустую строку.

ExtractFileExt(const FileName: string): string — выделяет расширение файла из его имени.

ExtractFileName(const FileName: string): string — выделяет имя файла,

отбрасывая путь к нему.

IsPathDelimiter(const S: string; Index: Integer): Boolean — проверяет,

является ли символ S[Index] разделителем каталогов.

MatchesMask(const Filename, Mask: string): Boolean — проверяет,

удовлетворяет ли имя файла заданной маске.

2.15. Указатели

2.15.1. Понятие указателя

Все переменные, с которыми мы имели дело, известны уже на этапе компиляции. Однако во многих задачах нужны переменные, которые по мере необходимости можно создавать и удалять во время выполнения программы. С этой целью в языке Delphi организована поддержка так называемых указателей, для которых введен специальный тип данных

Pointer.

Не секрет, что любая переменная в памяти компьютера имеет адрес. Переменные, которые содержат адреса других переменных, принято называть указателями. Указатели объявляются точно так же, как и обычные переменные:

var

P: Pointer; // переменная-указатель

N: Integer; // целочисленная переменная

Переменная P занимает 4 байта и может содержать адрес любого участка памяти, указывая на байты со значениями любых типов данных: Integer, Real, string, record, array и других. Чтобы инициализировать переменную P, присвоим ей адрес переменной N. Это можно сделать двумя эквивалентными способами:

131

P := Addr(N); // с помощью вызова встроенной функции Addr

или

P := @N;

// с помощью оператора @

В дальнейшем мы будем использовать более краткий и удобный второй способ.

Если некоторая переменная P содержит адрес другой переменной N, то говорят, что P указывает на N. Графически это обозначается стрелкой, проведенной из P в N (рисунок 2.12 выполнен в предположении, что N имеет значение 10):

Рисунок 2.12. Графическое изображение указателя P на переменную N

Теперь мы можем изменить значение переменной N, не прибегая к идентификатору N. Для этого слева от оператора присваивания запишем не N, а P вместе с символом ^:

P^ := 10; // Здесь умышленно опущено приведение типа

Символ ^, записанный после имени указателя, называется оператором доступа по адресу. В данном примере переменной, расположенной по адресу, хранящемуся в P, присваивается значение 10. Так как в переменную P мы предварительно занесли адрес N, данное присваивание приводит к такому же результату, что и

N := 10;

Однако в примере с указателем мы умышленно допустили одну ошибку. Дело в том, что переменная типа Pointer может содержать адреса переменных любого типа, не только Integer. Из-за сильной типизации языка Delphi перед присваиванием мы должны были бы преобразовать выражение

P^ к типу Integer:

Integer(P^) := 10;

Согласитесь, такая запись не совсем удобна. Для того, чтобы сохранить простоту и избежать постоянных преобразований к типу, указатель P следует объявить так:

132

var

P: ^Integer;

При такой записи переменная P по прежнему является указателем, но теперь ей можно присваивать адреса только целых переменных. В данном случае указатель P называют типизированным, в отличие от переменных типа Pointer, которые называют нетипизированными указателями. При использовании типизированных указателей лучше предварительно вводить соответствующий указательный тип данных, а переменные-указатели просто объявлять с этим типом. Поэтому предыдущий пример можно модифицировать следующим образом:

type

PInteger = ^Integer; var

P: PInteger;

PInteger — это указательный тип данных. Чтобы отличать указательные типы данных от других типов, будем назначать им идентификаторы, начинающиеся с буквы P (от слова Pointer). Объявление указательного типа данных является единственным способом введения указателей на составные переменные, такие как массивы, записи, множества и другие. Например, объявление типа данных для создания указателя на некоторую запись TPerson может выглядеть так:

type

PPerson = ^TPerson; TPerson = record

FirstName: string[20]; LastName: string[20]; BirthYear: Integer;

end;

var

P: PPerson;

Переменная P, описанная с типом данных PPerson, является указателем и может содержать адрес любой переменной типа TPerson. Впредь все указатели мы будем вводить через соответствующие указательные типы данных. Типом Pointer будем пользоваться лишь тогда, когда это действительно необходимо или оправдано.

2.15.2. Динамическое распределение памяти

133

После объявления в секции var указатель содержит неопределенное значение. Поэтому переменные-указатели, как и обычные переменные, перед использованием нужно инициализировать. Отсутствие инициализации указателей является наиболее распространенной ошибкой среди новичков. Причем если использование обычных неинициализированных переменных приводит просто к неправильным результатам, то использование неинициализированных указателей обычно приводит к ошибке "Access violation" (доступ к неверному адресу памяти) и принудительному завершению приложения.

Один из способов инициализации указателя состоит в присваивании ему адреса некоторой переменной соответствующего типа. Этот способ мы уже рассмотрели. Второй способ состоит в динамическом выделении участка памяти под переменную соответствующего типа и присваивании указателю его адреса. Работа с динамическими переменными и есть основное назначение указателей. Размещение динамических переменных производится в специальной области памяти, которая называется Heap (куча). Ее размер равен размеру свободной памяти компьютера.

Для размещения динамической переменной вызывается стандартная процедура

New(var P: Pointer);

Она выделяет требуемый по размеру участок памяти и заносит его адрес в переменную-указатель P. В следующем примере создаются 4 динамических переменных, адреса которых присваиваются переменным-указателям P1, P2, P3 и P4:

program Console;

{$APPTYPE CONSOLE}

uses SysUtils;

type

PInteger = ^Integer;

PDouble = ^Double; PShortString = ^ShortString;

var

P1, P2: PInteger; P3: PDouble;

P4: PShortString;

134

begin New(P1); New(P2); New(P3); New(P4);

...

end.

Далее по адресам в указателях P1, P2, P3 и P4 можно записать значения:

P1^ := 10;

P2^ := 20;

P3^ := 0.5;

P4^ := 'Hello!';

В таком контексте динамические переменные P1^, P2^, P3^ и P4^ ничем не отличаются от обычных переменных соответствующих типов. Операции над динамическими переменными аналогичны подобным операциям над обычными переменными. Например, следующие операторы могут быть успешно откомпилированы и выполнены:

if P1^

<

P2^ then

 

в

P1^ заносится

30

P1^ :=

P1^ + P2^; //

P3^ :=

P1^;

//

в

P3^ заносится

30.0

После работы с динамическими переменными необходимо освободить занимаемую ими память. Для этого предназначена процедура:

Dispose(var P: Pointer);

Например, в приведенной выше программе явно не хватает следующих строк:

Dispose(P4);

Dispose(P3);

Dispose(P2);

Dispose(P1);

После выполнения данных утверждений указатели P1, P2, P3 и P4 опять перестанут быть связаны с конкретными адресами памяти. В них будут случайные значения, как и до обращения к процедуре New. Не стоит делать попытки присвоить значения переменным P1^, P2^, P3^ и P4^, ибо в противном случае это может привести к нарушению нормальной работы программы.

135

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