Explorar el Código

feat: 新增第七课函数例程与配套测试说明

zhuts hace 3 días
padre
commit
aba9ec24e3
Se han modificado 4 ficheros con 291 adiciones y 3 borrados
  1. 8 3
      README.md
  2. 153 0
      lessons/07_functions/README.md
  3. 75 0
      lessons/07_functions/main.go
  4. 55 0
      lessons/07_functions/main_test.go

+ 8 - 3
README.md

@@ -27,9 +27,13 @@
     ├── 05_arrays_slices/   # 第 05 课:数组与切片
     │   ├── README.md       # 固定长度、追加、共享和复制
     │   └── main.go         # 带逐节注释的参考代码
-    └── 06_maps/            # 第 06 课:map 键值对
-        ├── README.md       # 增删改查、遍历、nil 与共享
-        └── main.go         # 按姓名查询成绩的参考代码
+    ├── 06_maps/            # 第 06 课:map 键值对
+    │   ├── README.md       # 增删改查、遍历、nil 与共享
+    │   └── main.go         # 按姓名查询成绩的参考代码
+    └── 07_functions/       # 第 07 课:函数
+        ├── README.md       # 参数、返回值、值传递和练习
+        ├── main.go         # 普通函数、多返回值、可变参数
+        └── main_test.go    # 本课函数测试
 ```
 
 根目录不放例程代码。各课共享根目录的 `go.mod`,不需要逐课初始化模块,也不需要 `go.work`。当前模块路径 `example.com/go-study` 是学习用标识,不必与 Git 地址相同。
@@ -44,6 +48,7 @@
 | 04 | [for 循环](./lessons/04_loops/README.md) | 三段式、条件循环、break、continue、range | 已建立 |
 | 05 | [数组与切片](./lessons/05_arrays_slices/README.md) | 索引、遍历、append、截取、make、copy | 已建立 |
 | 06 | [map 键值对](./lessons/06_maps/README.md) | 增删改查、存在性判断、遍历、nil | 已建立 |
+| 07 | [函数](./lessons/07_functions/README.md) | 参数、返回值、值传递、多返回值、可变参数 | 已建立 |
 
 后续按编号逐步新增独立目录。不一次性铺开后续课程,也不把新例程写进旧例程的入口文件。
 

+ 153 - 0
lessons/07_functions/README.md

@@ -0,0 +1,153 @@
+# 第 07 课:函数
+
+## 学习目标
+
+第一课已经见过函数,这一课正式学习怎样拆分逻辑:参数传入数据、return 返回结果、多返回值表达是否有效,以及可变参数。
+
+先手敲 [main.go](./main.go),运行成功后再阅读配套的 [main_test.go](./main_test.go)。不要一开始就同时抄完测试代码。本课共享根目录的 `go.mod`,无需第三方依赖。
+
+## 1. 声明和调用
+
+```go
+func add(a, b int) int {
+    return a + b
+}
+
+result := add(2, 3) // 这一行调用放在 main 等函数内部。
+```
+
+- `func`:声明函数。
+- `add`:函数名。
+- `a, b int`:两个 int 参数,完整写法是 `a int, b int`。
+- 参数列表后面的 `int`:返回值类型。
+- `return`:结束当前函数,并把结果交给调用方;不是结束整个程序。
+
+与 C 相比,Go 的类型写在名称后面,而且支持直接返回多个值。不需要为后面定义的普通函数先写函数原型,也不能直接在另一个函数内部声明具名函数;后续会学匿名函数。
+
+函数定义的前后顺序不决定调用顺序:程序完成初始化后进入 main,按实际调用执行。普通函数不会因为写在文件里就自动运行。
+
+## 2. 参数与值传递
+
+```go
+func increase(value int) int {
+    value++
+    return value
+}
+```
+
+调用 `increase(count)` 时,参数接收的是值的副本;修改内部整数 value,不会改变调用方的 count。想保留新值,可以用 `count = increase(count)`。
+
+Go 的参数都是按值传递。但切片和 map 的值包含对底层数据的引用,复制参数值不等于复制全部数据:函数内修改切片元素或 map 条目,可能影响调用方。不能理解为“传进函数的任何数据都完全独立”。指针和更深入的共享行为后续单独学习。
+
+## 3. 多返回值与提前返回
+
+```go
+func average(scores []int) (float64, bool) {
+    if len(scores) == 0 {
+        return 0, false
+    }
+    // 累加计算……
+    return 85, true // 此处仅示意返回形式,完整计算见 main.go。
+}
+```
+
+调用时按顺序接收:
+
+```go
+avg, ok := average(scores)
+```
+
+本课约定:ok 为 true 才表示平均值有效。空切片提前返回 false;真正的零分则返回 `(0, true)`,避免混淆。
+
+不需要某个返回值,可以使用 `_`,例如 `_, ok = average(nil)`。这里使用 `=`,因为 ok 已在同一作用域声明。
+
+该函数只检查是否为空,不验证成绩是否位于 0~100,也暂不处理极大整数累加溢出。后续可在学习 error 后扩展输入校验。
+
+## 4. 可变参数
+
+```go
+func sum(values ...int) int {
+    total := 0
+    for _, value := range values {
+        total += value
+    }
+    return total
+}
+```
+
+- 调用 `sum(1, 2, 3)` 可以传多个整数,`sum()` 也合法。
+- 在函数内部,values 作为 `[]int` 使用。
+- 已有切片时写 `sum(numbers...)`,将切片展开为参数。
+- 如果有其他参数,可变参数必须放在最后。
+
+先掌握常规函数即可,本课暂不引入递归、闭包、泛型和 defer。
+
+## 编译并运行
+
+在**仓库根目录**执行:
+
+```powershell
+New-Item -ItemType Directory -Force bin/07_functions | Out-Null
+go build -o ./bin/07_functions/functions.exe ./lessons/07_functions
+.\bin\07_functions\functions.exe
+```
+
+修改源码后需要重新编译。快速编译并运行:
+
+```powershell
+go run ./lessons/07_functions
+```
+
+或者从根目录进入本课:
+
+```powershell
+Set-Location lessons/07_functions
+go run .
+```
+
+## 预期输出
+
+```text
+=== 无参数函数 ===
+欢迎学习 Go 函数
+
+=== 参数与返回值 ===
+2 + 3 = 5
+
+=== 值传递 ===
+原变量:10,函数返回的新值:11
+
+=== 多返回值与提前返回 ===
+平均分:85.00
+没有成绩,无法计算平均分
+
+=== 可变参数 ===
+直接传入多个整数:6
+展开切片作为参数:60
+不传参数:0
+```
+
+## 测试
+
+在仓库根目录执行:
+
+```powershell
+go test -v ./lessons/07_functions
+```
+
+测试覆盖加法、整数值传递、正常平均值、小数平均值、零分、空切片和可变参数。这里的小数用例是可以精确表示的 80.5,因此直接比较;一般浮点计算测试可能需要容差判断。
+
+## 动手练习
+
+- [ ] 把 add 的输入改成负数,观察结果。
+- [ ] 用 `count = increase(count)` 更新调用方变量,比较前后区别。
+- [ ] 修改成绩切片,核对平均分;再改为空切片,观察提前返回。
+- [ ] 新写 `max(a, b int) int`,返回较大的值。
+- [ ] 为 max 添加相等、正数、负数的测试用例。
+- [ ] 使用 sum 计算一组新整数的总和。
+
+若另行手敲,放到本课的 `practice/` 子目录,用 `go run ./lessons/07_functions/practice` 运行,不要在同一个包中重复声明 main 或其他同名函数。
+
+下一课建议学习结构体,把姓名、年龄、成绩等不同类型的数据组合成一个对象。
+
+[上一课:map 键值对](../06_maps/README.md) · [学习总览](../../README.md)

+ 75 - 0
lessons/07_functions/main.go

@@ -0,0 +1,75 @@
+package main
+
+import "fmt"
+
+// 1. 无参数、无返回值。main 可以调用它,函数定义不必放在 main 前面。
+func greet() {
+	fmt.Println("欢迎学习 Go 函数")
+}
+
+// 2. 参数类型写在名称后面,返回值类型写在参数列表后面。
+// 相同类型的相邻参数,可以简写成 a, b int。
+func add(a, b int) int {
+	return a + b
+}
+
+// 3. int 参数按值传递:修改形参不会修改调用方的整数变量。
+func increase(value int) int {
+	value++
+	return value
+}
+
+// 4. 多返回值:用 bool 表示本次计算是否有效。
+// 本课先用 bool,后续再学习 Go 中常见的 (结果, error) 形式。
+func average(scores []int) (float64, bool) {
+	if len(scores) == 0 {
+		return 0, false // 提前返回,避免空切片导致除以零。
+	}
+
+	total := 0
+	for _, score := range scores {
+		total += score
+	}
+	return float64(total) / float64(len(scores)), true
+}
+
+// 5. 可变参数:可接收零个或多个 int,函数内部 values 是 []int。
+func sum(values ...int) int {
+	total := 0
+	for _, value := range values {
+		total += value
+	}
+	return total
+}
+
+func main() {
+	fmt.Println("=== 无参数函数 ===")
+	greet()
+
+	fmt.Println("\n=== 参数与返回值 ===")
+	result := add(2, 3)
+	fmt.Printf("2 + 3 = %d\n", result)
+
+	fmt.Println("\n=== 值传递 ===")
+	count := 10
+	next := increase(count)
+	fmt.Printf("原变量:%d,函数返回的新值:%d\n", count, next)
+
+	fmt.Println("\n=== 多返回值与提前返回 ===")
+	scores := []int{80, 90, 85}
+	avg, ok := average(scores)
+	if ok {
+		fmt.Printf("平均分:%.2f\n", avg)
+	}
+
+	_, ok = average(nil) // 不需要结果时,用 _ 忽略它。
+	if !ok {
+		fmt.Println("没有成绩,无法计算平均分")
+	}
+
+	fmt.Println("\n=== 可变参数 ===")
+	fmt.Printf("直接传入多个整数:%d\n", sum(1, 2, 3))
+	numbers := []int{10, 20, 30}
+	fmt.Printf("展开切片作为参数:%d\n", sum(numbers...))
+	fmt.Printf("不传参数:%d\n", sum())
+}

+ 55 - 0
lessons/07_functions/main_test.go

@@ -0,0 +1,55 @@
+package main
+
+import "testing"
+
+func TestAdd(t *testing.T) {
+	if got := add(2, 3); got != 5 {
+		t.Fatalf("add(2, 3) = %d, want 5", got)
+	}
+}
+
+func TestIncrease(t *testing.T) {
+	value := 10
+	if got := increase(value); got != 11 {
+		t.Fatalf("increase(10) = %d, want 11", got)
+	}
+	if value != 10 {
+		t.Fatalf("caller value = %d, want 10", value)
+	}
+}
+
+func TestAverage(t *testing.T) {
+	tests := []struct {
+		name   string
+		scores []int
+		want   float64
+		ok     bool
+	}{
+		{name: "normal", scores: []int{80, 90, 85}, want: 85, ok: true},
+		{name: "fraction", scores: []int{80, 81}, want: 80.5, ok: true},
+		{name: "zero", scores: []int{0}, want: 0, ok: true},
+		{name: "empty", scores: []int{}, want: 0, ok: false},
+		{name: "nil", scores: nil, want: 0, ok: false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, ok := average(tt.scores)
+			if got != tt.want || ok != tt.ok {
+				t.Fatalf("average(%v) = (%v, %t), want (%v, %t)", tt.scores, got, ok, tt.want, tt.ok)
+			}
+		})
+	}
+}
+
+func TestSum(t *testing.T) {
+	if got := sum(1, 2, 3); got != 6 {
+		t.Fatalf("sum(1, 2, 3) = %d, want 6", got)
+	}
+	if got := sum(); got != 0 {
+		t.Fatalf("sum() = %d, want 0", got)
+	}
+	values := []int{10, 20, 30}
+	if got := sum(values...); got != 60 {
+		t.Fatalf("sum(%v...) = %d, want 60", values, got)
+	}
+}