6.4. 嵌入结构体

在声明结构体类型时,如果某个成员的类型是结构体,但省略该成员的名称,这种用法被称为嵌入结构体,例如下面代码中,结构体 Sc 中嵌入了 Sp 成员:

type Sp struct {
    x: i32
}

type Sc struct {
    Sp // 嵌入结构体
    y: i32
}

嵌入结构体的成员名称就是其类型名称,我们依然可以使用选择符 . 访问它,例如下面的打印代码:

    v: Sc
    println(v.Sp.x)

在这个例子中,甚至可以省略 .Sp 的部分,比如上面的代码跟下述代码是等价的:

    v: Sc
    println(v.x)

在这种用法中,结构体 Sp 看起来似乎被嵌到结构体 Sc 中去了,这也是嵌入结构体名称的来源。但是如果结构体中包含了和被嵌结构体同样名称的成员,则访问被嵌结构体同名成员时不能进行省略,例如:

// 版权 @2023 凹语言 作者。保留所有权利。

type Sp struct {
    x: i32
}

type Sc2 strct {
    Sp
    x: f32
}

func main(){
    v: Sc2
    println(v.x)    // 打印的是Sc2.x,f32类型
    println(v.Sp.x) // 打印的是Sc2.Sp.x,i32类型
}

嵌入结构体除了可以复用类型的数据布局,另一个重要的功能是它可以复用类型方法,结构体会自动拥有被嵌入类型的方法,例如:

// 版权 @2023 凹语言 作者。保留所有权利。

type Sp struct {
    x: i32
}

func Sp.Show {
    println(this.x)
}

type Sc struct {
    Sp
    y: i32
}

func main {
    v := Sc{Sp:Sp{x: 42}, y: 13}
    v.Show() // 42
}

在声明嵌入结构体字面量时,不能省略被嵌入结构体名,比如上例中的 Sc{Sp:Sp{x: 42}, y: 13},如果省略为 {x: 42, y: 13} 将被视为非法。

Sc 中 嵌入 Sp 后,获得了后者的方法,使得 Sc 类型的变量 v 可以执行 Show 操作;在该例中,v.Show() 等价于 v.Sp.Show() 。如果结构体拥有和被嵌结构体同样名称的方法,处理方法与同名成员类似,例如:

// 版权 @2023 凹语言 作者。保留所有权利。

type Sp struct {
    x: i32
}

func Sp.Show {
    println(this.x)
}

type Sc struct {
    Sp
    x: f32
}

func Sc.Show {
    println(this.x)    
}

func main {
    v := Sc{Sp:Sp{x: 42}, x: 13.14}
    v.Show()    // 13.14
    v.Sp.Show() // 42
}

为了实现对象复用,凹语言没有采用继承的设计(这与C++不同),而是使用了组合的设计。嵌入结构体就是组合的具体表现,嵌入结构体复用了被嵌入类型的内存布局和方法集,与接口(将在第7章介绍)一起,构成了凹语言对象抽象、复用的基础。