从面向对象到多态与异常处理:快速过一遍 Golang 核心知识点

一、 核心知识点补充与原理解析

1. Go 与传统面向对象语言的差异

  • Go 没有什么:没有 class(用 struct 代替),没有传统的继承(用组合/嵌套代替),没有方法重载(同名方法编译不通过),没有传统的 try/catch(用 defer/recover 或多返回值 error 处理)。
  • Go 有什么:结构体(struct)、常量枚举(iota)、极简接口实现(Duck Typing 鸭子类型)、并发基因(goroutinechannel)。

2. 面向对象:方法与指针接收者 (Method Sets)

在 Go 中,结构体的方法可以绑定到“值”上,也可以绑定到“指针”上。方法集(Method Set)规则

  1. 类型 T 的方法集只包含接收者为 T 的方法。
  2. 类型 *T(指针)的方法集包含接收者为 T*T 的所有方法。
  3. 调用时的语法糖:无论定义的是 T 还是 *T,由于 Go 编译器的语法糖,可以用实例值或指针去调用所有方法(编译器会自动解引用或取地址,如 u.Run() 会被转成 (&u).Run())。
  4. 接口实现的严格限制:如果方法的接收者是 *T,那么只有 *T 类型的指针才算实现了该接口,T 类型的值不算实现!

3. 接口与多态 (Duck Typing)

  • 隐式实现:Go 没有 implements 关键字。只要一个结构体实现了某个接口定义的所有方法,它就自动实现了该接口。
  • 多态表现:定义一个接收接口类型作为参数的函数(如 PersonCase(person Person)),传入不同的实体(TeacherStudent),会执行各自绑定的方法。

4. 类型断言 (Type Assertion)

Go 1.18+已支持真泛型,但在之前版本或处理未知类型时,依然大量使用 interface{} 和断言。

  • 语法value, ok := interfaceVar.(TargetType)。如果转换成功,oktruevalue 为目标类型的值;否则 okfalse,安全防止 panic。

5. Defer、Panic 与 Recover (异常处理与执行顺序)

  • defer 的作用:延迟执行,通常用于资源释放(关闭文件、断开连接)和异常捕获。无论函数是正常 return 还是发生 panicdefer 都会执行。
  • 执行顺序(LIFO):后进先出(栈结构),最后声明的 defer 最先执行。
  • 参数预计算 vs 闭包
    • 如果在 defer func(j int)(i) 中通过传参传入,参数的值在 defer 声明时就已经固定(副本)。
    • 如果在 defer func() { fmt.Println(i) }() 中使用闭包直接引用外部变量,则会读取变量在函数执行完毕时的最新值
  • 对返回值的修改:如果函数的返回值是命名返回值(如 func f() (res int)),defer 可以在 return 之后、实际返回给调用方之前,修改 res 的值。

二、 完整代码复现

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package main

import (
"fmt"
)

// 1. 枚举与基础结构体定义

// 定义 Gender 枚举
type Gender uint8

const (
FEMALE Gender = iota // 0
MALE // 1
THIRD // 2
UNKNOWN // 3
)

// 2. 接口与多态的结构体定义

// Person 接口:只要实现了 Run 和 Sleep 方法,就是 Person
type Person interface {
Run()
Sleep()
}

// 定义 User 结构体
type User struct {
Name string
Age uint8
Gender Gender
}

func (u *User) Run() {
fmt.Println("user run")
}
func (u *User) Sleep() {
fmt.Println("user sleep")
}

// 定义 Teacher 结构体
type Teacher struct {
Name string
Age uint8
Gender Gender
}

func (t *Teacher) Run() {
fmt.Println("在公园跑步")
}
func (t *Teacher) Sleep() {
fmt.Println("在家睡觉")
}

// 定义 Student 结构体
type Student struct {
// 字段省略
}

func (s *Student) Run() {
fmt.Println("学生在操场跑步")
}
func (s *Student) Sleep() {
fmt.Println("学生在宿舍睡觉")
}

// 3. 业务函数:多态与类型断言

// UserCase 测试基础方法调用
func UserCase() {
fmt.Println("--- UserCase ---")
u := &User{} // 返回指针
u.Run()
u.Sleep()
}

// PersonCase 测试多态与 defer 的场景
func PersonCase(person Person) {
// defer 演示:无论方法如何,最后都会执行(在 return 之后)
defer func() {
fmt.Println("person defer")
}()

person.Run()
person.Sleep()
return
}

// PersonCase1 测试基于空接口 interface{} 的类型断言
func PersonCase1(person interface{}) {
fmt.Println("--- PersonCase1 (Type Assertion) ---")
// 断言:判断传入的参数是否实现了 Person 接口
if p1, ok := person.(Person); ok {
p1.Run()
} else {
fmt.Println("类型不能识别")
}
}

// 4. 异常处理:Panic 与 Recover

func TryCatchCase() {
fmt.Println("--- TryCatchCase ---")
defer func() {
err := recover() // 捕获异常
if err != nil {
fmt.Println("Recovered:", err)
}
}()
PanicCase()
}

func PanicCase() {
panic("程序出现异常了") // 触发异常
}

// 5. Defer 的进阶场景:传参 vs 闭包

func DeferCase() {
fmt.Println("--- DeferCase (传参 vs 闭包) ---")
i := 1

// 传参:在声明时计算出参数值 i+1 = 2,放入 defer 栈中
defer func(j int) {
fmt.Println("defer j: ", j)
}(i + 1)

// 闭包:直接引用外部变量 i。执行时读取 i 的最终真实值
defer func() {
i++
fmt.Println("defer i: ", i)
}()

i = 99
return
// j = 2, i = 100
}

// 6. Defer 的进阶场景:对返回值与外部变量的影响

var j int = 1

func DeferCase1() {
fmt.Println("--- DeferCase1 (修改返回值) ---")
i, i1 := f1()
// 输出 f1返回的副本(i)、f1返回的指针指向的值(*i1)、外部变量(j)
fmt.Println(i, *i1, j)
// i = 1, i1 = 100, j = 100
}

func f1() (int, *int) {
defer func() {
j = 100 // defer 会在 return 之后执行,修改了全局变量 j 的值
}()
fmt.Println("f1 j: ", j)
// 第一个返回值是值拷贝(复制了 1),第二个返回值是指针(指向 j 的内存地址)
return j, &j
}

// 主函数入口
func main() {
// 1. 测试对象方法
UserCase()

// 2. 测试多态 (Teacher 和 Student 都实现了 Person 接口)
fmt.Println("--- Polymorphism (多态) ---")
t := &Teacher{}
s := &Student{}
PersonCase(t)
PersonCase(s)

// 3. 测试类型断言
PersonCase1(t) // 正常识别
PersonCase1("hello world") // 无法识别

// 4. 测试异常拦截
TryCatchCase()

// 5. 测试 Defer 顺序与闭包
DeferCase()

// 6. 测试 Defer 修改变量及指针现象
DeferCase1()
}

三、 运行结果与深度剖析

解析 1:DeferCase 的输出

1
2
3
--- DeferCase (传参 vs 闭包) ---
defer i: 100
defer j: 2
  • 为什么 j 是 2? defer func(j int)(i+1) 中,参数是值传递的。当代码执行到这一行时,i 还是 1,i+1 算出来是 2,所以 2 作为参数被打包进了 defer 栈。后来 i 怎么变,与它无关。
  • 为什么 i 是 100? 闭包里的 defer func(){ i++ } 直接拿到了 i 的内存地址引用。当函数 return 前执行到它时,i 已经被赋为了 99,闭包再执行 i++,所以变成了 100
  • 为什么先打印 i 后打印 j 因为 defer后进先出 (LIFO),后面的闭包 defer 后压入栈,所以先执行。

解析 2:DeferCase1 的输出

1
2
3
--- DeferCase1 (修改返回值) ---
f1 j: 1
1 100 100
  • 全局变量 j 初始是 1。
  • 进入 f1(),先打印 f1 j: 1
  • 执行 return j, &j。此时第一部分返回值(匿名)保存了 j 的副本,即 1;第二部分返回值保存了 &j(内存地址)。
  • 执行 defer,将全局变量 j 修改为 100
  • 回到 DeferCase1 接收:
    • i 拿到了第一部分的值拷贝,依然是 1
    • *i1 是解引用第二部分的指针,由于指向的内存数据已经被 defer 改成了 100,因此打印 100
    • 外部全局变量 j 确实被修改了,打印 100