略微加速

略速 - 互联网笔记

go匿名字段和struct嵌套

2021-02-22 leiting (2309阅读)

标签 Golang

匿名字段和struct嵌套

struct中的字段可以不用给名称,这时称为匿名字段。匿名字段的名称强制和类型相同。例如:

type animal struct {
    name string
    age int
}
type Horse struct{
    int
    animal
    sound string
}

上面的Horse中有两个匿名字段intanimal,它的名称和类型都是int和animal。等价于:

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
}

上面的o是outer struct的实例,但o除了具有自己的显式字段ou1和ou2,还具备int字段和inner字段,它们都是嵌套字段。一被嵌套,内部struct的属性也将被外部struct获取,所以o.into.in1o.in2都属于o。也就是说,外部struct has a 内部struct,或者称为struct has a field

输出以下外部struct的内容就很清晰了:

fmt.Println(o)  // 结果:&{1 2 3 {4 5}}

上面的outer实例,也可以直接赋值构建:

o := outer{1,2,3,inner{4,5}}

在赋值inner中的in1和in2时不能少了inner{},否则会认为in1、in2是直接属于outer,而非嵌套属于outer。

显然,struct的嵌套类似于面向对象的继承。只不过继承的关系模式是"子类 is a 父类",例如"轿车是一种汽车",而嵌套struct的关系模式是外部struct has a 内部struct,正如上面示例中outer拥有inner。而且,从上面的示例中可以看出,Go是支持"多重继承"的。

具名struct嵌套

前面所说的是在struct中以匿名的方式嵌套另一个struct,但也可以将嵌套的struct带上名称。

直接带名称嵌套struct时,不会再自动深入到嵌套struct中去查找属性和方法。想要访问内部struct属性时,必须带上该struct的名称。

例如:

type animal struct {
    name string
    age int
}
type Horse struct{
    a animal
    sound string
}

这时候,想要访问嵌套在Horse中animal的name属性,则只能通过h.a.name的方式(h为Horse的实例对象),且访问h.name时将直接报错,因为在Horse里找不到name属性。

嵌套struct的名称冲突问题

假如外部struct中的字段名和内部struct的字段名相同,会如何?

有以下两个名称冲突的规则:

  1. 外部struct覆盖内部struct的同名字段、同名方法
  2. 同级别的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 <-> C,现在要将B节点加入到A和C的中间,即A<->B<->C,那么A的右节点必须设置为B,B的左节点必须设置为A,B的右节点必须设置为C,C的左节点必须设置为B。也就是涉及了4次原子性操作,它们要么全设置成功,失败一个则链表被破坏。

例如,定义一个二叉树:

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>}

当然,使用二叉树的时候,必须为二叉树结构设置相关的方法,例如添加节点、设置数据、删除节点等等。

另外需要注意的是,一定不要将某个新节点的左、右同时设置为树中已存在的节点,因为这样会让树结构封闭起来,这会破坏了二叉树的结构。

 

转载请注明出处:https://www.cnblogs.com/f-ck-need-u/p/9882315.html

北京半月雨文化科技有限公司.版权所有 京ICP备12026184号-3