Beginners Guide to Types as Objects (Part 2)
 

    介绍。

    欢迎来到本教程的第二部分,在这部分我假设你已经阅读了第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)


    您可以看到,我们的索引属性的声明与常规属性非常相似,只不过这次我们为索引添加了一个参数。我包含一个虚拟的整数成员,因为一个类型必须至少有一个数据成员。可以看出,该属性然后与(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,其中包含构造函数中指定的元素数。然后当调用属性函数时,我检查索引是否在数组的边界内,如果然后我执行请求的get或set。如果指定的索引超出范围,则会出现“错误6”,这是一种使用FB“超出边界错误”中止程序的方式,您可以用自己的错误处理例程来替换它。尝试这样做,通过将代码'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


    这将是非常有用的,因为我现在将解释。

    深/浅拷贝。

    在上一个例子中,我们做了代码“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 'Uses the copy constructor

    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    'The assignment operator (Let) gets used this time.

    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 'Uses the copy constructor

    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 ' Now using the assignment operator

    Print a.x[0], a.x[1], b.x[0], b.x[1]


    这可能看起来相当复杂,值得再次阅读,并尝试这些例子,一旦你习惯了它,这不是太棘手。

    将对象传递给函数ByVal

    深层和浅层复制的想法也适用于通过值将对象传递给函数。当您传递对对象(ByRef)的引用时,您可以修改对象,这些修改将保持不变,但是您也可以通过值传递,这意味着您可以修改它,而不会在该函数之外持续更改。当一个对象通过值传递给一个函数时,会创建一个新的副本,如果该对象具有一个复制构造函数,那么这个对象被调用,如果没有,则执行隐藏的浅层拷贝。一旦函数结束,就调用对象析构函数。

    New/Delete

    新建和删除是用于动态赋值内存的特殊操作符,然后销毁它。因为它与动态内存一起使用,它与指针一起使用。在所有直到现在的例子中,我们只是使用Dim来创建我们的对象,这将在堆栈中创建它们,但是通过使用new,我们可以动态地创建它们,这样可以允许更多的灵活性,就像使用Allocate / DeAllocate与正常内存一样。新的另一个重要的事情是,你不需要检查新的指针是否为NULL,就像你赋值一样。如果新的失败,它会导致异常,这将导致程序结束。在FreeBASIC的后期版本中,可能会创建某种类型的try..catch机制,以便更好地处理异常,但在撰写本文时,尚未实现。

    新/删除有两种不同的品种。第一种类型,仅创建一个元素或对象,例如:

    Dim As Integer Ptr foo = New Integer

    *foo = 1
    Print *foo

    Delete foo


    这将创建一个新的整数,然后在我们调用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


    您将看到该对象的构造函数和析构函数被调用。

    第二种类型的新/删除是用于创建数组,这时元素的数量放在dataype之后的方括号'[]'中。使用阵列版本时,您还必须使用'delete []'而不是'delete',这样FreeBASIC就知道你正在删除一个数组,下面是一个使用Integer类型的简单例子:

    Dim As Integer Ptr foo = New Integer[20]

    foo[1] = 1
    Print foo[1]

    Delete[] foo


    这将创建一个具有20个整数元素的动态数组。应该注意的是,这不同于Allocate,它以字节数作为参数;使用新的,你应该指定元素的数量。数组方法与对象相同:

    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或删除[]为任何赋值给新内存的内存,否则将导致内存泄漏,就像您必须记住为使用赋值功能赋值的任何内存调用DeAllocate的方式一样。

    名称芒果

    名称称谓,也称为名字装饰,是在幕后发生的,在较低层次上,因此不是必不可少的。名称变动的原因是解决涉及多个功能共享相同名称的问题,当功能重载或发生在类型的一部分时发生。举个例子,如下所示:

    Sub foo Overload ()

    End Sub

    Sub foo(ByVal i As Integer)

    End Sub


    如果我们没有名字,那么两者都可能在较低的级别被知道为FOO,这将导致名称冲突,所以他们必须进行装饰,以便知道在使用时应该调用哪一个。对于第一个子文件,编译器实际上创建一个名为_Z3FOOv的子类,第二个它创建一个名为_Z3FOOi的子类。编译器然后记住这些,并根据你所调用的方式选择适当的子进程,例如'foo()'将实际调用_Z3FOOv,'foo(1)'实际上会调用_Z3FOOi。我们可以从中发现一些东西,'v'代表无效(没有参数),'i'代表整数。名称变化的全部细节是相当复杂的,并且在编译器之间有所不同,Microsoft编译器对GNU编译器使用不同的名称调整方案,其他编译器也可能使用不同的方案。我们需要知道的主要因素是,FreeBASIC遵循GCC 3.x ABI(应用程序二进制接口),这意味着任何重载函数或复杂类型只能与使用相同方案的其他编译器兼容。这是一个不幸的限制,但它并不是真正的FreeBASIC问题,所有使用高级功能的编译器是常见的,即使所有的编译器作者都同意一个通用的名称调整方案,还有其他问题会导致不相容性。

    隐藏这个

    这一点也不一定要知道大部分是在较低级别幕后发生的事情。当你调用一个对象的成员函数时,实际发生的是一个隐藏的第一个参数被传递,这样函数就知道该对象的哪个实例被引用。属性/构造函数/析构函数/操作符成员也是如此。如果我们看一个非常简单的例子:

    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”的方法经常用于没有设施使其更容易的语言。OOP实际上只是一组概念,几乎可以用任何语言编码,有些事情更难实现,比如构造函数,你必须明确地调用一个'create'或'init'函数。对于一些诸如私人/公共区别的东西,由于编译器不知道执行它们,所以更难或不可能。为语言添加OOP功能的原因是隐藏了很多这样的内容,并添加了语法糖,使其更简单,或更透明的使用,例如我们可以像使用属性一样是普通的数据成员,而不是功能,这是他们真正的。

    调试/分析提示

    当使用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) - 使用整数参数的构造函数(注意没有特殊符号来表示ByVal,因为这是C / C ++中的默认传递方法)
    BAR ::?BAR() - 析构函数
    BAR :: operator void *() - 转换为任何ptr(void类似于Any,*表示指针)
    BAR :: operator =(int) - C / C ++'='中的赋值运算符(Let),以'='表示,是赋值,'=='是相等性测试。
    BAR :: FOO(int) - 属性foo setter,取整数参数
    BAR :: FOO() - 属性foo getter

    成员子/函数以与属性相同的方式显示,索引属性也显示相同,只是索引的额外参数。

    以下是FB数据类型的显示方式:

    任何PTR - 无效*
    ZString PTR - 字符*
    字符串 - FBSTRING
    字节 - 已签名字符
    ubyte - 布尔
    短短
    ushort - 无符号短
    integer - int
    uinteger - unsigned int
    longint - 长长
    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