【笔记】Go语言的面向对象

前言

Go语言的面向对象学习笔记

方法

  • Go中的方法是作用在指定类型上的,任何自定义类型都可以有自己的方法
  • 将函数与结构体绑定,形成逻辑上的方法

值传递

定义方法

  • 在调用方法时,结构体变量会作为参数传递给方法,而函数不会
    • 对于函数而言,在传递参数时,定义函数时的参数为值类型,调用函数时必须传递值类型;定义函数时的参数为引用类型,调用函数时必须传递引用类型
    • 对于方法而言,在传递参数时,无论定义方法时的参数为引用类型还是值类型,调用方法时既可以是值类型,也可以是引用类型,不过实际是值类型还是引用类型由定义方法时的形参决定
1
2
3
4
5
6
7
type 结构体名 struct {
...
}

func (结构体变量名 结构体名) 方法名(形参列表) 返回值类型 {
...
}
内置类型也可以定义方法
1
2
3
4
5
6
7
8
9
10
type integer int

func (i integer) print() {
fmt.Println(i)
}

func main() {
var i integer = 1
i.print()
}
实现String()方法
  • 如果一个结构体实现了String()方法,那么在fmt.Println()时会执行这个方法
1
2
3
4
5
6
7
8
9
10
type integer int

func (结构体变量名 结构体名) String() string {
return 结构体变量名.属性名
}

func main() {
var 结构体名 = 结构体名{"属性名": 属性值}
fmt.Println(&结构体名)
}

方法的调用

1
2
3
4
func main() {
var 结构体变量名 结构体名
结构体名.方法名(实参列表)
}

指针传递

方法的定义

  • 由于结构体变量的赋值是值传递,所以如果直接传递结构体变量给方法会降低效率,为了提升效率通常将结构体变量指针传递给方法
1
2
3
4
5
6
7
type 结构体名 struct {
...
}

func (结构体变量名 *结构体名) 方法名(形参列表) 返回值类型 {
...
}

方法的调用

1
2
3
4
func main() {
var 结构体变量名 结构体名
(&结构体名).方法名(实参列表)
}
简写
  • 编译器会在编译时将结构体名改为(&结构体名)
1
2
3
4
func main() {
var 结构体变量名 结构体名
结构体名.方法名(实参列表)
}

封装

  • 通过工厂模式模拟构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type persion struct {
name string
}

func newPersion(name string) *Persion {
return &student{
name: name,
}
}

func (persion *persion) GetName() string {
return persion.name
}

func (persion *persion) SetName(name string) string {
persion.name = name
}

继承

  • 通过匿名结构体实现继承的特性

指定匿名结构体作为继承关系

继承一个结构体

1
2
3
4
5
6
7
type Father struct {
...
}

type Son struct {
Father
}
调用父结构体的属性和方法
  • 可以直接使用父结构体的属性和方法
    • 先看当前结构体中是否存在指定的属性或方法,如果有就调用当前结构体中的属性或方法
    • 如果没有再看匿名结构体中是否存在指定的属性或方法,如果有就调用匿名结构体中的属性或方法
    • 如果匿名结构体中仍然不存在指定的属性或方法,就报错
  • 被嵌套的结构体无论是首字母大写还是首字母小写,都能够被继承复用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Father struct {
privateField string
PublicField string
}

func (father *Father) privateMethod() {
...
}

func (father *Father) PublicMethod() {
...
}

type Son struct {
Father
}

func main() {
var son = Son{}
son.privateField = ""
son.PublicField = ""
son.privateMethod()
son.PublicMethod()
}
  • 也可以使用父结构体名调用父结构体的属性和方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Father struct {
privateField string
PublicField string
}

func (father *Father) privateMethod() {
...
}

func (father *Father) PublicMethod() {
...
}

type Son struct {
Father
}

func main() {
var son = Son{}
son.Father.privateField = ""
son.Father.PublicField = ""
son.Father.privateMethod()
son.Father.PublicMethod()
}

继承多个结构体

1
2
3
4
5
6
7
8
9
10
11
12
type Father struct {
...
}

type Mother struct {
...
}

type Son struct {
Father
Mother
}
调用父结构体的属性和方法
  • 必须使用父结构体名调用父结构体的属性和方法
  • 被嵌套的结构体无论是首字母大写还是首字母小写,都能够被继承复用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
type Father struct {
privateField string
PublicField string
}

func (father *Father) privateMethod() {
...
}

func (father *Father) PublicMethod() {
...
}

type Mother struct {
privateField string
PublicField string
}

func (mother *Mother) privateMethod() {
...
}

func (mother *Mother) PublicMethod() {
...
}

type Son struct {
Father
Mother
}

func main() {
var son = Son{}
son.Father.privateField = ""
son.Father.PublicField = ""
son.Father.privateMethod()
son.Father.PublicMethod()
son.Mother.privateField = ""
son.Mother.PublicField = ""
son.Mother.privateMethod()
son.Mother.PublicMethod()
}

直接实例化结构体

实例化值属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Father struct {
Name string
}

type Son struct {
Father
}

func main() {
var son = Son{
Father{
privateField: ""
}
}
}
实例化引用属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Father struct {
Name string
}

type Son struct {
*Father
}

func main() {
var son = Son{
&Father{
privateField: ""
}
}
}

使用基本类型匿名属性作为父结构体

1
2
3
4
5
6
7
8
type Son struct {
int
}

func main() {
var son = Son{}
son.int = 1
}

接口(interface)

  • 接口中只能声明没有实现的方法,不能有方法体,接口中不允许定义任何属性
  • 接口能实现高内聚低耦合的编程思想
  • 实现接口就是指结构体实现了接口所指定的所有抽象方法,必须实现所有接口定义的抽象方法才是实现接口
    • Go中接口不需要显式实现,只要结构体实现了接口所指定的所有抽象方法,就完成了对接口的实现
  • 接口类型是一个引用类型,如果没有初始化就使用会返回nil
  • 接口名推荐以er结尾
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 定义接口
type 接口名 interface {
抽象方法名(形参列表) 返回值列表
}

// 实现接口
type 实现接口的结构体名 struct {
...
}
func (this 实现接口的结构体名) 抽象方法名() 返回值列表 {
...
}

// 调用实现接口的结构体
func main() {
var 结构体变量名 接口名 = 实现接口的结构体名{}
结构体变量名.抽象方法名()
}

实现多个接口

  • 同时实现所有接口的抽象方法就是实现多个接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 定义接口
type 接口名1 interface {
抽象方法名1(形参列表)
}
type 接口名2 interface {
抽象方法名2(形参列表)
}

// 实现接口
type 实现接口的结构体名 struct {
...
}
func (this 实现接口的结构体名) 抽象方法名1() {
...
}
func (this 实现接口的结构体名) 抽象方法名2() {
...
}

// 使用接口执行函数
func main() {
var 结构体变量名1 接口名1 = 实现接口的结构体名{}
结构体变量名1.抽象方法名1()

var 结构体变量名2 接口名2 = 实现接口的结构体名{}
结构体变量名2.抽象方法名2()
}

实现继承了多接口的接口

  • 一个接口继承了多个接口时,不允许出现同名方法,否则会编译报错:重复定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 定义接口
type 接口名1 interface {
抽象方法名1(形参列表)
}
type 接口名2 interface {
抽象方法名2(形参列表)
}
type 接口名3 interface {
接口名1
接口名2
抽象方法名3(形参列表)
}

// 实现接口
type 实现接口的结构体名 struct {
...
}
func (this 实现接口的结构体名) 抽象方法名1() {
...
}
func (this 实现接口的结构体名) 抽象方法名2() {
...
}
func (this 实现接口的结构体名) 抽象方法名3() {
...
}

// 使用接口执行函数
func main() {
var 结构体变量名3 接口名3 = 实现接口的结构体名{}
结构体变量名3.抽象方法名1()
结构体变量名3.抽象方法名2()
结构体变量名3.抽象方法名3()
}

空接口

  • 没有任何方法的接口就是空接口,所以所有类型都实现了空接口
1
2
3
type 接口名 interface {}

var 变量名 接口名 = 任意类型变量
1
var 变量名 interface{} = 任意类型变量

引用类型和值类型结构体的实现

值类型结构体实现接口

1
2
3
4
5
6
7
8
9
10
11
type 接口名 interface {
抽象方法名(形参列表)
}

type 实现接口的结构体名 struct {}
func (this 实现接口的结构体名) 抽象方法名() {}

func main() {
var 结构体变量名 接口名 = 实现接口的结构体名{}
结构体变量名.抽象方法名()
}

引用类型结构体实现接口

1
2
3
4
5
6
7
8
9
10
11
type 接口名 interface {
抽象方法名(形参列表)
}

type 实现接口的结构体名 struct {}
func (this *实现接口的结构体名) 抽象方法名() {}

func main() {
var 结构体变量名 接口名 = &实现接口的结构体名{}
结构体变量名.抽象方法名()
}

接口体现多态的形式

  • 多态参数
    • 在定义函数的形参时为接口类型,在调用函数时传递的实参为实现接口的类型
  • 多态数组
    • 在定义数组时定义数组中存放的数据类型为接口类型,在向数组中添加元素时添加实现了接口的数据类型的数据

类型断言

  • 将实现了接口的结构体变量赋值给接口变量时,数据类型就变成了接口,无法继续使用之前的数据类型
  • 可以通过类型断言.(数据类型),将接口类型转换为实现接口的结构体类型,如果转换失败就报错
1
2
3
4
var a interface{}
var b int
a = b
b, ok = a.(int)

判断类型断言是否失败

  • 如果类型断言失败会panic导致程序崩溃,为了防止程序崩溃可以通过第二个返回值判断断言是否失败,如果成功返回true,如果失败返回false
1
2
3
4
5
6
7
var a interface{}
var b float32
a = b
b, ok = a.(float64)
if !ok {
fmt.Println("convert fail")
}

完成

参考文献

哔哩哔哩——喔咔咔耶耶
哔哩哔哩——尚硅谷
哔哩哔哩——筱筱知晓