🔖 Go 语言学习教程 · 第 09 篇
📌 本系列基于 Go 1.23 编写,代码兼容 Go 1.21+
全文约 4000 字 | 阅读约 12 分钟
欢迎来到第九篇 👋
前几篇我们学习了基本数据类型和集合。今天来学习 Go 的函数——代码复用的基本单位。
Go 的函数有其独特之处:多返回值、命名返回值、可变参数、函数作为值传递。让我们开始吧
✅ 上一篇练习题答案
以下是上一篇练习题的参考答案,欢迎在评论区分享你的解法~
1. 动手实践:创建 map[string][]string 存储爱好
hobbies := map[string][]string{
"张三": {"读书", "游泳"},
"李四": {"编程", "音乐"},
}
for name, h := range hobbies {
fmt.Printf("%s: %v
", name, h)
}
2. 词频统计
func wordCount(text string) map[string]int {
words := strings.Fields(text)
count := make(map[string]int)
for _, w := range words {
count[strings.ToLower(w)]++
}
return count
}
3. 思考题:为什么 Map 遍历顺序是随机的?
这是有意为之的设计。Go 团队为了防止开发者依赖 Map 的遍历顺序(不同版本实现可能不同),故意随机化遍历顺序,强制开发者如果需要有序遍历,应该先收集 key 再排序。
4. 拓展阅读
Go Blog Go Maps in Action 介绍了 Map 的使用技巧和底层实现。 🚀
🧐 一、函数定义
基本语法
func 函数名(参数列表) 返回值类型 {
// 函数体
return 返回值
}
示例
// 无参数,无返回值
func sayHello() {
fmt.Println("Hello, Go!")
}
// 有参数,有返回值
func add(a int, b int) int {
return a + b
}
// 参数类型相同时可以简写
func add2(a, b int) int {
return a + b
}
// 调用
fmt.Println(add(3, 5)) // 8
📖 二、多返回值
Go 函数可以返回多个值,这是 Go 的核心特性之一。
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为零")
}
return a / b, nil
}
// 调用
result, err := divide(10, 3)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Println("结果:", result)
}
命名返回值
func swap(a, b int) (x, y int) {
x = b
y = a
return // 裸 return,自动返回命名变量
}
first, second := swap(1, 2)
fmt.Println(first, second) // 2 1
💡 命名返回值让代码更清晰,但如果返回值含义不明显,建议用注释说明。
三、可变参数
函数可以接受不定数量的参数:
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// 调用
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum(1, 2, 3, 4, 5)) // 15
// 传入切片
values := []int{10, 20, 30}
fmt.Println(sum(values...)) // 60(用 ... 展开切片)
💡
...int表示可变参数,nums在函数内部是一个[]int切片。
📖 四、defer 延迟执行
defer 用于在函数返回前执行某些清理操作:
func readFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close() // 函数返回时自动关闭文件
data, err := io.ReadAll(f)
if err != nil {
return nil, err
}
return data, nil
}
defer 的执行顺序
func main() {
defer fmt.Println("第一")
defer fmt.Println("第二")
defer fmt.Println("第三")
fmt.Println("函数开始")
}
// 输出:
// 函数开始
// 第三
// 第二
// 第一
💡 defer 是后进先出(LIFO),最后 defer 的最先执行。
五、函数作为值与闭包
函数是一等公民
// 函数可以赋值给变量
add := func(a, b int) int {
return a + b
}
fmt.Println(add(3, 5)) // 8
// 函数可以作为参数
func apply(f func(int, int) int, a, b int) int {
return f(a, b)
}
result := apply(add, 3, 5) // 8
闭包
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
c := counter()
fmt.Println(c()) // 1
fmt.Println(c()) // 2
fmt.Println(c()) // 3
闭包让函数能够"记住"外部变量的状态,非常适合实现迭代器、装饰器等模式。
📝 六、本篇小结
| 要点 | 总结 |
|---|---|
| 函数定义 | func name(params) returnType |
| 多返回值 | Go 的核心特性,常用于返回 (value, error) |
| 命名返回值 | 让返回值更清晰,支持裸 return |
| 可变参数 | ...int,函数内是切片 |
| defer | 延迟执行,LIFO 顺序,适合资源清理 |
| 函数值 | 函数可以赋值、传参、作为返回值 |
| 闭包 | 函数记住外部变量状态 |
💬 一句话总结:
Go 函数支持多返回值、可变参数、defer 延迟执行、函数作为值传递和闭包,是构建 Go 程序的基石。
✏️ 七、练习题
- 🖥️ 动手实践:写一个函数
minMax(nums []int) (min, max int),同时返回切片的最小值和最大值。 - 🔧 可变参数:写一个
join(sep string, parts ...string) string函数,用分隔符连接多个字符串。 - 🤔 思考题:
defer的执行顺序为什么是 LIFO?如果改成 FIFO 会有什么问题? - 📚 拓展阅读:查阅 Go Blog 上的 Defer, Panic, and Recover。
💡 练习题答案会在下一篇中公布,欢迎在评论区分享你的答案~
关注我,一起学编程
如果本文对你有帮助,欢迎 点赞、在看、转发、关注。