05 函数
本节目标:掌握函数的定义与调用、多返回值(Go 特色)、defer 延迟执行。
一、函数的基本定义
func 函数名(参数名 参数类型) 返回值类型 {
// 函数体
return 返回值
}
例子:
package main
import "fmt"
// 计算两数之和
func add(a int, b int) int {
return a + b
}
func main() {
result := add(3, 5)
fmt.Println(result) // 8
}
小技巧:连续多个参数类型相同时,可以只写一次类型:
func add(a, b int) int { // 等价于 (a int, b int)
return a + b
}
没有返回值就不写返回值类型:
func sayHello(name string) {
fmt.Println("你好,", name)
}
二、多返回值:Go 的招牌特性
Go 函数可以返回多个值,这在很多语言里做不到:
// 同时返回商和余数
func divide(a, b int) (int, int) {
return a / b, a % b
}
func main() {
quotient, remainder := divide(17, 5)
fmt.Println(quotient, remainder) // 3 2
}
多返回值最重要的应用:返回错误
Go 的惯例是最后一个返回值放 error,调用方检查它:
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为零")
}
return a / b, nil // nil 表示"没有错误"
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("出错了:", err)
return
}
fmt.Println("结果:", result)
}
if err != nil 是 Go 代码里出现频率最高的三个词,第四章会深入讲错误处理,现在先熟悉这个模式。
不需要的返回值用 _ 丢弃
quotient, _ := divide(17, 5) // 只要商,不要余数
三、命名返回值(了解即可)
返回值可以起名字,函数内可以直接当变量用,return 可以不带参数:
func divide(a, b int) (q, r int) {
q = a / b
r = a % b
return // 自动返回 q 和 r
}
短函数里偶尔见到,新手写代码时建议还是用普通返回值,更直观。
四、可变参数
参数类型前加 ...,表示可以传任意个:
func sum(nums ...int) int {
total := 0
for _, n := range nums { // nums 在函数内就是一个切片
total += n
}
return total
}
func main() {
fmt.Println(sum(1, 2)) // 3
fmt.Println(sum(1, 2, 3, 4)) // 10
s := []int{1, 2, 3}
fmt.Println(sum(s...)) // 切片展开传入:6
}
你早就用过它了——fmt.Println 就是可变参数函数。
五、函数是"一等公民"
函数可以赋值给变量、当参数传递、当返回值:
// 把函数赋值给变量
double := func(x int) int {
return x * 2
}
fmt.Println(double(5)) // 10
// 函数作为参数
func apply(nums []int, f func(int) int) []int {
result := make([]int, 0, len(nums))
for _, n := range nums {
result = append(result, f(n))
}
return result
}
fmt.Println(apply([]int{1, 2, 3}, double)) // [2 4 6]
上面 func(x int) int { ... } 这种没有名字的函数叫匿名函数(闭包)。新手阶段能看懂即可,用多了自然就熟。
六、defer:延迟执行
defer 后面的语句会推迟到函数即将返回时才执行:
func main() {
defer fmt.Println("我最后执行")
fmt.Println("我先执行")
}
// 输出:
// 我先执行
// 我最后执行
defer 有什么用?——确保资源被释放
最典型场景:打开的文件一定要关闭。用 defer 把"打开"和"关闭"写在一起,永远不会忘:
func readFile() error {
f, err := os.Open("data.txt")
if err != nil {
return err
}
defer f.Close() // 打开成功后立刻写 defer,函数无论从哪里 return 都会关闭文件
// ...放心地读文件,不用惦记关闭的事...
return nil
}
多个 defer:后进先出
func main() {
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
}
// 输出:3 2 1(像叠盘子,后放的先拿)
defer 的参数在声明时就确定了
func main() {
x := 1
defer fmt.Println("defer 看到的 x =", x) // 此刻 x=1 被"拍照"记下
x = 100
fmt.Println("现在 x =", x)
}
// 输出:
// 现在 x = 100
// defer 看到的 x = 1 ← 不是 100!
七、作用域小知识
- 函数内声明的变量只在函数内有效
{}块内声明的变量只在块内有效- 内层可以访问外层变量;同名时内层遮蔽外层(这是常见 bug 来源,注意别在 if 块里用
:=意外新建了同名变量)
新手常见坑
- 函数返回多个值时只接收一个 → 编译报错,要么全接收,要么用
_占位 - 忽略 error 返回值:语法上允许
result, _ := ...,但请养成检查 err 的习惯 - Go 不支持函数重载:同一个包里不能有两个同名函数(哪怕参数不同)
- Go 不支持默认参数:没有
func f(x int = 10)这种写法
练习
- 写函数
max(a, b int) int返回较大值。 - 写函数
minMax(nums []int) (int, int)同时返回切片中的最小值和最大值。 - 写函数
safeDivide(a, b float64) (float64, error),除数为零时返回错误,并在 main 里正确处理这个错误。 - 猜测下面代码的输出,再运行验证:
func main() {for i := 0; i < 3; i++ {defer fmt.Println(i)}}
🎉 第二章完成!你已经掌握了 Go 的核心语法,能写出有逻辑的程序了。
下一章学习真正实用的数据结构:第三章 01 数组与切片