Автор rdc
Есть моменты при создании программы, когда может потребоваться определить
совокупные структуры, такие как записи персонала или враг в игре. И хотя вы
можете сделать это с помощью индивидуальных типов данных, но это затруднит
управление в рамках программы. Составные типы данных позволяют вам
группировать связанные элементы данных в единую структуру, которая может
управляться как единое целое. FreeBASIC предлагает два составных типов
данных
Type и
Union.
FreeBASIC позволяет группировать несколько типов данных в единую структуру
под названием определение типа, который можно использовать для описания этих
агрегированных структур данных.
Базовая структура определения типа:
Type typename
Var definition
Var definition
...
End Type
Блок
Type-
End Type определяет область определения. Вы
определяете элементы структуры типа в нужном порядке, как с помощью
ключевого слова Dim, так и без использования Dim. Следующий фрагмент кода
показывает, как создать тип сотрудников.
Type EmployeeType
fname As String * 10
lname As String * 10
empid As Integer
dept As Integer
End Type
Вы можете использовать любой из поддерживаемых типов данных в качестве
элементов данных, в том числе указатели и другие определения типов. При
создании определения типа, например, в приведенном выше примере, вы просто
создаете шаблон для компилятора. Для того чтобы использовать определение
типа, необходимо создать переменную типа, как показано в следующем фрагменте
кода.
Dim Employee As EmployeeType
После того как вы создали переменную типа, вы можете получить доступ к
каждому элементу в типе, используя нотацию точки
var_name.field_name.
В приведенном выше примере, для доступа к полю fname вы будете использовать:
Employee.fname = "Susan"
Чтобы получить доступ к нескольким полям одновременно, вы можете
использовать блок
With-
End With. Следующий фрагмент кода
показывает, как использовать блок
With.
With Employee
.fname = "Susan"
.lname = "Jones"
.empid = 1001
.dept = 24
End With
Компилятор автоматически привязать переменную
Employee к
отдельным элементам данных в рамках блока
With. Блок
With не только дает сокращенное написание , но
также данная структура оптимизирована и немного быстрее, чем при
использовании доступа с нотацией точки.
Одним из преимуществ использования типов в вашей программе, что вы можете
передать структуры в процедуры или функции целиком. В следующем фрагменте
кода частично показано определение процедуры.
Sub UpdateEmployeeDept(ByRef Emp As EmployeeType)
.
.
.
End Sub
Обратите внимание, что параметр соответствует требованиям
Byref.
Это важно, так как вы хотите обновить тип внутри процедуры. Существует два
режима передачи параметра в FreeBASIC:
Byref и
Byval.
ByRef и ByVal: Краткое введение
Byref и
Byval сообщает компилятору , что параметр передается
по ссылке в процедуру или функцию. При использовании
Byref, или
По ссылке, вы передаете указатель на параметр, и любые изменения,
внесенные вами в параметре внутри процедуры или функции будут отражены в
фактической переменной, которая была принята. Другими словами, параметр
Byref
указывает на фактическую переменную в памяти.
Byval, или
По значению, делает копию параметра и любые
изменения, внесенные вами внутри процедуры или функции являются локальными и
не будут отражены в фактической переменной, которая была принята. Параметр
Byval
указывает на экземпляр переменной, а не на фактическую переменную.
Значение по умолчанию для FreeBASIC.17 — передача параметров с помощью
Byval.
Для того чтобы изменить переданный параметр, необходимо указать квалификатор
Byref.
В прошлом примере подпрограмма обновляет идентификатор типа работника, и
поскольку параметр квалифицирован как
Byref , то подпрограмма может
любое поле типа.
С другой стороны вы можете не обновлять тип, как в следующем фрагменте кода.
Sub PrintEmployeeRecord(Emp As EmployeeType)
.
.
.
End Sub
В этой процедуре вы только печатаете запись о сотруднике на экран или
принтер и поэтому вам не нужно ничего менять в переменной типа. Здесь по
умолчанию используется
Byval, который передает копию записи
сотрудника в процедуру, а не ссылку на переменную. С помощью
Byval
вы не сможете случайно что-то изменить в переменной типа, что не требует
изменений.
Вы должны использовать
Byref , если вы намерены изменить данные
параметров. Если нужно обезопасить реальные данные типа, то лучше
использовать
Byval .
Помимо встроенных типов данных , поля типа могут также основываться на
определении типа. Почему вы хотите это сделать? Одной из причин является
абстракция данных. Чем больше общих данных структуры, тем больше вы можете
повторно использовать код в других частях программы. И главное меньше кода
вы должны будете написать, что даст меньшую вероятность ошибки в вашей
программе.
Используя пример
Employee, предположим на минуту, что вы
должны будете отслеживать более чем просто идентификатор отдела. Возможно,
вам придется отслеживать имя начальника отдела, расположение отдела или
основной номер телефона отдела. Поставив эту информацию в отдельное
определение типа, вы можете использовать эту информацию саму по себе, или
как часть другого определения такого, как тип Employee. Обобщая свои
структуры данных, ваша программа будет гораздо более надежной.
Использование типа в типе такое же, как использование одного из встроенных
типов данных. Следующий фрагмент кода иллюстрирует расширенный тип отдела и
обновленный вид сотрудника.
Type DepartmentType
id As Integer
managerid As Integer
floor As Integer
End Type
Type EmployeeType
fname As String * 10
lname As String * 10
empid As Integer
dept As DepartmentType
End Type
Dim Employee As EmployeeType
Обратите внимание, что в определении
Employee,
поле отдела определяется как
DepartmentType , так же как
один из встроенных типов данных. Для доступа к информации
department в рамках типа
Employee, вы так же
должны использовать оператор точка , как и к любому другому элементу типа.
Employee.dept.id = 24
Employee.dept.managerid = 1012
Employee.dept.floor = 13
Верхний уровень определения типа является
Employee, так что
ссылка от него на первом месте. Поскольку
dept в
настоящее время является определением типа, вы должны использовать его имя
для доступа к полям
DepartmentType.
Employee
относится к типу
employee ,
dept относится к
типу department , а
id,
managerid и
floor
являются полями типа
department.
Вы даже можете дополнять это дальше, в том числе второй тип в рамках первого
типа , а третий в рамках второго типа. Для доступа так же нужно будет
использовать точку и кол-во точек будет соответствовать уровню вложенности.
Хотя нет никаких ограничений с уровнем вложенных определений типов, все таки
слишком большая многоуровневая вложенность сделает код громоздким и может
даже малопонятным.
Вы также можете использовать блок
With-
End With с
вложенными типами, путем вложенного блока
With , как пример:
With Employee
.fname = "Susan"
.lname = "Jones"
.empid = 1001
With .dept
.id = 24
.managerid = 1012
.floor = 13
End With
End With
Обратите внимание, что второй блок
With использует нотацию точки,
.dept,
для того, чтобы указать на следующий уровень определений типов. При
использовании вложенных блоков
With, вы должны быть уверены, что не
забыли указать запись окончания блока
End With для каждого
With,
во избежание ошибок компиляции.
Расширяя
идею абстракции данных, было бы неплохо иметь возможность отделить
инициализацию типа department от инициализации типа employee. Разделив эти две
функции, вы можете легко добавить дополнительную информацию в department при
необходимости. В данном случае можно использовать присваивание типа
Так же, как вы можете назначить один внутренний тип данных другому, вы
можете назначить одну переменную типа другой переменной типа, если они
разделяют то же самое определение типа.
В следующем фрагменте кода абстрагируется функция инициализации department и
присваивается результат с типом department в тип
Employee.
'Эта функция будет инициализировать тип
dept и возвращать результат
Function InitDept(deptid As Integer) As DepartmentType
Dim tmpDpt As DepartmentType
Select Case deptid
Case 24 'dept 24
With tmpDpt
.id = deptid
.managerid = 1012
.floor = 13
End With
Case 48 'dept 48
With tmpDpt
.id = deptid
.managerid = 1024
.floor = 12
End With
Case Else 'В
случае, если ID department был передан не
корректный
With tmpDpt
.id = 0
.managerid = 0
.floor = 0
End With
End Select
'Возврат информации dept
Return tmpDpt
End Function
'Создание экземпляра типа
Dim Employee As EmployeeType
'Инициализация типа Employee
With Employee
.fname = "Susan"
.lname = "Jones"
.empid = 1001
.dept = InitDept(24) 'получение
информации dept
End With
Как вы можете видеть во фрагменте, поле
dept типа
employee
инициализируется с вызовом функции. Функция
InitDept
возвращает
DepartmentType и компилятор присвоит этот тип в
поле
dept Employee .
Добавив простую функцию в программу, вы сделали программу проще в
обслуживании. Если новый
department будет создан,
вы можете просто обновить функцию
InitDept новой информацией
department, перекомпилировать и программа готова к
работе.
Существует еще один тип данных, который может быть использован в
определениях типа, это битовые поля. Битовые поля определяются как
variable_name: bits As DataType.
За именем переменной должно следовать двоеточие с числом битов, а затем тип
данных. Только целые(
integer) типы (все числовые
типы, за исключением "
single" и "
double"
, а также 64-битных типов) допускаются в битового поля. Битовые поля
полезны, когда вам нужно отслеживать информацию логического типа. Бит может
быть
0 или
1, который может представлять ДА или НЕТ,
ВКЛЮЧЕНИЕ или ВЫКЛЮЧЕНИЕ или даже ЧЕРНЫЙ или БЕЛЫЙ.
Следующий фрагмент кода иллюстрирует определение битового поля.
Type BitType
b1: 1 As Integer
b2: 4 As Integer
End Type
b1 определяется как один бит, а
b2 определяется как четыре
бита. Вы инициализируете битовые поля, передавая индивидуальные биты в поля
типа.
myBitType.b1 = 1
myBitType.b2 = 1101
Тип данных битового поля определяет, сколько бит можно объявить в битовое
поле. Так как
integer 32 бита, можно объявить до
32 бит в поле. Однако в большинстве случаев будет достаточно объявить один
бит для каждого поля и использовать большое количество полей для определения
маски бит, которые вы хотите использовать. Использование одного бита
упрощает кодирование, потому как все что вам нужно сделать, это определить
два значения установлен бит или нет.
Когда Вы создаете переменную определения типа, тип дополняется
(выравнивается) в памяти. Дополнение допускает более быстрый доступ к
элементам типа, так как область типа выравнивается по 4 байта или по границе
слова. Однако это может вызвать проблемы, пытаясь прочитать запись типа из
файла, тип которого не дополнен (не выровнен). Вы можете изменять дополнение
при определении типа.
Ключевое слово
field используется сразу после имени типа и может
иметь значение
1 для 1-байтового выравнивания (нет выравнивания),
2
для 2-байтовго выравнивания и
4 для 4-байтового выравнивания.
Определение типа без дополнения, может иметь следующий синтаксис.
Type myType Field = 1
v1 As Integer
v2 As Byte
End Type
Для выравнивания 2 байта можно использовать
field = 2. Если
ключевое слово
field не используется, то тип выровнен по 4 байта.
Если вы читаете определение типа, созданное FreeBASIC(ом) с помощью
выравнивания по умолчанию, то вам не нужно использовать свойство поля.
Quick Basic
Вы можете инициализировать тип при определении, так же, как и любую из
встроенных переменных. Следующий фрагмент кода иллюстрирует синтаксис.
Type aType
a As Integer
b As Byte
c As String * 10
End Type
Dim myType As aType => (12345, 12, "Hello")
В заявлении
Dim, оператор
=> используется, чтобы
сообщить компилятору, что инициализируется переменная типа. Значения
элементов типа должны быть заключены в скобки и разделены запятыми. Порядок
списка значений соответствует порядку элементов типа, где
a
будет установлено
12345,
b -
12 и
c
-
"Hello".
Нельзя инициализировать динамическую
строку в определении типа, с помощью этого метода. Строка должна быть
фиксированной длины. |
Инициализация при определении типа в операторе Dim полезна, когда вам нужно
иметь набор начальных значений для типа или значения, которые не будут
изменяться во время выполнения программы. Поскольку значения известны во
время компиляции, то во время выполнения на это не будет тратится время.
Объеденения выглядят как типы в их определении.
Union aUnion
b As Byte
s As Short
i As Integer
End Union
Если это тип, то можно получить доступ к каждому отдельному полю в
определении. Однако
Union при доступе к разным его
полям, будет предоставлять доступ к одному единственному полю; все поля в
рамках
Union занимают тот же сегмент памяти, и
размер
Union определяется размером большего
элемента.
В нашем случае
Union будет занимать четыре байта, то есть размером
Integer,
поле
b занимает 1 байт, поле
s занимает 2
байта, и поле
i занимает 4 байта. Каждое поле начинается с
первого байта, так поле
s будет включать поле
b,
а поле
i будет включать оба поля
b и
s.
Хорошим примером данной техники будет определения типа
Large_Integer
, объявленного в
winnt.bi. Тип данных
Large_Integer
используется в ряде функций Windows с С
RinTime. В
следующем коде фрагмент объявления
Large_Integer.
Union LARGE_INTEGER
Type
LowPart As DWORD
HighPart As Long
End Type
QuadPart As LONGLONG
End Union
Тип данных
Dword объявлен в
windef.bi и
соответствует FreeBASIC
Uinteger, а тип
Longlong
соответствует
Longint.
Long - это
просто псевдоним для типа данных integer. Помните, что тип занимает место
непрерывной памяти, так поле
HighPart следует за
LowPart
в памяти. Поскольку это
Union, тип занимает тот же
сегмент памяти как поле QuadPart.
Когда Вы устанавливаете для
QuardPart огромное значение из 2
integer (по сути
Longint),
также задаются значения полей типа, которые потом можно извлечь как
LowPart
и
HighPart. Вы также можете сделать наоборот; установив
LowPart
и
HighPart в типе, вы устанавливаете значение поля
QuadPart.
Как вы можете видеть, использование типа в рамках
union
дает простой способ для задания или извлечения отдельных значений типа
данных компонента не прибегая к большим кол-вам преобразований кода.
Расположение сегментов памяти выполняет преобразование за вас.
Объединение в определении типа является эффективным способом для управления
данными, когда одно поле в типе должно иметь только одно из нескольких
значений. Наиболее распространенным примером этого является тип данных
Variant в других языках программирования.
FreeBASIC не имеет собственный тип данных
Variant в данный момент. Тем не менее, с помощью расширенного синтаксиса
Type, можно создать тип данных Variant для
использования в вашей программе. |
Когда используется
Union в типе, это является обычной практикой,
чтобы создать
id поля внутри типа, который указывает, что
union содержит в данный момент. Следующий фрагмент
кода иллюстрирует эту концепцию.
'идентификатор поля Union
#define vInteger 0
#define vDouble 1
'Определение защиты типа с переменными полями данных
Type vType
vt_id As Integer
Union
d As Double
i As Integer
End Union
End Type
Определение
Union здесь называется анонимным
union, так как он не определен с именем. Поле
vt_id в определении типа указывает значение
union.
Для инициализации типа вы должны использовать код вроде следующего.
Dim myVarianti As vType
Dim myVariantd As vType
myVarianti.vt_id = vInteger
myVarianti.i = 300
myVariantd.vt_id = vDouble
myVariantd.d = 356.56
myVarianti содержит значение
integer , а
id
установлено в
vInteger.
myVariantd содержит
значение
double , а id установлено
vDouble. Если бы
вы создали подпрограмму, которая принимала бы параметр VType, вы могли бы
рассматривать поле vt_
id , чтобы определять какое
значение вам нужно
integer или
double.
Вы не можете использовать динамические
строки в union. |
Используя сочетание
union и
type в программе, позволяет создавать собственные типы данных,
имеющие большую гибкость, но необходимо соблюдать осторожность, чтобы
убедиться, что вы используете данные конструкции правильно. Неправильное
использование этих типов данных, может привести к долгому поиску ошибок.
Преимущества однако, перевешивают риски и как только вы освоите данную
технику, вы получите мощный инструмент программирования.