函数:Go 代码的积木,以及它的七种形态
函数:Go 代码的积木,以及它的七种形态
这一章讲 Go 的函数能玩出什么花样。 别的语言里函数只是「起个名字的一段代码」, Go 里函数还是一种值——能赋给变量、能当参数、能当返回值。 读完你会拿到七个具体的东西:多返回值、命名返回值、裸返回、函数类型、 匿名函数、闭包、defer、变参——以及一个主线对比:传值和传引用,Go 默认站哪边。
1. 顶层全景:函数在 Go 里是什么
书里给的定义:函数是一组完成特定任务、产出结果的语句;用函数是为了复用—— 写好一次,换着输入反复用1。
Go 程序的启动方式比书里任何一节都重要,先钉死:
程序启动
→ 运行 main 包里的 init() 函数(有的话,只跑一次)
→ 运行 main 包里的 main() 函数(入口,无参数、无返回值)
→ main 返回,程序结束书里给的输出顺序是:initialization of main function 在前,
Program execution begins 在后2——init 是 main 的「开场白」,
适合做全程只需一次的初始化(打开连接、装配置)。
声明一个函数,第一行叫签名(signature),包含四样:
func 关键字、函数名、参数列表(可以没有)、返回值列表(可以没有),
然后是一对花括号包住函数体3:
func sum(a int, b int) int {
sum_value := a + b
return sum_value
}同类型的相邻参数可以合并写:func sum(a, b int)——类型写在最后一个参数后面,
前面的共享它4。
2. 多返回值:Go 最不像 C 的地方
一个 Go 函数可以同时返回好几个值,每个都有自己的类型, 返回值列表用括号 + 逗号5:
func sum(a, b int, c string) (int, string) {
sum_value := a + b
return sum_value, c // 两个值一起返回
}调用处要几个接几个:result, str := sum(a, b, "Hello Go") → 120 Hello Go6。
这个设计不是炫技,它是 Go 错误处理的物理基础—— 函数把正常结果和 error 并排返回,调用方永远见得到错误(第 17 章展开)。 在这里先记住形状:Go 的函数「最后一个返回值经常是 error」, 就是从多返回值这个能力长出来的惯例。
只想要一部分?空白标识符第三次登场
多返回值里有不想要的,用 _ 接住扔掉:result, _ := sum(a, b, "Hello Go")7。
(不让扔?Go 不许有没用的变量,这个洁癖前面已经见过两次了。)
3. 主线例子:传值 vs 传引用,一对 100 和 200 的旅程
这是本章的主线例子,原书用 a = 100、b = 200 把两种方式各跑了一遍,
我们把两条线并排走8:
传值(call by value):call_by_value(a, b)
函数收到的是 a、b 的复制品( Copies )
函数里 x 改成 80,y 改成 170 ← 只动了副本
函数返回后:a 还是 100,b 还是 200 ← 外面的原值纹丝不动
传引用(call by reference):call_by_reference(&a, &b)
函数收到的是 a、b 的地址(门牌号)
函数里顺着地址把内存里的值改成 80、170
函数返回后:a 变成 80,b 变成 170 ← 外面的原值被改了图说:默认传值——函数怎么折腾都伤不到外面;传引用——函数直接进屋改东西。
Go 的立场写在默认值里:所有参数默认传值,想传引用必须显式写。
显式的写法是两个符号配合:&a 表示「把 a 的地址给我」,
函数那边的参数要写成指针类型 x *int,函数里用 *x 表示「顺着这个地址取值/改值」8。
(指针的完整故事是第 10 章;这里只需要知道 & 是「取门牌号」、* 是「按门牌号进屋」。)
判断(我们的,不是书里的): 「默认传值」是 Go 安全性账本的又一笔: 和「不同整数不许隐式互换」「溢出不给警告」放在一起看,会发现一个模式—— Go 允许危险的事,但每一步都要你签字。想改外面的变量?可以,写出
&来。 如果错,会错在: 传值对大结构体(比如含几千个字段的 struct)有复制成本, 性能敏感的代码会普遍传指针——这时「签字」退化成形式主义,默认的「安全」 反而成了性能负担。这是真实的张力,不是书里的重点。
4. 命名返回值与裸返回:签名里就给结果起名
返回值可以在签名里直接起名字,起完名,函数体里就是普通变量:
func sum(a, b int, c string) (sum_value int, c_val string) {
sum_value = a + b // 直接用,不用 :=(它们已经声明过了)
c_val = c
return sum_value, c_val
}更进一步:裸返回(naked return)——return 后面什么都不写, 按签名上名字的顺序把当前值全部带回去9:
func sum(a, b int, c string) (sum_value int, c_val string) {
sum_value = a + b
c_val = c
return // 裸返回:等价于 return sum_value, c_val
}书里对裸返回的评价很克制:「好用,但长函数里没必要」9。 我们的补充:裸返回在小函数里是享受,在两百行的函数里是灾难—— 读的人要翻到签名才知道返回了什么。短函数裸返回,长函数写全。
5. 函数是一种类型:赋值、传递、返回
这是本章观念上最重要的一跳:在 Go 里,函数和整数、字符串一样,是一种类型。
函数类型长这样:type sub func(a int, b int) int——读作
「sub 是『吃两个 int、吐一个 int』的函数类型」10。
声明了类型之后,可以把一个符合这个形状的函数赋给变量、再调用:
var result sub
result = func(a int, b int) int { return a - b }
fmt.Println(result(100, 70)) // → 30result 现在是一个变量,但括号一跟,就是函数调用10。
匿名函数:不取名字的函数
没有名字的函数叫匿名函数(anonymous function,别的语言也叫 lambda)。 两种用法:
a := func() { fmt.Println("Introducing anonymous function") }
a() // 赋给变量再调用
func() { fmt.Println("Introducing anonymous function") }()
// 定义完当场调用(结尾多一对括号)两种写法的输出一样11。当场调用那种写法,在并发一章会变身成 「开一个 goroutine 就跑这段」的标准姿势。
函数返回函数
既然函数是值,函数当然能返回函数:
func return_anon() func(string) { // 返回值本身是一个函数类型
return func(message string) {
fmt.Println(message)
}
}
f := return_anon() // f 现在是那个匿名函数
f("Hello") // → Hello书里特别提示:打印 f 本身,看到的是函数的地址(形如 0x47d880)——
函数在内存里也是个有门牌号的东西12。
6. 闭包:函数带着它的「私有行李」
闭包(closure)是一个引用了自己函数体之外变量的函数;它出门的时候, 把那个变量一起打包带走——而且带走的是「活的」绑定,不是复印件。13
这是全书最适合逐行走一遍的例子,原书的计数器:
func return_anon() func() int {
count := 0
return func() int { // 这个匿名函数引用了外层的 count
count++
return count
}
}
counter := return_anon()
fmt.Println(counter()) // → 1
fmt.Println(counter()) // → 2 ← count 被记住了!
counter2 := return_anon() // 再要一个新的
fmt.Println(counter2()) // → 1 ← 各记各的账图说:每次调用 return_anon(),都会新造一个 count,并且把匿名函数和这个
count 绑在一起寄出去。counter 和 counter2 带走的是两个互不相干的 count。
书里点明了关键:「引用函数体之外的变量」就是闭包的定义本身13。
闭包是 Go 里「函数 + 它周围的数据」打包复用的最小单元, 以后看到「选项模式」「中间件」这类高级玩法,底下都是它。
7. 递归:函数调用自己
递归就是一个函数直接或间接调用自己,并且必须有终止条件, 否则就是无限递归——栈被一层层压满,程序崩掉14。
书里给的教科书例子,阶乘(num > 1 时 num × factorial(num-1),
num ≤ 1 时返回 1):factorial(10) → 362880015。
递归的两件套:往简单缩(问题每步变小)+ 停得下来(有终止条件),
缺一件都是事故。
8. defer:「走之前记得关火」
defer 的意思是:这个函数调用先记账,等外层函数(比如 main)快退出时再执行。 如果写了多个 defer,执行顺序是后进先出(LIFO)——最后记的账最先还16:
func main() {
fmt.Println("Before defer statement")
defer defer_fun() // 记账
fmt.Println("After defer statement") // 正常执行
}
// 输出顺序:
// Before defer statement
// After defer statement
// Hi I am introducing deferred function ← main 退出时才轮到它图说:defer 像出门前贴在门上的便签——「走之前关火」。 便签一张压一张,执行时从最后一张往前做16。
两个细节书里明确给了:
- defer 那一行的参数,在记账那一刻就算好了;真正执行的是「函数调用」本身16;
- 用途:关文件、还锁、收尾清理——第 17 章会看到它和错误处理、panic/recover 组成 Go 的完整善后体系。现在记住一句:打开资源(文件、锁、网络连接这类 占着就得还的东西)的下一行就写 defer 关它, 永远不会忘。
9. 变参函数:参数个数随便
变参(variadic)函数接受任意个数的同类型参数,写法是在最后一个参数的类型前
加 ...:args ...int 念作「零个或多个 int」,函数体内它就是个切片17:
func add(args ...int) int {
total := 0
for _, v := range args {
total += v
}
return total
}
add(1, 2, 3) // → 6
add() // 也行,args 是空切片,返回 0... 只能出现在最后一个参数上;已经握在手里的切片想拆开来传,用 slice...
(补充,不在书里,来自通用知识)。
10. 作者的判断与证据
这一章同样是规则为主。带判断色彩的话有两处:
| 书里的说法 | 性质 |
|---|---|
| 「裸返回好用,长函数里不必要」 | 作者经验之谈,属实且克制 |
| 「闭包能记住外部变量的值」 | 机制事实,例子可跑验 |
书中示例代码有个别排版瑕疵(如把 sum_value := a+b return sum_value, c
挤在一行),逻辑不受影响,照机制读即可。
11. 边界与局限
- 方法(method)没有出现:给 struct 绑函数的「方法」是第 11–13 章的内容, 本章的函数都是「自由函数」;
- defer 一节只给了顺序和语法,没讲 defer + return 值的经典坑 (defer 里改命名返回值会改变最终返回)——用到时留意(补充,不在书里);
- 变参只演示了 int,实际上
...对任意类型可用; - 没有讲「高阶函数」这个名目,但函数作参数、作返回值的机制已经全部给齐, 第 13 章接口会再见到它们。
12. 可带走的
- 启动顺序:init() → main() → 结束;init 只跑一次,做一次性初始化;
- 签名 = func + 名 + 参数 + 返回值;同类型参数可合并写;
- Go 函数可以一次返回多个值——错误处理的物理基础;
- 只想要部分返回值,用
_丢弃; - 默认传值:函数收到的是复制品;想改外面的变量,传
&地址, 参数写成指针,函数里用*进屋; - 返回值可以起名;裸返回(return 不带值)按签名顺序带回——短函数可用,长函数别用;
- 函数是类型:能赋给变量、能当参数、能当返回值;没有名字的函数直接调用,是 goroutine 的雏形;
- 闭包 = 引用外部变量的函数,它把那个变量活着打包带走;每次「造」一个就有一份新数据;
- 递归两件套:问题每步变小 + 有终止条件;
- defer 记账、退出时还:后进先出;参数记账时就算好;用于关文件、还锁、善后。
13. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 函数的作用 | Functions | text/14-fm-learning-go-programming.txt:73(搜「reusability」) |
| init 先于 main | Functions | text/14-fm-learning-go-programming.txt:76(搜「entrance door」) · text/14-fm-learning-go-programming.txt:109(搜「initialization of main function」) |
| 函数签名与语法 | Functions | text/14-fm-learning-go-programming.txt:118(搜「signature of the function」) |
| 参数合并写、多返回值 | Functions | text/14-fm-learning-go-programming.txt:196(搜「avoid repetition」) · text/14-fm-learning-go-programming.txt:235(搜「returning multiple values」) |
| 调用函数 | Functions | text/14-fm-learning-go-programming.txt:259(搜「control is transferred」) |
| 传值 | Functions | text/14-fm-learning-go-programming.txt:316(搜「do not reflect」) · text/14-fm-learning-go-programming.txt:337(搜「In function, value of a」) |
| 传引用 | Functions | text/14-fm-learning-go-programming.txt:409(搜「address of a variable」) · text/14-fm-learning-go-programming.txt:481(搜「After calling function, value of a」) |
| 命名返回值与裸返回 | Functions | text/14-fm-learning-go-programming.txt:490(搜「name values」) · text/14-fm-learning-go-programming.txt:508(搜「naked return」) |
| 多返回值 + 空白标识符 | Functions | text/14-fm-learning-go-programming.txt:565(搜「blank identifiers are used」) |
| 函数类型 | Functions | text/14-fm-learning-go-programming.txt:622(搜「create our function type」) |
| 匿名函数 | Functions | text/14-fm-learning-go-programming.txt:676(搜「lambda or anonymous」) |
| 返回函数 | Functions | text/14-fm-learning-go-programming.txt:748(搜「return an anonymous function」) |
| 闭包与计数器 | Functions | text/14-fm-learning-go-programming.txt:802(搜「out of its body」) · text/14-fm-learning-go-programming.txt:868(搜「count is: 2」) |
| 递归与阶乘 | Functions | text/14-fm-learning-go-programming.txt:880(搜「termination point」) · text/14-fm-learning-go-programming.txt:943(搜「3628800」) |
| defer 与后进先出 | Functions | text/14-fm-learning-go-programming.txt:949(搜「Last In First Out」) · text/14-fm-learning-go-programming.txt:991(搜「deferred function」) |
| 变参函数 | Functions | text/14-fm-learning-go-programming.txt:997(搜「arbitrary number of arguments」) · text/14-fm-learning-go-programming.txt:1033(搜「6」) |
Footnotes
-
出处:「Functions」第 73 段(
text/14-fm-learning-go-programming.txt:73,搜「reusability」)。 ↩ -
出处:「Functions」第 76、109 段(
text/14-fm-learning-go-programming.txt:76,搜「entrance door」;text/14-fm-learning-go-programming.txt:109,搜「initialization of main function」)。 ↩ -
出处:「Functions」第 118、124 段(
text/14-fm-learning-go-programming.txt:118,搜「signature of the function」)。 ↩ -
出处:「Functions」第 196–217 段(
text/14-fm-learning-go-programming.txt:196,搜「avoid repetition」)。 ↩ -
出处:「Functions」第 235 段(
text/14-fm-learning-go-programming.txt:235,搜「returning multiple values」)。 ↩ -
出处:「Functions」第 295–307 段(
text/14-fm-learning-go-programming.txt:307,搜「Values returned from the function are」)。 ↩ -
出处:「Functions」第 565–616 段(
text/14-fm-learning-go-programming.txt:604,搜「result, _」)。 ↩ -
出处:「Functions」第 316–340 段(
text/14-fm-learning-go-programming.txt:316,搜「do not reflect」;text/14-fm-learning-go-programming.txt:337,搜「In function, value of a」)与第 409–484 段(text/14-fm-learning-go-programming.txt:409,搜「address of a variable」;text/14-fm-learning-go-programming.txt:481,搜「After calling function, value of a」)。两组输出照原书。 ↩ ↩2 -
出处:「Functions」第 490、508 段(
text/14-fm-learning-go-programming.txt:490,搜「name values」;text/14-fm-learning-go-programming.txt:508,搜「naked return」)。 ↩ ↩2 -
出处:「Functions」第 622–670 段(
text/14-fm-learning-go-programming.txt:622,搜「create our function type」;text/14-fm-learning-go-programming.txt:670,搜「Result is」)。 ↩ ↩2 -
出处:「Functions」第 676–742 段(
text/14-fm-learning-go-programming.txt:676,搜「lambda or anonymous」;text/14-fm-learning-go-programming.txt:742,搜「same output」)。 ↩ -
出处:「Functions」第 748–796 段(
text/14-fm-learning-go-programming.txt:778,搜「address of function」;text/14-fm-learning-go-programming.txt:793,搜「Hello」)。 ↩ -
出处:「Functions」第 802–874 段(
text/14-fm-learning-go-programming.txt:802,搜「out of its body」;text/14-fm-learning-go-programming.txt:868,搜「count is: 2」;text/14-fm-learning-go-programming.txt:874,搜「key point in closures」)。 ↩ ↩2 -
出处:「Functions」第 880–895 段(
text/14-fm-learning-go-programming.txt:880,搜「termination point」)。 ↩ -
出处:「Functions」第 907–943 段(
text/14-fm-learning-go-programming.txt:943,搜「3628800」)。 ↩ -
出处:「Functions」第 949–991 段(
text/14-fm-learning-go-programming.txt:949,搜「Last In First Out」;text/14-fm-learning-go-programming.txt:988,搜「Before defer statement」)。原文明确:defer 语句的参数在 defer 时求值,函数执行被推迟。 ↩ ↩2 ↩3 -
出处:「Functions」第 997–1036 段(
text/14-fm-learning-go-programming.txt:997,搜「arbitrary number of arguments」;text/14-fm-learning-go-programming.txt:1033,搜「6」)。 ↩