考虑一个二维的绘图程序,提供了一个各种图形的库,例如矩形、椭圆形、星形和轮形等几 何形状。这里是其中两个的定义
type Circle struct { X, Y, Radius int } type Wheel struct { X, Y, Radius, Spokes int }
一个Circle代表的圆形类型包含了标准圆心的X和Y坐标信息,和一个Radius表示的半径信 息。一个Wheel轮形除了包含Circle类型所有的全部成员外,还增加了Spokes表示径向辐条的 数量。我们可以这样创建一个wheel变量:
var w Wheel w.X = 8 w.Y = 8 w.Radius = 5 w.Spokes = 20
随着库中几何形状数量的增多,我们一定会注意到它们之间的相似和重复之处,所以我们可 能为了便于维护而将相同的属性独立出来:
type Point struct{ x,y int } type Circle struct{ Center Point Radius int } type Wheel struct{ Circle Ciecle Spokes int }
这样改动之后结构体类型变的清晰了,但是这种修改同时也导致了访问每个成员变得繁琐:
var w Wheel w.Circle.Center.X = 8 w.Circle.Center.Y = 8 w.Circle.Radius = 5 w.Spokes = 20
Go语言有一个特性让我们只声明一个成员对应的数据类型而不指名成员的名字;这类成员就 叫匿名成员。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针。下面的 代码中,Circle和Wheel各自都有一个匿名成员。我们可以说Point类型被嵌入到了Circle结构 体,同时Circle类型被嵌入到了Wheel结构体。
type Circle struct { Point Radius int } type Wheel struct { Circle Spokes int }
得益于匿名嵌入的特性,我们可以直接访问叶子属性而不需要给出完整的路径:
var w Wheel w.X = 8 // equivalent to w.Circle.Point.X = 8 w.Y = 8 // equivalent to w.Circle.Point.Y = 8 w.Radius = 5 // equivalent to w.Circle.Radius = 5 w.Spokes = 20
在右边的注释中给出的显式形式访问这些叶子成员的语法依然有效,因此匿名成员并不是真 的无法访问了。其中匿名成员Circle和Point都有自己的名字——就是命名的类型名字——但是 这些名字在点操作符中是可选的。我们在访问子成员的时候可以忽略任何匿名成员部分。
不幸的是,结构体字面值并没有简短表示匿名成员的语法, 因此下面的语句都不能编译通 过:
w = Wheel{8, 8, 5, 20} //compile error: unknown fields w = Wheel{X:8, Y:8, Radius:5, Spokers:20} //compile error: unknown fields
w = Wheel{Circle{Point{8,8}, 5},20} w = Wheel{ Circle: Circle{ Point: Point{X:8,Y:8}, Radius: 5, }, Spokes: 20, // NOTE: trailing comma necessary here (and at Radius) } fmt.Printf("%#v\n",w) // Output: // Wheel{Circle:Circle{Point:Point{X:8, Y:8}, Radius:5}, Spokes:20} w.X = 42 fmt.Printf("%#v\n", w) // Output: // Wheel{Circle:Circle{Point:Point{X:42, Y:8}, Radius:5}, Spokes:20}
需要注意的是Printf函数中%v参数包含的#副词,它表示用和Go语言类似的语法打印值。对于 结构体类型来说,将包含每个成员的名字。
因为匿名成员也有一个隐式的名字,因此不能同时包含两个类型相同的匿名成员,这会导致 名字冲突。同时,因为成员的名字是由其类型隐式地决定的,所有匿名成员也有可见性的规 则约束。在上面的例子中,Point和Circle匿名成员都是导出的。即使它们不导出(比如改成小 写字母开头的point和circle),我们依然可以用简短形式访问匿名成员嵌套的成员
w.X = 8 // equivalent to w.circle.point.X = 8
但是在包外部,因为circle和point没有导出不能访问它们的成员,因此简短的匿名成员访问语 法也是禁止的。
到目前为止,我们看到匿名成员特性只是对访问嵌套成员的点运算符提供了简短的语法糖。 稍后,我们将会看到匿名成员并不要求是结构体类型;其实任何命名的类型都可以作为结构 体的匿名成员。但是为什么要嵌入一个没有任何子成员类型的匿名成员类型呢?
答案是匿名类型的方法集。简短的点运算符语法可以用于选择匿名成员嵌套的成员,也可以 用于访问它们的方法。实际上,外层的结构体不仅仅是获得了匿名成员类型的所有成员,而 且也获得了该类型导出的全部的方法。这个机制可以用于将一个有简单行为的对象组合成有 复杂行为的对象。组合是Go语言中面向对象编程的核心。