Golang - 匿名函数

在 Go 中,匿名函数(Anonymous Function),也称为函数字面量,是指在代码中直接定义的、没有函数名的函数。它们是一等公民,意味着可以像任何其他类型(如 intstring)的值一样被使用。

基本语法

func(参数列表) (返回值列表) {
    // 函数体
}

常见用法

a) 直接调用(立即执行函数 IIFE)

定义后立即用 () 调用,通常用于创建一个临时的作用域。

func main() {
    // 定义并立即调用
    func() {
        fmt.Println("这是一个直接调用的匿名函数!")
    }()
}

b) 赋值给变量

可以将匿名函数赋值给一个变量,然后通过这个变量来调用它。

func main() {
    // 将匿名函数赋值给变量 add
    add := func(a, b int) int {
        return a + b
    }

    // 通过变量调用函数
    sum := add(10, 20)
    fmt.Println("Sum:", sum) // 输出: Sum: 30
}

c) 作为参数传递给其他函数(高阶函数)

当一个函数需要另一个函数作为其行为的一部分时,可以传递一个匿名函数。

func calculate(a, b int, operation func(int, int) int) int {
    return operation(a, b)
}

func main() {
    // 传递一个匿名函数作为参数
    result := calculate(10, 5, func(x, y int) int {
        return x * y
    })
    fmt.Println("Result:", result) // 输出: Result: 50
}

d) 作为函数的返回值(闭包)

一个函数可以返回另一个函数。返回的匿名函数可以“记住”并访问其创建时所在作用域的变量,即闭包

// makeAdder 返回一个“加法器”函数
func makeAdder(x int) func(int) int {
    // 返回的匿名函数“记住”了变量 x
    return func(y int) int {
        return x + y
    }
}

func main() {
    add5 := makeAdder(5)  // 创建一个“加5”的函数
    add10 := makeAdder(10) // 创建一个“加10”的函数

    fmt.Println(add5(2))  // 输出: 7
    fmt.Println(add10(2)) // 输出: 12
}

e) 在 Goroutine 中使用

这是 Go 并发编程中的核心模式,可以直接将一个匿名函数交给新的 Goroutine 去执行。

func main() {
    // 在一个新的 Goroutine 中执行匿名函数
    go func() {
        fmt.Println("我在一个新的 Goroutine 中运行!")
    }()

    // 等待一下,不然 main 函数可能在 Goroutine 运行前就退出了
    time.Sleep(100 * time.Millisecond)
}

与C++的区别

从核心概念上讲,Go的匿名函数和C++的lambda函数非常相似。它们都是:

  • 没有名字的函数。
  • 可以被赋值给变量、作为参数传递、作为返回值。
  • 都是闭包,可以捕获其定义时所在作用域的变量。

但是变量捕获机制上它们存在着一个至关重要的区别

在 Go 中,匿名函数总是通过“引用”的方式来捕获外部变量。这意味着,如果在匿名函数内部修改了捕获的变量,外部的原始变量也会被修改

var factor = 2
multiply := func(n int) int {
    factor = 3 // 修改了外部的 factor
    return n * factor
}
fmt.Println(multiply(10)) // 输出: 30
fmt.Println(factor)      // 输出: 3 (原始变量被修改了)

这也是为什么在 for 循环中使用 Goroutine 时,必须通过参数传递循环变量的原因,因为所有 Goroutine 默认会共享同一个按引用捕获的变量。

    • [=]按值捕获 (Capture by Value)。Lambda 内部拥有外部变量的一份副本,修改它不会影响外部。
    • [&]按引用捕获 (Capture by Reference)。与 Go 的行为类似,修改会影响外部。
    • [x, &y]: 精确指定 x 按值捕获,y 按引用捕获。

C++ 的 Lambda 提供了强大的捕获列表 [],让程序员可以精确控制如何捕获外部变量:

int factor = 2;
// 按值捕获 factor
auto multiply = [=](int n) mutable -> int {
    factor = 3; // 修改的是内部的副本
    return n * factor;
};
std::cout << multiply(10) << std::endl; // 输出: 30
std::cout << factor << std::endl;      // 输出: 2 (原始变量未被修改)