199 lines
4.4 KiB
Markdown
199 lines
4.4 KiB
Markdown
---
|
|
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 |
|
|
| 闭包 | 匿名函数捕获外部变量,形成独立状态 |
|
|
| 值传递 | 所有参数都是值拷贝,引用类型的"引用"本质是指针拷贝 |
|