Введение в определение типа
 
Автор 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


Чтобы получить доступ к нескольким полям одновременно, вы можете использовать блок 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 и вложенные типы


Вы также можете использовать блок 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 полезна, когда вам нужно иметь набор начальных значений для типа или значения, которые не будут изменяться во время выполнения программы. Поскольку значения известны во время компиляции, то во время выполнения на это не будет тратится время.

Unions  (Объеденения)


Объеденения выглядят как типы в их определении.

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.

Types in Unions (Типы в объеденениях)


Хорошим примером данной техники будет определения типа 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 дает простой способ для задания или извлечения отдельных значений типа данных компонента не прибегая к большим кол-вам преобразований кода. Расположение сегментов памяти выполняет преобразование за вас.


Unions in Types (Объединения в типах)


Объединение в определении типа является эффективным способом для управления данными, когда одно поле в типе должно иметь только одно из нескольких значений. Наиболее распространенным примером этого является тип данных 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 в программе, позволяет создавать собственные типы данных, имеющие большую гибкость, но необходимо соблюдать осторожность, чтобы убедиться, что вы используете данные конструкции правильно. Неправильное использование этих типов данных, может привести к долгому поиску ошибок. Преимущества однако, перевешивают риски и как только вы освоите данную технику, вы получите мощный инструмент программирования.