Go匿名函数写作func(参数)返回类型{函数体},需完整声明;闭包是匿名函数引用外层局部变量并逃逸出作用域时形成的行为结果,捕获变量引用而非值。

Go 里匿名函数怎么写,直接上手就用
Go 的匿名函数就是没名字的函数字面量,写完就能调用或赋值,不用先声明再用。func 关键字开头、参数列表、返回类型、函数体,一个都不能少——少括号、少 return 类型、漏大括号都会编译报错。
常见错误现象:missing function body(漏了花括号)、undefined: xxx(想在定义前调用)、non-name xxx on left side of :=(用 := 给函数字面量赋值时左边不是合法标识符)。
- 必须显式写出返回类型,哪怕没有返回值也要写
func() {},不能省成func() {}以外的简写 - 定义后要立刻调用,得加括号:
func() { fmt.Println("hi") }();如果只是赋值,就别加括号:f := func() { fmt.Println("hi") } - 参数名不能省,
func(int) {}合法,但func(int) {}里没写形参名,调用时无法传参——得写成func(x int) {}
闭包是怎么形成的,和匿名函数什么关系
闭包不是语法结构,是行为结果:当匿名函数引用了它外层函数的局部变量,并且这个匿名函数逃逸出外层作用域(比如作为返回值、存进 map、传给 goroutine),那它就“捕获”了那些变量,形成闭包。
关键点在于“逃逸”+“引用”。只定义不逃逸,不算真正闭包;只引用全局变量,也不算闭包(没捕获局部环境)。
立即学习“go语言免费学习笔记(深入)”;
- 闭包捕获的是变量的引用,不是值快照。多个匿名函数共享同一份变量,改一个,其他都看到变化
- 循环中创建匿名函数容易踩坑:
for i := 0; i 很可能全打出 <code>3——因为所有函数都闭包了同一个i变量。修复方式是传参:go func(i int) { fmt.Print(i) }(i) - 闭包会延长外层变量生命周期,可能导致内存无法及时回收,尤其在长生命周期对象(如 struct 字段)里存匿名函数时要注意
什么时候该用匿名函数,什么时候该写命名函数
匿名函数适合一次性逻辑、轻量回调、延迟执行、启动 goroutine 这类场景;一旦逻辑超过三四行、需要复用、或者要测试/调试,就该提成命名函数。
使用场景对比:
- 做
defer或go启动时:用匿名函数最自然,比如defer func() { mu.Unlock() }() - 作为
sort.Slice的比较函数:sort.Slice(data, func(i, j int) bool { return data[i].Time.Before(data[j].Time) }) - 但如果你发现匿名函数里开始写 if/else 嵌套、调用多个外部函数、或者想加注释说明意图——这时候它已经不是“匿名”能掩盖复杂度的了,该拆
- 命名函数能被单元测试覆盖,匿名函数只能靠包裹它的上下文来测,可测性差一截
闭包里的变量修改会影响外层吗
会,而且是实时影响。Go 闭包捕获的是变量的地址,不是拷贝值。
这点和 JavaScript 类似,但和 Rust(默认移动所有权)或 Python(对不可变对象表现不同)有本质区别。
- 如果闭包里对 int、string、struct 等类型做了赋值(
i = 42),改的就是外层那个变量本身 - 但如果操作的是指针、slice、map、channel,那即使不显式取地址,它们本来就是引用类型,闭包内外看到的是同一底层数组或哈希表
- 最容易忽略的是:goroutine 中的闭包如果读写共享变量,又没加锁,就是典型的竞态条件——
go run -race会直接报出来
复杂点在于,你没法从函数签名看出它是不是闭包,也没法静态判断它捕获了哪些变量。只有读代码、跑竞态检测、看内存 profile 才能确认影响范围。


















