Урок 73 - Указатели

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

   Конечно, ячейка памяти - это структура размером в один байт. Объекты же, с которыми работает программа, в основном намного большего размера. Соответственно, указатель содержит в себе адрес только первого байта той области оперативной памяти компьютера, где располагается данный объект. Зная тип и соответственно размер объекта, можно прочитать его целиком.


Описание переменных - указателей

   Указатель описывается ключевым словом Pointer. По первой букве ключевого слова принято называть переменные - указатели с первой буквы P:

var 
	PIndexer: Pointer; //не типизированный указатель
   Ключевым словом Pointer задаётся так называемый не типизированный указатель, по аналогии с не типизированным файлом. Не типизированный указатель содержит просто адрес некой ячейки памяти. Объект, располагающийся начиная с этой ячейки, соответственно может быть совершенно любого типа и размера.

   Также в Delphi существуют и типизированные указатели. Они могут указывать на объект соответствующего типа и размера. Именно "могут указывать", потому что это по прежнему адрес одной - первой ячейки области памяти, где располагается объект. И далее его использование в программе зависит от программиста!
   Итак, типизированный указатель описывается ключевым словом означающим данный тип, перед которым ставится значок ^:

var 
	PInteger: ^Integer; //указатель на переменную целого типа
	PText: ^String; //указатель на переменную типа String
   Также можно описать любой свой тип, и задать переменную-указатель данного типа:

type 
	TMyType = Record
		X: Integer;
		S: String;
	end;

var 
	PMyPointer: ^TMyType;
   Естественно, можно определить тип, для описания через него переменных - указателей. Делается это в том числе и потому, что, например, в процедурах и функциях в качестве параметров можно использовать только заранее описанный тип данных. Например, следующее описание задаёт функцию с параметром, являющимся указателем ранее описанного типа, результат которой также является указателем данного типа:

type 
	TPMyPointer = ^TMyType;

function MyFunc(Point: TMyPointer): TMyPointer;
   Ну вот, надеюсь теперь вы не боитесь разнообразных вариантов описания таких переменных как указатели.


Использование переменных - указателей

   Использование указателей предполагает:
  1. присвоение значения указателю;
  2. изменение значения указателя;
  3. создание области памяти нужного типа и присвоение его адреса указателю;
  4. запись значения в область памяти, адресуемой указателем, и чтение из неё;
  5. освобождение области памяти, адресуемой данным указателем;
  6. применение переменных - указателей;
   Описанный указатель без присвоенного значения указывает на совершенно неопределённую ячейку памяти. Попытка использовать такой указатель чревата крахом программы. Поэтому всем указателям нужно явно присваивать значения.

   1. Указателю можно присвоить значение другого указателя. В результате оба указателя будут указывать на одну и ту же ячейку памяти. Также указателю можно присвоить пустое значение с помощью ключевого слова nil:

var 
	P1, P2: Pointer;
begin
	P1:=P2;//Присвоение указателю значения другого указателя
	P2:=nil;>//Присвоение указателю "пустого" значения
end;
   Указатель со значением nil не адресует никакой ячейки памяти и единственное, что с ним можно сделать - это сравнить с другим указателем или со значением nil.


   2. Значение типизированного указателя можно увеличить или уменьшить на размер области памяти, занимаемой объектом данного типа. Для этого служат операции инкремента и декремента:

type 
	P: ^Integer;
begin
	inc(P);	//увеличение значения указателя на 4 байта (размер типа Integer)
	dec(P);	//уменьшение значения указателя на 4 байта (размер типа Integer)
end;
   Попытка выполнить операциии inc либо dec с не типизированным указателем вызовет ошибку на этапе компиляции, так как компилятору неизвестно, насколько именно изменять значение указателя.


   3. Процедурой New можно создать область памяти соответствующего типа и присвоить её адрес указателю (инициировать указатель):

var 
	PInt: ^Integer;
begin
	New(PInt);  //Указатель PInt получает значение адреса созданной области памяти типа Integer
end;
   Поскольку с областью памяти, созданной с помощью процедуры New, не связана ни одна переменная, но там содержится реальное используемое значение, то можно считать, что это значение связано с некой "безымянной переменной". Обращаться к ней по имени переменной невозможно, а можно оперировать только используя указатель.

   Также задать указателю адрес объекта можно с помощью операции, называемой "взятие адреса", которая обозначается значком @. При этом создавать область памяти уже не нужно, так как она создана предварительным описанием данного объекта:

var 
	MyVar: TMyType;  //Описание переменной, при этом выделяется область памяти соответствующего размера
	P: ^TMyType;
beginr>
	P:=@MyVar;  //Указатель получает адрес области памяти, занимаемой переменной MyVar
end;
   4. Если область памяти уже создана и её адрес присвоен указателю, то в ячейку памяти, адресуемую данным указателем, можно записать значение объекта, соответствующего типу указателя. Для этого служит операция, обозначаемая также значком ^, стоящим после имени указателя, например: P^. Эта операция называется "разыменование указателя". Также с помощью этой операции со значением в данной ячейке памяти можно делать всё что нужно:

var 
	MyVar: Integer;
	P: ^Integer;
begin
	P:=@MyVar;  //Указатель получает адрес области памяти, занимаемой переменной MyVar
	P^:=2;  //В ячейку памяти по адресу переменной MyVar записывается значение 2
	Form1.Caption:=IntToStr(P^+3);  //В заголовке Формы появится число 5
end;
   С обычными переменными всё просто, но возникает вопрос, как получить значение по адресу указателя, если тип переменной - запись с несколькими полями? Аналогично:

type 
	TMyRec = Record
		N: Integer;
		S: String;
	end;

var 
	MyRec: TMyRec;
	PRec: ^TMyRec;

begin
	PRec:=@MyRec;  //Указатель получает адрес области памяти, занимаемой переменной MyRec
	PRec^.S:='Строка данных';  //С помощью указателя производится изменение строкового поля записи
	PRec^.N:=256;  //С помощью указателя производится изменение числового поля записи
end;
   А теперь уберите стрелку возле PRec: PRec.S:='Строка данных'; Вы увидите, что никакой ошибки ни компилятор, ни выполнение программы не показали! Выходит, выражения PRec^.S и PRec.S аналогичны.

   Далее, а как получить значение, если указатель это элемент массива, например:

var 
	PArray: Array[1..100] of ^Integer;
	X: Integer;
   Ни PArray^[10], ни PArray[10]^ не являются правильными выражениями. Ну конечно, нужно использовать скобки:

X:=(PArray[10])^;
   5. Память, выделенную процедурой New, всегда нужно явно освобождать. Освободить область памяти, адресуемую указателем, инициированным с помощью New, можно процедурой Dispose:

var 
	MyVar: TMyType;
	P: ^TMyType;
begin
	P:=@MyVar;
	Dispose(P);  //Освобождение области памяти, адресуемой указателем P
end;
   При выполнении процедуры Dispose указатель снова приобретает неопределённое значение, не равное даже nil, и его использование может привести к неопределённым результатам, даже к краху программы.


   6. Указатели особенно эффективны для манипуляций с объектами большого размера, например, записями с многочисленными полями. Для сортировки массива таких записей создаётся массив указателей, каждый из элементов которого указывает на соответствующий элемент массива записей. И вместо перемещения элемента массива записей перемещается соответствующий элемент массива указателей, на что требуется гораздо меньше времени.

Удачи!
Встретимся в следующем уроке!




Добавил(а): Joker1999obp Дата: 2013-11-11
а где это использовать?


    No results found.
Отменить.