1.看一个问题,引出继承的必要性
package main
import (
"fmt"
)
//编写一个学生考试系统
type Pupil struct {
Name string
Age int
Score float64
}
//显示信息
func (p *Pupil) showInfo() {
fmt.Printf("学生姓名:%v,年龄:%v,成绩:%v\n", p.Name, p.Age, p.Score)
}
//设置成绩
func (p *Pupil) SetScore(s float64) {
//逻辑判断
p.Score = s
}
//考试状态
func (p *Pupil) status() {
fmt.Println("正在考试")
}
// 大学生,高中生,...
func main() {
p := &Pupil{
Name: "Jack",
Age: 11,
Score: 110.22,
}
p.status()//正在考试
p.SetScore(22)
p.showInfo()//学生姓名:Jack,年龄:11,成绩:22
}
对上面代码总结:
1.Pupil和Graduate两个结构体字段和方法几乎一样,有很多相同的代码
2.出现代码的冗余,而且代码不利于维护,同时也不利于功能的扩展
3.解决方法:通过继承方式来解决
2.继承的基本介绍和图示
继承可以解决代码的复用,让编程更加靠近人类思维.
当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法.
其他结构体不需要重新定义这些属性(字段)和方法,只需要嵌套一个匿名结构体即可
也就是说:
在go语言中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体中的字段和方法,从而实现继承特性
3.嵌套匿名结构体的基本语法
type Goods struct {
Name string
Proce int
}
type Book struct {
Goods // 这里就是嵌套匿名结构体Goods
Writer string
}
4.案例
package main
import (
"fmt"
)
//因为小学生,大学生,高中生都有共有的属性和方法,故抽出来放到Student结构体中
type Student struct {
Name string
Age int
Score float64
}
func (stu *Student) showInfo() {
fmt.Printf("学生姓名:%v,年龄:%v,成绩:%v\n", stu.Name, stu.Age, stu.Score)
}
//设置成绩
func (stu *Student) SetScore(s float64) {
//逻辑判断
stu.Score = s
}
//编写一个学生考试系统
// 小学生,大学生,高中生,...
//小学生:
type Pupil struct {
Student //嵌入了Student匿名的结构体
}
//考试状态:小学生结构体特有的方法,保留
func (p *Pupil) status() {
fmt.Println("小学生正在考试")
}
//大学生:
type Graduate struct {
Student //嵌入了Student匿名的结构体
}
//考试状态:大学生结构体特有的方法,保留
func (g *Graduate) status() {
fmt.Println("大学生正在考试")
}
func main() {
//当对结构体嵌入了匿名的结构体时,使用的方法会发生变化
p := &Pupil{}
p.Student.Name = "jack"
p.Student.Age = 1
p.status()//小学生正在考试
p.Student.SetScore(90.01)
p.Student.showInfo()//学生姓名:jack,年龄:1,成绩:90.01
g := &Graduate{}
g.Student.Name = "Mary"
g.Student.Age = 23
g.status()//大学生正在考试
g.Student.SetScore(190.01)
g.Student.showInfo()//学生姓名:Mary,年龄:23,成绩:190.01
}
package main
import (
"fmt"
)
//因为小学生,大学生,高中生都有共有的属性和方法,故抽出来放到Student结构体中
type Student struct {
Name string
Age int
Score float64
}
func (stu *Student) showInfo() {
fmt.Printf("学生姓名:%v,年龄:%v,成绩:%v\n", stu.Name, stu.Age, stu.Score)
}
//设置成绩
func (stu *Student) SetScore(s float64) {
//逻辑判断
stu.Score = s
}
//给Student增加一个方法,那么Pupil,Graduate都可以使用这个方法
func (stu *Student) GetSum(n1 int, n2 int) int {
return n1 + n2
}
//编写一个学生考试系统
// 小学生,大学生,高中生,...
//小学生:
type Pupil struct {
Student //嵌入了Student匿名的结构体
}
//考试状态:小学生结构体特有的方法,保留
func (p *Pupil) status() {
fmt.Println("小学生正在考试")
}
//大学生:
type Graduate struct {
Student //嵌入了Student匿名的结构体
}
//考试状态:大学生结构体特有的方法,保留
func (g *Graduate) status() {
fmt.Println("大学生正在考试")
}
func main() {
//当对结构体嵌入了匿名的结构体时,使用的方法会发生变化
p := &Pupil{}
p.Student.Name = "jack"
p.Student.Age = 1
p.status()//小学生正在考试
p.Student.SetScore(90.01)
p.Student.showInfo()//学生姓名:jack,年龄:1,成绩:90.01
result := p.Student.GetSum(10, 20)
fmt.Println(result)
g := &Graduate{}
g.Student.Name = "Mary"
g.Student.Age = 23
g.status()//大学生正在考试
g.Student.SetScore(190.01)
g.Student.showInfo()//学生姓名:Mary,年龄:23,成绩:190.01
result2 := g.Student.GetSum(110, 20)
fmt.Println(result2)
}
5.继承给程序带来了便利
(1).代码的复用性提高了
(2).代码的扩展性和维护性提高了
6.继承的深入讨论
(1). 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段,方法都可以使用
package main
import(
"fmt"
)
type A struct {
Name string
age int
}
func (a *A) SayOk() {
fmt.Println("A SayOk", a.Name)
}
func (a *A) hello() {
fmt.Println("A hello", a.Name)
}
type B struct {
A
}
func main() {
var b B
b.A.Name = "Mary"
b.A.age = 10
b.A.SayOk()//A SayOk Mary
b.A.hello()//A hello Mary
}
(2).匿名结构体字段访问可以简化
package main
import(
"fmt"
)
type A struct {
Name string
age int
}
func (a *A) SayOk() {
fmt.Println("A SayOk", a.Name)
}
func (a *A) hello() {
fmt.Println("A hello", a.Name)
}
type B struct {
A
}
func main() {
var b B
b.A.Name = "Mary"
b.A.age = 10
b.A.SayOk()//A SayOk Mary
b.A.hello()//A hello Mary
//上面的写法可以简化
b.Name = "Jack"
b.age = 20
b.SayOk()//A SayOk Jack
b.hello()//A hello Jack
}
对上面代码的说明:
1).当我们直接通过 b 访问字段或方法时,其执行流程如下比如 b.Name 2).编译器会先看 b 对应的类型有没有 Name ,如果有,则直接调用 B 类型的 Name 字段 3).如果没有就去看 B 中嵌入的匿名结构体 A 有没有声明 Name 字段,如果有就调用,如果役有继续查找,如果都找不到就报错
(3).当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分
package main
import(
"fmt"
)
type A struct {
Name string
age int
}
func (a *A) SayOk() {
fmt.Println("A SayOk", a.Name)
}
func (a *A) hello() {
fmt.Println("A hello", a.Name)
}
type B struct {
A
Name string
}
func (b *B) SayOk() {
fmt.Println("B SayOk", b.Name)
}
func main() {
var b B
b.Name = "Linda" //ok
b.age = 30 //ok
b.SayOk()//b SayOk Linda
b.hello()//A hello
b.A.Name = "Tom"
b.hello()//A hello Tom
b.A.SayOk()//A SayOk Tom
}
(4).结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错
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.Name = "tom" //error
// c.A.Name = "tom" //ok
}
(5).如果一个 struct 嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字
package main
import(
"fmt"
)
type A struct{
Name string
age int
}
//组合结构体
type D struct{
a A//有名结构体
}
func main() {
//如果D中是一个有名结构体,则访问有名结构体的字段时,必须带上有名结构体的名字,如:d.a.Name = "tom"
var d D
// d.Name = "Mary"//error: 结构体D中没有Name,里面有一个有名的结构体A
d.a.Name = "tom" //ok
}
(6).嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
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
}
type TV2 struct {
*Goods
*Brand
}
func main() {
// var c C
//如果C没有Name字段,而A,B有Name,这时就必须通过指定匿名结构体名字区分
//c.Name报错,编译错误,这个规则对方法同样适用
// c.Name = "tom" //error
// c.A.Name = "tom" //ok
//如果D中是一个有名结构体,则访问有名结构体的字段时,必须带上有名结构体的名字,如:d.a.Name = "tom"
var d D
// d.Name = "Mary"//error: 结构体D中没有Name,里面有一个有名的结构体A
d.a.Name = "tom" //ok
//嵌套匿名结构体后,也可在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
tv := TV{Goods{"电视剧机", 110.22}, Brand{"华硕","四川"},}
fmt.Println("tv:", tv)
tv2 := TV{
Goods{
Name : "电视剧机",
Price : 110.22,
},
Brand{
Name : "华硕",
Address : "四川",
},
}
fmt.Println("tv2:", tv2)
tv3 := TV2{&Goods{"电视剧机", 110.22}, &Brand{"华硕","四川"},}
fmt.Println("tv3:", *tv3.Goods, *tv3.Brand)
tv4 := TV2{
&Goods{
Name : "电视剧机",
Price : 110.22,
},
&Brand{
Name : "华硕",
Address : "四川",
},
}
fmt.Println("tv4:" , *tv4.Goods, *tv4.Brand)
}
说明:
1).如果一个结构体有int类型的匿名字段,就不能有第二个
2).如果需要有多个int的字段,则必须给int字段指定名字
package main
import(
"fmt"
)
type Monster struct{
Name string
Age int
}
type E struct{
Monster
int
n int
}
func main() {
//演示匿名字段是基本数据类型
var e E
e.Name = "猪八戒"
e.Age = 12
e.int = 20
e. = 30
fmt.Println("e:",e)
}
7.多重继承
如一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承
package main
import(
"fmt"
)
type Goods struct{
Name string
Price float64
}
type Brand struct{
Name string
Address string
}
type TV struct {
Goods
Brand
}
func main() {
//嵌套匿名结构体后,也可在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
tv := TV{Goods{"电视剧机", 110.22}, Brand{"华硕","四川"},}
fmt.Println("tv:", tv)
tv2 := TV{
Goods{
Name : "电视剧机",
Price : 110.22,
},
Brand{
Name : "华硕",
Address : "四川",
},
}
fmt.Println("tv2:", tv2)
多重继承细节说明:
1).如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分
2).为了保证代码的简洁性,建议大家尽量不使用多重继承