Введение.
Добро пожаловать во вторую часть урока. В этой части я предполагаю, что вы
прочитали 1 часть , попытались понять примеры, и поэкспериментировали.
Теперь я расскажу то, что я не включил в 1 часть.
Индексированные свойства.
Индексированное свойство - это свойство, которое ведет себя как массив. Так
же как обычное свойство, функция вызывается при доступе через экземпляр
объекта. Я начну с очень короткого примера, чтобы показать синтаксис.
Type foo
Declare Property bar(ByVal index As Integer, ByVal value As Integer)
Declare Property bar(ByVal index As Integer) As Integer
dummy As Integer
End Type
Property foo.bar(ByVal index As Integer, ByVal value As Integer)
Print "Property set, index=" & index & ", value=" & value
End Property
Property foo.bar(ByVal index As Integer) As Integer
Print "Property get, index=" & index
Property = 0
End Property
Dim baz As foo
baz.bar(0) = 42
Print baz.bar(0)
Как вы можете видеть, декларация для нашего индексированного свойства очень
похожа на обычную, за исключением того, что на этот раз мы добавили аргумент
для индекса. Я включил элемент
Integer, потому что
тип должен иметь по крайней мере один элемент данных. Как вы можете видеть,
при вызове свойство использует (0), чтобы обозначить, что мы хотим получить
или задать нулевой индекс, так же, как для обычного массива. Теперь я покажу
вам более полезный пример, и ниже дам описание его:
Type foo
Declare Constructor(ByVal num_elements As Integer)
Declare Destructor()
Declare Property bar(ByVal index As Integer, ByVal value As Integer)
Declare Property bar(ByVal index As Integer) As Integer
Private:
x As Integer Ptr
size As Integer
End Type
Constructor foo(ByVal num_elements As Integer)
x = CAllocate(num_elements * SizeOf(Integer))
size = num_elements
End Constructor
Destructor foo()
Deallocate(x)
End Destructor
Property foo.bar(ByVal index As Integer, ByVal value As Integer)
If (index >= 0) And (index < size) Then
x[index] = value
Else
Error 6
End If
End Property
Property foo.bar(ByVal index As Integer) As Integer
If (index >= 0) And (index < size) Then
Property = x[index]
Else
Error 6
End If
End Property
Dim baz As foo = foo(10)
baz.bar(1) = 42
Print baz.bar(1)
На
этот раз, я добавил конструктор и деструктор, который будет выделять и
освобождать массив динамической памяти x, с количеством элементов, указанным в
конструкторе. Далее при вызове функции свойств, я проверяю: если индекс
находится в пределах границ массива, то будет выполняться запрошенное
действие
получения или установки. Если указанный индекс находится вне границ, то
произойдет ошибка 'Error 6'
, которая прервёт программу FB, выведя 'out of bounds error'. При желании можно
установить собственную процедуру обработки ошибок. Попробуйте
этот код, изменив запись ' baz.bar(1) = 42' на ' baz.bar(10) = 42', и вы увидите
обработку ошибок в действии, ведь мы указали только 10 элементов (индекс 0-9).
Конструктор копирования.
Конструктор копирования — особый тип конструктора, который используется для
копирования из существующего объекта. Когда вы пишете код следующим образом:
Type foo
...
End Type
Dim As foo a
Dim As foo b = a
FreeBASIC автоматически создает скрытый код для построения b, сделав копию.
Этим занимается конструктор копирования по умолчанию, он просто делает копии
всех данных полей (элементов) объекта. Мы можем определить наши собственные
конструкторы копирования. Ниже просто краткий фрагмент, чтобы показать его
декларацию.
Type foo
Declare Constructor(ByRef obj As foo)
...
End Type
This will come in very useful for a reason I will now explain.
Глубокая\мелкая копия.
В предыдущем примере, код, где мы писали 'Dim As foo b = a', можно
назвать мелкой копией. Были скопированы данные полей, однако иногда это не
желательно. Предположите, что один из элементов - указатель, и что
произойдет при копировании? Конечно адрес, на который указывает указатель,
будет скопирован, и в результате оба объекта укажут на ту же самую память.
Пример этого:
Type foo
x As Integer Ptr
End Type
Dim As foo a
a.x = Allocate(SizeOf(Integer))
*a.x = 42
Dim As foo b = a
Print *a.x, *b.x
*a.x = 420
Print *a.x, *b.x
Deallocate(a.x)
Вы можете видеть, что оба указывают на ту же самую память, то есть изменяя
один, затрагивается и другой. Как объяснено в предыдущей записи про
конструктор копирования, FreeBASIC создает код, делая мелкие копии по
умолчанию. Это также верно, если мы делаем присваивание так:
Dim As foo a, b
b = a
В этом случае FreeBASIC также создает оператор присваивания по умолчанию (
Let),
чтобы выполнить мелкую копию. Чтобы сделать глубокие копии, мы должны
определить конструктор копирования и оператор присваивания, который кстати
перегружен, чтобы принять наш тип. Вот пример использования.
Type foo
Declare Constructor()
Declare Constructor(ByRef obj As foo)
Declare Destructor()
Declare Operator Let(ByRef obj As foo)
x As Integer Ptr
End Type
Constructor foo()
Print "Default ctor"
x = CAllocate(SizeOf(Integer))
End Constructor
Constructor foo(ByRef obj As foo)
Print "Copy ctor"
x = CAllocate(SizeOf(Integer))
*x = *obj.x
End Constructor
Destructor foo()
Print "dtor"
Deallocate(x)
End Destructor
Operator foo.Let(ByRef obj As foo)
Print "Let"
*x = *obj.x
End Operator
Dim As foo a
*a.x = 42
Dim As foo b = a 'Использование
конструктора копирования
Print *a.x, *b.x
*a.x = 420
Print *a.x, *b.x
Как Вы видите к конструктору копирования относится линия 'Dim As foo b
= a' и на сей раз, мы выделяем некоторую память и копируем данные в новом
конструкторе копирования, для того, чтобы мы могли использовать x в одном
объекте без затрагивания другого. Если мы изменим код следующим образом:
Dim As foo a, b
*a.x = 42
b = a 'Используется
оператор присваивания (Let).
Print *a.x, *b.x
*a.x = 420
Print *a.x, *b.x
То здесь уже используется оператор присваивания. Обратите внимание, что в
коде под оператор присваивания не надо выделять память, потому что она уже
была распределена в конструкторе по умолчанию, нам нужно просто скопировать
данные. Линия '*x = *obj.x' выполняет эту копию. Если у нас было бы что-то
более продвинутое, например массив динамической памяти, то необходимо было
бы перераспределить память правильно, сообразно размеру копируемых данных.
Вот более продвинутая версия примера.
Type foo
Declare Constructor(ByVal num_elements As Integer)
Declare Constructor(ByRef obj As foo)
Declare Destructor()
Declare Operator Let(ByRef obj As foo)
x As Integer Ptr
size As Integer
End Type
Constructor foo(ByVal num_elements As Integer)
Print "Default ctor"
x = CAllocate(SizeOf(Integer) * num_elements)
size = num_elements
End Constructor
Constructor foo(ByRef obj As foo)
Print "Copy ctor"
x = CAllocate(SizeOf(Integer) * obj.size)
size = obj.size
For i As Integer = 0 To size - 1
x[i] = obj.x[i]
Next i
End Constructor
Destructor foo()
Print "dtor"
Deallocate(x)
End Destructor
Operator foo.Let(ByRef obj As foo)
Print "Let"
x = Reallocate(x, SizeOf(Integer) * obj.size)
size = obj.size
For i As Integer = 0 To size - 1
x[i] = obj.x[i]
Next i
End Operator
Dim As foo a = foo(5)
a.x[0] = 42
a.x[1] = 420
Dim As foo b = a 'Используется
конструктор копирования
Print a.x[0], a.x[1], b.x[0], b.x[1]
b.x[0] = 10
b.x[1] = 20
Print a.x[0], a.x[1], b.x[0], b.x[1]
b = a ' А
сейчас используется оператор присваивания
LET
Print a.x[0], a.x[1], b.x[0], b.x[1]
Это может показаться довольно сложным на первый взгляд. Вам стоит просто
хорошенько изучить пример и поэкспериментировать с ним, в итоге все будет
понятно (я надеюсь).
Передача объектов в функции по ByVal
Идея глубокой и мелкой копий относится также к передаче объекта в функцию по
значению. При передаче ссылки на объект (ByRef), можно изменить объект, и
эти изменения будут распространяться на объект. Однако вы также можете
передать объект по значению, и это будет означать, что вы можете изменить
его без глобальных изменений, то есть сам объект, находящийся за пределами
функций меняться не будет. Когда объект передается по значению в функцию, то
создается новая копия, и если этот объект имеет конструктор копирования, то
он вызывается, а если нет, то выполняется скрытая мелкая копия. После
окончания функции, для объекта вызывается деструктор.
New/Delete
New и
Delete - это специальные операторы для
динамического выделения памяти и последующего освобождения ее. Они
используется для динамической памяти с указателями. Во всех примерах до сих
пор мы просто использовали Dim для создания наших объектов, а это в свою
очередь будет создавать их в стеке. Но с помощью
New
мы можем создавать их динамически, и это дает большую гибкость, так же, как
с помощью
Allocate/DeAllocate с нормальной
памятью. Еще одна характерная вещь для
New:
вам не нужно проверять указатели на равенство нулю (NULL) после вызова
New. Если
New даст сбой,
то это вызовет исключение, которое завершит программу. В более поздних
версиях FreeBASIC вполне вероятно будет создан механизм, позволяющий лучшую
обработку исключений, но по состоянию на момент написания, это еще не
реализовано.
Существует два различных варианта
new/delete.
Первый тип, создает только один элемент или объект, например:
Dim As Integer Ptr foo = New Integer
*foo = 1
Print *foo
Delete foo
New будет создавать новый
Integer, а затем
уничтожит его, когда мы вызовем delete. Помните, что я использовал ptr,
потому что это динамическая память. Для простых типов данных можно также
указать значение по умолчанию, поместив его в скобках после типа данных, то
есть:
Dim As Integer Ptr foo = New Integer(42)
Print *foo
Delete foo
Это также работает для определяемого пользователем типа (
UDT)
с простыми полями данных:
Type foo
x As Integer
y As Integer
End Type
Dim As foo Ptr bar = New foo(1, 2)
Print bar->x, bar->y
Delete bar
Эта инициализация не будет работать для более сложных типов с участием
конструкторов , деструкторов и т.д., однако полезной особенностью является
то, что при использовании
New\Delete с объектами,
также вызываются конструктор и деструктор, попробуйте следующий пример:
Type foo
Declare Constructor()
Declare Destructor()
x As Integer
y As Integer
End Type
Constructor foo()
Print "ctor"
End Constructor
Destructor foo()
Print "dtor"
End Destructor
Dim As foo Ptr bar = New foo
Delete bar
Вы увидите, что вызываются конструктор и деструктор для объекта.
Второй
тип new/delete предназначен для создания массивов. На этот раз количество
элементов помещается после типа данных в квадратных скобках '[]'. При
использовании версии массива, необходимо также использовать 'delete []'
вместо '
delete', для того, чтобы FreeBASIC знал,
что удаляет массив. Вот простой
пример использования с типом Integer:
Dim As Integer Ptr foo = New Integer[20]
foo[1] = 1
Print foo[1]
Delete[] foo
Это позволит создать динамический массив, с 20 элементами
Integer. Следует отметить, что это отличается от
Allocate, который принимает количество байт в качестве аргумента; с
использованием
New, вы должны указать количество
элементов. Метод с массивом работает точно так же для объектов:
Type foo
Declare Constructor()
Declare Destructor()
x As Integer
y As Integer
End Type
Constructor foo()
Print "ctor"
End Constructor
Destructor foo()
Print "dtor"
End Destructor
Dim As foo Ptr bar = New foo[3]
Delete[] bar
Когда вы запустите этот код, вы увидите, что три пары конструкторов и
деструкторов вызываются, потому что мы создали массив из трех экземпляров
foo.
Вы должны помнить, чтобы необходимо вызывать Delete, или Delete [] для любой
памяти, выделенной с
New, или это приведет к
утечке памяти. Все так же , как DeAllocate вызывается когда нужно освободить
память, выделенную с помощью
Allocate.
Искажение имени
Искажение имени, это то, что происходит скрыто, на более низком уровне, и по
факту совсем не важно знать как это делается. Искажение имени применяется
для решения проблем, которые связаны с более одной функцией с одним именем,
которое происходит, когда функции перегружены, или являются частью типа.
Возьмем, к примеру перегруженные процедуры, показанные ниже:
Sub foo Overload ()
End Sub
Sub foo(ByVal i As Integer)
End Sub
Если у нас бы не было искажения имен, то оба объекта будут известны на более
низком уровне как FOO, и это приведет к столкновению имен. Поэтому они
должны быть оформлены соответствующим образом, чтобы знать, какой из них
должен быть вызван для использования. Для первого sub компилятор фактически
создает sub под названием _Z3FOOv, а для второго он создаёт sub под
названием _Z3FOOi. Компилятор запоминает это и выбирает соответствующий sub
при вызове, в зависимости от того, как идет вызов. К примеру 'foo()' на
самом деле будет вызывать _Z3FOOv, и 'foo(1)' на самом деле будет вызывать
_Z3FOOi. «v» означает
VOID (без аргументов),
и «
i» расшифровывается как
integer. Все детали искажения имени являются довольно сложными и
варьируются в зависимости от компиляторов. Компиляторы Microsoft используют
другие схемы , чем GNU компиляторы, так же как компиляторы других фирм
используют свои различные схемы искажения имен. Главное, что нам нужно
знать, это то, что FreeBASIC следует за GCC 3.x, ABI (двоичный интерфейс
приложений), что означает, что любые перегруженные функции, или сложные типы
будут совместимы только с компиляторами, по той же схеме. Это конечно
ограничение, но это не совсем проблема
FreeBasic,
это общая проблема всех компиляторов.
Неявный this
Опять
же нет необходимости знать о внутреннем устройстве
THIS
и как все происходит на более низком уровне. При вызове
функции-элемента объекта, в нее передается первым скрытый параметр, в
результате функция знает какой экземпляр объекта в настоящее время
используется. Это также верно для элементов property/constructor/destructor/operator. Если мы рассмотрим очень
простой пример:
Type foo
Declare Sub bar(ByVal n As Integer)
x As Integer
End Type
Sub foo.bar(ByVal n As Integer)
x = n
End Sub
Dim baz As foo
baz.bar(5)
То по существу верхний пример эквивалентен этому:
Type foo
x As Integer
End Type
Sub foo_bar(ByRef _this As foo, ByVal n As Integer)
_this.x = n
End Sub
Dim baz As foo
foo_bar(baz, 5)
Этот
метод с использованием явного «this» часто используется в языках, которые не
имеют возможностей сделать проще. ООП - это просто набор понятий, которые
могут кодироваться на практически любом языке. Некоторые вещи являются более
трудными для осуществления, например конструкторы, нужно будет явным образом
вызывать методы 'create', или 'init' функциями. А некоторые вещи, такие как
private/public очень трудны в реализации или даже невозможны,
потому что компилятор не знает как обеспечить их выполнение. Причиной для
добавления функций ООП в языки — скрыть много лишнего и добавить
"синтаксический сахар" для более простого использования. Таким образом, мы можем использовать
свойства, как если бы они были элементами обычных данных, а не функции, которые
является тем, чем они в действительности являются.
Подсказки для отладки/профилирования
При использовании GDB или других отладчиков и средства профилирования gprof,
информация находится в синтаксисе C++, и все ваши имена переменных и другие
символы отображаются в верхнем регистре. Здесь только очень короткий обзор,
чтобы помочь вам понять:
Вот пример типа:
Type bar
Declare Constructor()
Declare Constructor(ByRef obj As bar)
Declare Constructor(ByVal n As Integer)
Declare Destructor()
Declare Operator Cast() As Any Ptr
Declare Operator Let(ByVal n As Integer)
Declare Property foo(ByVal n As Integer)
Declare Property foo() As Integer
member As Any Ptr
End Type
При использовании GDB, они будут показаны следующим образом (Обратите
внимание на C++, они используют
:: где мы используем
. (точку), '
::' известен как
оператор разрешения области):
BAR::BAR() - Конструктор по умолчанию
BAR::BAR(BAR&) - Конструктор копирования (& в C++ означает ссылку, как byref)
BAR::BAR(int) - Конструктор принимает аргумент
Integer
(обратите внимание, что нет специального символа для обозначения ByVal, так
как это метод прохождения по умолчанию в C / C++)
BAR::~BAR() - Деструктор
BAR::operator void*() - Приведение к Any ptr (void симулирует Any, * означает
указатель)
BAR::operator=(int) - Оператор присваивания (Let), обозначается '=', в C/C++ '='
присваивание, '==' проверка на равенство.
BAR::FOO(int) - Свойство foo установка, принимающий аргумент
Integer
BAR::FOO() - Свойство foo получение
Элементы
Sub/
Function
показываются так же, как свойства, индексированные свойства то же самое,
только с дополнительным аргументом для индекса.
Вот как типы данных FB будут показаны:
Any ptr - void *
ZString ptr - char *
String - FBSTRING
byte - signed char
ubyte - bool
short - short
ushort - unsigned short
integer - int
uinteger - unsigned int
longint - long long
ulongint - unsigned long long
Я надеюсь, что это поможет вам начать работу в GDB/GProf, но немного
экспериментов не будет лишним.
Дополнительная информация
http://www.freebasic.net/wiki/wikka.php?wakka=KeyPgOpNew
http://www.freebasic.net/wiki/wikka.php?wakka=KeyPgOpDelete
http://en.wikipedia.org/wiki/Copy_constructor
http://en.wikipedia.org/wiki/Object_copy
http://en.wikipedia.org/wiki/Name_mangling