Go 从 0 到精通 · 第 06 课:函数基础
Go 从 0 到精通 · 第 06 课:函数基础
学习定位:这是整套 Go 教程的第 6 课,也是入门基础阶段的最后一课。
前置要求:已经完成第 5 课,掌握了for循环的所有常见用法、break、continue以及for range遍历。
本课目标:掌握函数的定义、调用、参数传递、返回值机制,理解多返回值、命名返回值和可变参数,能将程序逻辑拆分成函数进行复用。
1. 本课你要解决的核心问题
前五课你写的所有代码都塞在 main() 一个函数里。随着逻辑变复杂,这种写法会越来越难维护。
函数就是让你把一段逻辑"包装"起来,给它一个名字,需要的时候调用它。
你需要搞明白以下问题:
- 函数怎么定义、怎么调用
- 参数是什么,怎么传
- 返回值是什么,怎么接收
- 为什么 Go 支持多返回值,有什么用
- 什么是命名返回值
- 什么是可变参数
- 函数作为"一等公民"是什么意思
- 函数相关的常见坑有哪些
学完这一课,你就完成了入门基础阶段的全部内容,具备了写基本 Go 程序的能力。
2. 函数的基本概念
2.1 什么是函数
函数可以理解为:
一段有名字、可以被反复调用的代码块。
它可以接收输入(参数),可以产出结果(返回值),也可以什么都不接收、什么都不返回。
你之前已经用过函数了:
fmt.Println是函数fmt.Printf是函数strconv.Atoi是函数main()本身也是函数
现在你要学的是:怎么自己定义函数。
2.2 为什么需要函数
假如你有一段"计算两个数之和"的逻辑,要在程序中用 5 次。
如果不用函数,你就要把同样的代码复制 5 次。一旦逻辑需要修改,你就要改 5 个地方。
函数解决的就是这个问题:
- 复用:写一次,用多次
- 组织:让代码更有结构
- 可读性:给逻辑起一个有意义的名字
- 可维护性:改一处,处处生效
3. 函数定义与调用
3.1 最简单的函数
1 | package main |
输出:
1 | 你好,Go! |
这里:
func sayHello()定义了一个名为sayHello的函数- 它没有参数,也没有返回值
- 在
main()里通过sayHello()调用它
3.2 函数定义的基本语法
1 | func 函数名(参数列表) 返回值类型 { |
各个部分的含义:
func:关键字,表示定义函数函数名:遵循 Go 的命名规则参数列表:函数接收的输入,可以为空返回值类型:函数返回的结果类型,可以没有函数体:具体的执行逻辑
3.3 函数名的命名规范
Go 的函数命名遵循以下惯例:
- 使用驼峰命名法(
camelCase) - 首字母大写的函数是导出的(可以被其他包调用)
- 首字母小写的函数是未导出的(仅当前包可用)
- 名字应该简洁、有意义
例如:
calculateSum:好名字,清楚表达功能cs:不好,看不出是做什么的CalculateSum:首字母大写,表示可以被其他包调用
导出规则的细节在后面"包与模块管理"那一课会详细讲。
4. 带参数的函数
4.1 单个参数
1 | package main |
输出:
1 | 你好, 小明 |
name string 表示这个函数接收一个 string 类型的参数,调用时需要传入一个字符串。
4.2 多个参数
1 | package main |
输出:
1 | 3 + 5 = 8 |
4.3 相同类型的参数可以简写
如果连续多个参数类型相同,可以只在最后写一次类型:
1 | func add(a, b int) { |
这等价于:
1 | func add(a int, b int) { |
这种简写在参数较多且类型相同时很实用。
5. 带返回值的函数
5.1 单个返回值
1 | package main |
输出:
1 | 结果是: 8 |
注意:
- 函数签名里
int写在参数列表后面,表示返回值类型是int - 函数体里用
return返回结果 - 调用方用变量接收返回值
5.2 return 的作用
return 做两件事:
- 把值返回给调用方
- 立刻结束函数执行
例如:
1 | func check(age int) string { |
当 age >= 18 时,第一个 return 执行,函数立刻结束,不会执行到第二个 return。
6. 多返回值:Go 的标志性特性
6.1 基本写法
Go 的函数可以返回多个值。这是很多语言没有的特性。
1 | package main |
输出:
1 | 结果:3.33 |
多返回值的语法:
- 返回值类型用括号包起来:
(float64, string) return后面按顺序写多个值- 调用方用多个变量接收
6.2 为什么 Go 要支持多返回值
最核心的原因是:错误处理。
Go 没有异常机制(没有 try...catch),它用返回值来传递错误。最典型的模式是:
1 | result, err := someFunction() |
这种"结果 + 错误"的双返回值模式,是 Go 代码中最常见的模式之一。你之前用过的 strconv.Atoi 就是这样:
1 | n, err := strconv.Atoi("42") |
第一个返回值是转换结果,第二个返回值是错误信息。
6.3 用 _ 忽略不需要的返回值
如果你只关心某个返回值,可以用 _ 忽略其他的:
1 | result, _ := divide(10, 3) |
_ 表示"我不需要这个值"。
但在实际开发中,不建议随意忽略错误。忽略错误是很多 bug 的根源。
7. 命名返回值
7.1 基本写法
Go 允许在函数签名中给返回值起名字:
1 | package main |
输出:
1 | 面积:15.0,周长:16.0 |
注意:
- 返回值写成
(area float64, perimeter float64) - 函数体里直接给
area和perimeter赋值 return后面不用写具体值,它会自动返回命名的返回值
7.2 命名返回值的相同类型简写
和参数一样,如果多个命名返回值类型相同,可以简写:
1 | func swap(a, b int) (x, y int) { |
7.3 什么时候用命名返回值
命名返回值适合的场景:
- 返回值有 2 个以上,且含义需要明确区分
- 函数逻辑比较复杂,命名能提高可读性
- 想使用"裸 return"(不带值的
return)
但也有争议的地方:
- 函数比较短时,命名返回值可能显得多余
- "裸 return"在长函数中可能降低可读性,因为你需要回头看函数签名才知道返回了什么
建议:
短函数直接
return 值就好;长函数或返回值较多时考虑用命名返回值。
8. 可变参数
8.1 基本写法
有时候你希望一个函数能接收不确定数量的参数。Go 用 ... 来实现:
1 | package main |
输出:
1 | 6 |
nums ...int 表示 nums 是一个可变参数,类型是 int。在函数内部,nums 实际上是一个 []int(整数切片)。
8.2 可变参数的规则
- 可变参数必须是函数参数列表的最后一个
- 一个函数最多只能有一个可变参数
- 调用时可以传 0 个、1 个或多个值
1 | // 正确:可变参数在最后 |
8.3 传递切片给可变参数
如果你已经有一个切片,想传给可变参数函数,需要用 ... 展开:
1 | package main |
输出:
1 | 15 |
numbers... 表示把切片展开成一个个单独的参数传入。切片的详细内容会在第 9 课讲。
9. Go 中的参数传递:值传递
9.1 Go 是值传递
Go 的函数参数传递方式是值传递。
这意味着:调用函数时,传入的值会被复制一份给函数内部的参数。函数内部修改参数,不会影响外部的原始变量。
1 | package main |
输出:
1 | 函数内部 n = 100 |
x 的值没有被改变,因为 tryChange 拿到的只是 x 的一份副本。
9.2 如果想在函数里修改外部变量怎么办
答案是用指针。这是下一课(第 7 课)的重点内容。
现在你只需要记住:
Go 的参数传递是值传递。函数内部修改参数不会影响外部变量。
10. 函数作为值:一等公民
10.1 什么意思
在 Go 中,函数是"一等公民",这意味着:
- 函数可以赋值给变量
- 函数可以作为参数传给另一个函数
- 函数可以作为返回值从另一个函数返回
这一课先看最基本的用法。
10.2 把函数赋值给变量
1 | package main |
输出:
1 | 8 |
f := add 把 add 函数赋给了变量 f,之后通过 f(3, 5) 调用它。
10.3 匿名函数
Go 支持不给函数起名字,直接定义使用:
1 | package main |
输出:
1 | 你好, 小明 |
匿名函数没有函数名,直接赋给变量使用。
10.4 立即执行的匿名函数
匿名函数也可以定义后立刻调用:
1 | package main |
输出:
1 | 8 |
这种写法在实际开发中偶尔会用到,例如初始化某些变量时。
11. 函数作为参数
函数可以作为参数传给另一个函数:
1 | package main |
输出:
1 | 8 |
apply 函数的第三个参数 op 的类型是 func(int, int) int,表示"一个接收两个 int 参数、返回一个 int 的函数"。
调用时传入不同的函数,就能实现不同的行为。这是一种非常强大的抽象能力。
你现在理解基本原理就好,后面学到接口和并发时,函数作为值会更频繁出现。
12. 一段综合示例
把本课核心知识串到一个程序里:
1 | package main |
输出:
1 | 3 + 5 = 8 |
13. 常见坑总结
13.1 忘记接收返回值
1 | func add(a, b int) int { |
Go 不会因为你丢弃了返回值而报错(除非返回值是错误且有 lint 规则),但这通常说明你的调用意图有问题。
13.2 多返回值只接收了部分
1 | // divmod 返回 3 个值,但你只接收了 1 个 |
Go 要求你必须接收所有的返回值。如果不需要某个返回值,用 _ 忽略:
1 | quotient, _, _ := divmod(17, 5) |
13.3 以为函数内部能修改外部变量
Go 是值传递。函数内部修改参数不影响外部:
1 | func change(s string) { |
要修改外部变量,需要用指针(下一课讲)。
13.4 可变参数不在最后
1 | // 编译错误 |
可变参数必须是参数列表的最后一个。
13.5 “裸 return” 在长函数中降低可读性
1 | func process(data string) (result string, err string) { |
如果函数很长,建议还是显式写 return result, err,可读性更好。
13.6 匿名函数忘了调用
1 | // 只是定义了匿名函数,没有调用 |
如果要立即执行,后面加参数:
1 | func(name string) { |
如果要保存后调用,赋给变量:
1 | f := func(name string) { |
14. 本课练习
一定要亲手写,不要只看。
练习 1:写一个求最大值的函数
要求:
- 定义函数
max(a, b int) int - 返回两个整数中的较大值
- 在
main中调用并打印结果
练习 2:写一个多返回值函数
要求:
- 定义函数
minMax(a, b, c int) (int, int) - 返回三个整数中的最小值和最大值
- 在
main中调用并打印结果
练习 3:体验值传递
要求:
- 定义函数
doubleValue(n int),在函数内部把n乘以2并打印 - 在
main中定义变量x,调用doubleValue(x)后打印x - 观察
x是否被修改,解释原因
练习 4:可变参数求平均值
要求:
- 定义函数
average(nums ...float64) float64 - 计算并返回所有参数的平均值
- 考虑参数为空的情况(返回
0) - 在
main中测试多种调用
练习 5:写一个简单计算器函数
要求:
- 定义函数
calculate(a, b float64, op string) (float64, string) - 根据
op的值("+"、"-"、"*"、"/")执行对应运算 - 除零时返回错误信息
- 未知运算符返回错误信息
- 在
main中测试各种情况
练习 6:用函数作为参数
要求:
- 定义
apply(a, b int, op func(int, int) int) int - 定义
add和subtract两个函数 - 通过
apply调用不同的运算函数
练习 7:用匿名函数改写练习 6
要求:
- 不单独定义
add和subtract - 直接在调用
apply时传入匿名函数
练习 8:命名返回值练习
要求:
- 定义函数
circleInfo(radius float64) (area, circumference float64) - 计算圆的面积(π × r²)和周长(2 × π × r)
- 使用
math.Pi作为 π 的值 - 在
main中调用并输出结果
15. 自测题
不看文档,试着回答:
15.1 概念题
- 函数定义的基本语法是什么?
- Go 的参数传递是值传递还是引用传递?
- 多返回值有什么用?最典型的使用模式是什么?
- 命名返回值是什么?“裸 return” 是什么意思?
- 可变参数的语法是什么?它在函数内部是什么类型?
- 可变参数必须在参数列表的什么位置?
_在接收多返回值时的作用是什么?- 函数名首字母大写和小写有什么区别?
- 匿名函数是什么?怎么立即执行?
- 为什么说 Go 的函数是"一等公民"?
如果你能流畅回答这些问题,说明这一课你已经真正理解了。
16. 本课总结
这一课你学到的是 Go 程序组织的基础能力——函数。
你现在应该已经理解:
- 函数用
func定义,可以有参数和返回值 - 相同类型的连续参数可以简写
return返回值并结束函数执行- Go 支持多返回值,最典型的模式是"结果 + 错误"
- 命名返回值可以提高可读性,支持"裸 return"
- 可变参数用
...表示,必须在参数列表最后 - Go 是值传递,函数内修改参数不影响外部变量
- 函数是一等公民,可以赋值给变量、作为参数传递
- 匿名函数可以直接定义并使用
17. 阶段一完成回顾
到这里,你已经完成了阶段一:入门基础阶段的全部 6 课内容。
回顾一下你现在的能力:
| 课次 | 主题 | 你获得的能力 |
|---|---|---|
| 第 01 课 | 环境搭建 | 能创建并运行 Go 程序 |
| 第 02 课 | 变量常量类型 | 能声明变量、理解零值和类型系统 |
| 第 03 课 | 输入输出与类型转换 | 能写交互式程序、做类型转换 |
| 第 04 课 | 条件判断 | 能让程序根据条件做出决定 |
| 第 05 课 | 循环结构 | 能让程序重复执行逻辑 |
| 第 06 课 | 函数基础 | 能把逻辑封装成可复用的函数 |
你现在已经具备了写简单 Go 控制台程序的完整基础。接下来我们进入阶段二:核心语法阶段,开始学习指针、数组、切片、map、结构体和方法——这些是写出更复杂程序的核心工具。
18. 下一课预告
下一课我们进入阶段二,首先学习:指针入门。
会重点讲:
- 什么是指针,为什么需要它
- 取地址
&和解引用*的含义 - 指针和函数参数的关系
- 为什么有了指针就能在函数里修改外部变量
- 指针的零值是什么
- 新手常见的指针误区
学完下一课,你就能理解"为什么 fmt.Scan 需要加 &"这个问题了。






