讲解

函数用 func 关键字定义:func 名字(参数) 返回值 { }。参数类型写在名字后面(和其他很多语言相反),相邻同类型参数可以合并写 a, b int。Go 函数最鲜明的特性是「多返回值」:func divide(a, b float64) (float64, error) 同时返回结果和错误,调用方用两个变量接收——这是 Go 错误处理体系的地基,错误处理章节会全面展开。

可变参数用 ...类型 声明,函数内部它就是一个切片:func sum(nums ...int)。调用时传任意个数的值,或用 切片... 把现有切片展开传入。函数在 Go 里是「一等公民」:可以赋给变量、当参数传、当返回值返回,回调和高阶函数都由此而来。

闭包是「捕获了外层变量的函数」:内层函数引用了外层函数的局部变量,外层函数返回后这些变量仍然活着,跟随闭包继续存在。经典例子是计数器工厂——每次调用工厂得到一个有独立计数的函数。闭包让状态可以优雅地封装在函数里,是 Go 并发编程里 goroutine 捕获上下文变量的基础。

示例

多返回值与 comma ok 风格:

package main

import "fmt"

func divide(a, b float64) (float64, bool) {
	if b == 0 {
		return 0, false
	}
	return a / b, true
}

func main() {
	if q, ok := divide(10, 4); ok {
		fmt.Println("商:", q)
	}
	if _, ok := divide(1, 0); !ok {
		fmt.Println("除数不能为零")
	}
}

可变参数与切片展开:

package main

import "fmt"

func sum(nums ...int) int {
	total := 0
	for _, n := range nums {
		total += n
	}
	return total
}

func main() {
	fmt.Println(sum(1, 2, 3))
	fmt.Println(sum()) // 一个都不传也行

	s := []int{4, 5, 6}
	fmt.Println(sum(s...)) // 切片展开传入
}

闭包:计数器工厂与函数字面量:

package main

import "fmt"

func counter() func() int {
	n := 0 // 被闭包捕获,函数返回后依然存活
	return func() int {
		n++
		return n
	}
}

func main() {
	next := counter()
	fmt.Println(next(), next(), next()) // 1 2 3

	another := counter() // 每个闭包有独立的 n
	fmt.Println(another())

	add := func(a, b int) int { return a + b } // 匿名函数赋给变量
	fmt.Println("3 + 4 =", add(3, 4))
}

常见坑

  • 丢弃错误返回值:q, _ := divide(1, 0) 拿到无意义的 0 还接着用,bug 就此埋下。Go 的多返回值设计就是逼你面对错误,不要用 _ 把 error 吞掉。
  • 可变参数与切片混用:sum(nums) 传切片编译不过,要 sum(nums...);反过来函数内把 nums 当切片用是完全合法的。记清「声明 ...、调用 ...、内部是切片」三个位置。
  • 命名返回值滥用:func f() (result int) 这类命名返回值配合裸 return 看似省事,超过几行的函数里会让人看不清到底返回了什么。短函数可以用,长函数老实写 return 值。
  • 闭包捕获的是变量不是值:循环里起闭包引用循环变量,Go 1.22 前全部捕获同一个变量;1.22 起每次迭代独立。读老代码时注意这个语义分界线。

小结

函数支持多返回值与可变参数,函数值可传递,闭包捕获外层变量实现状态封装。下一节直面很多人最怕的主题:指针。