Введение в указатели
 
Автор rdc

Что такое указатель?

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

Чтобы понять, указатели, можно представить коробку яиц, которая имеет 12 отверстий под яйца от 1 до 12. Эти отверстия, если говорить образно, ячейки памяти в компьютере; каждое отверстие, или ячейка памяти, имеет адрес, в этом примере, с 1 по 12. Так же образно можно представить яйцо, которое может быть помещено в одно из отверстий, представляет собой элемент данных. То есть яйцо в отверстии 1 имеет адрес 1.

Как правило доступ к данным будет непосредственно с помощью переменных. Когда вы размеряете переменную определенного типа, то вы устанавливаете сторону пространства хранения для данных. Вам не нужно знать, где находятся данные, так как вы можете получить доступ к данным непосредственно через переменную. Это как охват и собирание яйца в отверстие 1, (чтение данных) или положить яйцо в отверстие 1 (установка данных) не глядя на цифры, написанные на нижней части отверстия.

Использование указателей немного отличается. Представьте, у вас небольшой клочок бумаги, который будет представлять наш указатель. Прямо сейчас он пустой и ни на что не указывает. Этот указатель не может быть использован, пока не будет инициализирован. Для инициализации указателя, напишите 1 на нем. Теперь наш указатель "указывает" на отверстие 1 в нашей коробке яиц. Чтобы поместить данные (яйцо) в отверстие 1, мы смотрим на наш клочок бумаги, сопоставляем его с отверстием 1 и помещаем яйцо в отверстие. Чтобы получить яйцо, мы сопоставляем наш листок бумаги с отверстием 1, а затем захватываем яйцо. Все действия (положить и получить) яйца должны быть сделаны через листок бумаги и это называется разыменованием. То есть, мы получаем данные через ссылку, содержащуюся в указателе, число 1-указатель не содержит данных; он содержит ссылку на данные.

В FreeBASIC мы определяем указатель с помощью Dim и Ptr :

Dim aptr As Integer Ptr



Это утверждение соответствует нашему чистому листу бумаги в приведенном выше примере. Указатель ни на что не указывает и не инициализирован. Если бы мы попытались использовать указатель прямо сейчас, то скорее всего программа бы завершилась с ошибкой.

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

Dim aptr As Integer Ptr

aptr = Allocate(SizeOf(Integer))



Здесь мы используем Allocate и откладываем достаточно места в памяти для Integer и загружаем адрес этого пространства в aptr. Макрос SizeOf возвращает размер в байтах передаваемого типа данных. Можно использовать Len вместо SizeOf (начиная с версии .13b) если хотите конечно.

После того, как мы инициализировали указатель, мы можем теперь использовать его:

*aptr = 5
Print "aptr: "; *aptr



Обратите внимание на префикс * к aptr.  * - это оператор ссылки. Это как соответствие номера на листке бумаги с номером отверстия на коробке яиц. Используя оператор *, мы можем добраться до данных (яйца), содержащегося в отверстии, на который указывает aptr.

Вот полный пример программы:

Dim aptr As Integer Ptr

aptr = Allocate(SizeOf(Integer))
*aptr = 5
Print "aptr: "; *aptr
Deallocate aptr
Sleep



Функция Deallocate освобождает память указателя aptr, и делает aptr снова неинициализированным(необъявленным). Это как стирание числа на нашем клочке бумаги. Если вы будете использовать aptr после освобождения памяти, программа скорее всего завершится с ошибкой.

Что хорошего в указателях?

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

Другое использование указателя в определениях Type. Type в FreeBASIC может содержать только строки фиксированной длины, но что, если вы не знаете длину строки, до того времени , пока это не нужно будет в ходе выполнения программы? Указатель может послужить для этой цели.

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

Type mytptr
    sptr As ZString Ptr
End Type
'Эта функция будет выделять место для переданной строки
'и загружать его в ячейку памяти, возвращая
'указатель на строку.

Declare Function pSetString(ByVal s As String) As ZString Ptr

'тип var
Dim mytype As mytptr

'Установка строки переменной длины в тип
mytype.sptr = pSetString("Hello World From FreeBasic!")
Print "aptr: "; *mytype.sptr
Deallocate(mytype.sptr)
Sleep
End

Function pSetString(ByVal s As String) As ZString Ptr
    Dim sz As ZString Ptr
   
    'Выделение некоторого пространства + 1 для chr(0)
    sz = Allocate(Len(s) + 1)
    'Загрузка строки в ячейку памяти
    *sz = s
    'Возврат указателя
    Return sz
End Function



Здесь мы определяем наш тип с полем sptr as ZString Ptr. Zstrings - это строки, заканчивающиеся нулем. Они используются многими внешними библиотеками и предназначены для динамического распределения. После того, как мы определим наш тип, мы создаем его экземпляр с помощью оператора Dim:

Dim mytype As mytptr



Затем мы вызываем нашу функцию pSetString, чтобы получить адрес строки переменной длины, которую мы хотим установить в наш Type.

mytype.sptr = pSetString("Hello World From FreeBasic!")



Запомните sptr определяется как указатель, а не как строка переменной длины, поэтому pSetString возвращает указатель (адрес памяти) строки, а не саму строку. Другими словами, если строка в отверстии № 1, pSetString возвращает 1.

Функция pSetString использует временную ZString sz, выделяя (Allocate) для нее пространство , такое же как у строки,  переданной в параметре s. Поскольку ZString является нуль-терминированной строкой, мы должны добавить 1 к длине для нулевого терминатора в функции при выделении (Allocate).

'Выделение некоторого пространства + 1 для chr(0)
sz = Allocate(Len(s) + 1)



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

'Загрузка строки в ячейку памяти
*sz = s



Мы возвращает указатель (адрес строки) нашего типа, который сохраняется в mytype.sptr.

'Возврат указателя
Return sz



Теперь мы можем ссылаться на строку в нашем типе, используя оператор ссылки.

Print "aptr: "; *mytype.sptr



Указатели могут ввести в заблуждение новичков, но вам нужно запомнить главное: указатель не содержит данных, он просто указывает на некоторые данные. Указатель представляет собой адрес памяти, и вы управления этими данными через контрольный оператор *. Это действительно не сильно отличается от обычной переменной.