Go 语言系列教程(八) : 结构体深入解析

前言

学习之前大家先思考一个问题–Go有对象?Go没对象,为什么呢?这个好难回答,不如换个问题:“You 为啥没对象?”
记得刚刚学c语言的时候,嗯,没有对象是很正常的。
学Java的时候,第一堂课,就是:“恭喜在座的各位,你们开始有对象了。”
Go没有对象?那还咋活!
没对象,又想要原来那种有对象的日子,怎么办?
好说好说,那就是假装自己有对象
于是啊,Go 没有继承,就找了干儿子当儿子。然后,表现出我有继承人。

下面给大家列举一个很有意思的例子
来自 [go语言中文社区–习惯研究所所长] https://studygolang.com/articles/22985

package main

// 1,养父类
type AdoptedFather struct {
    
    
}

// 2, 养父的遗产
func (af AdoptedFather) Show() {
    
     // 关键字func表示这是一个函数,第二个参数是结构体类型和实例变量(这样一来Go就知道了这是一个为结构体定义的方法。),第三个是函数名称,第四个是函数返回值
	println("I'm your Father! You succeeded in inheriting my legacy.")
}

// 3, 父类
type AdoptedChild struct {
    
    
	AdoptedFather
}

func main() {
    
    
	child := AdoptedChild{
    
    } // 干儿子
	child.Show()            // 继承干爹的遗产
}

结果

I'm your Father! You succeeded in inheriting my legacy.

PS :看不太懂?没有关系通过下面几小节的学习再回过头来就看的明白了。

一个成熟的类,具备成员变量和成员函数,结构体本身就有成员变量,再给他绑定上成员函数,一拍大腿,成了,这就是个类,谁来也是个类。好吧好吧,开个玩笑,Go确实没有类,但是可以通过组合模式来体现类的特征。毕竟Go语言可是号称万物皆类型。

下面让我们进入正题

结构体的声明

结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。

	type Employee struct {
    
    
		ID        int
		Name      string
		Address   string
		DoB       time.Time //若未初始化则表示UTC时间,公元1年,1月,1日,0时,0分,0秒
		Position  string
		Salary    int
		ManagerID int
	}
	var dilbert Employee
	dilbert.Name = "dilbert"
	dilbert.Salary = 5000
	fmt.Println(dilbert)  // {0 dilbert  0001-01-01 00:00:00 +0000 UTC  5000 0}

除了.操作符我们还可以对成员取地址,然后通过指针访问

	var dilbert Employee
	dilbert.Name = "dilbert"
	name := &dilbert.Name
	*name = "new" + *name
	fmt.Println(*name)  // newdilbert

结构体的比较

如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体 将可以使用==或!=运算符进行比较

	type Point struct {
    
    
		X ,Y int
	}
	p := Point{
    
    1,2}
	q := Point{
    
    2,1}
	fmt.Println(p.X == q.X && p.Y == q.Y)  // false
	fmt.Println(p == q)   //false

结构体嵌入和匿名成员

我们将看到如何使用Go语言提供的不同寻常的结构体嵌入机制让一个命名的结构 体包含另一个结构体类型的匿名成员,这样就可以通过简单的点运算符x.f来访问匿名成员链 中嵌套的 x.d.e.f 成员。
以下案例来源 The Go Programming Language p147

考虑一个二维的绘图程序,提供了一个各种图形的库,例如矩形、椭圆形、星形和轮形等几 何形状。这里是其中两个的定义

	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 Circle
		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结构体。

package main

func main() {
    
    
	type Point struct {
    
    
		X, Y int
	}
	type Circle struct {
    
    
		Point
		Radius int
	}
	type Wheel struct {
    
    
		Circle
		Spokes int
	}

	//得意于匿名嵌入的特性,我们可以直接访问叶子属性而不需要给出完整的路径
	//又可以愉快的玩耍了
	var w Wheel
	w.X = 8  // w.Circle.Point.X = 8
	w.Y = 8  // w.Circle.Point.Y = 8
	w.Radius =5 // w.Circle.Radius	= 5 
	w.Spokes =20
}

注意:在右边的注释中给出的显式形式访问这些叶子成员的语法依然有效,因此匿名成员并不是真的无法访问了。其中匿名成员Circle和Point都有自己的名字——就是命名的类型名字——但是 这些名字在点操作符中是可选的。我们在访问子成员的时候可以忽略任何匿名成员部分。
注意:因为匿名成员也有一个隐式的名字,因此不能同时包含两个类型相同的匿名成员,这会导致名字冲突

不幸的是,结构体字面值并没有简短表示匿名成员的语法, 因此下面的语句都不能编译通过

	var w Wheel
	w = Wheel{
    
    8, 8, 5, 20} // compile error: unknown fields 
	w = Wheel{
    
    X: 8, Y: 8, Radius: 5, Spokes: 20} // compile error: unknown fields 

结构体字面值必须遵循形状类型声明时的结构,所以我们只能用下面的两种语法,它们彼此是等价的

	var w Wheel
	w = Wheel{
    
    Circle{
    
    Point{
    
    8,8},5},20}

	w = Wheel{
    
    
		Circle:Circle{
    
    
			Point: Point{
    
    X:8, Y:8} ,
			Radius: 5,
		},
		Spokes: 20,
	}

	fmt.Printf("%#v\n",w)

	w.X = 42
	fmt.Printf("%#v\n",w)

结果(需要注意的是Printf函数中%v参数包含的#副词,它表示用和Go语言类似的语法打印值。对于 结构体类型来说,将包含每个成员的名字。
)

main.Wheel{
    
    Circle:main.Circle{
    
    Point:main.Point{
    
    X:8, Y:8}, Radius:5}, Spokes:20}
main.Wheel{
    
    Circle:main.Circle{
    
    Point:main.Point{
    
    X:42, Y:8}, Radius:5}, Spokes:20}

结构体的函数绑定的两种方式

一个成熟的类,具备成员变量和成员函数,结构体本身就有成员变量,那么可不可以再给他绑定上成员函数

  • 普通绑定
type people struct {
    
    
	name string
	age int
}

func (p people) toString() {
    
    
	p.age *= 2
	fmt.Printf("%s的地址 %p \n",p.name, &p)
}
func main() {
    
    
	p1 := people{
    
    "p1",3}
	p1.toString()
	fmt.Println("p1的age:",p1.age)

	p2 := people{
    
    "p2",4}
	p2.toString()
	fmt.Println("p2的age:",p2.age)
}

结果

p1的地址 0xc0000044a0 
p1的age: 3
p2的地址 0xc0000044e0 
p2的age: 4

解释: func (p people) toString() 关键字func表示这是一个函数,第二个参数是结构体类型和实例变量(这样一来Go就知道了这是一个为结构体定义的方法。),第三个是函数名称及参数,第四个是函数返回值
另外这里是值传递所以不影响到原本的值

  • 指针绑定
type people struct {
    
    
	name string
}

func (p *people) sayHello() {
    
    
	p.age *= 2
	fmt.Printf("*%s的地址 %p \n",p.name, p)
}

func main() {
    
    
	p3 := people{
    
    "p3",3}
	p3.sayHello()
	fmt.Println("p3的age:",p3.age)

	p4 := people{
    
    "p3",4}
	p4.sayHello() //系统帮你转成(*people).sayHello
	fmt.Println("p4的age:",p4.age)
}

结果

*p3的地址 0xc000096440 
p3的age: 6
*p3的地址 0xc000096480 
p4的age: 8

在Go语言中 不存在引用传递,要么传递值的副本要么从传递指针的副本,所以指针地址不一样,但是两个指针指向的值是一样的。关于值传递我会在下一章详细说一下。

PS:指针我就不专门出一章了,主要原因就是相对于C语言没有太大的变化,没有什么好说的。对这块不熟悉的可以去看一下C语言的指针。

----办法总是有的,尽管并不怎么妥

猜你喜欢

转载自blog.csdn.net/qq_37806753/article/details/108755738