09-函数:定义、多返回值与可变参数

0 阅读4分钟

🔖 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 程序的基石。


✏️ 七、练习题

  1. 🖥️ 动手实践:写一个函数 minMax(nums []int) (min, max int),同时返回切片的最小值和最大值。
  2. 🔧 可变参数:写一个 join(sep string, parts ...string) string 函数,用分隔符连接多个字符串。
  3. 🤔 思考题:defer 的执行顺序为什么是 LIFO?如果改成 FIFO 会有什么问题?
  4. 📚 拓展阅读:查阅 Go Blog 上的 Defer, Panic, and Recover。

💡 练习题答案会在下一篇中公布,欢迎在评论区分享你的答案~


关注我,一起学编程

如果本文对你有帮助,欢迎 点赞、在看、转发、关注。