Указатели

В этой статье речь пойдет о указателях. Признаюсь честно, когда только начинал изучение языка FreeBasic, недоумевал, видя чужие написания кода, примеры и прочее , которые просто кишат указателями. Самое главное в них легко можно было обойтись без них. Но как потом стало ясно, указатели используются практически всеми API функциями, предоставляемыми системой, большинством разработчиков, да и вообще к таким фичам просто привыкаешь. Как бы там не было, однако в этой статье на маленьких примерах, я не смогу показать всю пользу указателей, ни принципы работы с ними вы надеюсь понять сможете.
По сути указатель - это переменная, содержащая адрес памяти, по которому находятся определенные данные. Этим адресом может быть адрес переменной, процедуры, функции, структуры и прочего.  А теперь пример, а после рассмотрим его:

Dim a As String="Freebasic"
Dim addr As String Ptr = @a
? *addr
Sleep

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

Как можно заметить указатель на переменные объявляется как обычная переменная, но с использованием ключевого слова PTR от сокращенного Pointer. Ведь можно было записать и так:

Dim addr As String Pointer = @a


Ничего бы не поменялось в программе, разница лишь в том, что вам дополнительно придется писать несколько букв.
По той же причине, при получении адреса переменной, функции, структуры и прочего используется оператор @  вместо оператора VarPtr 
Ведь для получения адреса можно было записать и так:

Dim addr As String Pointer = Varptr(a)


Разницы никакой нет.
Для получения значения из адреса используется оператор * вместо длинной записи:

Peek(String , addr)


В данном случае оператор Peek - это получение значения по определенному адресу, при том вы заметили, что еще нужно указать и тип String.  Наверно все таки удобнее использовать *

Хотя бывают случаи, когда получить значение с помощью * нельзя, а с помощью Peek так же просто. Пример:

Dim a As String="Freebasic"
Dim addr As Integer = @a
? Peek(String,addr)
Sleep


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

Implicit conversion

Если вы используете редактор FbEdit , то в нижнем окошке это предупреждение видно. По сути компилятор говорит нам:

" я конечно  скомпилирую, но вы присвоили переменной значение, которое я считаю неправильным"

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

CAST(преобразуемый тип , переменная для преобразования)

И прошлый пример можно переписать так:

Dim a As String="Freebasic"
Dim addr As Integer = Cast(Integer , @a)
? Peek(String,addr)
Sleep


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

Для получения адреса так же можно использовать еще 2 похожих оператора на оператор VarPtr :

  • ProcPtr - для получения адреса функций или процедур
  • StrPtr - для получения адреса строковых типов Zstring

 Мне кажется удобнее использовать вместо всех их оператор @
 Хотя примеры использования ProcPtr и StrPtr, я все же покажу:

Sub one (a As Integer)
    a+=100
    ? a
End Sub

Dim  two As Sub (a As Integer) =ProcPtr(one)
two(55)
Sleep


Выше пример объявления переменной two с типом Sub. По сути переменная two , объявленная с типом Sub, уже олицетворяет собой конструкцию процедуры one ,  ей не хватает только адреса процедуры. Мы как раз и присваиваем ей адрес с помощью ProcPtr. То есть у нас переменная two стала как бы двойником функции one  и мы ее вызвали точно так же, как если бы вызвали функцию one.

Далее пример StrPtr :

Dim As ZString*20 aa="Freebasic"

Dim As Zstring Ptr bb =StrPtr(aa)

?  *bb
Sleep


Ничего особенного, просто используется тип Zstring и получаем адрес с помощью strptr , а в остальном похоже на самый первый пример.

Вообще указатели чаще всего используют с переменными и со своими типами данных. Первое мы рассмотрели, можно перейти ко второму. Как вы наверно помните, когда мы использовали структуры, мы обращались к ее полям через точку. При помощи указателей появляется возможность обращаться к полям по указателю с помощью специального оператора ->
Давайте рассмотрим на примере:

Type OB
    a As Integer
    b As Integer
End Type

Sub rr (ff As OB Ptr)
    ? ff->a
    ? ff->b
End Sub

Dim ob As OB 
ob.a=10
ob.b=20
rr (@ob)
Sleep


Мы создали свой тип данных из двух полей (2 переменных с типом Integer). Далее мы создали процедуру и в ее принимающем параметре адрес на структурированную переменную.  В теле процедуры как раз мы получаем значение по указателю. Все так же как с точкой, только здесь мы обращаемся не напрямую к полям структурированной переменной, а через указатель, поэтому используется оператор -> . Ниже мы создали саму структурированную переменную, потом определили каждому из ее полей значения и вызвали процедуру передав в параметре ей адрес нашей структурированной переменной.
Тот пример что мы сейчас разобрали, легко можно переписать без указателей. Более того, советую вам самим это сделать. 
 
А мы далее рассмотрим пример, который позволяет объявлять свой тип данных с помощью указателя:

Type OB
    a As Integer
    b As Integer
End Type

Sub rr (ff As OB Ptr)
    ? ff->a
    ? ff->b
End Sub

Dim ob As OB Ptr = Allocate(Sizeof(OB)) 
ob->a=10
ob->b=20
rr (ob)
Deallocate(ob)
Sleep


Вы должны понимать, что объявляя тип данных таким образом:

Dim ob As OB Ptr


мы всего лишь выделяем память для одной переменной, в которой по умолчанию записывается 0. Вы скажете почему 0, ведь мы определили указатель на OB. Да действительно, только вот беда самой переменной  OB нет, то есть наш указатель показывает на ничто. А ведь нам надо туда записать адрес чего-либо.  Вот когда мы пишем такую запись:

Dim ob As OB Ptr = Allocate(Sizeof(OB))


Мы не только объявляем о переменной OB, но и выделили память, которую она занимает для всех ее полей. Ведь команда Allocate -это выделение памяти. Ее единственный параметр это кол-во выделяемых байт, которые как раз и подсчитывает команда SizeOf , которой мы в параметре передаем имя нашей структуры. По своей сути обе представленные ниже записи будут работать одинаково:

1)

Dim ob As OB
Dim ob1 As OB Ptr = @ob


2)

Dim ob1 As OB Ptr = Allocate(Sizeof(OB))


Просто в первой записи мы определили переменную и выделили память, а потом определили указатель на эту переменную и присвоили ей адрес переменной. А во второй мы определили переменную- указатель, а потом выделили память. Но в прошлом примере вы заметили кроме команды Allocate еще команду DeAllocate - это команда удаляющая объект, в нашем случае структурированную переменную и всю занимаемую ей память. Ее необходимо применять тогда,  когда переменная уже не требуется во избежание переполнения памяти.
Во многих верхних примерах я использовал свой тип данных с названием OB. Когда вы создаете свой тип данных в своей библиотеке(настанет время и мы поговорим об библиотеках) , то в последствии вызвать функцию из этой библиотеки, имеющую параметр такого вида:

As OB Ptr

скажем так затруднительно, ведь компилятор знает этот тип в библиотеке и не знает в вашем коде, из которого вы вызываете функцию. Другими словами компилятору неизвестно о типе OB. Что в таких случаях можно сделать:

  1. Написать еще одну структуру OB, точно такую же как в библиотеке
  2. Использовать вместо As OB Ptr  тип As Any Ptr

Тип As Any Ptr используют когда тип неизвестен, даже из названия становится ясно если перевести:

 Any - любой

Но не надо думать, что это панацея и этим типом можно заменить все остальные. Как бы правильно сказать....  Его нужно применять только тогда, когда по другому никак. В остальных случаях желательно четко определять тип.

А теперь несколько примеров работы с памятью без пояснений просто в вашу копилку знаний:


 Обычная отправка массива в процедуру:

Dim a(2) As Integer={33,44,55}

Sub dd ( b() As Integer)
   ? b(0)
   ? b(1)
   ? b(2)
    Sleep
End Sub

dd(a())

Извращенная работа с массивом:

Dim a(2) As Integer={33,44,55}

Sub dd ( b As Integer Ptr)
   ? *b
   ? *(b+1)
   ? *(b+2)
    Sleep
End Sub

Dim b As Integer Ptr=@a(0)
dd(b)

Еще один способ обращения к полям структуры:

Type OB
    a As Integer
    b As Integer
End Type

Sub rr (ff As Integer)
    ? Peek(ff)
     ? Peek(ff+4)
End Sub

Dim ob As OB 
ob.a=33
ob.b=77
rr (@ob)
Sleep


А теперь обобщим:

Способы получение адреса:

  1. @
  2. VarPtr
  3. StrPtr
  4. ProcPtr

Способы получения значения по адресу или указателю:

  1. *
  2. Peek
  3. ->

И напоследок: раз уж мы коснулись команды PEEK, то хотелось бы отметить команду POKE для записи значения по определенному адресу. То есть эта как бы обратная команда по отношению к PEEK.

Пример ее работы:

Dim As Integer Ptr a=Allocate(4)
Poke a,17
? Peek(a)
Deallocate(a)
Sleep

Когда вам встроенных возможностей FreeBasic станет мало, вы начнете искать дополнительные резервы, начиная с функций API предоставляемых системой и продолжая различными библиотеками и наработками кода от других разработчиков. Вот тут вы окажетесь в "плену указателей". Даже если остались какие то непонятные места, не страшно. Тема эта нелегкая и вы еще не один зуб сломаете об указатели, но придет время и все выстроится по полочкам. Только практика доведет вас до совершенства понимания. Всего доброго!

содержание | назад | вперед