Introduction to the Type Def
 
Written by rdc

有时候创建一个程序可能需要定义聚合结构,如人事记录或游戏中的敌人。虽然您可以使用单独的数据类型执行此操作,但是在程序中难以管理。复合数据类型允许您将相关数据项分组到单个结构中,该结构可以作为单个实体进行操作。FreeBASIC提供两种复合数据类型:TypeUnion.

类型


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块内的各个数据元素。这不仅意味着你没有那么多的打字,但是结构是优化的,比使用全点符号要快一点。

将类型传递给子程序和函数


在程序中使用类型的一个优点是您可以将结构传递给子程序或函数,并对整个结构进行操作。以下代码片段显示了部分子例程定义。

Sub UpdateEmployeeDept(ByRef Emp As EmployeeType)
    .
    .
    .
End Sub


请注意,参数符合Byref的限制。这是很重要的,因为你想更新子程序中的类型。FreeBASIC有两种参数传递模式:ByrefByval.

ByRefByVal:快速介绍

ByrefByval告诉编译器如何传递对子例程或函数的引用。当您使用ByrefBy Reference 时,您将传递对该参数的指针引用,并且对sub或func中的参数所做的任何更改都将反映在传递的实际变量中。换句话说,Byref参数指向内存中的实际变量。

另一方面,ByvalBy Value 创建了一个参数的副本,在sub或func中进行的任何更改都是本地的,不会反映在传递的实际变量中。Byval参数指向变量的副本,而不是实际变量本身。

FreeBASIC .17的默认值是使用Byval传递参数。为了更改传递的参数,您需要指定Byref限定符。在此示例中,子例程更新了员工类型的部门ID,因此该参数被限定为Byref,以便子例程可以更新类型变量的dept字段。

另一方面,您可能不需要像下面的代码片段那样更新类型。

Sub PrintEmployeeRecord(Emp As EmployeeType)
    .
    .
    .
End Sub


在这个sub中,您只需将员工记录打印到屏幕或打印机,并且不需要更改类型变量中的任何内容。这里使用默认的Byval,它将员工记录的副本传递给子代,而不是对变量的引用。在这种情况下使用Byval,您不会意外地更改您不想更改的类型变量中的某些内容。

如果您打算更改参数数据,则只能使用Byref.在需要使用参数数据的情况下使用Byval更安全,但要防止意外更改数据。这些意外的变化在您的程序中产生难以找到的错误。

类型内的类型


除了内在数据类型之外,类型字段也可以基于类型定义。你为什么想做这个?一个原因是数据抽象。您的数据结构越一般,您可以在程序的其他部分重用代码的次数越多。编写的代码越少,错误找到进入程序的机会就越少。

使用Employee 示例,假设您需要跟踪部门ID跟踪更多的部门信息。您可能需要跟踪部门经理,部门的位置,如楼层或建筑物,或部门的主要电话号码。通过将此信息放入单独的类型定义中,您可以单独使用此信息,也可以使用其他类型定义(如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定义中,dept字段定义为DepartmentType 而不是内在数据类型之一。要访问Employee 类型的部门信息,您可以使用复合点符号来访问dept字段。

Employee.dept.id = 24
Employee.dept.managerid = 1012
Employee.dept.floor = 13


类型定义的顶层是Employee ,因此引用是首先。由于dept现在也是类型定义,因此您需要使用dept标识符来访问DepartmentType 中的各个字段。Employee 是指employee 类型,dept 是指部门类型,id managerid floor department 类型的字段。

您甚至可以通过在类型中的类型中包含类型来进一步。您只需要根据需要使用附加类型级别的点符号。虽然嵌套类型定义的级别没有限制,但在与多个级别一起使用时,它将变得有点笨重。

与和嵌套类型


您还可以通过嵌套With块来使用嵌套类型的With - End 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语句相匹配,以避免编译错误。

类型赋值


进一步扩展数据抽象的想法,能够将部门类型的初始化与员工类型的初始化分开是很好的。通过分离这两个功能,您可以根据需要轻松添加其他部门信息。这是您可以使用类型赋值的地方。

正如您可以将一个内部数据类型赋值给另一个,您可以将一个类型变量赋值给另一个类型变量,只要它们共享相同的类型定义。

以下代码片段摘录部门初始化函数,并将结果赋值给Employee 类型中的部门类型。

'This function will init the dept type and return it to caller
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 'In case a bad department id was passed
                With tmpDpt
                    .id = 0
                    .managerid  = 0
                    .floor  = 0
                End With
    End Select

    'Return the dept info
    Return tmpDpt
End Function

'Create an instance of the type
Dim Employee As EmployeeType

'Initialize the Employee type
With Employee
    .fname = "Susan"
    .lname = "Jones"
    .empid = 1001
    .dept = InitDept(24) 'get dept info
End With


如您在片段中所看到的那样,employee 类型的dept字段将使用函数调用进行初始化。InitDept 函数返回DepartmentType ,编译器会将该类型赋值给Employee 记录的dept字段。

只需向程序添加一个简单的功能,就可以使程序更容易维护。如果创建了新的部门,您可以使用新的部门信息来更新InitDept 功能,重新编译,程序已准备就绪。

位字段


还有另一种可以在类型定义中使用的数据类型,位字段。位字段定义为variable_name : bits As DataType .变量名必须跟随冒号,位数,后跟数据类型。只有整数类型(除了两个浮点类型“single”和“double”之外的所有数字类型,并且不包括64位类型)在位字段内被允许。当您需要跟踪布尔类型信息时,位字段很有用。有一点可以是01,可能代表是或否,开或关,甚至黑白。

以下代码片段说明了位字段定义。

Type BitType
    b1: 1 As Integer
    b2: 4 As Integer
End Type


b1 定义为单个位,b2 定义为四位。通过将各个位传递给类型字段来初始化位域。

myBitType.b1 = 1
myBitType.b2 = 1101


位字段的数据类型决定了可以在位字段中声明多少位。由于integer是32位长,您可以在该字段中声明最多32位。但是,在大多数情况下,您将为每个字段声明一个位,并使用多个字段来定义要使用的位掩码。使用单个位可以简化编码,您需要做的是确定位是否被置位或清除,并允许您轻松识别类型定义中的某种含义。

现场财产


创建类型定义的变量时,类型将被填充到内存中。填充允许更快地访问类型成员,因为类型字段在4字节或Word边界上对齐。但是,尝试从未填充的文件中读取类型记录时,可能会导致问题。您可以使用field属性更改类型定义的填充。

field关键字在类型名称之后使用,可以具有值为1,用于1字节对齐(无填充),2用于2字节对齐,4用于4字节对齐。要定义不带填充的类型,您将使用以下语法。

Type myType Field = 1
     v1 As Integer
    v2 As Byte
End Type


对于2字节对齐,您将使用field = 2.如果没有赋值field = property ,则填充将为4个字节。如果您正在读取由FreeBASIC使用默认对齐方式创建的类型定义,则不需要使用field属性。

快速基本

类型初始化


您可以在类型定义时初始化类型定义,就像您可以使用任何内在变量一样。以下代码片段说明了语法。

Type aType
        a As Integer
        b As Byte
        c As String * 10
End Type

Dim myType As aType => (12345, 12, "Hello")


Dim语句中,箭头运算符=>用于告知编译器您正在初始化类型变量。类型元素值必须用括号括起来,并用逗号分隔。值列表的顺序对应于类型元素的顺序,其中a 将被设置为12345b 12c "Hello".

您不能使用此方法初始化类型定义中的动态字符串。字符串必须是固定长度。


当您需要为一个类型设置一组初始值时,或者在程序执行期间不会更改的值,则在Dim语句中初始化类型定义很有用。由于值在编译时已知,因此编译器可能不必在运行时期间花费周期来加载值。

工会


工会在其定义中看起来类似于类型。

Union aUnion
    b As Byte
    s As Short
    i As Integer
End Union


如果这是Type,您可以访问定义中的每个字段。然而,对于Union,您只能在任何给定时间访问一个字段; Union中的所有字段占用相同的内存段,并且Union的大小是最大成员的大小。

在这种情况下,Union将占用四个字节,大小为Integerb 字段占用1个字节,s 字段占用2个字节,i 占用完整的4个字节。每个字段从第一个字节开始,所以s 字段将包含b 字段,i 字段将包含b s 字段。

工会类型


在联合中使用类型定义的一个很好的例子是winnt.bi中定义的Large_Integer 定义。Large_Integer 数据类型用于C运行时库中的许多Windows函数。以下代码片段显示了Large_Integer 定义。

Union LARGE_INTEGER
    Type
        LowPart As DWORD
        HighPart As Long
    End Type
    QuadPart As LONGLONG
End Union


Dword 数据类型在windef.bi中定义为FreeBASIC UintegerLonglong 类型定义为Longint .A Long只是整数数据类型的别名。请记住,类型占用连续的内存位置,因此HighPart 字段位于内存中的LowPart 部分字段。由于这是一个联合,因此类型与QuadPart 字段占用相同的内存段。

当您将QuardPart 设置为大整数值时,您还将设置类型字段的值,然后您可以将其解压缩为LowPart HighPart .你也可以做相反的事情;那就是设置LowPart HighPart 的类型,你设置QuadPart 字段的值。

您可以看到,在联合中使用类型是一种简单的方法来设置或检索组件数据类型的各个值,而不需要采用大量的转换代码。内存段的布局为您进行转换,只要内存段在组件类型的上下文中有意义。

Large_Integer case 中,LowPart HighPart 已定义为返回相应的组件值。使用Dword Long 以外的其他值将不会返回LowPart HighPart 的正确值。您需要确保在联合中定义类型时,正在类型定义中正确分割联合内存段。

类型联盟


类型定义中的联合是在类型中的某个字段只能包含几个值中的一个时,管理数据的有效方式。最常见的例子是其他编程语言中的Variant数据类型。

FreeBASIC目前没有Native Variant数据类型。但是,通过使用扩展类型语法,您可以创建一个Variant数据类型以供您的程序使用。


在类型中使用Union时,通常的做法是在类型中创建一个id 字段,指示在任何给定时刻联合包含的内容。以下代码段说明了这一概念。

'Union field ids
#define vInteger 0
#define vDouble 1

'Define type def with variable data fields
Type vType
    vt_id As Integer
    Union
        d As Double
        i As Integer
    End Union
End Type


这里的联合定义称为匿名联合,因为它没有用名称定义。类型定义的vt_id 字段指示联合的值。要初始化类型,您将使用以下代码。

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_type 字段以确定是否已将integer double 传递到子例程。

您不能在联合中使用动态字符串。


在程序中使用unions type 的组合可以设计具有很大灵活性的自定义数据类型,但必须注意确保正确使用数据结构。不正确使用这些数据类型可能会导致难以找到的错误。但是,一旦掌握了风险,那么这个好处就是一个强大的编程工具。