开发者学堂课程【Go 语言核心编程 - 面向对象、文件、单元测试、反射、TCP 编程:继承的深入讨论(2)】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/626/detail/9694
继承的讨论(2)
内容简介:
一、结构体嵌入两个(或多个)匿名结构体时的继承
二、组合关系
三、创建结构体变量时的继承
一、 结构体嵌入两个(或多个)匿名结构体时的继承
1. 说明
结构体嵌入两个(或多个)匿名结构体,如下段代码中有 A 结构体和B 结构体, A 结构体和 B 结构体都被嵌入到 C 结构体中,并且 A结构体有 Name 字段和 Age 字段,而 B 结构体中也有 Name 字段,这样应当如何处理?如果两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),就如下段代码中两个匿名结构体当中有 Name 字段,但是 C 结构体中没有同名的字段或方法( Name ),在这种情况下,访问时就必须明确指定匿名结构体名字,否则编译报错,总的来说如果嵌了两个结构体,且这两个结构体有相同的字段和方法,但是 C 结构体却没有同名的字段或名字,这种时候为了区分要使用 A 结构体的还是 B 结构体的,就必须把匿名结构体的名字带上,否则编译报错。
type A struct {
Name string
age int
}
type B struct {
Name string
Score float64
}
type C struct {
A
B
//Name string
}
2. 代码实现
首先新建一个文件夹称为 extendsdetails2 ,在其中新建一个文件称为 main.go ,并输入以下代码:
package main
import (
“fmt”
)
type A struct {
Name string
age int
}
type B struct {
Name string
Score float64
}
type C struct {
A
B
}
func main() {
var c c
c.Name = “tom”// error
}
3. 编辑器显示” error (错误)”
为什么编译器会显示错误?因为在 c.Name = “tom”// error 访问Name 字段时会根据上文提到的访问规则和顺序来访问,即会先到 C结构体中查找, C 结构体没有 Name 字段就会去嵌入的 A 结构体和B 结构体中去查找,会发现上文代码中 A 结构体内有一个 Name 字段, B 结构体内也有一个 Name 字段,这时它便不知道把 Name 字段给 A 结构体里的 Name 字段还是给 B 结构体的字段,于是就会编译报错。保存以上代码后就会在 c.Name = “tom”// error 段代码中出现编译出错,因为此段代码是模糊不明确的,它并不知道选择器应当如何选择,这时若将此段代码修改为 c.A.Name = “tom”// error ,便明确显示要找 A 结构体内的 Name 字段;假设 Name 字段内就有 Name string ,如以下代码:
type C struct {
A
B
Name string
}
这样也不会报错,因为 C 结构体内本身就有 Name 字段,这时目的很清晰,首先会去 C里查找 Name 字段,若在 C 内找到了便不会去 A 结构体和 B 结构体中去查找,也就不会发生选择器不确定的问题,修改完以上代码之后点击保存,显示有个小错误就是没有用到”fmt”,将以上代码修改为:
package main
import (
“fmt”
)
type A struct {
Name string
age int
}
type B struct {
Name string
Score float64
}
type C struct {
A
B
//Name string
}
func main() {
var c c
//如果 c 没有 Name 字段,而 A 和 B 有 Name ,这时就必须通过指定匿名结构体名字来区分
//所以 c.Name 就会报编译错误,这个规则对方法也是一样的!
c.A.Name = “tom”// error
fmt.Println(“c”)
}
二、组合关系
1. 说明
如果一个 struct 嵌套了一个有名结构体,这种模式就是组合,这一般不称之为继承,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字。如以下代码:
type A struct {
Name string
Age int
}
type C struct {
a A
}
这便是组合,在 Golang 里继承是非常灵活的,其中 A 结构体内有Name 和 Age 字段,在 C 中嵌套了一个 A 同时指定了名字,这时若要访问 A 里的 Name ,就必须把名字带上,否则访问不成功。
2. 代码实现
在上文新建的文件 main.go 中继续编写以下代码:
package main
import (
“fmt”
)
type A struct {
Name string
age int
}
type B struct {
Name string
Score float64
}
type C struct {
A
B
//Name string
}
type D struct {
a A //有名结构体
}
func main() {
var c c
//如果c 没有 Name 字段,而 A 和 B 有 Name ,这时就必须通过指定匿名结构体名字来区分
//所以 c.Name 就会报编译错误,这个规则对方法也是一样的!
c.A.Name = “tom”// error
fmt.Println(“c”)
var d D
d.Name = “jack”
}
3. 代码报错
点击保存,保存完后编辑器立即就会报错,它提示 d.Name undefined (type D has no filed or method Name) 也就是说 D 里没有这些方法,因为
type D struct {
a A //有名结构体
}
中的”a A //有名结构体“不是匿名结构体,在检查规则时编辑器会先看 d 对应的类型是否有 Name ,如果没有便直接报错,总的来说因为不是匿名结构体,它就不会往上去找,就直接在 D 里看是否有Name ,如果没有就直接提示 D 中没有 Name 字段,所以在 Golang里承继关系很清晰,如果将 a 去掉,那就不会报错,或者在 d.Name = “jack” 代码中添加 a 也不会报错,即修改为 d.a.Name = “jack”;如果在 D 结构体中添加 Name string ,这样编辑器也是不会报错的。根据上文内容,将上文中的代码修改为正确的代码,如下:
package main
import (
“fmt”
)
type A struct {
Name string
age int
}
type B struct {
Name string
Score float64
}
type C struct {
A
B
//Name string
}
type D struct {
a A //有名结构体
}
func main() {
var c c
//如果 c 没有 Name 字段,而 A 和 B 有 Name ,这时就必须通过指定匿名结构体名字来区分
//所以 c.Name 就会报编译错误,这个规则对方法也是一样的!
c.A.Name = “tom”// error
fmt.Println(“c”)
//如果 D 中是一个有名结构体,则访问有名结构体的字段时,就必须带上有名结构体的名字
//比如 d.a.Name
var d D
d.a.Name = “jack”
}
三、创建结构体变量时的继承
1. 说明
嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值。在前面写细节时,比如建一个 pupil ,建完之后再给 Name 和 Age 赋词,很麻烦。因此在 Golang 里可以在直接创建结构体变量时,它直接给各个匿名结构体字段赋值。
2. 代码实现
在之前建的 main.go 文件中改写代码,如下:
package main
import (
“fmt”
)
type A struct {
Name string
age int
}
type B struct {
Name string
Score float64
}
type C struct {
A
B
//Name string
}
type D struct {
a A //有名结构体
}
type Goods struct {
Name string
Price float64
}
type Brand struct {
Name string
Address string
}
type TV struct {
Goods
Brand
}
func main() {
var c c
//如果 c 没有 Name 字段,而 A 和 B 有 Name ,这时就必须通过指定匿名结构体名字来区分
//所以 c.Name 就会报编译错误,这个规则对方法也是一样的!
c.A.Name = “tom”// error
fmt.Println(“c”)
//如果 D 中是一个有名结构体,则访问有名结构体的字段时,就必须带上有名结构体的名字
//比如 d.a.Name
var d D
d.a.Name = “jack”
//嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
tv := TV{ Goods{“电视机001”, 5000.99}, Brand{“海尔”,“山东”}, }
fmt.Println(“tv”,tv)
}
执行结果为:
D:\goproject\src\go_code\chapter11\extendsdetails2>cd ..
D:\goproject\src\go_code\chapter11>cd extendsdetails2
D:\goproject\src\go_code\chapter11\extendsdetails2>go run main.go
c
tv <<电视机001 5000.99> <海尔 山东>>
3. 将代码同等转换1
可以将上文中的 tv := TV{ Goods{“电视机001”, 5000.99}, Brand{“海尔”,“山东”}, }
写为以下形式(为了方便测试执行结果,修改了名字价格和地址):
tv2 := Tv{
Goods{
Prince : 5000.99,
Name : “电视机002”,
},
Brand{
Name : ”夏普”,
Address : “北京”,
},
fmt.Println(“tv2”,tv2)
}
执行结果为:
D:\goproject\src\go_code\chapter11\extendsdetails2>go run main.go
c
tv <<电视机001 5000.99> <海尔 山东>>
tv2 <<电视机002 5000.99> <夏普 北京>>
4. 嵌套结构体的另种方法
(1)如何嵌套
可以这样去嵌套一个结构体,即在
type TV struct {
Goods
Brand
}
位置下面添加一段新代码:
type TV2 struct {
*Goods
*Brand
}
总的来说在嵌套一个匿名结构体时,也可以嵌套匿名结构体的指针类型,这样它嵌套的就是指针,返回来就是地址,效率更高,下面演示如何使用此种方法(在前文输入的代码后继续输入以下代码):
tv3 := TV2{ &Goods{“电视机003”, 7000.99}, &Brand{“创维”,“河南”}, }
fmt.Println(“tv3”,tv3)
}
执行结果为:
D:\goproject\src\go_code\chapter11\extendsdetails2>go run main.go
c
tv <<电视机001 5000.99> <海尔 山东>>
tv2 <<电视机002 5000.99> <夏普 北京>>
Tv3 <0xc042002400 0xc042002420>
(2)代码改进
由于前文给的是地址,于是执行结果中显示的是地址,可以修改为:
tv3 := TV2{ &Goods{“电视机003”, 7000.99}, &Brand{“创维”,“河南”}, }
fmt.Println(“tv3”, *tv3.Goods, *tv3.Brand)
}
执行结果为:
D:\goproject\src\go_code\chapter11\extendsdetails2>go run main.go
c
tv <<电视机001 5000.99> <海尔 山东>>
tv2 <<电视机002 5000.99> <夏普 北京>>
Tv3 <电视机003 7000.99> <创维 河南>
5. 将代码同等转换2
可以将上文中的 tv3 := TV2{ &Goods{“电视机003”, 7000.99}, &Brand{“创维”,“河南”}, }写为以下形式(为了方便测试执行结果,修改了名字价格和地址):
tv4 := Tv2{
&Goods{
Name : “电视机002”,
Price : 9000.99,
},
&Brand{
Name : ”长虹”,
Address : “四川”,
},
fmt.Println(“tv4”, *tv4.Goods, *tv4.Brand)
}
执行结果为:
D:\goproject\src\go_code\chapter11\extendsdetails2>go run main.go
c
tv <<电视机001 5000.99> <海尔 山东>>
tv2 <<电视机002 5000.99> <夏普 北京>>
tv3 <电视机003 7000.99> <创维 河南>
Tv4 <电视机004 9000.99> <长虹 四川>