2.17. 结构

Daslang 使用类似于 C/C++、Java、C# 等语言的结构机制。 但是,存在一些重要的区别。 结构体是一等对象,如整数或字符串,可以存储在表槽、其他结构体、局部变量、数组、元组、变体等中,并作为函数参数传递。

2.17.1. 结构体声明

通过关键字 struct 创建结构体对象:

struct Foo {
    x, y: int
    xf: float
}

结构可以是 privatepublic:

struct private Foo {
    x, y: int
}

struct public Bar {
    xf: float
}

如果未指定,则结构继承模块 public(即在公共模块中,结构是 public,而在私有模块中,结构是私有的)。

结构实例是通过 ‘new expression’ 或变量声明语句创建的:

let foo: Foo
let foo: Foo? = new Foo()

有意没有成员函数。只有数据成员,因为它本身就是一种数据类型。 结构可以处理将函数类型作为数据的成员(这意味着它是一个可以在执行期间更改的函数指针)。 有一些初始值设定项可以简化编写复杂结构初始化的过程。 基本上,与结构体本身同名的函数用作初始化器。 如果有任何成员具有初始值设定项,则编译器将生成 ‘default’ 初始值设定项:

struct Foo {
    x: int = 1
    y: int = 2
}

默认情况下,结构字段初始化为零,无论成员的“初始值设定项”如何,除非你专门调用初始值设定项:

let fZero : Foo     // no initializer is called, x, y = 0
let fInited = Foo() // initializer is called, x = 1, y = 2

在可能的情况下,推断结构体字段类型:

struct Foo {
    x = 1    // inferred as int
    y = 2.0    // inferred as float
}

创建期间的显式结构初始化会将所有未初始化的成员归零:

let fExplicit = Foo(uninitialized x=13)  // x = 13, y = 0

前面的代码示例是:

let fExplicit: Foo
fExplicit.x = 13

构造后初始化只需要指定覆盖的字段:

let fPostConstruction = Foo(x=13)  // x = 13, y = 2

前面的代码示例是:

let fPostConstruction: Foo
fPostConstruction.x = 13
fPostConstruction.y = 2

“Clone initializer” 是一种有用的模式,当两个结构都在堆上时,用于创建现有结构的克隆:

def Foo ( p : Foo? ) {               // “clone initializer” 采用指向现有结构的指针
    var self := *p
    return <- self
}
...
let a = new Foo(x=1, y=2.)          // 在堆上创建新的 Foo 实例,初始化它
let b = new Foo(a)                  // 在此处创建 b 的克隆

2.17.2. 结构函数成员

Daslang 没有嵌入的结构成员函数、虚拟 (可以在继承的结构中重写) 或非虚拟。 这些功能是为类实现的。 为了便于面向对象编程,非虚杆件函数可以用管道作符 |> 轻松模拟:

struct Foo {
    x, y: int = 0
}

def setXY(var self: Foo; X, Y: int) {
    with ( self ) {
        x = X
        y = Y
    }
}

var foo: Foo
foo |> setXY(10, 11)   // 这是 setXY(foo, 10, 11) 的语法糖
setXY(foo, 10, 11)     // 与上面的行完全相同

由于函数指针是一个东西,因此可以通过将函数指针存储为成员来模拟“虚拟”函数:

struct Foo {
    x, y: int = 0
    set = @@setXY
}

def setXY(var self: Foo; X, Y: int) {
    with ( self ) {
        x = X
        y = Y
    }
}
...
var foo: Foo = Foo()
foo->set(1, 2)  // 如果在派生类中重写,则可以调用其他内容。
                // 它也只是函数指针调用的语法糖
invoke(foo.set, foo, 1, 2)  // 与上述完全相同

这使得 OOP 范例中虚拟调用和非虚拟调用之间的区别变得明确。 事实上,Daslang 类正是以这种方式实现虚拟函数的。

可以在结构体中声明虚函数。这相当于上面的示例:

struct Foo {
    x, y: int = 0
    def setXY(X, Y: int) {
        x = X
        y = Y
    }
}

2.17.3. 继承

Daslang 的结构支持单继承,方法是在结构声明中添加 ‘ : ‘,后跟父结构的名称。 派生结构的语法如下:

struct Bar: Foo {
    yf: float
}

声明派生结构后,Daslang 首先将所有 base 的成员复制到新结构中,然后继续计算声明的其余部分。

派生结构具有其基本结构的所有成员。它只是先手动复制所有成员的语法糖。

可以在派生结构中覆盖虚函数:

struct Foo {
    x, y: int = 0
    def setXY(X, Y: int) {
        x = X
        y = Y
    }
}

struct Bar: Foo {
    yf: float = 0.0
    def override setXY(X, Y: int) {
        x = X + 1
        y = Y + 1
        yf = x + y
    }
}

2.17.4. 对齐

结构体大小和对齐方式类似于 C++:

  • 单个成员单独对齐

  • 整体结构对齐是最大成员对齐的对齐

继承的结构对齐可以通过 [cpp_layout] 注释进行控制:

[cpp_layout (pod=false)]
struct CppS1 {
    vtable : void?              // 我们正在模拟 C++ 类
    b : int64 = 2l
    c : int = 3
}

[cpp_layout (pod=false)]
struct CppS2 : CppS1 {         // d 将在类边界上对齐
    d : int = 4
}

2.17.5. OOP 实现详细信息

在结构之上,有足够的基础设施来支持基本的 OOP。 但是,它已经以类的形式提供,具有一些固定的内存开销 (参阅 Classes).

可以使用 override 语法覆盖基类的方法。 下面是一个示例:

struct Foo {
    x, y: int = 0
    set = @@Foo_setXY
}

def Foo_setXY(var this: Foo; x, y: int) {
    this.x = x
    this.y = y
}

struct Foo3D: Foo {
    z: int = 3
    override set = cast<auto> @@Foo3D_setXY
}

def Foo3D_setXY(var thisFoo: Foo3D; x, y: int) {
    thisFoo.x = x
    thisFoo.y = y
    thisFoo.z = -1
}

使用 cast 关键字将派生结构实例强制转换为其父类型是安全的:

var f3d: Foo3D = Foo3D()
(cast<Foo> f3d).y = 5

将 base 结构体强制转换为其派生的子类型是不安全的:

var f3d: Foo3D = Foo3D()
def foo(var foo: Foo) {
    (cast<Foo3D> foo).z = 5  // error, won't compile
}

如果需要,upcast 可以与 unsafe 关键字一起使用:

struct Foo {
    x: int
}

struct Foo2:Foo {
    y: int
}

def setY(var foo: Foo; y: int) {  // 警告!如果它不是真正的 Foo2,可能会对你的应用程序造成糟糕的影响
    unsafe {
        (upcast<Foo2> foo).y = y
    }
}

由于上面的示例非常危险,为了使其更安全,您可以将其修改为以下内容:

struct Foo {
    x: int
    typeTag: uint = hash("Foo")
}

struct Foo2:Foo {
    y: int
    override typeTag: uint = hash("Foo2")
}

def setY(var foo: Foo; y: int) {  // 这不会做任何真正坏的事情,但会在错误的引用时 panic
    unsafe {
        if ( foo.typeTag == hash("Foo2") ) {
            (upcast<Foo2> foo).y = y
            print("Foo2 type references was passed\n")
        } else {
            assert(false, "Not Foo2 type references was passed\n")
        }
    }
}