Введение
Этот учебник направлен на людей, которые хотят узнать больше о новых
возможностях, добавленных в
Type, обычно именуемый «тип как объект"
и "ООП штучки". Новые возможности описаны для людей , которые не понимают
данную тему, но хотят понять.
Type FreeBASIC представляет собой
совокупность типов данных такие, как структуры в C, или запись в Паскале.
Вот простенький типичный
Type.
Type person_info
first_name As String
last_name As String
house_number As Integer
street_name As String
town As String
End Type
При таком использовании он используется как своего рода контейнер для
связанных данных; в данном примере это может быть как запись в адресной
книге. С новыми возможностями он может быть использован больше как класс в
C++, потому что может сделать гораздо больше, чем только содержать простые
поля данных. Это дает способ выражать идею объекта, и делает
объектно-ориентированного программирование гораздо проще. Теперь мы будем
рассматривать эти новые возможности.
Property (свойства)
Мы начнем со свойств. При добавлении свойства в тип, доступ к нему будет
такой же как будто это рядовой элемент типа, но вместо простой
установки\получения значения в переменную-элемент , происходит вызов
функции. Взгляните на этот пример:
Type bar
Declare Property x() As Integer
Declare Property x(ByVal n As Integer)
p_x As Integer
End Type
Property bar.x() As Integer
Print "bar.x()"
Property = p_x
End Property
Property bar.x(ByVal n As Integer)
Print "bar.x(ByVal n As Integer)"
p_x = n
End Property
'---
Dim foo As bar
foo.x = 5
Print foo.x
Мы включаем в наш
Type некоторые декларации для
Property;
они очень похожи на обычные функции. Первая из них декларирует процедуру
получения, вторая процедуру установки.
p_x элемент является просто
обычным элементом
Integer.
Далее мы пишем код для свойства; Опять же синтаксис очень похож на запись
нормальных функций. Обратите внимание на то, как мы возвращаем значение:
вместо
Function = value, мы используем
Property = value.
Так же можно использовать такую запись:
Return value. Также,
обратите внимание, что вы можете обратиться к элементу напрямую как
p_x;
Вы также можете использовать ключевое слово
this, например
this.p_x = n;
В реале в ключевом слове
this нет необходимости, но оно может
помочь в некоторых неоднозначных обстоятельствах.
Далее следует некоторый тестовый код; Он показывает, что мы можем
использовать свойство так же, как если бы это был любой рядовой элемент. При
запуске программы, результат будет выводиться на экран, чтобы показать, что
свойства получения и установки были вызваны.
Сейчас этот код довольно тривиальный, но это только для теста, в конце
концов вы увидите , что данный метод можно применять и в практических целях.
Представьте, к примеру, вы пишете GUI и
Type представляет кнопку на
экране, вы могли бы сделать
button.text = "Hello World!" и сделать
код свойства обновления экрана, чтобы показать изменения. Или может быть вы
используете
Type поддерживающий какой-то список; Вы могли бы
сделать
list.size += 10 и затем положить некоторый код в ваше
свойство, чтобы увеличить размер списка.
Constructor/Destructor (Конструктор\Деструктор)
Конструкторы - это функции, которые вызываются, когда создается
Type
например при использовании
Dim. Деструктор - это функция, которая
вызывается, когда
Type выходит из области видимости; Это может
быть, когда программа заканчивается, для
Type в основном коде, или
когда функция заканчивается, для локального
Type. Посмотрите на
следующий пример:
Type bar
Declare Constructor()
Declare Destructor()
Declare Property x() As Integer
Declare Property x(ByVal n As Integer)
p_x As Integer Ptr
End Type
Constructor bar()
Print "Constructor bar()"
p_x = Allocate(SizeOf(Integer))
*p_x = 10
End Constructor
Destructor bar()
Print "Destructor bar()"
Deallocate(p_x)
End Destructor
Property bar.x() As Integer
Print "bar.x()"
Property = *p_x
End Property
Property bar.x(ByVal n As Integer)
Print "bar.x(ByVal n As Integer)"
*p_x = n
End Property
'---
Dim foo As bar
Print foo.x
foo.x = 5
Print foo.x
Опять же синтаксис несколько похож на обычные функции. Обратите внимание,
что в этот раз я изменил тип
p_x на
Integer ptr.
Конструктор выделяет память для
p_x при создании
foo и
присваивает
p_x значение по умолчанию; Деструктор же освобождает
эту память после того, как
foo будет уничтожен. Поэтому вы можете
использовать конструкторы и деструкторы, для установки значений и для того,
чтобы подчищать за объектами. Конечно же это опять тривиальный пример, но он
может подсказать принцип создания списков в более удобной форме.
Методы
Вы можете также иметь регулярные
Subs и
Functions внутри
вашего
Type; в некоторой терминологии они называются методами.
Посмотрим на пример:
Type bar
Declare Constructor()
Declare Destructor()
Declare Property x() As Integer
Declare Property x(ByVal n As Integer)
Declare Sub Mul5()
Declare Function Addr() As Integer Ptr
p_x As Integer Ptr
End Type
Constructor bar()
Print "Constructor bar()"
p_x = Allocate(SizeOf(Integer))
*p_x = 10
End Constructor
Destructor bar()
Print "Destructor bar()"
Deallocate(p_x)
End Destructor
Property bar.x() As Integer
Print "bar.x()"
Property = *p_x
End Property
Property bar.x(ByVal n As Integer)
Print "bar.x(ByVal n As Integer)"
*p_x = n
End Property
Sub bar.mul5()
*p_x *= 5
End Sub
Function bar.Addr() As Integer Ptr
Function = p_x
End Function
'---
Dim foo As bar
Print foo.x
foo.x = 5
Print foo.x
foo.mul5()
Print foo.x
Print "address p_x points to", foo.Addr()
На этот раз мы добавили
Sub, в которой умножается на пять(5) число
, на которое указывает указатель
p_x, а так же функция, которая
возвращает адрес памяти, который содержит указатель.
Private/Public (Приватный доступ\Открытый доступ)
По умолчанию всех элементы типа имеют открытый доступ; Это означает, что мы
можем читать, записывать и вызывать их. Однако иногда может потребоваться
сделать их приватными. Возьмем, к примеру, наш элемент
p_x; в
настоящее время мы можем написать так:
Print *foo.p_x и это
позволит нам напечатать значение по указателю
p_x. Мы могли бы
сделать его приватным, так чтобы только элементы объекта
bar
(конструктор, деструктор, свойства и методы) имели к нему доступ. Таким
образом, мы можем быть уверены, что мы работаем с
p_x только
нужными нам способами. Если к примеру мы сделали '
DeAllocate(foo.p_x)'
в нашем основном коде, то затем при выполнении деструктора, память будет
освобождаться снова (что неправильно). Измените декларацию
Type
следующим образом:
Type bar
Declare Constructor()
Declare Destructor()
Declare Property x() As Integer
Declare Property x(ByVal n As Integer)
Declare Sub Mul5()
Declare Function Addr() As Integer Ptr
Private:
p_x As Integer Ptr
End Type
Теперь попробуйте добавить
Print *foo.p_x в основной код и
скомпилировать его. Вы получите сообщение об ошибке от
FBC "
error 173: Illegal member access, found 'p_x' in 'Print *foo.p_x'",
компилятор подтверждает нам тот факт, что мы сделали
p_x приватным.
При использовании определений
private: или
public:, все
последующие элементы будут следовать правилам этих определений. Вот довольно
бессмысленный пример для того, чтобы просто показать синтаксис:
Type bar
Private:
a As Integer
b As Integer
Public:
c As Integer
d As Integer
Private:
e As Integer
End Type
В типе выше, элементы
a,
b,
e являются
приватными;
c и
d являются глобальными.
Перегрузка операторов
Перегрузкой операторов является способ сказать компилятору, что делать в
случае, когда мы хотим выполнить какую-то работу с участием нашего
Type.
Возьмем такой пример:
Type bar
n As Integer
End Type
Dim As bar x, y, z
z = x + y
Сейчас компилятор выдаст ошибку, когда увидит подобное, поскольку он не
имеет ни малейшего представления как сложить два типа, но мы можем
определить такую возможность:
Type bar
n As Integer
End Type
Operator +(ByRef lhs As bar, ByRef rhs As bar) As bar
Operator = Type(lhs.n + rhs.n)
End Operator
Dim As bar x, y, z
x.n = 5
y.n = 10
z = x + y
Print z.n
В этом коде я использую
lhs и
rhs, чтобы обратиться к
левому и правому операндам оператора. Обратите внимание также на выражения
type(lhs.n + rhs.n);
это создает тот тип, который будет возвращен. Если у вас есть такой тип:
Type bar
x As Integer
y As Integer
z As Integer
End Type
тогда вы бы могли построить его так
type(xpart, ypart, zpart).
Большинство
или почти все операторы могут быть перегружены, и большинство из них
являются бинарными, то есть они имеют два операнда, как в случае с +.
Некоторые из них унарные, имеющие только правую часть, как
Not и унарный
минус. Их можно определить так: '
Operator Not(ByRef rhs As bar) As bar'.
Есть некоторые специальные случаи, когда операторы должны быть
продекларированы внутри
Type; это операторы присваивания и преобразования
Операторы присваивания такие как
+= -= mod= и др., а также
Let.
Let
используется, когда вы делаете присваивание так:
Dim As bar foo
Dim As Integer x
foo = x
А операторы преобразования являются своего рода обратными; они используются,
когда вы преобразовываете в другой тип данных:
Dim As bar foo
Dim As Integer x
x = foo
Ниже маленький пример использования
Let и
Cast:
Type bar
n As Integer
Declare Operator Let(ByRef rhs As Integer)
Declare Operator Let(ByRef rhs As String)
Declare Operator Cast() As String
End Type
Operator bar.Let(ByRef rhs As Integer)
n = rhs
End Operator
Operator bar.Let(ByRef rhs As String)
n = Val(rhs)
End Operator
Operator bar.Cast() As String
Operator = Str(n)
End Operator
Operator +(ByRef lhs As bar, ByRef rhs As bar) As bar
Operator = Type(lhs.n + rhs.n)
End Operator
Dim As bar x, y, z
x = 5
y = "10"
z = x + y
Print z
Вам
нужно иметь отдельные let and cast для каждого типа данных, который вы хотите
поддерживать. Операторы, которые должны быть продекларированы в пределах типа известны
как нестатические, и те, которые не известны как глобальные. Есть
технические основания для этого; нестатическим нужно знать какой
экземпляр (в техническом жаргоне
; в нашем примере выше, можно сказать, что
x
является экземпляром
bar) типа они имеют в виду, и это достигнуто путем
скрытой ссылки '
this'. Благодаря этой ссылке '
this' другие
элементы , такие как операторы и методы знают, к какому
Type
относится вызов.
Большинство операторов могут быть перегружены; Вот список тех, которые в
настоящее время могут быть перегружены:
Присваивание:
let,
+=,
-=,
*=,
/=,
\=,
mod=,
shl=,
shr=,
and=,
or=,
xor=,
imp=,
eqv=,
^=
Унарные:
-,
not,
@,
*,
->
Бинарные:
+,
-,
*,
/,
\,
mod,
shl,
shr,
and,
or,
xor,
imp,
eqv,
^,
=,
<>,
<,
>,
<=,
>=
Перегруженные Конструкторы/Методы
Как и обычные функции, конструкторы и методы нашего типа могут быть
перегружены. Для конструкторов это предоставляет способ для указания
сведений о том, как должен быть создан экземпляр. Вот короткий пример:
Type bar
Declare Constructor()
Declare Constructor(ByVal initial_val As Integer)
x As Integer
End Type
Constructor bar()
x = 10
End Constructor
Constructor bar(ByVal initial_val As Integer)
x = initial_val
End Constructor
Dim foo As bar
Print foo.x
Dim baz As bar = bar(25)
Print baz.x
Первый
Constructor без аргументов, известен как конструктор по
умолчанию. Он инициализирует
foo.x значением 10. Однако мы также
указали другой конструктор, который принимает начальное значение. Обратите
внимание, как мы вызываем его
Dim baz As bar = bar(25). Вы всегда
можете вызвать конструктор по умолчанию, создавая экземпляр типа без
аргументов, но если экземпляр типа будет создаваться с аргументом, то
конструктор по умолчанию не сработает и будет вызван второй конструктор. Вы
не можете иметь перегруженный деструктор, потому что нет никакого способа
вручную выбрать, какой деструктор должен быть вызван.
Перегруженные методы очень похожи:
Type bar
Declare Sub foo()
Declare Sub foo(ByVal some_value As Integer)
Declare Sub foo(ByRef some_value As String, ByVal some_other As Integer)
x As Integer
End Type
Они работают так же, как обычные перегруженные функции.
Закрытие
Я надеюсь, что этот урок был полезен для вас. Он довольно информативный,
хотя есть еще чему учиться; я надеюсь вы дочитали до этого места, и у вас не
возникло трудностей. Существует еще некоторая информация, доступная в вики и
на форумах, а также во второй части данного руководства: -
Руководство для начинающих
по теме: Типы как объекты (Часть 2)
Больше информации