Указатели в Delphi

Указатель — это всего лишь специальная переменная. В отличие от обычных переменных, которые хранят значение определенного типа, указатель хранит ад­рес ячейки памяти. Название "указатель" обусловлено тем, что указатель не со­держит конкретного значения, а указывает на ячейку памяти, в которой хранится 1 нужное значение.

В Delphi существует два типа указателей: типизированные и нетипизированные (общие). Типизированные указатели можно использовать только с перемен­ными конкретного типа, в то время как нетипизированные указатели могут указы­вать на любые данные.

 

Закажи видеокурс по Delphi и получи 106 видеоуроков. Детальное объяснение каждого момента программирования. Кликни на картинку:

ЗАКАЗАТЬ


Для объявления типизированного указателя перед идентификатором типа дан­ных потребуется поместить символ ^:

 

var  I:  Integer;   
{ ordinary static variable } 
P:^Integer;   
{ a typed pointer }

 

Указатель может использоваться для указания на любую ячейку памяти, ко­торая содержит целочисленное значение.

 

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

Чтобы инициализировать указатель, ему следует присвоить адрес ячейки па­мяти. Для чтения адреса переменной можно использовать операцию @ или функ­цию Addr:

 

Р := Addr(I);

Р:= 01;

 

Теперь указатель Р указывает на ячейку памяти переменной I. Если требуется выяснить точное местоположения переменной I в памяти, необходимо преобразо­вать тип указателя в целое число и отобразить его на экране:

Р := 01

WriteLn(Integer(Р));

 После того, как указателю присвоен адрес, указатель можно использовать для изменения значения переменной, на которую он указывает. Для получения досту­па к переменной, на которую указывает указатель, потребуется разыменовать ука­затель, записав символ ^после имени указателя, как продемонстрировано в лис­тинге 9.1.

Листинг 9.1. Использование простого типизированного указателя 


01.program Project1;

02.{$APPTYPE CONSOLE}
03.uses
04.SysUtils;
05.var
06.I: Integer; { ordinary static variable }
07.P: ^Integer; { a typed pointer }
08.begin
09.I := 101;
10.WriteLn(I); { 101 }
11.P := @I; { point to I }
12.P^ := 202{ change I to 202 }
13.WriteLn(P^); { 202 }
14.ReadLn; end.

 

Чтобы изменить ячейку, на которую указывает указатель, указателю необходи­мо присвоить адрес. Однако для изменения значения, хранящегося в ячейке памя­ти, на которую указывает указатель, потребуется разыменовать указатель. Проце­дура WriteLn правильно отображает значение, поскольку указатель Р является типизированным и после разыменования в действительности представляет цело­численное значение. 


 Вторым типом указателя в Delphi является Pointer. Pointer представляет собой тип нетипизированного указателя, который может использоваться для указания на переменную любого типа данных. Чтобы работать со значением, на которое указы­вает нетипизированньил указатель, вначале нетипизированный указатель нужно привести к другому типу указателя и выполнить его разыменование. Приведение к другому типу указателя не представляет особой сложности, поскольку в Delphi каж­дый тип данных уже обладает соответствующим типом указателя. Например, указа­телем на переменную типа Char является PChar, указателем на строку — PString. указателем на целочисленную переменную— PInteger и так далее.

В листинге 9.2 демонстрируется использование типа Pointer и его приведение к другим типам указателей.


Листинг 9.2. Использование нетилизированного указателя  

program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
var
I: Integer;
C: Char;
P: Pointer; { untyped pointer }
begin
I := 2004;
C := 'd';
P := @I; { point to I }
{ typecast to an integer pointer, dereference and increment }
Inc(PInteger(P)^);
WriteLn('I = ', PInteger(P)^);
P := @C; { point to C }
{ typecast to a char pointer, dereference and convert to 'D' }
PChar(P)^ := Chr(Ord(PChar(P)^) - 32);
WriteLn('C = ', PChar(P)^);
ReadLn;
end.

 

Чтобы использование указателей было полностью безопасным, следует убе­диться, что указатель ссылается на существующую ячейку памяти. Для того чтобы указатель ссылался на несуществующую ячейку памяти, можно воспользоваться зарезервированным словом nil. Оно представляет собой постоянное значение, отмечающее несуществующие ячейки памяти. Указатель nil не ссылается ни на одну из ячеек памяти:

 

program Project1; {SAPPTYPE CONSOLE} 
uses 
SysUtils; var 
I: Integer = 2005
P:^Integer  @I;   { автоматически указывает на I  } begin 
WriteLn(P^);   {  P указывает на I  } 
P := nil;   {  P указывает на несуществующую ячейку } ReadLn; end.

 

 

 Динамические переменные — это переменные, для которых память распреде­ляется в куче (называемой также свободным хранилищем). Куча представляет со­бой область основной памяти за исключением стека. Размер кучи ограничен толь­ко объемом виртуальной памяти компьютера (физическая память плюс файл под­качки).

Резервирование памяти для динамических переменных выполняется с помо­щью процедуры New. которая принимает единственный параметр указателя. Про­цедура New резервирует соответствующий объем памяти для хранения значения и присваивает переданному указателю адрес зарезервированного блока памяти. При выделении памяти для указателя указатель не ссылается на другую перемен­ную, а получает собственную область памяти:

var 

Р: PInteger; 
begin 
New(P);   { создание динамической целочисленной переменной } 
end.
 
Я записал для Вас подробный видеоурок по работе с динамическими массивами - так гораздо лучше воспринимается информация. Посмотрите видео и все поймете:
СКАЧАЙТЕ ПРЯМО СЕЙЧАС БЕСПЛАТНО ЦЕЛЫЙ ВИДЕОКУРС ПО DELPHI - СКАЧАТЬ!

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

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

 

var 
Р:  PInteger; begin 
New(P);   { создание динамической целочисленной переменной ) 
{ использование динамической переменной } 
Р^ := 2005; WriteLn(Р<sup>А</sup>); 
Dispose(Р);   { освобождение памяти } 
ReadLn; 
end.
 

 Строки и динамические массивы — это динамические типы, память для которых распределяется в куче, но управляется Delphi автоматически. Для резервирования памяти под динамический массив используется процедураSetLength. Для освобо­ждения памяти, занятой массивом, можно либо вызвать процедуру Finalize, либо присвоить массиву значение nil.

 

 

При работе со строковой переменной память резервируется во время присвое­ния текста переменной. Можно также воспользоваться процедурой SetLength для явного определения длины строковой переменной. Для освобождения памяти, за­нятой строкой, строковой переменной потребуется присвоить нулевую строку. 

var 
s: string
begin 
s := 'Dynamic allocation'
( освобождение памяти )
s : = end.

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

В следующем примере демонстрируется применение процедуры BlockRead для считывания всего файла в динамически зарезервированный блок памяти (резуль­тат показан на рис. 9.1).


Листинг 9.3. Загрузка файла в динамически зарезервированный блок памяти  


 

Код загрузки файла в память определен в процедуре ReadFile. Прежде чем ре­зервировать новый блок памяти, процедура должна определить, указывает ли ука­затель, переданный как параметр Р, на блок памяти. Если да, то необходимо осво­бодить старый блок памяти. В противном случае возникнет серьезная утечка па­мяти.

 

if  Р <> nil then FreeMem(Р);  { освобождение от старых данных }

 

Процедура GetMem использует функцию FileSize для определения точного объема памяти (в байтах), необходимого для хранения всего файла. После того как память зарезервирована, процедура начинает считывание файла в память.

Рис. 9.1. Приведение типа данных блока памяти в строку

 

Вероятно, наиболее необычной частью процедуры является локальный указа­тель Buf ferPos. Прежде чем начать считывание файла в цикле while, процедура присваивает указатель указателю Bu f fer Pos:

 

BufferPos := P;

 

Указатель BufferPos указывает на точный адрес памяти, по которому проце­дура BlockRead должна сохранить 1024байта, считываемые из файла:

 

BlockRead(Src, BufferPos",   1024, BytesRead);

 

После того как процедура BlockRead сохранит 1024 байта в блоке памяти, не­обходимо обновить указатель BufferPos.Если этого не сделать, процедура BlockRead выполнит считывание всего файла, но при этом перезапишет старые данные последними 1024 байтами из файла.

По завершении считывания 1024 байт процедурой BlockRead указатель BufferPos потребуется переустановить для указания на следующие 1024 байта памяти. Это выполняется путем приведения типа указателя BufferPos к Integer и последующего увеличения адреса указателя на число байт, считанных из файла:

 

Ine(Integer(BufferPos), BytesRead);

 

Без использования указателя BufferPos эта процедура не работала бы. Указа­тель BufferPos позволяет выполнять над параметром-переменной типа указателя действия, которые в противном случае были бы недопустимыми — изменять ад­рес, на который он указывает. Если бы мы изменяли адрес, указываемый исход­ным указателем, каждый вызов процедуры BlockRead уменьшал бы объем доступ­ной памяти на 1024 байта, поскольку следующая строка перемещала бы указатель на 1024 байта вглубь блока памяти.

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

WriteLn(string(FilePtr)); 


 Указатели можно использовать для доступа к элементам массива. Для считы­вания адреса первого элемента массива {указания на первый элемент) можно вос­пользоваться следующим кодом:

var 

Numbers: array[1..20of Integer; PI : PInteger; 
begin 
PI  := ^Numbers; end.

 

Для указания на другой элемент массива достаточно увеличить значение ука­зателя с помощью процедуры Inc.Однако перед его передачей процедуре Ine тип указателя потребуется привести к целочисленному:

 

Inс(Integer(PI));   { перемещение к следующему элементу }

 

Процедура Ine увеличивает значение указателя не на 1, а на SizeOf ( ТипЭлемента)В данном случае она увеличивает значение указателя на 4 (SizeOf (Integer) ), тем самым выполняя перемещение на байта вглубь памяти к адресу следующего эле­мента массива. Листинг 9.4 демонстрирует доступ к элементам массива с помо­щью указателя.

 

Листинг 9.4. Доступ к массиву через указатель 


01.program Project1;
02.{$APPTYPE CONSOLE}
03.uses
04.SysUtils;
05.var
06.Numbers: array[1..20of Integer;
07.I: Integer;
08.PI: PInteger;
09.begin
10.{ standard access }
11.for I := Low(Numbers) to High(Numbers) do
12.begin
13.Numbers := I;
14.WriteLn(Numbers);
15.end;
16.{ pointer access }
17.PI := @Numbers; { point to first element in Numbers }
18.for I := Low(Numbers) to High(Numbers) do
19.begin
20.PI^ := I;
21.WriteLn(PI^);
22.Inc(Integer(PI)); { move to next element }
23.end;
24.ReadLn; end.
dle

Помоги проекту! Расскажи друзьям об этом сайте: