Файл: Учебное пособие для студентов Авторы А. Н. Вальвачев, К. А. Сурков, Д. А. Сурков, Ю. М. Четырько Содержание Содержание 1.doc

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 04.05.2024

Просмотров: 151

Скачиваний: 4

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.


RemoveDir(const Dir: string): Boolean — удаляет каталог с диска; каталог должен быть пустым. Если удалить каталог невозможно, функция возвращет значение False.

RenameFile(const OldName, NewName: string): Boolean — изменяет имя файла. Если это сделать невозможно, функция возвращет значение False.

ChangeFileExt(const FileName, Extension: string): string — возвращает имя файла с измененным расширением.

ExcludeTrailingPathDelimiter(const S: string): string — отбрасывает символ-разделитель каталогов (символ ‘/’ — для Linux и ‘\’ — для Windows), если он присутствует в конце строки.

IncludeTrailingPathDelimiter(const S: string): string — добавляет символ-разделитель каталогов (символ ‘/’ — для Linux и ‘\’ — для Windows), если он отсутствует в конце строки.

ExpandFileName(const FileName: string): string — возвращает полное имя файла (с абсолютным путем) по неполному имени.

ExpandUNCFileName(const FileName: string): string — возвращает полное сетевое имя файла (с абсолютным сетевым путем) по неполному имени. Для операционной системы Linux эта функция эквивалентна функции ExpandFileName.

ExpandFileNameCase(const FileName: string; out MatchFound: TFilenameCaseMatch): string — возвращает полное имя файла (с абсолютным путем) по неполному имени, допуская несовпадения заглавных и строчных букв в имени файла для тех файловых систем, которые этого не допускают (например, файловая система ОС Linux).

ExtractFileDir(const FileName: string): string — выделяет путь из полного имени файла; путь не содержит в конце символ-разделитель каталогов.

ExtractFilePath(const FileName: string): string — выделяет путь из полного имени файла; путь содержит в конце символ-разделитель каталогов.

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. Это можно сделать двумя эквивалентными способами:

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 следует объявить так:

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. Динамическое распределение памяти

После объявления в секции 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;
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^ := P1^ + P2^; // в P1^ заносится 30

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

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

Поэтому следует четко придерживаться последовательности действий при работе с динамическими переменными:

  • создать динамическую переменную;

  • выполнить с ней необходимые действия;

  • разрушить динамическую переменную.

2.15.3. Операции над указателями

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

P3^ := 20;

P1^ := 50;

P3 := P1; // теперь P3^ = 50

После выполнения этого оператора оба указателя P1 и P3 будут указывать на один и тот же участок памяти. Однако будьте осторожны при операциях с указателями. Только что сделанное присваивание приведет к тому, что память, выделенная ранее для указателя P3, будет потеряна. Отдавая программе участок свободной памяти, система помечает его как занятый. После работы вся память, которая была выделена динамически, должна быть возвращена системе. Поэтому изменение значения указателя P3 без предварительного освобождения связанной с ним динамической переменной является ошибкой.


Использование одинаковых значений в разных указателях открывает некоторые интересные возможности. Так после оператора P3 := P1 изменение значения переменной P3^ будет равносильно изменению значения P1^.

P3^ := 70; // теперь P3^ = P1^ = 70

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

Указатели можно сравнивать. Так уж сложилось, что понятие больше-меньше в адресации памяти разных моделей компьютеров может иметь противоположный смысл. Из-за этого операции сравнения указателей ограничены двумя: сравнение на равенство или неравенство.

if P1 = P2 then ... // Указатели ссылаются на одни и те же данные
if P1 <> P2 then ... // Указатели ссылаются на разные данные

Чаще всего операции сравнения указателей используются для проверки того, связан ли указатель с динамической переменной. Если еще нет, то ему следует присвоить значение nil (зарезервированное слово):

P1 := nil;

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

if P1 = nil then New(P1);

или

if P1 <> nil then Dispose(P1);

2.15.4. Процедуры GetMem и FreeMem

Для динамического распределения памяти служат еще две тесно взаимосвязанные процедуры: GetMem и FreeMem. Подобно New и Dispose, они во время вызова выделяют и освобождают память для одной динамической переменной:

GetMem(var P: Pointer; Size: Integer) — создает в динамической памяти новую динамическую переменную c заданным размером Size и присваивает ее адрес указателю P. Переменная-указатель P может указывать на данные любого типа.

FreeMem(var P: Pointer [; Size: Integer] ) — освобождает динамическую переменную.

Если в программе используется этот способ распределения памяти, то вызовы GetMem и FreeMem должны соответствовать друг другу. Обращения к GetMem и FreeMem могут полностью соответствовать вызовам New и Dispose.

Пример:

New(P4); // Выделить блок памяти для указателя P4

...

Dispose(P4); // Освободить блок памяти

Следующий отрывок программы даст тот же самый результат:

GetMem(P4, SizeOf(ShortString)); // Выделить блок памяти для P4

...

FreeMem(P4); // Освободить блок памяти