В данной статье попробую разобраться с темой указателей. Попробую понять их на самом простом уровне, так как ранее очень мало их использовал.
Что такое указатели и зачем они нужны?
В браузере у нас есть страница, например та, которую вы читаете сейчас, а есть адресная строка со ссылкой на данную страницу, скажем http://digital-flame.ru/2016/02/09/delphi-ukazateli-po-prostomu/. Если проводить аналогию, то указатель это ссылка на страницу, а сама страница это данные, на которые ссылается указатель.
Теперь перенесемся в мир Windows – есть адресное пространство программы. В этом адресном пространстве хранятся данные, используемые программой. Когда мы объявляем переменную, это адресное пространство задействуется и используется какая-то его часть.
Если переменная занимает немного места, то производить с ней операции достаточно легко, скажем перемещать из одного места адресного пространства в другое. Так переменная integer занимает всего 4 байта и для подавляющего большинства современных компьютеров манипуляции с переменными такого размера легки.
Но что делать если наша переменная занимает несколько десятков мегабайт? Скажем фотография, или аудиофайл? Тут становятся очевидными издержки транзакций с такими переменными. Скажем, у нас список из таких переменных, например TObjectList. Отсортировать такой список будет накладно, даже при учете наиболее оптимального алгоритма сортировки, скажем быстрой сортировки.
Здесь и выходят на сцену указатели. Они созданы для того, чтобы лишь хранить информацию о местонахождении самих данных. Манипуляции с ними легки, так как указатели занимают как правило считанные байты (в 32 разрядных системах – 4 байта, в 64 разрядных – 8 байт).
Можно манипулировать ими очень интенсивно, сортировать, перемещать и так далее, а когда понадобится реальное обращение к данным, просто перейти по адресу, на который указывает тот или иной указатель.
Какие виды указателей бывают?
Нетипизированный указатель
1 |
var P:Pointer |
Я бы сказал так, что все указатели, которые не объявлены как Pointer – типизированные указатели. Рассмотрим примеры ниже.
Типизированный указатель (на примере integer)
1 2 3 |
var P: ^Integer; // либо P2:Pinteger |
Типизированный указатель (на примере записи – структуры данных)
1 2 3 4 5 6 7 |
type MyRecord = record s:string i:integer end; var P: ^MyRecord; // Указывает на структуру данных |
Как узнать адрес переменных, на которые указывают указатели?
В самом простом примере это будет выглядеть так…
Для нетипизированного указателя
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var p:pointer; s:string ... s:='somevalue'; edit.Text := IntToHex(Longword(p), 8); //<<< Покажет случайный адрес //Так как мы ещё не сообщили программе на что должен указывать p p := Addr(s); // Теперь p указывает на s //p:=@s // Аналогично, p указывает на s edit.Text := IntToHex(Longword(p), 8); //<<< Покажет адрес s ... |
Для типизированного указателя точно также
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var p: ^string; s:string // либо p:PString // <<Так тоже Delphi поймет ... s := 'somevalue'; edit.Text := IntToHex(Longword(p),8); //<<< Покажет случайный адрес //Так как мы ещё не сообщили программе на что должен указывать p p:=Addr(s); // Теперь p указывает на s //p:=@s // Аналогично, p указывает на s edit.Text := IntToHex(Longword(p),8); //<<< Покажет адрес s ... |
Как пользоваться указателями?
Как видно из предыдущего примера, если не показать программе, на что указывает указатель, адрес будет случайным. Но если, указать, то указатель будет указывать на место в адресном пространстве, где находится наша переменная.
Присваивание адреса переменной – основное назначение указателя.
1 2 3 4 5 6 7 8 9 10 11 12 |
var p1:pointer; s1:string p2:^string; s2:string; ... p1 := @s1; p2:=@s2; // Присвоили адреса типизированному и нетипизированному указателям p1^ := 'somevalue'; // Выдаст ошибку, так как указатель нетипизированный p2^ := 'somevalue2'; // Сработает корректно так как указан тип ... |
Обнуление указателя
1 2 |
p := nil; //Если запросить адрес, то теперь покажет нули edit.Text := IntToHex(Longword(p), 8); // Выдаст 00000000 |
Присвоение указателей
1 |
P1 := P2; |
Смещение, на размер области памяти, занимаемой объектом, на который он указывает. Только для типизированных указателей.
1 2 3 4 5 6 |
type P: ^Integer; begin inc(P);//увеличение значения указателя на 4 байта (размер типа Integer) dec(P);//уменьшение значения указателя на 4 байта (размер типа Integer) end; |
Выделение памяти и очистка памяти. Типичный пример использования указателя. Только для типизированных указателей.
1 2 3 4 5 6 7 8 9 |
var p:^integer; ... New(p); // Выделили память p^:=14; //<<Присвоили некоторое целое число myStringlist.Add(IntToStr(P^)); // Добавили его в список Dispose(p); // Очистили память ... |
Работа со структурами данных (записями).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
type TMyRecord = Record i: Integer; s: String; end; var MyRecord: TMyRecord; P: ^TMyRec; ... P := @MyRecord; P^.s := 'somestring'; //Будет работать и без стрелки, то есть так P.s:='somestring'; P^.i := 1024; //Будет работать и без стрелки, то есть так P.i:=1024; ... |
Работа с массивами
1 2 3 4 |
var PArray: Array[1..100] of ^Integer; X: Integer; X:=(PArray[10])^; // Вот так корректно |