Go 程序员为什么更喜欢把函数值叫做闭包

夏群林 2025.9.17 原创

最近痴迷于 Go。

在编程语言的世界里,“函数作为值”的概念并不新鲜:C 有函数指针,C# 有 delegate(委托)和 lambda 表达式,Go 则有函数值(function value)。

有趣的是,Go 程序员更习惯把“函数值”直接称为“闭包”(closures)。这并非命名上的随意,而是源于程序员的洞察: Go 对“函数作为值”的设计,本质上与闭包的核心特性深度绑定,甚至可以说,Go 的函数值就是闭包的具象化实现。

要理解这一点,我们不妨从其他语言的类似概念入手,看看Go的设计究竟特殊在哪里。

一、C 的函数指针:只有代码,没有状态

C语言是最早支持“函数作为值”的语言之一,其载体是“函数指针”。函数指针本质上是一个指向函数代码入口地址的指针,它能让函数像变量一样被传递或赋值。例如:

#include <stdio.h>

// 定义一个函数
int add(int a, int b) {
return a + b;
} // 函数指针作为参数
void calculate(int (*func)(int, int), int a, int b) {
printf("结果: %d\n", func(a, b));
} int main() {
// 函数指针指向add函数
int (*func_ptr)(int, int) = add;
calculate(func_ptr, 2, 3); // 输出:结果: 5
return 0;
}

C 的函数指针只包含函数的代码地址,不携带任何状态。“状态”指函数执行时依赖的外部变量上下文。C 函数要访问外部变量,只能通过全局变量(或参数传递),而函数指针本身无法记住这些变量的值。

例如,若想实现一个带偏移量的加法器,C 的函数指针做不到记住偏移量:

// 尝试实现带偏移量的加法器(无法通过函数指针记住offset)
int makeAdder(int offset) {
// 错误:C不允许嵌套函数,更无法捕获外部变量
int addWithOffset(int x) {
return x + offset;
}
return addWithOffset; // 编译失败
}

因此,C 的函数指针只是代码的引用,与闭包毫无关系,因为没有捕获状态的能力。

二、C# 的 delegate 与 lambda:闭包是可选特性

C# 的 delegate(委托)比 C 的函数指针更灵活:它可以封装一个方法,还能通过 lambda 表达式创建匿名函数。更重要的是,C# 的lambda 可以捕获外部变量,形成闭包。

例如,用 C# 实现“带状态的加法器”:

using System;

class Program {
static Func<int, int> MakeAdder(int offset) {
// lambda表达式捕获外部变量offset
return x => x + offset;
} static void Main() {
Func<int, int> adder = MakeAdder(10);
Console.WriteLine(adder(5)); // 输出:15(记住了offset=10)
}
}

这里的 lambda 表达式x => x + offset就是一个闭包——它捕获了外部变量offset,即使MakeAdder执行结束,offset仍能被adder访问。

但 C# 中委托与闭包是包含关系而非等同关系:

  • 委托是一种类型,它可以指向任何匹配签名的方法(包括普通函数、实例方法、lambda);
  • 只有当 lambda(或匿名方法)捕获了外部变量时,它才是闭包。如果 lambda 不捕获变量(如x => x * 2),它本质上和普通函数指针差异不大。

也就是说,在 C# 中,闭包是委托的一种特殊情况,而非委托的全部。

三、Go 的函数值:天生就是闭包

Go 的函数值(function value)指的是“函数作为一种值”,可以被赋值给变量、作为参数传递、作为返回值返回。但与 C 的函数指针、C# 的委托不同,Go 的函数值从设计上就与闭包深度绑定:它不仅包含函数的代码,还天然携带对外部变量的引用(如果有)。

1. 函数值必然捕获状态(如果需要)

在Go中,任何函数(包括匿名函数)只要引用了外部变量,就会自动形成闭包——编译器会确保这些变量的生命周期与函数值绑定。例如:

package main

import "fmt"

// 返回一个函数值(闭包)
func makeAdder(offset int) func(int) int {
// 匿名函数引用了外部变量offset
return func(x int) {
return x + offset // 捕获offset
}
} func main() {
adder := makeAdder(10)
fmt.Println(adder(5)) // 输出:15(记住了offset=10)
}

这个例子中,makeAdder返回的匿名函数是一个函数值,它同时也是闭包:它捕获了offset变量,即使makeAdder执行完毕,offset仍能被adder访问和修改。

更关键的是:即使函数值不引用外部变量,Go 的实现逻辑也与闭包一致。它的底层结构始终包含“代码指针”和“环境指针”(即使环境为空),这与 C# 中非闭包 lambda 的实现不同。

2. 函数值是引用类型,状态可共享

Go 的函数值是引用类型:当你将函数值赋值给另一个变量时,复制的是对“代码+状态”的引用,而非状态本身。这意味着多个函数值变量可以共享同一份被捕获的状态:

func main() {
f1 := makeAdder(10)
f2 := f1 // f2与f1引用同一个闭包 fmt.Println(f1(5)) // 15
fmt.Println(f2(3)) // 13(共享offset=10)
}

这种特性完全符合闭包“代码与状态绑定”的核心定义,而 C 的函数指针(无状态)、C#的非闭包委托(状态独立)都不具备这种天然的状态共享能力。

3. 不可比较性:闭包状态的必然结果

Go明确规定:函数值不能比较(除了与nil比较)。这正是因为函数值是闭包——它的唯一性不仅取决于代码,还取决于被捕获的状态。即使两个函数值由同一函数生成,只要捕获的状态不同,它们就不应该被视为相等:

func main() {
f1 := makeAdder(10)
f2 := makeAdder(10)
// if f1 == f2 { ... } // 编译错误:函数值不能比较
}

f1f2虽然代码相同,且初始offset都是10,但它们捕获的是两个独立的offset变量(状态不同),因此比较毫无意义。这种设计进一步印证了:Go 的函数值本质是闭包,状态是其不可分割的一部分。

为什么 Go 程序员更爱说“闭包”?

从上面的对比可以看出:

  • C 的函数指针:只有代码,无状态,与闭包无关;
  • C# 的委托:闭包是可选特性,多数时候只是函数的容器;
  • Go 的函数值:从实现到特性,完全符合闭包“代码+状态”的定义,闭包是其本质,而非附加特性。

对 Go 程序员来说,“函数值”这个术语描述的是“函数作为值的形态”,而“闭包”描述的是其“代码与状态绑定的本质”。当他们说“闭包”时,不仅指“函数可以作为值”,更强调其捕获状态、共享环境的核心能力。这正是 Go 函数值最强大、最常用的特性。

这种命名习惯,本质上是对 Go 设计简洁性的呼应:既然函数值的实现和行为都与闭包完全一致,何必两个术语?直接叫闭包,既精准又直观。

结语

Go 的“函数值”与“闭包”的等同称呼,大概不是语言设计者的刻意为之,而是其设计逻辑的自然结果。当函数作为值时,必然要携带它所依赖的状态,否则失去灵活性。Go 使用闭包(closures)技术实现函数值,代码与状态共生,也让闭包成为描述 Go 函数值最贴切的词汇。

这也正是 Go 的魅力所在:用最简单的设计,实现最本质的功能。

Go 程序员为什么更喜欢把函数值叫做闭包的更多相关文章

  1. 不出意外,排名第一的还是它,程序员为什么都喜欢用Chrome?

    程序员为什么喜欢使用Chrome? 其实不单单是程序员喜欢使用Chrome,现在大多数的小伙伴都使用Chrome. 我们可以看到Netmarketshare发布了2020年7月的操作系统与浏览器市场份 ...

  2. 为什么程序员都不喜欢使用switch而使用if来做条件跳转

    请用5秒钟的时间查看下面的代码是否存在bug.   OK,熟练的程序猿应该已经发现Bug所在了,在第8行和第10行下面我没有添加关键字break; 这就导致这段代码的行为逻辑与我的设计初衷不符了. 缺 ...

  3. 为什么程序员都不喜欢使用switch,而是大量的 if……else if ?

    作者:熊爸爸 原文:http://3g.163.com/tech/article/E02RDE6C0511SDDL.html 请用5秒钟的时间查看下面的代码是否存在bug. OK,熟练的程序猿应该已经 ...

  4. 不是吧,阿sir,2020年程序员要不好过?

    自从网传程序员到了35岁之后必须要转行,现在又有人传言:“疫情之下,程序员今年要过苦日子了,降薪裁员是大趋势.” 不是,我就不明白了,你们怎么就看不得程序员好呢?天天巴望着程序员降薪.转行.裁员…   ...

  5. Java程序员岗位

    Java程序员岗位面试题有哪些?   1.面向对象的特征有哪些方面(1)抽象:抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面.抽象并不打算了解全部问题,而只是选择 ...

  6. 程序员的成长与规划 | 送签名书啦 | StuQ专访foruok

    StuQ(InfoQ的朋友)对我做了一次专访,下面是原文. 福利:送一本签名版<你好哇,程序员>,参与方式在文末.

  7. 做10年Windows程序员与做10年Linux程序员的区别

    如果一个程序员从来没有在linux,unix下开发过程序,一直在windows下面开发程序, 同样是工作10年, 大部分情况下与在linux,unix下面开发10年的程序员水平会差别很大.我写这篇文章 ...

  8. 程序员谈学习:我为什么要学习Linux?

    http://kb.cnblogs.com/page/196876/ 好长时间没好好写点东西了,前段时间由于项目的需要出差了一个多月,期间各种加班,每天晚上加班到十点,回到宾馆实现是没什么精力再写博客 ...

  9. 做10年Windows程序员与做10年Linux程序员的区别(附无数评论)(开源软件相当于熟读唐诗三百首,不会作诗也会吟)

    如果一个程序员从来没有在linux,unix下开发过程序,一直在windows下面开发程序, 同样是工作10年, 大部分情况下与在linux,unix下面开发10年的程序员水平会差别很大.我写这篇文章 ...

  10. 面试挂了阿里却拿到网易offer,一个三年Java程序员的面试总结!

    前言 15年毕业到现在有三年多了,最近去面试了阿里集团(菜鸟网络,蚂蚁金服),网易,滴滴,点我达,最终收到点我达,网易offer,蚂蚁金服二面挂掉,菜鸟网络一个月了还在流程中... 最终有幸去了网易. ...

随机推荐

  1. C# 去掉字符串中的html 标签,保留指定的标签和属性

    /// <summary> /// 使用示例 /// </summary> public static void HtmlRemove() { string requestBo ...

  2. C# 用Linq或Lambda查询DataGridView行中的数据是否包含(各种操作)

    http://blog.csdn.net/xht555/article/details/38685845 https://www.cnblogs.com/wuchao/archive/2012/12/ ...

  3. .net一般应用处理程序

    .net一般应用处理程序 public void ProcessRequest (HttpContext context) { context.Response.ContentType = " ...

  4. 前端开发系列121-进阶篇之defineProperty

    本文介绍`Object.defineProperty()`方法,并基于此简单讨论数据劫持的实现方案. defineProperty Object.getOwnPropertyDescriptor(ta ...

  5. matlab 求解高阶方程

    简介 van der Pol 方程 code dy = @(t,y)[y(2); 1000 * (1-y(1)^2)*y(2)-y(1)]; % 定义匿名函数 [t,y]= ode15s(dy,[0 ...

  6. 一文说清楚ETL与Kafka如何实现集成

    ETL与Kafka为何需要集成? 随着企业对实时流数据的处理要求越来越高,很多企业都把实时流数(日志.实时CDC采集数据.设备数据-)先推入到kafka中,再通过ETL对kafka中的数据进行消费通过 ...

  7. USB.org + USB 3.0 Type-C + PD(Power Delivery)240W

    www.usb.org: USB.org Document Library USB Charger (USB Power Delivery) | USB-IF Type-C USB Type-C Ca ...

  8. POLIR-Society-Organization-Psychology-Persuasion: The ELM(Elaboration Likelihood Model) of Persuasion Explained

    https://www.verywellmind.com/the-elaboration-likelihood-model-of-persuasion-7724707 Theories > So ...

  9. FCC(Federal Communications Commission)授权许可及其FCC ID查询和订阅有兴趣公司的FCC批文

    FCC(Federal Communications Commission 45 L Street NE. Link . Phone: 1-888-225-5322) 美国联邦通讯委员会 An FCC ...

  10. SciTech-Mathmatics-Physics-Particle+Movement-Election-The Maxwell Equations-Wave-Particle Duality. 电场(Election)•磁场(磁通量)•光(Photon) + Hertz's Proof。

    The Maxwell Equations: 电.磁.光 Static Electric Field Static Magnetic Field Changing Electric Field Cha ...