所谓继承指的是,我们可能会在一些类(结构体)中,写一些重复的成员,我们可以将这些重复的成员,
单独的封装到一个类(结构体)中,作为这些类的父类(结构体),我们可以通过如下图来理解:
当然严格意义上,GO语言中是没有继承的,但是我们可以通过”匿名组合”来实现继承的效果。
根据上一篇章介绍中的图,我们发现学生类(结构体),讲师类(结构体)等都有共同的成员(属性和方法),这样就存在重复。
所以我们把这些重复的成员封装到一个**父类(结构体)**中。
然后让学生类(结构体)和讲师类(结构体)继承父类(结构体)
接下来,我们可以先将公共的属性,封装到**父类(结构体)**中实现继承,关于方法(函数)的继承后面再讲。
那么怎样实现属性的继承呢?
使用匿名字段的继承结构体
可以通过**匿名字段(也叫匿名组合)**来实现,什么是匿名字段呢?通过如下使用,大家就明白了。
type Person struct {id intname stringage int
}type Student struct {Person // 匿名字段score float64
}
以上代码通过匿名字段实现了继承,将公共的属性封装在Person中,在Student中直接包含Person,那么Student中就有了Person中所有的成员,Person就是匿名字段。
注意:Person匿名字段,只有类型,没有名字。
包含匿名字段的结构体初始化
那么接下来说我们就可以给Student
赋值了,具体初始化的方式如下:
type Person struct {id intname stringage int
}type Student struct {Person // 匿名字段score float64
}func main() {//初始化var s1 Student = Student{Person{101, "mike", 18}, 98.5}fmt.Println("s1 = ", s1)
}// 执行如下:
s1 = {{101 mike 18} 98.5}
以上代码中创建了一个结构体变量s1
, 这个s1
我们可以理解为就是Student
对象,但是要注意语法格式,以下的写法是错误的:
var s2 Student=Student{101,"mike",18,98.5}
其它初始化方式如下:
(1)自动推导类型
// 自动推导类型
s1 := Student{Person{102, "zhangsan", 18}, 98.5}
fmt.Printf("s2 = %+vn", s1)// 执行如下:
s2 = {Person:{id:102 name:zhangsan age:18} score:98.5}
(2)指定初始化成员
// 指定初始化成员
s3 := Student{score: 97}
fmt.Printf("s3 = %+vn", s3)// 执行:
s3 = {Person:{id:0 name: age:0} score:97}
接下来还可以对Person中指定的成员进行初始化。
s4 := Student{Person: Person{name: "mike"}, score: 97}
fmt.Printf("s4 = %+vn", s4)// 执行:
s4 = {Person:{id:0 name:mike age:0} score:97}
创建完成对象后,可以根据对象来操作对应成员属性,是通过“.”
运算符来完成操作的。
对象操作成员属性的案例:
s1 := Student{Person: Person{101,"mike", 18}, score: 97}
s1.age = 20
s1.Person.id = 120
s1.score = 99
fmt.Printf("s1 = %+vn", s1)// 执行:
s1 = {Person:{id:120 name:mike age:20} score:99}
由于Student
继承了Person
,所以Person
具有的成员,Student
也有,所以根据Student
创建出的对象可以直接对age
成员项进行修改。
由于在Student
中添加了匿名字段Person
,所以对象s1
,也可以通过匿名字段Person
来获取age
,进行修改。
当然也可以进行如下修改:
s1 := Student{Person: Person{101,"mike", 18}, score: 97}
s1.Person = Person{112,"zhangsan", 18}
fmt.Printf("s1 = %+vn", s1)// 执行:
s1 = {Person:{id:112 name:zhangsan age:18} score:97}
直接给对象s1
中的Person
成员(匿名字段)赋值
通过以上案例我们可以总结出,根据类(结构体)可以创建出很多的对象,这些对象的成员(属性)是一样的,但是成员(属性)的值是可以完全不一样的。
现在将Student
结构体与Person
结构体,进行如下的修改:
type Person struct {id intname stringage int
}type Student struct {Person // 匿名字段name string // 和Persion同名score float64
}
在Student中也加入了一个成员name,这样与Person重名了,那么如下代码是给Student中name赋值还是给Person中的name 进行赋值?
var s1 Student
s1.name = "zhangsan"
fmt.Printf("s1 = %+vn", s1)
输出的结果如下:
s1 = {Person:{id:0 name: age:0} name:zhangsan score:0}
通过结果发现是对Student中的name进行赋值,所以在操作同名字段时,有一个基本的原则:如果能够在自己对象所属的类(结构体)中找到对应的成员,那么直接进行操作,如果找不到就去对应的父类(结构体)中查找。
这就是所谓的就近原则。
结构体(类)中的匿名字段的类型,也可以是指针。
例如:
type Person struct {id intname stringage int
}type Student struct {*Person // 指针类型匿名字段name string // 和Persion同名score float64
}func main() {var s1 Students1 = Student{&Person{101, "lisi", 19}, "zhangsan", 94}fmt.Printf("s1 = %+vn", s1)
}
输出结果:
s1 = {Person:0xc00004e3c0 name:zhangsan score:94}
输出了结构体的地址。
如果要取值,可以进行如下操作:
type Person struct {id intname stringage int
}type Student struct {*Person // 指针类型匿名字段name string // 和Persion同名score float64
}func main() {var s1 Students1 = Student{&Person{101, "lisi", 19}, "zhangsan", 94}fmt.Println(s1.id, s1.name, s1.age, s1.score)
}// 执行
101 zhangsan 19 94
在定义对象s时,完成初始化,然后通过"."
的操作完成成员的操作。
但是,注意以下的写法是错误的:
type Person struct {id intname stringage int
}type Student struct {*Person // 指针类型匿名字段name string // 和Persion同名score float64
}func main() {var s1 Students1.id = 103s1.name = "wangwu"s1.age = 19s1.score = 89fmt.Println(s1.id, s1.name, s1.age, s1.score)
}
大家可以思考一下,以上代码为什么会出错?
会出错,错误信息如下:
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x1 addr=0x0 pc=0xb379b8]
翻译成中文:无效的内存地址或nil指针引用
意思是*Person
没有指向任何的内存地址,那么其默认值为nil
.
也就是指针类型匿名字段*Person
没有指向任何一个结构体,所以对象s1
也就无法操作Person
中的成员。
具体的解决办法如下:
type Person struct {id intname stringage int
}type Student struct {*Person name string score float64
}func main() {var s1 Students1.Person = new(Person) // 使用new分配空间s1.id = 103s1.name = "wangwu"s1.age = 19s1.score = 89fmt.Println(s1.id, s1.name, s1.age, s1.score)
}// 执行
103 wangwu 19 89
new()
的作用是分配空间,new()
函数的参数是一个类型,这里为Person
结构体类型,返回值为指针类型,所以赋值给*Person
,
这样*Person
也就指向了结构体Person
的内存
在上面的案例,Student类(结构体)继承了Person类(结构体),那么Person是否可以在继承别的类(结构体)呢?
可以,这就是多重继承。
多重继承指的是一个类可以继承另外一个类,而另外一个类又可以继承别的类,
比如: A类继承B类,而B类又可以继承C类,这就是多重继承。
具体案例如下:
type Object struct {id intflag bool
}type Person struct {//id intObject // 继承 Objectname stringage int
}type Student struct {Person // 继承Personname string // 和Person同名score float64
}
接下来,看一下怎样对多重继承中的成员进行操作:
var s1 Student
s1.name = "zhangsan"
s1.Person.name = "laozhang"
s1.Person.Object.id = 101
fmt.Printf("s1 = %+vn", s1)// 执行:
s1 = {Person:{Object:{id:101 flag:false} name:laozhang age:0} name:zhangsan score:0}
操作的方式,与前面讲解的是一样的。
“注意:尽量在程序中,减少多重继承,否则会增加程序的复杂度。
”
通过以上内容的讲解,大家能够体会出面向对象编程中继承的优势了。
接下来会给大家介绍:
面向对象编程中另外的特性:封装性,其实关于封装性,在前面的编程中,大家也已经能够体会到了,就是通过函数来实现封装性。
大家仔细回忆一下,当初在讲解函数时,重点强调了函数的作用,就是将重复的代码封装来,用的时候,直接调用就可以了,不需要每次都写一遍,这就是封装的优势。
在面向对象编程中,也有封装的特性。面向对象中是通过方法来实现。下面,将详细的给大家讲解一下方法的内容。
本文发布于:2024-01-29 10:34:38,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170649568314669.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |