目录

Go 函数

函数是 Go 程序的基本构建单元。Go 的函数功能丰富,支持多返回值、命名返回值、可变参数、defer 延迟执行、匿名函数和闭包等特性。本篇将全面讲解 Go 函数的各种用法。

https://img.zhaojq.top/20260729161338503.png
闭包概念

函数定义与调用

Go 使用 func 关键字定义函数:

package main

import "fmt"

// 定义一个接受两个参数并返回一个值的函数
func add(a int, b int) int {
    return a + b
}

// 参数类型相同时可以简写
func multiply(a, b int) int {
    return a * b
}

func main() {
    sum := add(3, 5)
    product := multiply(4, 6)

    fmt.Println("3 + 5 =", sum)         // 8
    fmt.Println("4 * 6 =", product)     // 24
}

多返回值

Go 函数可以返回多个值,这是 Go 语言的一大特色:

package main

import "fmt"

// 返回商和余数
func divide(a, b int) (int, int) {
    return a / b, a % b
}

func main() {
    quotient, remainder := divide(17, 5)
    fmt.Printf("17 / 5 = %d 余 %d\n", quotient, remainder)
}

错误处理惯例

Go 通常用最后一个返回值表示操作是否成功:

package main

import (
    "errors"
    "fmt"
)

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数不能为零")
    }
    return a / b, nil
}

func main() {
    result, err := divide(10, 3)
    if err != nil {
        fmt.Println("错误:", err)
        return
    }
    fmt.Printf("10 / 3 = %.2f\n", result)

    // 除零的情况
    _, err = divide(10, 0)
    if err != nil {
        fmt.Println("错误:", err)
    }
}

命名返回值

Go 允许为返回值命名,命名返回值在函数开始时被初始化为零值,可以直接使用 return 返回:

package main

import "fmt"

func divide(a, b int) (quotient int, remainder int) {
    quotient = a / b
    remainder = a % b
    return // 裸 return,自动返回命名返回值
}

func main() {
    q, r := divide(17, 5)
    fmt.Printf("商: %d, 余数: %d\n", q, r)
}

建议:命名返回值适合用于简短函数,可以增强可读性。复杂函数中过度使用命名返回值反而会让代码难以理解。

可变参数

使用 ...Type 语法定义可变参数函数,参数在函数内部表现为切片:

package main

import "fmt"

// sum 接受任意数量的 int 参数
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

func main() {
    fmt.Println(sum())          // 0
    fmt.Println(sum(1))         // 1
    fmt.Println(sum(1, 2, 3))   // 6
    fmt.Println(sum(1, 2, 3, 4, 5)) // 15
}

传递切片给可变参数

使用 ... 展开切片:

package main

import "fmt"

func printNums(nums ...int) {
    for i, n := range nums {
        fmt.Printf("[%d] = %d\n", i, n)
    }
}

func main() {
    values := []int{10, 20, 30, 40}
    printNums(values...) // 展开切片
}

defer 语句

defer 用于延迟函数的执行,直到包含它的函数返回。多个 defer 按照**后进先出(LIFO)**的顺序执行。

基本用法

package main

import "fmt"

func main() {
    fmt.Println("开始")

    defer fmt.Println("defer 1")
    defer fmt.Println("defer 2")
    defer fmt.Println("defer 3")

    fmt.Println("结束")
}
// 输出顺序:
// 开始
// 结束
// defer 3
// defer 2
// defer 1

defer 的典型场景

package main

import (
    "fmt"
    "os"
)

func readFile(filename string) {
    // 打开文件
    f, err := os.Open(filename)
    if err != nil {
        fmt.Println("打开文件失败:", err)
        return
    }
    // 确保函数结束时关闭文件
    defer f.Close()

    // 读取文件内容...
    fmt.Println("文件已打开,正在处理...")
}

func main() {
    readFile("test.txt")
}

defer 的参数在声明时就确定

package main

import "fmt"

func main() {
    x := 10

    defer fmt.Println("defer:", x) // x 的值在 defer 声明时就确定了

    x = 20
    fmt.Println("main:", x)
}
// 输出:
// main: 20
// defer: 10

匿名函数

匿名函数是没有名字的函数,可以直接定义并调用:

package main

import "fmt"

func main() {
    // 定义并立即调用
    result := func(a, b int) int {
        return a + b
    }(3, 5)
    fmt.Println("结果:", result) // 8

    // 赋值给变量
    greet := func(name string) {
        fmt.Println("Hello,", name)
    }
    greet("Go")
}

闭包

闭包是函数与其引用的外部变量的组合。闭包可以"记住"外部变量的状态。

计数器示例

package main

import "fmt"

// newCounter 返回一个闭包函数
func newCounter() func() int {
    count := 0 // 闭包捕获的变量

    return func() int {
        count++
        return count
    }
}

func main() {
    counter := newCounter()

    fmt.Println(counter()) // 1
    fmt.Println(counter()) // 2
    fmt.Println(counter()) // 3

    // 每个闭包实例有独立的 count
    counter2 := newCounter()
    fmt.Println(counter2()) // 1
    fmt.Println(counter2()) // 2
}

闭包的实际应用

package main

import (
    "fmt"
    "strings"
)

// 生成带前缀的日志函数
func makeLogger(prefix string) func(string) {
    return func(msg string) {
        fmt.Printf("[%s] %s\n", prefix, strings.ToUpper(msg))
    }
}

func main() {
    infoLog := makeLogger("INFO")
    errorLog := makeLogger("ERROR")

    infoLog("程序启动")    // [INFO] 程序启动
    errorLog("连接失败")   // [ERROR] 连接失败
    infoLog("处理完成")    // [INFO] 处理完成
}

panic 和 recover 基础

Go 使用 panicrecover 来处理程序中的严重错误(类似其他语言的异常机制)。

panic

panic 会中断正常的控制流,开始执行 defer 函数链:

package main

import "fmt"

func main() {
    fmt.Println("程序开始")

    // 模拟一个不可恢复的错误
    panic("发生了严重错误!")

    fmt.Println("这行不会执行")
}

recover

recover 只能在 defer 函数中使用,用于捕获 panic

package main

import "fmt"

func safeDivide(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("发生 panic: %v", r)
        }
    }()

    if b == 0 {
        panic("除数为零")
    }
    result = a / b
    return
}

func main() {
    r1, e1 := safeDivide(10, 2)
    fmt.Printf("10/2 = %d, err = %v\n", r1, e1)

    r2, e2 := safeDivide(10, 0)
    fmt.Printf("10/0 = %d, err = %v\n", r2, e2)
}

注意panicrecover 应该谨慎使用。对于可预期的错误,推荐使用 error 返回值。panic 适用于真正的"不可能发生"的情况。

总结

本篇全面介绍了 Go 函数的各种特性。Go 函数支持多返回值,配合 error 类型实现优雅的错误处理;命名返回值增强了代码可读性;可变参数让函数更加灵活;defer 语句确保了资源的可靠释放;匿名函数和闭包为函数式编程提供了支持,计数器模式是闭包的经典应用。最后,panicrecover 提供了处理严重错误的机制,但日常开发中应优先使用 error 返回值。