强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Go 语言完全指南 / 07 - 函数:多返回值、命名返回、可变参数、闭包、init

07 - 函数

7.1 函数声明

package main

import "fmt"

// 基本函数
func add(a int, b int) int {
    return a + b
}

// 相同类型参数可合并
func add2(a, b int) int {
    return a + b
}

// 无返回值
func greet(name string) {
    fmt.Printf("你好, %s!\n", name)
}

// 无参数无返回值
func main() {
    fmt.Println(add(3, 5))
    greet("Go")
}

7.2 多返回值

Go 函数可以返回多个值,这是 Go 最重要的特性之一。

package main

import (
    "errors"
    "fmt"
    "math"
)

// 返回商和余数
func divide(a, b int) (int, int, error) {
    if b == 0 {
        return 0, 0, errors.New("division by zero")
    }
    return a / b, a % b, nil
}

// 返回值和错误(Go 最常见的模式)
func sqrt(x float64) (float64, error) {
    if x < 0 {
        return 0, errors.New("square root of negative number")
    }
    return math.Sqrt(x), nil
}

func main() {
    // 接收所有返回值
    quotient, remainder, err := divide(17, 5)
    if err != nil {
        fmt.Println("错误:", err)
        return
    }
    fmt.Printf("17 / 5 = %d 余 %d\n", quotient, remainder)

    // 忽略某些返回值
    result, _ := sqrt(16)
    fmt.Println("√16 =", result)

    // 忽略所有返回值
    divide(10, 2) // 仅调用,不接收返回值
}

7.3 命名返回值

// 命名返回值
func rectangle(width, height float64) (area, perimeter float64) {
    area = width * height
    perimeter = 2 * (width + height)
    return // 裸返回(naked return),返回命名的值
}

// 等价于
func rectangle2(width, height float64) (float64, float64) {
    area := width * height
    perimeter := 2 * (width + height)
    return area, perimeter
}

func main() {
    a, p := rectangle(10, 5)
    fmt.Printf("面积: %.1f, 周长: %.1f\n", a, p)
}

⚠️ 注意:命名返回值有助于文档化,但裸返回在长函数中降低可读性。短函数可以使用,长函数建议显式返回。

7.4 可变参数(Variadic)

package main

import "fmt"

// 可变参数函数
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// 混合固定参数和可变参数
func log(level string, messages ...string) {
    for _, msg := range messages {
        fmt.Printf("[%s] %s\n", level, msg)
    }
}

func main() {
    // 直接传递多个参数
    fmt.Println(sum(1, 2, 3, 4, 5)) // 15

    // 传递切片展开
    nums := []int{10, 20, 30}
    fmt.Println(sum(nums...)) // 60

    // 调用可变参数
    log("INFO", "服务器启动", "监听端口 8080")
    log("ERROR", "连接失败", "重试中", "超时")
}

7.5 函数作为值(函数类型)

package main

import "fmt"

// 函数类型
type MathFunc func(int, int) int

func add(a, b int) int { return a + b }
func mul(a, b int) int { return a * b }

// 函数作为参数
func apply(f MathFunc, a, b int) int {
    return f(a, b)
}

// 函数作为返回值
func getOperator(op string) MathFunc {
    switch op {
    case "+":
        return add
    case "*":
        return mul
    default:
        return nil
    }
}

// 函数切片
func main() {
    // 函数作为变量
    var op MathFunc = add
    fmt.Println(op(3, 5)) // 8

    // 函数作为参数
    fmt.Println(apply(add, 3, 5))  // 8
    fmt.Println(apply(mul, 3, 5))  // 15

    // 函数作为返回值
    fn := getOperator("*")
    fmt.Println(fn(4, 6)) // 24

    // 匿名函数
    double := func(x int) int { return x * 2 }
    fmt.Println(double(10)) // 20

    // 立即调用函数表达式 (IIFE)
    result := func(a, b int) int {
        return a * b
    }(3, 4)
    fmt.Println(result) // 12
}

7.6 闭包(Closure)

闭包是引用了外部变量的函数。闭包可以读取和修改其捕获的变量。

package main

import "fmt"

// 计数器工厂
func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

// 累加器
func accumulator(initial int) func(int) int {
    sum := initial
    return func(x int) int {
        sum += x
        return sum
    }
}

// 斐波那契生成器
func fibonacci() func() int {
    a, b := 0, 1
    return func() int {
        result := a
        a, b = b, a+b
        return result
    }
}

func main() {
    // 独立的计数器
    c1 := counter()
    c2 := counter()
    fmt.Println(c1(), c1(), c1()) // 1, 2, 3
    fmt.Println(c2(), c2())       // 1, 2(独立的)

    // 累加器
    acc := accumulator(100)
    fmt.Println(acc(10))  // 110
    fmt.Println(acc(20))  // 130
    fmt.Println(acc(30))  // 160

    // 斐波那契
    fib := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Printf("%d ", fib())
    }
    fmt.Println() // 0 1 1 2 3 5 8 13 21 34
}

闭包的陷阱

func main() {
    // 陷阱:闭包捕获变量的引用
    var funcs []func()
    for i := 0; i < 5; i++ {
        // Go 1.22+ 中 i 每次迭代是新变量,没问题
        // Go 1.21- 中需要手动复制
        v := i // 复制一份
        funcs = append(funcs, func() {
            fmt.Println(v)
        })
    }
    for _, f := range funcs {
        f()
    }
}

7.7 init 函数

init 函数在 main 之前自动执行,用于初始化工作。

package main

import "fmt"

var config map[string]string

func init() {
    fmt.Println("init 1: 加载配置")
    config = map[string]string{
        "db_host": "localhost",
        "db_port": "5432",
    }
}

func init() {
    fmt.Println("init 2: 验证配置")
    if config["db_host"] == "" {
        panic("db_host is required")
    }
}

func main() {
    fmt.Println("main: 程序启动")
    fmt.Printf("配置: %+v\n", config)
}

// 输出顺序:
// init 1: 加载配置
// init 2: 验证配置
// main: 程序启动

init 的执行顺序

1. 包级变量初始化(按声明顺序,依赖优先)
2. 包的 init 函数(按出现顺序)
3. main 包的 init 函数
4. main 函数
// a/a.go
package a

import "fmt"

var A = initA()

func initA() string {
    fmt.Println("a: 变量初始化")
    return "A"
}

func init() {
    fmt.Println("a: init 函数")
}

// main.go
package main

import (
    "fmt"
    _ "myproject/a" // 导入但不使用
)

func main() {
    fmt.Println("main")
}

// 输出:
// a: 变量初始化
// a: init 函数
// main

init 的实际用途

// 数据库驱动注册
import _ "github.com/go-sql-driver/mysql"

// 正则表达式预编译
var emailRegex *regexp.Regexp

func init() {
    emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
}

// 验证必需的环境变量
func init() {
    required := []string{"DATABASE_URL", "API_KEY"}
    for _, env := range required {
        if os.Getenv(env) == "" {
            log.Fatalf("环境变量 %s 未设置", env)
        }
    }
}

⚠️ 注意:尽量减少 init 的使用。显式初始化比隐式更清晰、更易测试。

7.8 递归

package main

import "fmt"

// 阶乘
func factorial(n int) int {
    if n <= 1 {
        return 1
    }
    return n * factorial(n-1)
}

// 斐波那契
func fib(n int) int {
    if n <= 1 {
        return n
    }
    return fib(n-1) + fib(n-2)
}

// 尾递归优化(Go 不保证 TCO,但可以用累加器模拟)
func factorialTail(n, acc int) int {
    if n <= 1 {
        return acc
    }
    return factorialTail(n-1, n*acc)
}

// 二叉树遍历
type TreeNode struct {
    Val   int
    Left  *TreeNode
    Right *TreeNode
}

func inorder(root *TreeNode) []int {
    if root == nil {
        return nil
    }
    var result []int
    result = append(result, inorder(root.Left)...)
    result = append(result, root.Val)
    result = append(result, inorder(root.Right)...)
    return result
}

func main() {
    fmt.Println("5! =", factorial(5))          // 120
    fmt.Println("fib(10) =", fib(10))           // 55
    fmt.Println("5! (tail) =", factorialTail(5, 1)) // 120

    // 构建二叉树
    root := &TreeNode{Val: 4,
        Left: &TreeNode{Val: 2,
            Left:  &TreeNode{Val: 1},
            Right: &TreeNode{Val: 3},
        },
        Right: &TreeNode{Val: 6,
            Left:  &TreeNode{Val: 5},
            Right: &TreeNode{Val: 7},
        },
    }
    fmt.Println("中序遍历:", inorder(root)) // [1 2 3 4 5 6 7]
}

⚠️ 注意:Go 不保证尾递归优化。深递归可能导致栈溢出。大深度时考虑改用迭代。

7.9 方法(Method)

package main

import (
    "fmt"
    "math"
)

type Point struct {
    X, Y float64
}

// 值接收者方法
func (p Point) Distance(q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}

// 指针接收者方法(可修改接收者)
func (p *Point) Translate(dx, dy float64) {
    p.X += dx
    p.Y += dy
}

// 值接收者(不影响原值)
func (p Point) String() string {
    return fmt.Sprintf("(%.1f, %.1f)", p.X, p.Y)
}

func main() {
    p1 := Point{0, 0}
    p2 := Point{3, 4}
    fmt.Printf("距离: %.1f\n", p1.Distance(p2)) // 5.0

    p1.Translate(10, 20)
    fmt.Println("平移后:", p1) // (10.0, 20.0)
}
接收者类型可调用方法类型使用场景
值 (T)值方法和指针方法不修改接收者
指针 (*T)值方法和指针方法需要修改接收者、大结构体避免拷贝

7.10 defer 与函数配合

func readFile(path string) ([]byte, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer f.Close() // 确保资源释放

    return io.ReadAll(f)
}

// 多 defer 常见模式
func transaction(db *sql.DB) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    defer tx.Rollback() // 如果 Commit 了,Rollback 是空操作

    // 执行操作...
    if err := tx.Exec("INSERT ..."); err != nil {
        return err
    }

    return tx.Commit() // Commit 成功后,Rollback 不再执行
}

🏢 业务场景

  1. 错误处理模式:函数返回 (result, error) 是 Go 的标准错误处理模式
  2. 中间件链:函数作为参数/返回值,实现 HTTP 中间件
  3. 策略模式:用函数类型实现算法策略选择
  4. 连接池初始化:init 函数注册数据库驱动
  5. 事件回调:闭包作为回调函数捕获上下文

📖 扩展阅读