后端开发golang教程从面向对象到多态与异常处理:快速过一遍 Golang 核心知识点
Austoin一、 核心知识点补充与原理解析
1. Go 与传统面向对象语言的差异
- Go 没有什么:没有
class(用 struct 代替),没有传统的继承(用组合/嵌套代替),没有方法重载(同名方法编译不通过),没有传统的 try/catch(用 defer/recover 或多返回值 error 处理)。 - Go 有什么:结构体(
struct)、常量枚举(iota)、极简接口实现(Duck Typing 鸭子类型)、并发基因(goroutine 与 channel)。
2. 面向对象:方法与指针接收者 (Method Sets)
在 Go 中,结构体的方法可以绑定到“值”上,也可以绑定到“指针”上。方法集(Method Set)规则:
- 类型
T 的方法集只包含接收者为 T 的方法。 - 类型
*T(指针)的方法集包含接收者为 T 和 *T 的所有方法。 - 调用时的语法糖:无论定义的是
T 还是 *T,由于 Go 编译器的语法糖,可以用实例值或指针去调用所有方法(编译器会自动解引用或取地址,如 u.Run() 会被转成 (&u).Run())。 - 接口实现的严格限制:如果方法的接收者是
*T,那么只有 *T 类型的指针才算实现了该接口,T 类型的值不算实现!
3. 接口与多态 (Duck Typing)
- 隐式实现:Go 没有
implements 关键字。只要一个结构体实现了某个接口定义的所有方法,它就自动实现了该接口。 - 多态表现:定义一个接收接口类型作为参数的函数(如
PersonCase(person Person)),传入不同的实体(Teacher、Student),会执行各自绑定的方法。
4. 类型断言 (Type Assertion)
Go 1.18+已支持真泛型,但在之前版本或处理未知类型时,依然大量使用 interface{} 和断言。
- 语法:
value, ok := interfaceVar.(TargetType)。如果转换成功,ok 为 true,value 为目标类型的值;否则 ok 为 false,安全防止 panic。
5. Defer、Panic 与 Recover (异常处理与执行顺序)
defer 的作用:延迟执行,通常用于资源释放(关闭文件、断开连接)和异常捕获。无论函数是正常 return 还是发生 panic,defer 都会执行。- 执行顺序(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" )
type Gender uint8
const ( FEMALE Gender = iota MALE THIRD UNKNOWN )
type Person interface { Run() Sleep() }
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") }
type Teacher struct { Name string Age uint8 Gender Gender }
func (t *Teacher) Run() { fmt.Println("在公园跑步") } func (t *Teacher) Sleep() { fmt.Println("在家睡觉") }
type Student struct { }
func (s *Student) Run() { fmt.Println("学生在操场跑步") } func (s *Student) Sleep() { fmt.Println("学生在宿舍睡觉") }
func UserCase() { fmt.Println("--- UserCase ---") u := &User{} u.Run() u.Sleep() }
func PersonCase(person Person) { defer func() { fmt.Println("person defer") }()
person.Run() person.Sleep() return }
func PersonCase1(person interface{}) { fmt.Println("--- PersonCase1 (Type Assertion) ---") if p1, ok := person.(Person); ok { p1.Run() } else { fmt.Println("类型不能识别") } }
func TryCatchCase() { fmt.Println("--- TryCatchCase ---") defer func() { err := recover() if err != nil { fmt.Println("Recovered:", err) } }() PanicCase() }
func PanicCase() { panic("程序出现异常了") }
func DeferCase() { fmt.Println("--- DeferCase (传参 vs 闭包) ---") i := 1
defer func(j int) { fmt.Println("defer j: ", j) }(i + 1)
defer func() { i++ fmt.Println("defer i: ", i) }()
i = 99 return }
var j int = 1
func DeferCase1() { fmt.Println("--- DeferCase1 (修改返回值) ---") i, i1 := f1() fmt.Println(i, *i1, j) }
func f1() (int, *int) { defer func() { j = 100 }() fmt.Println("f1 j: ", j) return j, &j }
func main() { UserCase()
fmt.Println("--- Polymorphism (多态) ---") t := &Teacher{} s := &Student{} PersonCase(t) PersonCase(s)
PersonCase1(t) PersonCase1("hello world")
TryCatchCase()
DeferCase()
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。