第十二回:Go 语言函数

没枪头也能捅死人(函数全解)
唐伯虎
唐伯虎: 霸王枪的精髓不在枪头,而在招式。代码里的招式,就是函数 (Function)。 Go 语言的函数可是这门武学的核心,不仅能多返回值,还能像暗器一样随手丢出(匿名函数),甚至能延时爆发(defer)

1. 📜 招式心法 (函数基础)

函数是 Go 程序的基本代码块。使用 func 关键字定义。

特点:

// 标准招式:计算两数之和
func add(x int, y int) int {
    return x + y
}

// 简写招式:参数类型一样可以合并
func sub(x, y int) int {
    return x - y
}

2. 🎭 分身术 (多返回值)

Go 语言的函数最厉害的地方是:可以返回多个值! 通常用于返回结果和错误信息。

秋香
秋香: 公子,这“还我漂漂拳”打下去,不仅人变美了,还会掉落金币呢!
// 定义招式:还我漂漂拳
// 输入:名字 (string)
// 输出:新样貌 (string), 掉落金币 (int)
func 还我漂漂拳(name string) (string, int) {
    if name == "如花" {
        return "绝世美女", 100
    }
    return name, 0
}

func main() {
    // 接收所有返回值
    beauty, gold := 还我漂漂拳("如花")
    fmt.Println(beauty, gold) 
    
    // 只要美女,不要钱?(使用匿名变量 _ 丢弃)
    beauty2, _ := 还我漂漂拳("如花")
    fmt.Println(beauty2)
}

3. 🏷️ 提前挂号 (命名返回值)

返回值可以像参数一样命名,这样在函数里它们就是局部变量,最后直接 return 就行(裸返回)。

// 计算矩形面积和周长
func rectProps(length, width float64) (area, perimeter float64) {
    // area 和 perimeter 已经被定义为 float64 类型的变量了
    area = length * width
    perimeter = (length + width) * 2
    return // 自动返回 area 和 perimeter,这叫“裸返回”
}

4. 📦 乾坤袋 (变长参数)

秋香
秋香: 华安,我要吃鸡翅,还要蒸鱼,还要红烧肉... 哎呀,我不确定要点几个菜,你的函数能接得住吗?
唐伯虎
唐伯虎: 秋香姐放心!我的 ... 变长参数就是为您准备的。不管您点多少,我都用一个切片兜着!
// 接收任意个 string 类型的菜名
func Order(dishes ...string) {
    fmt.Printf("秋香姐点了 %d 道菜:\n", len(dishes))
    for _, dish := range dishes {
        fmt.Println("🍳 正在做:", dish)
    }
}

func main() {
    Order("烤鸡翅") 
    Order("蒸鱼", "红烧肉", "米饭")
}

5. 👤 蒙面大侠 (匿名函数与闭包)

函数可以没有名字,直接赋值给变量,或者立即执行。这叫匿名函数。如果它还引用了外面的变量,就成了闭包

唐伯虎
唐伯虎: 闭包就像是我随身带的锦囊,里面的东西(变量)一直都在,不会因为离开了原来的地方就消失。
func main() {
    // 1. 匿名函数赋值给变量
    add := func(a, b int) int {
        return a + b
    }
    fmt.Println(add(3, 4))

    // 2. 立即执行函数
    func(name string) {
        fmt.Println("你好," + name)
    }("秋香")

    // 3. 闭包:函数返回函数
    counter := getCounter()
    fmt.Println(counter()) // 1
    fmt.Println(counter()) // 2
    // 变量 i 依然活着!
}

func getCounter() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}

6. ⏱️ 临终遗言 (defer 延迟调用)

defer 关键字让函数在当前函数执行结束前才执行。通常用于关闭文件、解锁等清理工作。

秋香
秋香: 听说你会做饭?厨房要是弄乱了,我可要罚你!
唐伯虎
唐伯虎: 没问题!我用了 defer 打扫厨房()。不管饭做得怎么样,甚至中间炸了锅(panic),我最后一定会打扫的!

注意: 多个 defer 是后进先出(像压子弹一样)。

func Cooking() {
    fmt.Println("🔪 开始切菜")
    defer fmt.Println("🧹 打扫厨房 (这句最后执行)")
    defer fmt.Println("🗑️ 倒垃圾 (这句倒数第二执行)")
    
    fmt.Println("🍳 开始炒菜")
    // 假设这里发生意外 panic,defer 依然会执行
    fmt.Println("🍲 上菜")
}
// 输出顺序:切菜 -> 炒菜 -> 上菜 -> 倒垃圾 -> 打扫厨房

🎯 练功房(闭包陷阱)

看下面的代码,猜猜输出什么?

package main
import "fmt"

func main() {
    var fns []func()
    for i := 0; i < 3; i++ {
        // 注意:闭包捕获的是变量 i 的引用,而不是当时的值!
        fns = append(fns, func() {
            fmt.Println(i)
        })
    }
    
    for _, f := range fns {
        f()
    }
}

任务: 输出结果是 0 1 2 吗?

答案: 3 3 3

解析: 这是一个经典坑!闭包里引用的是变量 i 的地址。循环结束时 i 变成了 3。所有闭包执行时都去读 i,读到的都是 3。
修正: 在循环里写 temp := i,然后闭包用 temp