Go 语言高级函数特性
- [1. 高阶函数](#1. 高阶函数)
- [2. 匿名函数](#2. 匿名函数)
- [3. 匿名函数与闭包](#3. 匿名函数与闭包)
- [3. defer 语句](#3. defer 语句)
函数是 Go 语言编程的核心基石之一,除了基础的定义与调用,Go 还为函数赋予了更灵活、更强大的高级特性。掌握这些特性,能够让我们写出更简洁、高效、易维护的代码,充分发挥 Go 语言的编程优势
1. 高阶函数
高阶函数是函数式编程的重要特征,指的是接收函数作为参数,或者返回函数作为结果的函数。Go 语言虽然并非纯函数式编程语言,但完美支持高阶函数特性,极大提升了代码的抽象能力和复用性
函数作为参数
在 Go 中,函数是一种 "一等公民",可以像普通变量一样被传递。这意味着我们可以将一个函数作为参数传入另一个函数,让被调用的函数根据传入的不同逻辑实现不同的功能,实现逻辑的解耦与复用。
示例:通用计算函数
下面的示例定义了add(加法)、mul(乘法)两个基础运算函数,再定义一个通用的calc函数,接收两个整数和一个运算函数作为参数,通过传入不同的运算函数,实现不同的计算逻辑
go
// 加法函数
func add(x, y int) int {
return x + y
}
// 乘法函数
func mul(x, y int) int {
return x * y
}
// calc 高阶函数:接收两个int类型参数和一个运算函数,返回运算结果
func calc(x, y int, op func(int, int) int) int {
// 调用传入的运算函数,实现灵活计算
return op(x, y)
}
func main() {
// 传入加法函数,执行10+20
res := calc(10, 20, add)
fmt.Println("10 + 20 =", res) // 输出:10 + 20 = 30
// 传入乘法函数,执行10*20
resP := calc(10, 20, mul)
fmt.Println("10 * 20 =", resP) // 输出:10 * 20 = 200
}
这种模式常用于封装通用逻辑,比如:
- 数组遍历处理:传入不同的处理函数,对数组每个元素执行不同操作;
- 业务逻辑扩展:比如支付模块,传入不同的支付渠道函数,实现统一的支付流程适配不同渠道。
函数作为返回值
函数不仅可以作为参数传入,还可以作为另一个函数的返回值。这使得我们可以根据不同的条件,动态生成并返回不同逻辑的函数,进一步提升代码的灵活性。
示例:动态返回运算函数
下面的示例定义了do函数,根据传入的运算符字符串,返回对应的运算函数;若运算符不识别,则返回错误
go
// 加法函数
func add(x, y int) int {
return x + y
}
// 乘法函数
func mul(x, y int) int {
return x * y
}
// do 高阶函数:根据运算符返回对应的运算函数,若不识别则返回错误
func do(s string) (func(int, int) int, error) {
switch s {
case "+":
return add, nil // 返回加法函数,无错误
case "*":
return mul, nil // 返回乘法函数,无错误
default:
// 返回nil和错误信息
return nil, errors.New("无法识别的运算符:" + s)
}
}
func main() {
// 获取加法函数并调用
f, err := do("+")
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Println("10 + 20 =", f(10, 20)) // 输出:10 + 20 = 30
}
// 获取减法函数(不存在),处理错误
f2, err2 := do("-")
if err2 != nil {
fmt.Println("错误:", err2) // 输出:错误:无法识别的运算符:-
} else {
fmt.Println(f2(10, 20))
}
}
函数作为返回值常用于工厂模式:
- 配置化生成函数:根据不同的配置参数,生成适配该配置的业务处理函数;
- 闭包结合场景:返回的函数可以捕获外层函数的变量,形成闭包(后续匿名函数部分详细讲解)
2. 匿名函数
在 Go 语言中,函数内部无法直接定义命名函数(即不能嵌套定义命名函数),但可以定义匿名函数------ 没有函数名的函数。匿名函数是实现闭包、快速定义临时逻辑的核心手段
匿名函数的定义与普通函数类似,只是缺少函数名,格式如下:
go
func(参数列表)(返回值列表){
函数体逻辑
}
匿名函数没有函数名,无法直接通过名称调用,通常有两种使用方式:保存到变量、立即执行
1、方式 1:保存到变量
将匿名函数赋值给一个变量,通过变量名调用函数,适用于需要多次调用的场景
2、方式 2:立即执行
定义匿名函数后直接加()执行,适用于只需要执行一次的临时逻辑
go
func main() {
// 方式1:将匿名函数保存到变量,复用调用
add := func(x, y int) {
fmt.Printf("%d + %d = %d\n", x, y, x+y)
}
// 调用匿名函数
add(10, 20) // 输出:10 + 20 = 30
add(30, 40) // 输出:30 + 40 = 70
// 方式2:自执行函数(定义后立即执行)
func(x, y int) {
fmt.Printf("%d + %d = %d\n", x, y, x+y)
}(20, 20) // 输出:20 + 20 = 40
// 带返回值的匿名函数(立即执行并接收返回值)
res := func(x, y int) int {
return x * y
}(5, 6)
fmt.Println("5 * 6 =", res) // 输出:5 * 6 = 30
}
3. 匿名函数与闭包
匿名函数最核心的应用是实现闭包 ------ 匿名函数可以捕获外层函数的变量,即使外层函数执行完毕,匿名函数依然可以访问和修改该变量。这使得变量的生命周期被延长,常用于实现状态保持、封装私有数据等场景
go
// 定义一个函数,返回一个匿名函数(闭包)
func counter() func() int {
// 外层函数的局部变量,被匿名函数捕获
count := 0
// 返回匿名函数
return func() int {
count++ // 修改外层函数的变量
return count
}
}
func main() {
// 创建计数器1
c1 := counter()
fmt.Println(c1()) // 输出:1
fmt.Println(c1()) // 输出:2
fmt.Println(c1()) // 输出:3
// 创建计数器2(独立的闭包,count变量互不影响)
c2 := counter()
fmt.Println(c2()) // 输出:1
fmt.Println(c1()) // 输出:4(c1的count继续累加)
}
3. defer 语句
Go 语言中的defer语句用于延迟执行函数调用,它会将跟随的语句 / 函数调用加入到一个 "延迟调用栈" 中,当归属的函数即将返回时,再按逆序执行这些延迟语句(先 defer 的后执行,后 defer 的先执行)。defer是 Go 语言处理资源释放、异常恢复的核心特性,能极大简化资源管理逻辑
由于defer延迟执行的特性,它非常适合处理 "资源申请 - 释放" 的配对逻辑,确保资源无论函数正常返回还是异常退出,都能被正确释放:
- 文件操作:打开文件后 defer 关闭文件,避免忘记关闭导致资源泄漏
- 锁操作:加锁后 defer 解锁,避免死锁
- 数据库连接:建立连接后 defer 关闭连接,释放数据库资源
- 计时 / 日志:defer 记录函数执行耗时、打印收尾日志
go
func readFile(filePath string) {
// 打开文件
file, err := os.Open(filePath)
if err != nil {
fmt.Println("打开文件失败:", err)
return
}
// 延迟关闭文件,确保函数退出时执行
defer func() {
_ = file.Close()
fmt.Println("文件已关闭")
}()
// 读取文件(简化示例,仅打印提示)
fmt.Println("读取文件内容...")
}
func main() {
readFile("test.txt")
}
defer执行时机
Go 语言中,函数的return语句并非原子操作,底层分为两步:
- 给返回值赋值
- 执行 RET 指令(函数真正返回)
defer语句的执行时机,恰好介于 "返回值赋值" 之后、"RET 指令执行" 之前
