函数、defer 与闭包
约 1102 字大约 4 分钟
2025-04-23
函数定义
基本函数
func add(n1, n2 int) int {
return n1 + n2
}多参数传入(...)
func moreNum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
func main() {
fmt.Println(moreNum(1, 2, 3)) // 6
// 给函数传入切片(在后面加 ...)
aggSlice := []int{20, 21, 22, 23}
fmt.Println(moreNum(aggSlice...)) // 86
}命名返回值 vs 非命名返回值
非命名返回值
func MySqrt1(x float64) float64 {
return math.Sqrt(x)
}命名返回值
func MySqrt2(x float64) (result float64) {
result = math.Sqrt(x)
return // 无需再次写出变量名
}提示
命名返回值可以在函数体内直接作为变量使用,return 时自动返回该变量。
将函数作为参数传递
func add(a, b int) {
fmt.Println(a, b, a+b)
}
func F(i int, f func(int, int)) {
f(i, i+1)
}
func main() {
F(5, add) // 5 6 11
}函数作为 Map 的值
func main() {
m := map[int]func(){
1: signIn,
2: signUp,
3: user,
}
m[1]() // 调用 signIn
}空白符 _
用于丢弃函数返回的不需要的值:
func sum(num1, num2 int) (sum1, sum2, sum3 int) {
sum1 = num1 + num2
sum2 = num1 * num2
sum3 = num1 + num2 + 2
return
}
func main() {
i, _, k := sum(5, 10) // 丢弃第二个返回值
fmt.Println(i, k)
}defer
defer 将语句推迟到函数返回前执行,按先入后出的顺序执行。
func main() {
fmt.Println("上面")
defer fmt.Println("延迟执行")
fmt.Println("下面")
}
// 输出:
// 上面
// 下面
// 延迟执行重要
不建议在 for 循环中使用 defer。每创建一个 defer 就需要在当前协程申请一片内存空间,循环量很大时可能出现内存泄漏。
defer 实现源码追踪
func trace(s string) string {
fmt.Println("entering:", s)
return s
}
func un(s string) {
fmt.Println("leaving:", s)
}
func a() {
defer un(trace("a")) // trace 立即执行,un 延迟执行
fmt.Println("in a")
}
func b() {
defer un(trace("b"))
fmt.Println("in b")
a()
}
func main() {
b()
}
// 输出:
// entering: b
// in b
// entering: a
// in a
// leaving: a
// leaving: b
defer最主要的价值是:函数执行完毕后,及时释放函数创建的资源(如文件关闭、锁释放)。
defer 与闭包的微妙区别
defer + 闭包(引用外部变量)
func main() {
var a, b int = 1, 2
defer func() {
fmt.Println(sum(a, b)) // 延迟执行,此时 a=3, b=4
}()
a, b = 3, 4
}
// 输出: 7defer + 参数传递(值拷贝)
func main() {
var a, b int = 1, 2
defer fmt.Println(sum(a, b)) // 立即计算参数,a+b=3
a, b = 3, 4
}
// 输出: 3init 函数
init 函数在 main 函数之前自动执行,用于初始化操作。
var a int
func init() {
a = 5
fmt.Println("在 init 中 a 的值为", a)
}
func main() {
a = 10
fmt.Println("在 main 中 a 的值为", a)
}执行顺序:
- 全局变量定义
init函数main函数
跨包调用时的执行顺序:
- 被调用包的全局变量
- 被调用包的
init函数 - 主包的全局变量定义
- 主包的
init函数 - 主包的
main函数
匿名函数
// 方式1:赋给变量,可多次调用
var age = func(a, b int) int {
return a * b
}
fmt.Println(age(12, 5)) // 60
// 方式2:声明后立即执行
func(a, b int) {
fmt.Println(a + b)
}(3, 4) // 7闭包
闭包 = 函数 + 与其相关的引用环境(外部变量)
// 累加器案例
func AddUpper() func(int) int {
var n int = 10
return func(x int) int {
n = n + x // 内部函数引用了外部的 n
return n
}
}
func main() {
f := AddUpper()
fmt.Println(f(1)) // 11
fmt.Println(f(2)) // 13
fmt.Println(f(3)) // 16
}闭包理解
- 内部函数去上一层找变量,符合「外部环境」
- 「引用」是指调用的外部变量的地址,所以函数返回的值是动态变化的
- 每次调用
f()时,内部的n是同一个变量(地址不变,值在变)
值传递与引用传递
Go 中传递本质上都是值传递。
func main() {
name := "xiaoming"
// 值传递:传递 name 的副本
value := valueFunc(name)
fmt.Println("原值:", name) // 不变
fmt.Println("返回值:", value)
}
func valueFunc(s string) string {
s = "zhangsan" // 只修改副本
return s
}即使是指针传递,也是先创建指针的副本。副本指针与原指针指向同一个地址,通过副本指针可以修改目标地址的值:
func pointerFunc(name *string) string {
*name = "lisi" // 通过指针修改原值
return *name
}
func checkPointer(p *string) {
fmt.Printf("指针地址(副本): %p\n", &p) // 与原指针地址不同
}