Меню Закрыть

Delphi указатель на структуру

Содержание

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

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

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

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

var PIndexer: Pointer; //нетипизированный указатель

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

Также в 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. освобождение области памяти, адресуемой данным указателем.

Описанный указатель без присвоенного значения указывает на совершенно неопределённую ячейку памяти. Попытка использовать такой указатель чревата крахом программы. Поэтому всем указателям нужно явно присваивать значения.

Читайте также:  Что за кнопка нумпад

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; //Задаётся указатель соответствующего типа
begin
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;

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

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]^ не являются правильными выражениями. Ну конечно, нужно использовать скобки:

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

var MyVar: TMyType;
P: ^TMyType;
begin
P:=@MyVar;
Dispose(P); //Освобождение области памяти, адресуемой указателем P
end;

При выполнении процедуры Dispose указатель снова приобретает неопределённое значение, не равное даже nil, и его использование может привести к неопределённым результатам, даже к краху программы.

Указатели в Delphi

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

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

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

ЗАКАЗАТЬ

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

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

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

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

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

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

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

01. program Project1;

Читайте также:  Hp pavilion dv7 6102er

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

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

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

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

В данной статье попробую разобраться с темой указателей. Попробую понять их на самом простом уровне, так как ранее очень мало их использовал.

Что такое указатели и зачем они нужны?

В браузере у нас есть страница, например та, которую вы читаете сейчас, а есть адресная строка со ссылкой на данную страницу, скажем http://digital-flame.ru/2016/02/09/delphi-ukazateli-po-prostomu/. Если проводить аналогию, то указатель это ссылка на страницу, а сама страница это данные, на которые ссылается указатель.

Теперь перенесемся в мир Windows – есть адресное пространство программы. В этом адресном пространстве хранятся данные, используемые программой. Когда мы объявляем переменную, это адресное пространство задействуется и используется какая-то его часть.

Если переменная занимает немного места, то производить с ней операции достаточно легко, скажем перемещать из одного места адресного пространства в другое. Так переменная integer занимает всего 4 байта и для подавляющего большинства современных компьютеров манипуляции с переменными такого размера легки.

Но что делать если наша переменная занимает несколько десятков мегабайт? Скажем фотография, или аудиофайл? Тут становятся очевидными издержки транзакций с такими переменными. Скажем, у нас список из таких переменных, например TObjectList. Отсортировать такой список будет накладно, даже при учете наиболее оптимального алгоритма сортировки, скажем быстрой сортировки.

Здесь и выходят на сцену указатели. Они созданы для того, чтобы лишь хранить информацию о местонахождении самих данных. Манипуляции с ними легки, так как указатели занимают как правило считанные байты (в 32 разрядных системах – 4 байта, в 64 разрядных – 8 байт).

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

Рекомендуем к прочтению

Добавить комментарий

Ваш адрес email не будет опубликован.