0
点赞
收藏
分享

微信扫一扫

17.Go面向对象-匿名字段

17.Go面向对象-匿名字段

前言

所谓继承指的是,我们可能会在一些类(结构体)中,写一些重复的成员,我们可以将这些重复的成员,

单独的封装到一个类(结构体)中,作为这些类的父类(结构体),我们可以通过如下图来理解:


17.Go面向对象-匿名字段_指针

img

当然严格意义上,GO语言中是没有继承的,但是我们可以通过”匿名组合”来实现继承的效果。

1 匿名字段

根据上一篇章介绍中的图,我们发现学生类(结构体),讲师类(结构体)等都有共同的成员(属性和方法),这样就存在重复。

所以我们把这些重复的成员封装到一个**父类(结构体)**中。

然后让学生类(结构体)和讲师类(结构体)继承父类(结构体)

接下来,我们可以先将公共的属性,封装到**父类(结构体)**中实现继承,关于方法(函数)的继承后面再讲。

1.1 匿名字段创建与初始化

那么怎样实现属性的继承呢?

使用匿名字段的继承结构体

可以通过**匿名字段(也叫匿名组合)**来实现,什么是匿名字段呢?通过如下使用,大家就明白了。

type Person struct {
id int
name string
age int
}

type Student struct {
Person // 匿名字段

score float64
}

以上代码通过匿名字段实现了继承,将公共的属性封装在Person中,在Student中直接包含Person,那么Student中就有了Person中所有的成员,Person就是匿名字段。

注意:Person匿名字段,只有类型,没有名字。

包含匿名字段的结构体初始化

那么接下来说我们就可以给​​Student​​赋值了,具体初始化的方式如下:

type Person struct {
id int
name string
age 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 = %+v\n", s1)

// 执行如下:
s2 = {Person:{id:102 name:zhangsan age:18} score:98.5}

(2)指定初始化成员

// 指定初始化成员
s3 := Student{score: 97}
fmt.Printf("s3 = %+v\n", s3)

// 执行:
s3 = {Person:{id:0 name: age:0} score:97}

接下来还可以对Person中指定的成员进行初始化。

s4 := Student{Person: Person{name: "mike"}, score: 97}
fmt.Printf("s4 = %+v\n", s4)

// 执行:
s4 = {Person:{id:0 name:mike age:0} score:97}

1.2 成员操作

创建完成对象后,可以根据对象来操作对应成员属性,是通过​​“.”​​运算符来完成操作的。

对象操作成员属性的案例:

s1 := Student{Person: Person{101,"mike", 18}, score: 97}
s1.age = 20
s1.Person.id = 120
s1.score = 99
fmt.Printf("s1 = %+v\n", 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 = %+v\n", s1)

// 执行:
s1 = {Person:{id:112 name:zhangsan age:18} score:97}

直接给对象​​s1​​​中的​​Person​​成员(匿名字段)赋值

通过以上案例我们可以总结出,根据类(结构体)可以创建出很多的对象,这些对象的成员(属性)是一样的,但是成员(属性)的值是可以完全不一样的。

1.3  同名字段

现在将​​Student​​​结构体与​​Person​​结构体,进行如下的修改:

type Person struct {
id int
name string
age 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 = %+v\n", s1)

输出的结果如下:

s1 = {Person:{id:0 name: age:0} name:zhangsan score:0}

通过结果发现是对Student中的name进行赋值,所以在操作同名字段时,有一个基本的原则:如果能够在自己对象所属的类(结构体)中找到对应的成员,那么直接进行操作,如果找不到就去对应的父类(结构体)中查找。

这就是所谓的就近原则。

1.4 指针类型匿名字段

结构体(类)中的匿名字段的类型,也可以是指针。

例如:

type Person struct {
id int
name string
age int
}

type Student struct {
*Person // 指针类型匿名字段
name string // 和Persion同名
score float64
}

func main() {
var s1 Student
s1 = Student{&Person{101, "lisi", 19}, "zhangsan", 94}
fmt.Printf("s1 = %+v\n", s1)
}

输出结果:

s1 = {Person:0xc00004e3c0 name:zhangsan score:94}

输出了结构体的地址。

如果要取值,可以进行如下操作:

type Person struct {
id int
name string
age int
}

type Student struct {
*Person // 指针类型匿名字段
name string // 和Persion同名
score float64
}

func main() {
var s1 Student
s1 = 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 int
name string
age int
}

type Student struct {
*Person // 指针类型匿名字段
name string // 和Persion同名
score float64
}

func main() {
var s1 Student
s1.id = 103
s1.name = "wangwu"
s1.age = 19
s1.score = 89
fmt.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 int
name string
age int
}

type Student struct {
*Person
name string
score float64
}

func main() {
var s1 Student
s1.Person = new(Person) // 使用new分配空间
s1.id = 103
s1.name = "wangwu"
s1.age = 19
s1.score = 89
fmt.Println(s1.id, s1.name, s1.age, s1.score)
}

// 执行
103 wangwu 19 89

​new()​​​的作用是分配空间,​​new()​​​函数的参数是一个类型,这里为​​Person​​​结构体类型,返回值为指针类型,所以赋值给​​*Person​​,

这样​​*Person​​​也就指向了结构体​​Person​​的内存

1.5 多重继承

在上面的案例,Student类(结构体)继承了Person类(结构体),那么Person是否可以在继承别的类(结构体)呢?

可以,这就是多重继承。

多重继承指的是一个类可以继承另外一个类,而另外一个类又可以继承别的类,

比如: A类继承B类,而B类又可以继承C类,这就是多重继承。

具体案例如下:

type Object struct {
id int
flag bool
}

type Person struct {
//id int
Object // 继承 Object
name string
age int
}

type Student struct {
Person // 继承Person
name string // 和Person同名
score float64
}

接下来,看一下怎样对多重继承中的成员进行操作:

var s1 Student
s1.name = "zhangsan"
s1.Person.name = "laozhang"
s1.Person.Object.id = 101
fmt.Printf("s1 = %+v\n", s1)

// 执行:
s1 = {Person:{Object:{id:101 flag:false} name:laozhang age:0} name:zhangsan score:0}

操作的方式,与前面讲解的是一样的。

注意:尽量在程序中,减少多重继承,否则会增加程序的复杂度。

通过以上内容的讲解,大家能够体会出面向对象编程中继承的优势了。

接下来会给大家介绍:

  • 面向对象编程中另外的特性:封装性,其实关于封装性,在前面的编程中,大家也已经能够体会到了,就是通过函数来实现封装性。
  • 大家仔细回忆一下,当初在讲解函数时,重点强调了函数的作用,就是将重复的代码封装来,用的时候,直接调用就可以了,不需要每次都写一遍,这就是封装的优势。
  • 在面向对象编程中,也有封装的特性。面向对象中是通过方法来实现。下面,将详细的给大家讲解一下方法的内容。
举报

相关推荐

0 条评论