2.17. 结构
Daslang 使用类似于 C/C++、Java、C# 等语言的结构机制。 但是,存在一些重要的区别。 结构体是一等对象,如整数或字符串,可以存储在表槽、其他结构体、局部变量、数组、元组、变体等中,并作为函数参数传递。
2.17.1. 结构体声明
通过关键字 struct
创建结构体对象:
struct Foo {
x, y: int
xf: float
}
结构可以是 private
或 public
:
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")
}
}
}