Skip to content

构造函数、'=' 赋值运算符和析构函数(高级,第 #1 部分)


正确使用构造函数、'=' 赋值运算符和析构函数,它们是用于构造/初始化、赋值和销毁对象的特殊成员过程(第 #1 部分)。

前言:

某些成员过程在 Type 中具有预定义名称,具有构造、赋值和销毁对象的特定角色。这些是构造函数、'=' 赋值运算符和析构函数。

其中某些是特殊的,因为如果需要且用户未显式定义,编译器会自动构建一个版本。这些是默认构造函数、复制构造函数、复制赋值运算符和析构函数。

因此,通常有必要让用户定义自己的显式版本,以便在对象的整个生命周期内执行必须发生的操作:

  • 例如,如果对象包含动态分配的变量,则在创建对象时必须为其保留内存。

  • 对于对象赋值,通常必须重新分配内存。

  • 在销毁对象时,必须释放已分配的内存。

一旦用户定义了任何显式构造函数或显式析构函数,编译器将不再自动定义隐式默认构造函数或隐式析构函数。

特别是,如果用户只定义了一个带参数的显式构造函数,将不再能够在不提供该构造函数参数的情况下简单构造对象,除非用户也定义了显式默认构造函数(即不带参数的构造函数)。

在定义这些特殊成员过程时还必须考虑类型继承和虚拟性。

与任何成员一样,这些特殊成员过程可以具有任何访问权限:public、protected 或 private(但 private 访问并没有太大意义,甚至可能完全阻止任何直接或派生对象的构造)。

目录

1. 构造函数和析构函数

2. '=' 赋值运算符

3. 将变长数组作为 Type 的成员处理


1. 构造函数和析构函数

构造函数在实例化对象时自动被调用。析构函数在销毁对象时自动被调用。

对于自动存储类型的对象,这种销毁发生在退出当前作用域块时。

对于动态分配的对象,构造函数和析构函数由使用 'New'、'New[]' 和 'Delete'、'Delete[]' 运算符的表达式自动调用。因此,建议使用它们代替 '[C|Re]Allocate' 和 'Deallocate' 函数来动态创建对象。

另外,不要在 'Any Ptr' 指针上使用 'Delete' 或 'Delete[]',因为编译器必须根据指针类型确定调用哪个析构函数。

构造函数和析构函数的定义

构造函数在对象内存分配之后被调用,析构函数在释放该内存之前被调用。因此,使用 Types 进行内存动态分配管理得到了简化。

对于成员对象字段,构造顺序是其声明的顺序,销毁顺序是相反的。各成员对象字段的构造函数和析构函数按此顺序调用。

显式构造函数可以有参数。它们可以重载,但显式析构函数不能重载。这是因为通常知道对象创建的上下文,但无法知道它被销毁的上下文:只能有一个析构函数。

不接受参数或所有参数都有默认值的构造函数,如果 Types 中没有显式定义的构造函数,则会自动替换编译器定义的默认构造函数。这意味着这些构造函数将由派生 Types 的默认构造函数自动调用。

示例 - 构造函数和析构函数('ZstringChain' Type):

start GeSHi

vb
Type ZstringChain                                '' implement a zstring chain
    Dim As ZString Ptr pz                        '' define a pointer to the chain
    Declare Constructor ()                       '' declare the explicit default constructor
    Declare Constructor (ByVal size As Integer)  '' declare the explicit constructor with as parameter the chain size
    Declare Destructor ()                        '' declare the explicit destructor
End Type

Constructor ZstringChain ()
    This.pz = 0  '' reset the chain pointer
End Constructor

Constructor ZstringChain (ByVal size As Integer)
    This.pz = CAllocate(size + 1, SizeOf(ZString))  '' allocate memory for the chain
End Constructor

Destructor ZstringChain ()
    If This.pz <> 0 Then
        Deallocate This.pz  '' free the allocated memory if necessary
    This.pz = 0         '' reset the chain pointer
    End If
End Destructor

Dim As ZstringChain zc1  '' instantiate a non initialized chain : useless

Dim As ZstringChain zc2 = ZstringChain(9)  '' instantiate a szstring chain of 9 useful characters
'                                          '' shortcut: Dim As ZstringChain zc2 = 9
*zc2.pz = "FreeBASIC"                      '' fill up the chain with 9 characters
Print "zc2 chain:"
Print "'" & *zc2.pz & "'"                  '' print the chain

Sleep

end GeSHi

输出:

zc2 chain:
'FreeBASIC'

构造函数有时必须执行比此示例中给出的更复杂的任务。一般来说,它们可以执行普通成员过程中可行的所有操作,当然除了使用未初始化的数据。

特别是,在基类 Types 的构造函数被调用之前,继承对象的数据不会被初始化。因此,在运行被实例化 Type 的构造函数之前,必须始终先调用基类 Types 的构造函数。

如果未显式调用基类 Types 的构造函数,编译器将默认调用不带参数或参数具有默认值的基类 Types 的构造函数(如果基类 Types 中没有显式定义此类构造函数,则将调用这些 Types 的隐式默认构造函数)。

构造函数

构造函数是一种初始化其 Type 实例的成员过程。构造函数与 Type 同名,没有返回值。构造函数可以有任意数量的参数,一个 Type 可以有任意数量的重载构造函数。

如果未显式定义任何构造函数,编译器将生成(如果需要)不带参数的默认构造函数。用户可以通过显式声明和定义自己的默认构造函数来取消此行为。

如果用户定义了自己的构造函数,隐式默认初始化操作始终在用户构造函数代码执行之前完成。

构造顺序:

  • 调用 Type 构造函数。

  • 按声明顺序调用基类 Types 及其成员构造函数。

  • 如果 Type 有虚拟成员过程(包括从其 Base 继承的),则虚拟指针(vptr)被设置为指向 Type 的虚拟表(vtbl)。

  • 执行 Type 构造函数过程的主体。

默认构造函数:

默认构造函数没有参数。

它遵循稍微不同的规则:

  • 默认构造函数是特殊成员函数之一。

  • 如果 Type 中没有显式声明构造函数,编译器提供默认构造函数。

  • 如果声明了任何非默认构造函数,编译器不提供默认构造函数,如有必要,用户必须声明一个。

转换构造函数:

如果 Type 有一个单参数构造函数,或者至少其他所有参数都有默认值,则编译器可以将第一个参数的类型隐式转换为 Type 的类型。

在此类表达式中,术语 'variable' 可以被视为 'typename(variable)' 的简写。

这种转换在某些情况下可能很有用,但更多情况下会导致可读性差(在这些情况下,最好显式调用匹配的构造函数)。

示例 - 隐式转换:

start GeSHi

vb
Type UDT Extends Object
    Declare Constructor ()
    Declare Constructor (ByVal i0 As Integer)
    Declare Destructor ()
    Dim As Integer i
End Type

Constructor UDT ()
    Print "   => UDT.default-constructor", @This
End Constructor

Constructor UDT (ByVal i0 As Integer)
    Print "   => UDT.conversion-constructor", @This
    This.i = i0
End Constructor

Function RtnI (ByRef u As UDT) As Integer
    Return u.i
End Function
 
Destructor UDT ()
    Print "   => UDT.destructor", , @This
End Destructor

Scope
    Print "Construction: 'Dim As UDT u'"
    Dim As UDT u
    Print
    Print "Assignment: 'u = 123'"
    Print "      " & RtnI(123)            ''  RtnI(123): implicite conversion using the conversion-constructor,
    '                                     ''             short_cut of RtnI(UDT(123))
    Print
    Print "Going out scope: 'End Scope'"
End Scope

Sleep

end GeSHi

输出示例:

vb
Construction: 'Dim As UDT u'
   => UDT.default-constructor             1703588

Assignment: 'u = 123'
   => UDT.conversion-constructor          1703580
  123
   => UDT.destructor                      1703580

Going out scope: 'End Scope'
   => UDT.destructor                      1703588

但这种转换甚至可能导致代码中出现微妙但严重的错误。

示例 - 代码中的微妙/严重错误:

start GeSHi

vb
Type point2D
    Declare Constructor (ByVal x0 As Integer = 0, ByVal y0 As Integer = 0)
    Declare Operator Cast () As String
    Dim As Integer x, y
End Type

Constructor point2D (ByVal x0 As Integer = 0, ByVal y0 As Integer = 0)
    This.x = x0
    This.y = y0
End Constructor

Operator point2D.Cast () As String
    Return "(" & This.x & ", " & This.y & ")"
End Operator

Operator + (ByRef v0 As point2D, ByRef v1 As point2D) As point2D
    Return Type(v0.x + v1.x, v0.y + v1.y)
End Operator

Operator * (ByRef v0 As point2D, ByRef v1 As point2D) As Integer
    Return v0.x * v1.x + v0.y * v1.y
End Operator

Print "Construction of v1: 'Dim As point2D v1 = point2D(2, 3)'"
Dim As point2D v1 = point2D(2, 3)
Print "  => " & v1
Print
Print "Addition to v1: 'v1 + 4'"
Print "  => " & v1 + 4                  ''  4: implicite conversion using the conversion-constructor,
'                                       ''             short_cut of point2D(4, ) or point2D(4)
Print
Print "Multiplication of v1: 'v1 * 5'"
Print "  => " & v1 * 5                  ''  5: implicite conversion using the conversion-constructor,
'                                       ''             short_cut of point2D(5, ) or point2D(5)
Sleep

end GeSHi

输出示例:

vb
Construction of v1: 'Dim As point2D v1 = point2D(2, 3)'
  => (2, 3)

Addition to v1: 'v1 + 4'
  => (6, 3)

Multiplication of v1: 'v1 * 5'
  => 10

在这种情况下,声明带有可选参数的多参数构造函数是危险的,因为编译器可能将其一种形式解释为转换构造函数。

解决方法:定义一个默认构造函数和多参数构造函数,但不带可选参数(或至少除第一个之外有一个非可选参数)。

复制构造函数:

复制构造函数是一种特殊的成员过程,它以同类型对象的引用作为输入,并通过复制它来构造一个新对象。

如果用户未声明复制构造函数,编译器将为其生成(如果需要)复制构造函数。声明复制赋值运算符(见上文)不会阻止编译器生成复制构造函数。

有时需要创建复制构造函数。这种构造函数的目的是在从另一个对象实例化对象时初始化该对象。

任何 Type 如果需要,都会由编译器自动生成一个隐式复制构造函数,其唯一目的是将要复制的对象的字段逐个复制到要实例化的对象的字段中。

然而,这个隐式复制构造函数并不总是足够的,有时用户必须显式提供一个。

当某些数据对象已被动态分配时(仅 Type 中用于对象聚合的成员指针),情况尤其如此。对一个对象字段到另一个对象的浅复制只会复制指针,而不复制指向的数据。因此,为一个对象更改此数据将导致另一个对象数据的修改,这可能不是期望的效果。

如果用户实现了复制构造函数,建议也实现复制赋值运算符,以使代码的含义清晰。

除了显式调用复制构造函数的语法外,当对象按值传递给过程时以及函数使用 'Return' 关键字按值返回对象时,也会调用它。

复制构造函数不能按值接受其参数(要克隆的对象),因为"byval arg"本身通常会调用复制构造函数,这将导致无限循环。

对于上面定义的 'ZstringChain' Type,用户需要一个复制构造函数:

start GeSHi

vb
Type ZstringChain                                   '' implement a zstring chain
    Dim As ZString Ptr pz                           '' define a pointer to the chain
    Declare Constructor ()                          '' declare the explicit default constructor
    Declare Constructor (ByVal size As Integer)     '' declare the explicit constructor with as parameter the chain size
    Declare Constructor (ByRef zc As ZstringChain)  '' declare the explicit copy constructor
    Declare Destructor ()                           '' declare the explicit destructor
End Type

Constructor ZstringChain ()
    This.pz = 0  '' reset the chain pointer
End Constructor

Constructor ZstringChain (ByVal size As Integer)
    This.pz = CAllocate(size + 1, SizeOf(ZString))  '' allocate memory for the chain
End Constructor

Constructor ZstringChain (ByRef zc As ZstringChain)
    This.pz = CAllocate(Len(*zc.pz) + 1, SizeOf(ZString))  '' allocate memory for the new chain
    *This.pz = *zc.pz                                      '' initialize the new chain
End Constructor

Destructor ZstringChain ()
    If This.pz <> 0 Then
        Deallocate This.pz  '' free the allocated memory if necessary
        This.pz = 0         '' reset the chain pointer
    End If
End Destructor

Dim As ZstringChain zc1  '' instantiate a non initialized chain : useless

Dim As ZstringChain zc2 = ZstringChain(9)           '' instantiate a szstring chain of 9 useful characters
'                                                   '' shortcut: Dim As ZstringChain zc2 = 9
*zc2.pz = "FreeBASIC"                               '' fill up the chain with 9 characters
Print "zc2 chain:"
Print "'" & *zc2.pz & "'"                           '' print the chain
Print
Dim As ZstringChain zc3 = zc2                       '' instantiate a new szstring chain by copy construction
Print "zc3 chain (zc3 copy constructed from zc2):"
Print "'" & *zc3.pz & "'"                           '' print the chain
Print
*zc3.pz = "modified"                                '' modify the new chain
Print "zc3 chain (modified):"
Print "'" & *zc3.pz & "'"                           '' print the new chain
Print
Print "zc2 chain:"
Print "'" & *zc2.pz & "'"                           '' print the copied chain (not modified)

Sleep

end GeSHi

输出:

zc2 chain:
'FreeBASIC'

zc3 chain (zc3 copy constructed from zc2):
'FreeBASIC'

zc3 chain (modified):
'modified'

zc2 chain:
'FreeBASIC'

析构函数

析构函数是构造函数的逆函数。当对象被销毁时调用它。

析构函数通常用于在对象不再需要时"清理"。析构函数不能有参数。

如果未显式定义任何析构函数,编译器将生成(如果需要)析构函数。用户可以通过显式声明和定义自己的析构函数来覆盖此行为。

如果用户定义了自己的析构函数,隐式析构操作始终在用户析构代码执行之后完成。

当发生以下事件之一时,析构函数被调用:

  • 使用 New 运算符创建的对象使用 Delete 运算符显式销毁。

  • 具有块作用域的局部对象超出作用域。

  • 程序终止且存在全局或静态对象。

如果基类 Type 或数据成员有可访问的析构函数,并且 Type 未声明析构函数,则编译器会生成一个。

销毁顺序:

  • 调用 Type 析构函数

  • 如果 Type 有虚拟成员过程(包括从其 Base 继承的),则虚拟指针(vptr)被设置为指向 Type 的虚拟表(vtbl)。

  • 执行 Type 析构函数过程的主体。

  • 按声明相反顺序调用基类 Types 的析构函数。

继承时的构造函数和析构函数

当实例化和销毁派生 Type 实例时,如何调用基类 Types 的构造函数和析构函数?

编译器无法知道在潜在存在的不同重载构造函数中应调用哪个。要调用基类 Type 的除不带参数构造函数以外的其他构造函数,请使用关键字 'Base ()' 指定要传递的参数,且这仅允许在调用构造函数的第一行代码上。

另一方面,无需指定要调用的析构函数,因为它是唯一的。用户不必自己调用基类 Types 的析构函数,编译器通过链接析构函数自行完成。

示例 - 显式调用基类 Type 构造函数('Child' Type 扩展 'Parent' Type):

start GeSHi

vb
Type Parent  '' declare the parent type
    Dim As Integer I
    Declare Constructor ()
    Declare Constructor (ByVal i0 As Integer)
    Declare Destructor ()
End Type

Constructor Parent ()  '' define parent Type constructor
    Print "Parent.Constructor()"
End Constructor

Constructor Parent (ByVal i0 As Integer)  '' define parent Type constructor
    This.I = i0
    Print "Parent.Constructor(Byval As Integer)"
End Constructor

Destructor Parent ()  '' define parent Type destructor
    Print "Parent.Destructor()"
End Destructor

Type Child Extends Parent  '' declare the child Type
    Declare Constructor ()
    Declare Destructor ()
End Type

Constructor Child ()  '' define child Type default constructor
    Base(123)         '' authorize only on the first code line of the constructor body
    Print "  Child.Constructor()"
End Constructor

Destructor Child ()  '' define child Type destructor
    Print "  Child.Destructor()"
End Destructor

Scope
    Dim As Child c
    Print
End Scope

Sleep

end GeSHi

输出:

Parent.Constructor(Byval As Integer)
  Child.Constructor()

  Child.Destructor()
Parent.Destructor()

如果未指定要为基类 Type 调用的构造函数是接受 Integer 参数的构造函数,则编译器将调用基类 Type 的默认构造函数(在上面的示例中,可以注释掉 'Base(123)' 行并重新执行以看到这种不同的行为)。

如果 Type 派生自多个基类 Types(多级继承),则每个派生 Type 构造函数只能显式调用其直接基类 Type 的构造函数,从而构成基类构造函数的链接。

当使用继承多态(子类型多态)时,对象通过基类 Type 指针或引用进行操作:

  • 如果至少有一个派生 Type 定义了显式析构函数,则其所有基类析构函数必须是虚拟的,以便销毁可以从最派生的 Type 开始向下到最后一个基类 Type。

  • 为此,可能需要在任何尚未需要显式析构函数的地方添加带空主体的虚拟析构函数,以取代编译器构建的每个非虚拟隐式析构函数。

注意:

  • 当派生 Type 有一个定义了(隐式或显式)默认构造函数、复制构造函数或析构函数的基类 Type 时,编译器会为该派生 Type 定义默认构造函数、复制构造函数或析构函数。

  • 内置的 'Object' Type 具有隐式定义的默认构造函数和复制构造函数,因此所有(直接或间接)派生自 'Object' 的 Types 至少隐式地具有默认构造函数和复制构造函数。

构造函数和析构函数中的虚拟过程调用

通常在构造函数或析构函数中调用任何成员过程是安全的,因为对象在第一行用户代码执行之前已完全设置好(虚拟表已初始化等)。但是,对于抽象基类 Type,在构造或析构期间调用虚拟成员过程可能是不安全的。

在构造函数或析构函数中调用虚拟过程时要小心,因为在基类构造函数或析构函数中调用的过程是基类 Type 的版本,而不是派生 Type 的版本。当调用这样的虚拟过程时,调用的是为构造函数或析构函数自身 Type 定义的过程(或从其 Bases 继承的)。

返回顶部


2. '=' 赋值运算符

在所有 '=' 赋值运算符中,有一个特定的运算符,它以同类型对象的引用作为输入,并制作其副本(而不创建新对象)。

这就是复制赋值运算符。

复制赋值运算符

如果用户未声明复制赋值运算符,编译器将为其生成(如果需要)复制赋值运算符。声明复制构造函数(见上文)不会阻止编译器生成复制赋值运算符。

如果隐式复制不够,则必须定义复制赋值运算符。

当对象管理动态分配的内存或其他需要特殊复制的资源时(例如,如果成员指针指向动态分配的内存,隐式 '=' 赋值运算符将只是复制指针值,而不是分配内存然后执行数据复制),就会发生这种情况。

如果用户实现了复制赋值运算符,建议也实现复制构造函数,以使代码的含义清晰。

除了显式调用复制赋值运算符的语法外,当函数使用 'Function =' 关键字按值返回对象时,也会调用它。

复制赋值运算符不能按值接受其参数(要克隆的对象),因为在某些情况下,"byval arg" 被编码为默认构造 + 复制赋值(例如,当 UDT 没有复制构造函数但有带默认构造函数的对象字段或带默认构造函数的 Base 时),这将导致无限循环。

对于上面定义的 'ZstringChain' Type,用户还需要一个复制赋值运算符(请参阅"高级 #2"页中的"三法则"):

start GeSHi

vb
Type ZstringChain                                    '' implement a zstring chain
    Dim As ZString Ptr pz                            '' define a pointer to the chain
    Declare Constructor ()                           '' declare the explicit default constructor
    Declare Constructor (ByVal size As Integer)      '' declare the explicit constructor with as parameter the chain size
    Declare Constructor (ByRef zc As ZstringChain)   '' declare the explicit copy constructor
    Declare Operator Let (ByRef zc As ZstringChain)  '' declare the explicit copy assignment operator
    Declare Destructor ()                            '' declare the explicit destructor
End Type

Constructor ZstringChain ()
    This.pz = 0  '' reset the chain pointer
End Constructor

Constructor ZstringChain (ByVal size As Integer)
    This.pz = CAllocate(size + 1, SizeOf(ZString))  '' allocate memory for the chain
End Constructor

Constructor ZstringChain (ByRef zc As ZstringChain)
    This.pz = CAllocate(Len(*zc.pz) + 1, SizeOf(ZString))  '' allocate memory for the new chain
    *This.pz = *zc.pz                                      '' initialize the new chain
End Constructor

Operator ZstringChain.Let (ByRef zc As ZstringChain)
    If @zc <> @This Then                                       '' avoid self assignment destroying the chain
        If This.pz <> 0 Then
            Deallocate This.pz                                 '' free the allocated memory if necessary
        End If
        This.pz = CAllocate(Len(*zc.pz) + 1, SizeOf(ZString))  '' allocate memory for the new chain
        *This.pz = *zc.pz                                      '' initialize the new chain
    End If
End Operator

Destructor ZstringChain ()
    If This.pz <> 0 Then
        Deallocate This.pz  '' free the allocated memory if necessary
        This.pz = 0         '' reset the chain pointer
    End If
End Destructor

Dim As ZstringChain zc1  '' instantiate a non initialized chain : useless

Dim As ZstringChain zc2 = ZstringChain(9)           '' instantiate a szstring chain of 9 useful characters
'                                                   '' shortcut: Dim As ZstringChain zc2 = 9
*zc2.pz = "FreeBASIC"                               '' fill up the chain with 9 characters
Print "zc2 chain:"
Print "'" & *zc2.pz & "'"                           '' print the chain
Print
Dim As ZstringChain zc3 = zc2                       '' instantiate a new szstring chain by copy construction
Print "zc3 chain (zc3 copy constructed from zc2):"
Print "'" & *zc3.pz & "'"                           '' print the chain
Print
*zc3.pz = "modified"                                '' modify the new chain
Print "zc3 chain (modified):"
Print "'" & *zc3.pz & "'"                           '' print the new chain
Print
Print "zc2 chain:"
Print "'" & *zc2.pz & "'"                           '' print the copied chain (not modified)
Print
zc3 = zc2
Print "zc3 chain (zc3 copy assigned from zc2):"
Print "'" & *zc3.pz & "'"                           '' print the new chain
Print
*zc3.pz = "changed"                                 '' modify the new chain
Print "zc3 chain (changed):"
Print "'" & *zc3.pz & "'"                           '' print the new chain
Print
Print "zc2 chain:"
Print "'" & *zc2.pz & "'"                           '' print the copied chain (not modified)

Sleep

end GeSHi

输出:

zc2 chain:
'FreeBASIC'

zc3 chain (zc3 copy constructed from zc2):
'FreeBASIC'

zc3 chain (modified):
'modified'

zc2 chain:
'FreeBASIC'

zc3 chain (zc3 copy assigned from zc2):
'FreeBASIC'

zc3 chain (changed):
'changed'

zc2 chain:
'FreeBASIC'

返回顶部


3. 将变长数组作为 Type 的成员处理

变长数组不像变长字符串那样是伪对象,因为没有像变长字符串那样的默认复制构造函数和复制赋值运算符。

但是,当在 Type 中声明变长数组时,编译器会为该 Type 构建一个默认复制构造函数和一个默认复制赋值运算符。它包含了在需要时对目标数组调整大小和从源数组复制数据的所有代码:

编译器自动调整数组大小和复制的示例:

start GeSHi

vb
Type UDT
    Dim As Integer array(Any)
End Type

Dim As UDT u1, u2

ReDim u1.array(1 To 9)
For I As Integer = LBound(u1.array) To UBound(u1.array)
    u1.array(I) = I
Next I
 
u2 = u1
For I As Integer = LBound(u2.array) To UBound(u2.array)
    Print u2.array(I);
Next I
Print

Dim As UDT u3 = u1
For I As Integer = LBound(u3.array) To UBound(u3.array)
    Print u3.array(I);
Next I
Print

Sleep

end GeSHi

输出:

 1 2 3 4 5 6 7 8 9
 1 2 3 4 5 6 7 8 9

如果用户想指定自己的复制构造函数和复制赋值运算符(例如处理其他复杂字段成员),则上述编译器自动调整数组大小和复制将被破坏:

编译器自动调整数组大小和复制被显式复制构造函数和复制赋值运算符破坏的示例:

start GeSHi

vb
Type UDT
  Dim As Integer array(Any)
  'user fields
  Declare Constructor ()
  Declare Constructor (ByRef u As UDT)
  Declare Operator Let (ByRef u As UDT)
End Type

Constructor UDT ()
  'code for user fields in constructor
End Constructor

Constructor UDT (ByRef u As UDT)
  'code for user fields in copy-constructor
End Constructor

Operator UDT.Let (ByRef u As UDT)
  'code for user fields in copy-assignement operator
End Operator

Dim As UDT u1, u2

ReDim u1.array(1 To 9)
For I As Integer = LBound(u1.array) To UBound(u1.array)
  u1.array(I) = I
Next I
 
u2 = u1
For I As Integer = LBound(u2.array) To UBound(u2.array)
  Print u2.array(I);
Next I
Print

Dim As UDT u3 = u1
For I As Integer = LBound(u3.array) To UBound(u3.array)
  Print u3.array(I);
Next I
Print

Sleep

end GeSHi

输出(无):

基本变长数组成员不能像变长字符串成员那样作为伪对象进行复制,因为没有隐式赋值(参照上面的示例,'This.array = u.array' 是不允许的)。

用户必须显式编写数组成员的调整大小和复制代码:

在用户复制构造函数和复制赋值运算符中显式设置数组调整大小和复制的示例:

start GeSHi

vb
#include "crt/string.bi"

Type UDT
    Dim As Integer array(Any)
    'user fields
    Declare Constructor ()
    Declare Constructor (ByRef u As UDT)
    Declare Operator Let (ByRef u As UDT)
End Type

Constructor UDT ()
    'code for user fields in constructor
End Constructor

Constructor UDT (ByRef u As UDT)
    'code for user fields in copy-constructor
    If UBound(u.array) >= LBound(u.array) Then  '' explicit array sizing and copying
        ReDim This.array(LBound(u.array) To UBound(u.array))
        memcpy(@This.array(LBound(This.array)), @u.array(LBound(u.array)), (UBound(u.array) - LBound(u.array) + 1) * SizeOf(@u.array(LBound(u.array))))
    End If
End Constructor

Operator UDT.Let (ByRef u As UDT)
    'code for user fields in copy-assignement operator
    If @This <> @u And UBound(u.array) >= LBound(u.array) Then  '' explicit array sizing and copying
        ReDim This.array(LBound(u.array) To UBound(u.array))
        memcpy(@This.array(LBound(This.array)), @u.array(LBound(u.array)), (UBound(u.array) - LBound(u.array) + 1) * SizeOf(@u.array(LBound(u.array))))
    End If
End Operator

Dim As UDT u1, u2

ReDim u1.array(1 To 9)
For I As Integer = LBound(u1.array) To UBound(u1.array)
    u1.array(I) = I
Next I
 
u2 = u1
For I As Integer = LBound(u2.array) To UBound(u2.array)
    Print u2.array(I);
Next I
Print

Dim As UDT u3 = u1
For I As Integer = LBound(u3.array) To UBound(u3.array)
    Print u3.array(I);
Next I
Print

Sleep

end GeSHi

输出:

 1 2 3 4 5 6 7 8 9
 1 2 3 4 5 6 7 8 9

另一种优雅的可能性是保持编译器自动编写的这种调整大小/复制,但只需显式调用它。为此,成员数组的一个明显解决方案是不再将其放在 Type 本身的级别,而是放在另一个特定的 Type 中,但通过继承(从外部看,完全相同):

使用包含动态数组成员的基类 Type 的示例:

start GeSHi

vb
Type UDT0
    Dim As Integer array(Any)
End Type

Type UDT Extends UDT0
    'user fields
    Declare Constructor ()
    Declare Constructor (ByRef u As UDT)
    Declare Operator Let (ByRef u As UDT)
End Type

Constructor UDT ()
    'code for user fields in constructor
End Constructor

Constructor UDT (ByRef u As UDT)
    'code for user fields in copy-constructor
    Base(u)  '' inherited array sizing and copying from Base copy-constructor
End Constructor

Operator UDT.Let (ByRef u As UDT)
    'code for user fields in copy-assignement operator
    Cast(UDT0, This) = u  '' inherited array sizing and copying from Base copy-assignement operator
End Operator

Dim As UDT u1, u2

ReDim u1.array(1 To 9)
For I As Integer = LBound(u1.array) To UBound(u1.array)
    u1.array(I) = I
Next I
 
u2 = u1
For I As Integer = LBound(u2.array) To UBound(u2.array)
    Print u2.array(I);
Next I
Print

Dim As UDT u3 = u1
For I As Integer = LBound(u3.array) To UBound(u3.array)
    Print u3.array(I);
Next I
Print

Sleep

end GeSHi

输出:

 1 2 3 4 5 6 7 8 9
 1 2 3 4 5 6 7 8 9

返回顶部


参见

返回 目录

基于 FreeBASIC 官方文档翻译 如有侵权请联系我们删除
FreeBASIC 是开源项目,与微软公司无隶属关系