Go 允许你定义含有无变量名的字段的Struct,这些无名称的字段称之为匿名字段,让我们通过一些示例来了解它们到底是什么,以及它们为什么很有用。

在下面的示例中,我们定义一个名为 Kitchen 的Struct,它仅仅只包含一个名为 numOfPlates 的 int类型的字段用来保存盘子的个数,我还定义了另一个Struct名为 House ,它包含了一个 Kitchen 的实例,但是我没有为其指定字段名,所以,它是一个匿名字段。

package main
import "fmt"
type Kitchen struct {
    numOfPlates int
}
type House struct {
    Kitchen // 匿名字段
    numOfRooms int
}
func main() {
    h := House{Kitchen{10}, 3} // 初始化需要使用Struct名
    fmt.Println("房屋 h 有", h.numOfRooms, "间房") // numOfRooms 是 House 的一个字段
    fmt.Println("房屋 h 有", h.numOfPlates, "个盘子") // numOfPlates 是Kitchen提供的字段,而Kitchen又是House的一个匿名字段,所以这里可以访问到它
    fmt.Println("这间房屋的厨房有:", h.Kitchen) // 我们可以通过Struct的名称访问整个匿名Struct的内容
}

输出结果为:

房屋 h 有 3 间房
房屋 h 有 10 个盘子
这间房屋的厨房有: {10}

首先我们需要注意到的事情是,当我们定义了 Kitchen 为House的一个匿名字段之后,我们将可以直接通过Kitchen中的字段名称访问Kitchen中字段的值,这同样也“成了”House的字段,但是如果像在Java这样的语言中,我们做一下对比,它需要像下面这样的去实现同样的功能:

public class Kitchen {
    public int numOfPlates;
}

public class House {
    public Kitchen kitchen;
}

public static void main(String[] args) {
    House h = new House();
    h.kitchen.numOfPlates = 10; // 引用自字段的子字段
}

其次我们需要知道,匿名字段的完整的实例数据是可以被访问到的,需要通过它的Struct名称,在本示例中,通过 h.Kitchen 来访问获得,所以,如果你想打印出 Kitchen 中的 Plate 数量的话中,可以这样:

fmt.Println(h.Kitchen.numofPlates)

最后,要注意到,初始化的时候,匿名字段的Struct名称是不允许省略的,它必须像这样:h := House{Kitchen{10}, 3}*,而下面这样的写法都是错误的:*h := House{{10}, 3} 或者 h := House{10,3}。

匿名字段发生命名冲突

很有可能两个不同的Struct具有同样名称的字段,而一个Struct又是另一个Struct的匿名字段,这就产生的命名冲突的问题,当这个问题出现的时候,外层的Struct中的字段可以像以前一样直接被访问到,但是其匿名字段就需要通过Struct名称访问了。

在下面的示例中,我们的 House 与 Kitchen 同时具有 numOfLamps 字段,但是House是外层的struct,它的 numOfLamps* 字段将隐藏掉Kitchen的 *numOfLamps*,如果你还是需要访问Kitchen的numOfLamps值,那么需要通过引用它的Struct名:*h.Kitchen.numOfLamps :

package main
import "fmt"
type Kitchen struct {
    numOfLamps int
}
type House struct {
    Kitchen
    numOfLamps int
}
func main() {
    h := House{Kitchen{2}, 10} // Kitchen 有2个Lamps,House有10个
    fmt.Println("House有", h.numOfLamps, "个Lamps")
    fmt.Println("Kitchen有", h.Kitchen.numOfLamps, "个Lamps")
}

输出为:

House有 10 个Lamps
Kitchen有 2 个Lamps

所以从上面可以看到,对于不同层级的相同名称的字段,Go制定了相应的规则,但是对于同一层级的相同名称的字段,则需要我们自己去解决,在下面的示例中,Kitchen与Bedroom都具有 numOfLamps 字段,并且它们又都是House的匿名字段,现在,如果我们引用 House.numOfLamps,Go编译器就会不知道你到底是要使用哪一个 numOfLamps值:

package main
import "fmt"
type Kitchen struct {
    numOfLamps int;
}
type Bedroom struct {
    numOfLamps int;
}
type House struct {
    Kitchen
    Bedroom
}
func main() {
    h := House{Kitchen{2}, Bedroom{3}} // Kitchen 2, Bedroom 3
    fmt.Println("Ambiguous number of lamps:", h.numOfLamps) // 这会出错
}

输出为:

./anonymousfields3.go:15: ambiguous selector h.numOfLamps

因为Go不知道你到底是要 Bedroom 的还是 Kitchen 的 numOfLamps,所以,它直接就提供值了,结果就是返回 h.numOfLamps 有歧义。

解决这个问题的办法是通过匿名字段的Struct名称访问其值:

fmt.Println("House中Lamps的数量为:", h.Kitchen.numOfLamps + h.Bedroom.numOfLamps)

输出为:

House中Lamps的数量为:5

标签: none

评论已关闭