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