章节

6.9 练习

本篇通过若干分级练习巩固函数定义、参数、返回值、匿名函数、高阶函数、闭包与指针修改,并能独立拆分和复用基础程序逻辑。

练习

学完本章你应该掌握

  • 能根据任务拆出无参函数、有参函数、单返回值函数和多返回值函数,而不是把所有逻辑都堆在 main 里。
  • 能正确使用普通参数、可变参数、slice... 展开传参和命名返回值,写出更灵活的函数接口。
  • 能在函数中返回 boolerror 等结果,并在调用方完成判断、分流和错误处理。
  • 能在合适场景下使用匿名函数、高阶函数和闭包,把“行为”当作数据来传递和复用。
  • 能理解普通值参数与指针参数的区别,并通过 &* 完成对外部变量的修改。
  • 能把函数和前面学过的输入输出、基本类型、切片、map、判断、循环结合起来,写出结构更清晰的控制台程序。

简单

第 1 题:定义并调用问候函数

第 1 题 简单

编写一个 Go 程序,完成下面要求:

  • 定义一个无参无返回值函数 sayWelcome()
  • 这个函数输出 欢迎学习函数
  • main 中调用它两次
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import "fmt"

func sayWelcome() {
	fmt.Println("欢迎学习函数")
}

func main() {
	sayWelcome()
	sayWelcome()
}

输出结果:

1
2
欢迎学习函数
欢迎学习函数

第 2 题:编写返回较大值的函数

第 2 题 简单

编写一个函数 max(a, b int) int,完成下面要求:

  • 接收两个整数
  • 返回其中较大的那个值
  • main 中调用 max(18, 25),并输出 max=25
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main

import "fmt"

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func main() {
	fmt.Printf("max=%d\n", max(18, 25))
}

第 3 题:编写返回布尔值的偶数判断函数

第 3 题 简单

编写一个函数 isEven(n int) bool,完成下面要求:

  • 如果 n 是偶数,返回 true
  • 否则返回 false
  • main 中分别测试 811
  • 输出格式如下:
1
2
8 -> true
11 -> false
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import "fmt"

func isEven(n int) bool {
	return n%2 == 0
}

func main() {
	fmt.Printf("8 -> %t\n", isEven(8))
	fmt.Printf("11 -> %t\n", isEven(11))
}

说明:这道题的重点是让函数返回一个 bool,再由调用方决定怎么输出结果。

第 4 题:用函数统计切片总和

第 4 题 简单

已知有一个切片:

1
scores := []int{3, 5, 7, 9}

请编写一个函数 sumSlice(nums []int) int,完成下面要求:

  • 接收一个 []int
  • 使用 for range 计算总和
  • main 中调用它,并输出 total=24
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import "fmt"

func sumSlice(nums []int) int {
	total := 0
	for _, num := range nums {
		total += num
	}
	return total
}

func main() {
	scores := []int{3, 5, 7, 9}
	fmt.Printf("total=%d\n", sumSlice(scores))
}

第 5 题:修正可变参数的调用方式

第 5 题 简单

下面程序想分别输出 615,但它现在不能通过编译。
请改正代码,让它正常运行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func sum(nums ...int) int {
	total := 0
	for _, num := range nums {
		total += num
	}
	return total
}

func main() {
	base := []int{1, 2, 3}
	arr := [3]int{4, 5, 6}

	fmt.Println(sum(base))
	fmt.Println(sum(arr...))
}
查看参考答案

修正后的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func sum(nums ...int) int {
	total := 0
	for _, num := range nums {
		total += num
	}
	return total
}

func main() {
	base := []int{1, 2, 3}
	arr := [3]int{4, 5, 6}

	fmt.Println(sum(base...))
	fmt.Println(sum(arr[:]...))
}

关键点:

  • 向可变参数函数传切片时,要写成 base...
  • 数组不能直接展开,要先切成切片,再写成 arr[:]...

第 6 题:用命名返回值计算长方形信息

第 6 题 简单

编写一个函数 rectStats(width, height int) (area int, perimeter int),完成下面要求:

  • 使用命名返回值
  • 返回长方形的面积和周长
  • main 中调用 rectStats(5, 3)
  • 输出:
1
2
area=15
perimeter=16
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import "fmt"

func rectStats(width, height int) (area int, perimeter int) {
	area = width * height
	perimeter = 2 * (width + height)
	return
}

func main() {
	area, perimeter := rectStats(5, 3)

	fmt.Printf("area=%d\n", area)
	fmt.Printf("perimeter=%d\n", perimeter)
}

说明:命名返回值适合返回含义明确的多个结果,但函数体仍然要保持清晰,不要为了省几个字把逻辑写乱。

第 7 题:用指针参数给分数加分

第 7 题 简单

编写一个函数 addBonus(score *int),完成下面要求:

  • 接收一个整数指针
  • 把这个分数加 10
  • main 中定义 score := 78
  • 调用函数后输出修改后的结果
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main

import "fmt"

func addBonus(score *int) {
	*score += 10
}

func main() {
	score := 78

	addBonus(&score)
	fmt.Println(score)
}

说明:如果函数参数写成 int,函数里改到的只是副本;这里传的是 &score,所以可以通过 *score 改到外部变量。

一般

第 8 题:实现带错误返回的安全除法

第 8 题 一般

编写一个函数 safeDivide(a, b int) (int, error),完成下面要求:

  • 如果 b == 0,返回错误 除数不能为 0
  • 否则返回 a / b
  • main 中分别测试 safeDivide(20, 4)safeDivide(20, 0)
  • 调用方拿到 error 后先判断,再决定输出结果
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
	"errors"
	"fmt"
)

func safeDivide(a, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("除数不能为 0")
	}
	return a / b, nil
}

func main() {
	result, err := safeDivide(20, 4)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(result)
	}

	result, err = safeDivide(20, 0)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(result)
	}
}

输出结果:

1
2
5
除数不能为 0

说明:返回 error 不是为了“让函数更复杂”,而是为了让调用方知道这次计算是否真的成功。

第 9 题:编写可变参数统计函数

第 9 题 一般

编写一个函数 calcStats(nums ...int) (total int, avg float64),完成下面要求:

  • 接收任意个整数
  • 返回总和和平均值
  • 如果一个数字都没传,平均值保持 0
  • main 中准备切片 scores := []int{80, 90, 85}
  • 使用 calcStats(scores...) 调用它
  • 输出:
1
2
total=255
avg=85.0
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func calcStats(nums ...int) (total int, avg float64) {
	for _, num := range nums {
		total += num
	}

	if len(nums) > 0 {
		avg = float64(total) / float64(len(nums))
	}
	return
}

func main() {
	scores := []int{80, 90, 85}
	total, avg := calcStats(scores...)

	fmt.Printf("total=%d\n", total)
	fmt.Printf("avg=%.1f\n", avg)
}

说明:这道题把可变参数、切片展开传参和命名返回值放到了一起,是函数章节里很典型的组合题。

第 10 题:编写成绩等级函数

第 10 题 一般

编写一个函数 grade(score int) string,完成下面要求:

  • 如果分数小于 0 或大于 100,返回 非法
  • 90 ~ 100 返回 A
  • 80 ~ 89 返回 B
  • 60 ~ 79 返回 C
  • 其他返回 D

然后在 main 中遍历下面这个切片,并逐行输出每个分数对应的等级:

1
scores := []int{95, 83, 61, 45, 120}
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

func grade(score int) string {
	switch {
	case score < 0 || score > 100:
		return "非法"
	case score >= 90:
		return "A"
	case score >= 80:
		return "B"
	case score >= 60:
		return "C"
	default:
		return "D"
	}
}

func main() {
	scores := []int{95, 83, 61, 45, 120}

	for _, score := range scores {
		fmt.Printf("%d -> %s\n", score, grade(score))
	}
}

第 11 题:封装年龄查询函数

第 11 题 一般

请把 map 查询逻辑封装成一个函数,完成下面要求:

  • 先准备一张年龄表:
1
2
3
4
ages := map[string]int{
	"阿斌": 21,
	"小王": 18,
}
  • 编写函数 findAge(ages map[string]int, name string) (int, bool)
  • 再让用户输入一个姓名
  • 调用这个函数
  • 如果找到了,输出 小王 的年龄是 18
  • 如果没找到,输出 没有找到该用户
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

func findAge(ages map[string]int, name string) (int, bool) {
	age, ok := ages[name]
	return age, ok
}

func main() {
	ages := map[string]int{
		"阿斌": 21,
		"小王": 18,
	}

	var name string
	fmt.Print("请输入姓名:")
	fmt.Scan(&name)

	age, ok := findAge(ages, name)
	if ok {
		fmt.Printf("%s 的年龄是 %d\n", name, age)
	} else {
		fmt.Println("没有找到该用户")
	}
}

说明:这道题的重点不是 map 本身,而是让你把“查询并返回结果”的逻辑收进函数里,让调用方只关心“找到了没有”。

第 12 题:使用立即执行匿名函数统计及格人数

第 12 题 一般

已知有一个成绩切片:

1
scores := []int{78, 92, 56, 84, 67}

请使用“立即执行匿名函数”的写法,完成下面要求:

  • 统计及格人数,及格线是 60
  • 把结果保存到变量 passCount
  • 最后输出 passCount=4
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func main() {
	scores := []int{78, 92, 56, 84, 67}

	passCount := func(list []int) int {
		count := 0
		for _, score := range list {
			if score >= 60 {
				count++
			}
		}
		return count
	}(scores)

	fmt.Printf("passCount=%d\n", passCount)
}

说明:匿名函数不一定要先赋值给变量,也可以“定义完立刻执行”,这种写法常见于一次性的临时逻辑。

第 13 题:实现接收函数参数的计算器

第 13 题 一般

编写一个高阶函数 calc(a, b int, op func(int, int) int) int,完成下面要求:

  • calc 接收两个整数和一个函数参数
  • 这个函数参数负责决定“怎么计算”
  • main 中用匿名函数分别完成两次调用: 第一次计算 7 + 3 第二次取 73 中的较大值
  • 输出格式如下:
1
2
sum=10
max=7
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

func calc(a, b int, op func(int, int) int) int {
	return op(a, b)
}

func main() {
	sum := calc(7, 3, func(a, b int) int {
		return a + b
	})

	max := calc(7, 3, func(a, b int) int {
		if a > b {
			return a
		}
		return b
	})

	fmt.Printf("sum=%d\n", sum)
	fmt.Printf("max=%d\n", max)
}

说明:高阶函数的关键不在于“函数很多”,而在于“函数也能像普通值一样被传来传去”。

第 14 题:实现交换两个整数的函数

第 14 题 一般

编写一个函数 swap(a, b *int),完成下面要求:

  • 接收两个整数指针
  • 交换它们指向的值
  • main 中定义 x := 3y := 9
  • 调用函数前后都输出一次

期望输出类似这样:

1
2
before: 3 9
after: 9 3
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func swap(a, b *int) {
	temp := *a
	*a = *b
	*b = temp
}

func main() {
	x := 3
	y := 9

	fmt.Println("before:", x, y)
	swap(&x, &y)
	fmt.Println("after:", x, y)
}

说明:如果这里把函数参数写成 swap(a, b int),交换的只会是两个副本,main 里的 xy 不会真的变化。

进阶

下面四题会把 01-06 单元里的输入、判断、循环、切片、map 与函数组合起来,更接近“先拆函数,再组织流程”的真实写法。

第 15 题:实现一个闭包计数器

第 15 题 进阶

编写一个函数 makeCounter(start int) func() int,完成下面要求:

  • 外层函数接收起始值 start
  • 返回一个匿名函数
  • 每次调用这个匿名函数时,都先把内部值加 1,再返回
  • main 中这样测试:
1
2
3
4
counter := makeCounter(10)
fmt.Println(counter())
fmt.Println(counter())
fmt.Println(counter())

输出应为:

1
2
3
11
12
13
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func makeCounter(start int) func() int {
	return func() int {
		start++
		return start
	}
}

func main() {
	counter := makeCounter(10)

	fmt.Println(counter())
	fmt.Println(counter())
	fmt.Println(counter())
}

说明:start 原本是外层函数里的变量,但返回的匿名函数把它“记住”了,所以多次调用时状态会持续保留下来。

第 16 题:用闭包生成筛选函数

第 16 题 进阶

编写一个函数 makeMinChecker(min int) func(int) bool,完成下面要求:

  • 它返回一个新函数
  • 这个新函数接收一个分数,并判断该分数是否大于等于 min
  • 再编写一个辅助函数 countByChecker(scores []int, checker func(int) bool) int
  • 使用下面这个切片做测试:
1
scores := []int{58, 61, 73, 90, 100}
  • 创建两个检查函数: passChecker := makeMinChecker(60) excellentChecker := makeMinChecker(90)
  • 分别统计及格人数和优秀人数
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

func makeMinChecker(min int) func(int) bool {
	return func(score int) bool {
		return score >= min
	}
}

func countByChecker(scores []int, checker func(int) bool) int {
	count := 0
	for _, score := range scores {
		if checker(score) {
			count++
		}
	}
	return count
}

func main() {
	scores := []int{58, 61, 73, 90, 100}

	passChecker := makeMinChecker(60)
	excellentChecker := makeMinChecker(90)

	fmt.Printf("passCount=%d\n", countByChecker(scores, passChecker))
	fmt.Printf("excellentCount=%d\n", countByChecker(scores, excellentChecker))
}

一种可能的输出结果:

1
2
passCount=4
excellentCount=2

说明:这道题把“函数作为返回值”和“函数作为参数”都连起来了,能很好地体会函数复用的价值。

第 17 题:用函数表实现循环菜单

第 17 题 进阶

请编写一个程序,完成下面要求:

  • 先准备成绩切片:
1
scores := []int{78, 92, 85, 66}
  • 编写三个普通函数: calcTotal(scores []int) int calcAvg(scores []int) float64 bestScore(scores []int) int
  • 再准备一个函数表 map[int]func(),让它负责菜单分发: 1 输出总分 2 输出平均分 3 输出最高分
  • 使用 for {} 循环读取用户选项
  • 输入 0 时退出
  • 输入其他不存在的编号时输出 无效选项
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package main

import "fmt"

func calcTotal(scores []int) int {
	total := 0
	for _, score := range scores {
		total += score
	}
	return total
}

func calcAvg(scores []int) float64 {
	return float64(calcTotal(scores)) / float64(len(scores))
}

func bestScore(scores []int) int {
	best := scores[0]
	for _, score := range scores[1:] {
		if score > best {
			best = score
		}
	}
	return best
}

func main() {
	scores := []int{78, 92, 85, 66}

	actions := map[int]func(){
		1: func() {
			fmt.Printf("总分:%d\n", calcTotal(scores))
		},
		2: func() {
			fmt.Printf("平均分:%.1f\n", calcAvg(scores))
		},
		3: func() {
			fmt.Printf("最高分:%d\n", bestScore(scores))
		},
	}

	for {
		var choice int

		fmt.Print("请输入选项(1总分 2平均分 3最高分 0退出):")
		fmt.Scan(&choice)

		if choice == 0 {
			fmt.Println("退出程序")
			return
		}

		if action, ok := actions[choice]; ok {
			action()
		} else {
			fmt.Println("无效选项")
		}
	}
}

说明:这道题的重点不是菜单本身,而是体会“编号 -> 函数”的映射关系。分支一多,用函数表往往比堆很多层 if 更清晰。

第 18 题:拆分一个成绩录入分析器

第 18 题 进阶

请把下面这个需求拆成多个函数来实现:

  • 编写 readScores() []int 不断读取用户输入的成绩 输入 -1 时结束 如果分数小于 0 或大于 100,输出 分数无效,已跳过,然后继续下一轮 合法分数追加到切片里
  • 编写 calcStats(scores []int) (int, float64, error) 返回总分、平均分和错误信息 如果一个有效成绩都没有,返回错误 没有有效成绩
  • 编写 gradeByAvg(avg float64) string avg >= 90 返回 A avg >= 80 返回 B avg >= 60 返回 C 其他返回 D
  • main 中调用这些函数,最后输出总分、平均分和平均分等级
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package main

import (
	"errors"
	"fmt"
)

func readScores() []int {
	scores := []int{}

	for {
		var score int

		fmt.Print("请输入成绩(-1 结束):")
		fmt.Scan(&score)

		if score == -1 {
			break
		}

		if score < 0 || score > 100 {
			fmt.Println("分数无效,已跳过")
			continue
		}

		scores = append(scores, score)
	}

	return scores
}

func calcStats(scores []int) (int, float64, error) {
	if len(scores) == 0 {
		return 0, 0, errors.New("没有有效成绩")
	}

	total := 0
	for _, score := range scores {
		total += score
	}

	avg := float64(total) / float64(len(scores))
	return total, avg, nil
}

func gradeByAvg(avg float64) string {
	switch {
	case avg >= 90:
		return "A"
	case avg >= 80:
		return "B"
	case avg >= 60:
		return "C"
	default:
		return "D"
	}
}

func main() {
	scores := readScores()

	total, avg, err := calcStats(scores)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Printf("total=%d\n", total)
	fmt.Printf("avg=%.1f\n", avg)
	fmt.Printf("level=%s\n", gradeByAvg(avg))
}

说明:这道题把循环输入、切片追加、条件判断、错误返回和函数拆分全部串起来了。能顺利写出来,说明你已经开始真正把函数当成“组织程序结构”的工具在用了。

本文禁止转载
使用 Hugo 构建
主题 StackJimmy 设计 由 Hobin 魔改
最近构建时间:2026-04-17 19:07:48 CST
载入天数...载入时分秒...
发表了 1 篇文章 · 发表了 152 篇笔记 · 总计 18 万 0 千字