匿名字段和struct嵌套
struct中的字段可以不用给名称,这时称为匿名字段。匿名字段的名称强制和类型相同。例如:
type animal struct { name string age int } type Horse struct{ int animal sound string }
intanimal
type Horse struct{ int int animal animal sound string }
显然,上面Horse中嵌套了其它的struct(如animal)。其中animal称为内部struct,Horse称为外部struct。
以下是一个嵌套struct的简单示例:
package main import ( "fmt" ) type inner struct { in1 int in2 int } type outer struct { ou1 int ou2 int int inner } func main() { o := new(outer) o.ou1 = 1 o.ou2 = 2 o.int = 3 o.in1 = 4 o.in2 = 5 fmt.Println(o.ou1) // 1 fmt.Println(o.ou2) // 2 fmt.Println(o.int) // 3 fmt.Println(o.in1) // 4 fmt.Println(o.in2) // 5 }
ooo.into.in1o.in2o外部struct has a 内部structstruct has a field
输出以下外部struct的内容就很清晰了:
fmt.Println(o) // 结果:&{1 2 3 {4 5}}
上面的outer实例,也可以直接赋值构建:
o := outer{1,2,3,inner{4,5}}
inner{}
外部struct has a 内部structouter拥有inner
具名struct嵌套
前面所说的是在struct中以匿名的方式嵌套另一个struct,但也可以将嵌套的struct带上名称。
直接带名称嵌套struct时,不会再自动深入到嵌套struct中去查找属性和方法。想要访问内部struct属性时,必须带上该struct的名称。
例如:
type animal struct { name string age int } type Horse struct{ a animal sound string }
h.a.nameh.name
嵌套struct的名称冲突问题
假如外部struct中的字段名和内部struct的字段名相同,会如何?
有以下两个名称冲突的规则:
- 外部struct覆盖内部struct的同名字段、同名方法
- 同级别的struct出现同名字段、方法将报错
第一个规则使得Go struct能够实现面向对象中的重写(override),而且可以重写字段、重写方法。
第二个规则使得同名属性不会出现歧义。例如:
type A struct { a int b int } type B struct { b float32 c string d string } type C struct { A B a string c string } var c C
按照规则(1),直属于C的a和c会分别覆盖A.a和B.c。可以直接使用c.a、c.c分别访问直属于C中的a、c字段,使用c.d或c.B.d都访问属于嵌套的B.d字段。如果想要访问内部struct中被覆盖的属性,可以c.A.a的方式访问。
按照规则(2),A和B在C中是同级别的嵌套结构,所以A.b和B.b是冲突的,将会报错,因为当调用c.b的时候不知道调用的是c.A.b还是c.B.b。
递归struct:嵌套自身
如果struct中嵌套的struct类型是自己的指针类型,可以用来生成特殊的数据结构:链表或二叉树(双端链表)。
例如,定义一个单链表数据结构,每个Node都指向下一个Node,最后一个Node指向空。
type Node struct { data string ri *Node }
以下是链表结构示意图:
------|---- ------|---- ------|----- | data | ri | --> | data | ri | --> | data | nil | ------|---- ------|---- ------|-----
如果给嵌套两个自己的指针,每个结构都有一个左指针和一个右指针,分别指向它的左边节点和右边节点,就形成了二叉树或双端链表数据结构。
二叉树的左右节点可以留空,可随时向其中加入某一边加入新节点(像节点加入到树中)。添加节点时,节点与节点之间的关系是父子关系。添加完成后,节点与节点之间的关系是父子关系或兄弟关系。
A <-> CA<->B<->C
例如,定义一个二叉树:
type Tree struct { le *Tree data string ri *Tree }
最初生成二叉树时,root节点没有任何指向。
// root节点:初始左右两端为空 root := new(Tree) root.data = "root node"
随着节点增加,root节点开始指向其它左节点、右节点,这些节点还可以继续指向其它节点。向二叉树中添加节点的时候,只需将新生成的节点赋值给它前一个节点的le或ri字段即可。例如:
// 生成两个新节点:初始为空 newLeft := new(Tree) newLeft.data = "left node" newRight := &Tree{nil, "Right node", nil} // 添加到树中 root.le = newLeft root.ri = newRight // 再添加一个新节点到newLeft节点的右节点 anotherNode := &Tree{nil, "another Node", nil} newLeft.ri = anotherNode
简单输出这个树中的节点:
fmt.Println(root) fmt.Println(newLeft) fmt.Println(newRight)
输出结果:
&{0xc042062400 root node 0xc042062420} &{<nil> left node 0xc042062440} &{<nil> Right node <nil>}
当然,使用二叉树的时候,必须为二叉树结构设置相关的方法,例如添加节点、设置数据、删除节点等等。
另外需要注意的是,一定不要将某个新节点的左、右同时设置为树中已存在的节点,因为这样会让树结构封闭起来,这会破坏了二叉树的结构。