17.Go面向对象

阅读: 评论:0

17.Go面向对象

17.Go面向对象

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

前言

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

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

img

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

1 匿名字段

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

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

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

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

1.1 匿名字段创建与初始化

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

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

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

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}

1.2 成员操作

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

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

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成员(匿名字段)赋值

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

1.3  同名字段

现在将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进行赋值,所以在操作同名字段时,有一个基本的原则:如果能够在自己对象所属的类(结构体)中找到对应的成员,那么直接进行操作,如果找不到就去对应的父类(结构体)中查找。

这就是所谓的就近原则。

1.4 指针类型匿名字段

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

例如:

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的内存

1.5 多重继承

在上面的案例,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 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23