--- tags: [go, golang, go基础语法, 函数] create time: 2026-06-07 15:00 --- # Go 语言函数 ## 概述 Go 的函数是一等公民:可以赋值给变量、作为参数传递、返回匿名函数(闭包)。本文覆盖多返回值、变参、闭包、匿名函数和值传递机制。 ## 正文 ### 为什么 Go 函数支持多返回值? 在多数语言中,函数只能返回一个值——错误信息需要单独处理(如异常或输出参数)。Go 的选择是:**让错误成为显式的返回值**。 ```go name, age, err := GetUser(42) if err != nil { return err } ``` 这比隐藏的错误处理更透明——编译器强制你处理每一个返回值。 ### 基本定义与调用 ```go func swap(x, y string) (string, string) { return y, x } a, b := swap("Google", "Hello") fmt.Println(a, b) // Hello Google ``` 无返回值时省略返回类型;有返回值时必须写: ```go func max(a, b int) int { if a > b { return a } return b } func greet(name string) { // 无返回值 fmt.Printf("Hello, %s!\n", name) } ``` ### 命名返回值 Go 允许给返回值命名,`return` 可以不写具体值(直接返回): ```go func Split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return // 空白 return,返回当前 x, y 的值 } ``` > [!tip] 💡 命名返回值的用途 > 虽然不常用,但在复杂函数的"提前返回"场景中可以让代码更清晰——读者一眼就知道返回了什么。 ### 变参 变参函数接受零个或多个同类型参数: ```go func Sum(nums ...int) int { total := 0 for _, n := range nums { total += n } return total } Sum(1, 2, 3) // 6 Sum(1, 2, 3, 4, 5) // 15 // 将切片展开传入 nums := []int{1, 2, 3, 4} Sum(nums...) // 10 ``` > [!warning] ⚠️ `...int` 底层是 `[]int` > 在函数内部,`nums` 就是一个 slice。用 `range nums` 遍历时要注意前面提到的闭包陷阱。 ### 函数是一等公民 函数可以赋值给变量、作为参数传递: ```go // 函数变量 getSquareRoot := func(x float64) float64 { return math.Sqrt(x) } fmt.Println(getSquareRoot(9)) // 3 // 函数作为参数(回调) type Calculator func(int, int) int func apply(a, b int, op Calculator) int { return op(a, b) } result := apply(3, 4, func(x, y int) int { return x + y }) // 7 ``` ### 匿名函数与闭包 匿名函数是没有名称的函数,通常内联使用: ```go func() { fmt.Println("我是匿名函数") }() // 末尾 () 立即执行 ``` **闭包 = 匿名函数 + 捕获的外部变量**。闭包会"记住"它创建时的环境变量: ```go func counter() func() int { i := 0 return func() int { i++ return i } } next := counter() fmt.Println(next()) // 1 fmt.Println(next()) // 2 fmt.Println(next()) // 3 next2 := counter() // 全新的计数器 fmt.Println(next2()) // 1 — 独立于 next ``` > [!note] 📝 每次调用 counter() 都创建新的闭包环境 > `next` 和 `next2` 各自维护独立的 `i` 变量,互不影响。这是闭包的核心特性。 ```mermaid graph LR A["counter()"] --> B["i = 0"] B --> C["返回闭包 → next"] C --> D["next() 调用: i→1"] D --> E["next() 调用: i→2"] A --> F["counter() 再次调用"] F --> G["i = 0 新变量"] G --> H["返回闭包 → next2"] H --> I["next2() 调用: i→1"] ``` > [!warning] ⚠️ 闭包捕获的是引用 > 如果外部变量在闭包外被修改,闭包看到的也是修改后的值。配合 `for range` 使用时要特别注意(见「循环」章节)。 ### 值传递:Go 只有值传递 Go 的所有参数传递都是**值传递**——参数被拷贝一份传给函数。但对于 slice、map、chan 这些引用类型,拷贝的是内部的指针,所以修改内容会影响原数据。 ```go func addOne(x int) { x++ // 只修改了副本 } func main() { a := 5 addOne(a) fmt.Println(a) // 5 — 没变! } ``` 如果需要修改原值,传指针: ```go func addOnePtr(x *int) { *x++ } a := 5 addOnePtr(&a) fmt.Println(a) // 6 ✅ ``` ### 关键要点总结 | 特性 | 说明 | |------|------| | 多返回值 | Go 独有,广泛用于错误处理 | | 变参 `...T` | 底层是 `[]T`,可用 `...` 展开 slice | | 闭包 | 匿名函数捕获外部变量,形成独立状态 | | 值传递 | 所有参数都是值拷贝,引用类型的"引用"本质是指针拷贝 |