This commit is contained in:
2026-03-14 19:03:12 +08:00
parent 2805379586
commit 6d86d62825
34 changed files with 3213 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
package main
import "fmt"
func exercise1() func(int) int {
fmt.Println("=== 练习1:基础闭包 ===")
// 创建一个闭包函数,它捕获外部变量multiplier
// 该闭包接收一个int参数并返回乘以multiplier的结果
multiplier := 3
____ := func(num int) ____ {
return ____
}
return ____
}
func exercise2() func() int {
fmt.Println("=== 练习2:闭包状态保持 ===")
// 创建一个递增计数器闭包
// 每次调用返回的函数时,计数器加1
// 提示:使用匿名函数捕获外部变量
count := ____
____ := func() ____ {
____
return ____
}
return ____
}
func exercise3() func() int {
fmt.Println("=== 练习3:斐波那契数列闭包 ===")
// 创建一个生成斐波那契数列的闭包
// 每次调用返回下一个斐波那契数
// 数列: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34...
a, b := ____, ____
____ := func() ____ {
result := ____
a, b = ____
return ____
}
return ____
}
func exercise4() func(int) bool {
fmt.Println("=== 练习4:闭包配置 ===")
// 创建一个检查数字是否在范围内的闭包生成器
// 该生成器接受min和max两个参数,返回一个检查函数
// 检查函数判断输入数字是否在[min, max]范围内
min := 10
max := ____
____ := func(num int) ____ {
return num >= ____ && num <= ____
}
return ____
}
func exercise5(start int) func(int) int {
fmt.Println("=== 练习5:闭包累加器 ===")
// 创建一个累加器闭包
// 从start开始,每次调用累加参数值
// 提示:闭包应该能访问外部作用域的变量
total := ____
____ := func(n int) ____ {
____
return ____
}
return ____
}
func main() {
fmt.Println("=== 闭包 练习 ===\n")
add3 := exercise1()
fmt.Printf("练习1结果: add3(4) = %d\n", add3(4))
fmt.Printf("练习1结果: add3(5) = %d\n", add3(5))
counter := exercise2()
fmt.Printf("练习2结果: counter() = %d\n", counter())
fmt.Printf("练习2结果: counter() = %d\n", counter())
fmt.Printf("练习2结果: counter() = %d\n", counter())
fib := exercise3()
fmt.Printf("练习3结果: fib() = %d\n", fib())
fmt.Printf("练习3结果: fib() = %d\n", fib())
fmt.Printf("练习3结果: fib() = %d\n", fib())
fmt.Printf("练习3结果: fib() = %d\n", fib())
fmt.Printf("练习3结果: fib() = %d\n", fib())
inRange := exercise4()
fmt.Printf("练习4结果: inRange(15) = %t\n", inRange(15))
fmt.Printf("练习4结果: inRange(5) = %t\n", inRange(5))
fmt.Printf("练习4结果: inRange(20) = %t\n", inRange(20))
accumulator := exercise5(100)
fmt.Printf("练习5结果: accumulator(10) = %d\n", accumulator(10))
fmt.Printf("练习5结果: accumulator(20) = %d\n", accumulator(20))
fmt.Printf("练习5结果: accumulator(30) = %d\n", accumulator(30))
}
+113
View File
@@ -0,0 +1,113 @@
package main
import (
"bytes"
"os"
"strings"
"testing"
)
func captureOutput(f func()) string {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
f()
w.Close()
os.Stdout = old
var buf bytes.Buffer
buf.ReadFrom(r)
return buf.String()
}
func TestExercise1(t *testing.T) {
add3 := exercise1()
result1 := add3(4)
if result1 != 12 {
t.Errorf("期望 12, 实际: %d", result1)
}
result2 := add3(5)
if result2 != 15 {
t.Errorf("期望 15, 实际: %d", result2)
}
}
func TestExercise2(t *testing.T) {
counter := exercise2()
result1 := counter()
if result1 != 1 {
t.Errorf("期望 1, 实际: %d", result1)
}
result2 := counter()
if result2 != 2 {
t.Errorf("期望 2, 实际: %d", result2)
}
result3 := counter()
if result3 != 3 {
t.Errorf("期望 3, 实际: %d", result3)
}
}
func TestExercise3(t *testing.T) {
fib := exercise3()
expected := []int{0, 1, 1, 2, 3}
for i, exp := range expected {
result := fib()
if result != exp {
t.Errorf("第%d个斐波那契数期望 %d, 实际: %d", i+1, exp, result)
}
}
}
func TestExercise4(t *testing.T) {
inRange := exercise4()
if !inRange(15) {
t.Errorf("15应该在范围内")
}
if inRange(5) {
t.Errorf("5不应该在范围内")
}
if !inRange(20) {
t.Errorf("20应该在范围内")
}
if inRange(21) {
t.Errorf("21不应该在范围内")
}
}
func TestExercise5(t *testing.T) {
accumulator := exercise5(100)
result1 := accumulator(10)
if result1 != 110 {
t.Errorf("期望 110, 实际: %d", result1)
}
result2 := accumulator(20)
if result2 != 130 {
t.Errorf("期望 130, 实际: %d", result2)
}
result3 := accumulator(30)
if result3 != 160 {
t.Errorf("期望 160, 实际: %d", result3)
}
}
func TestMainFunction(t *testing.T) {
output := captureOutput(main)
requiredSections := []string{
"=== 闭包 练习 ===",
"练习1:基础闭包",
"练习2:闭包状态保持",
"练习3:斐波那契数列闭包",
"练习4:闭包配置",
"练习5:闭包累加器",
}
for _, section := range requiredSections {
if !strings.Contains(output, section) {
t.Errorf("期望输出包含 '%s'", section)
}
}
}
+120
View File
@@ -0,0 +1,120 @@
package main
import "fmt"
func exercise1(apply func(int) int, value int) int {
fmt.Println("=== 练习1:函数作为参数 ===")
// 接收一个函数apply和一个值value
// 调用apply函数处理value并返回结果
result := ____
return ____
}
func exercise2() func(int) int {
fmt.Println("=== 练习2:函数作为返回值 ===")
// 返回一个函数,该函数将输入值乘以2
// 提示:返回一个匿名函数
return ____
}
func exercise3(vals []int, filter func(int) bool) []int {
fmt.Println("=== 练习3:过滤器函数 ===")
// 接收一个int切片和一个过滤函数
// 返回过滤后的切片(只保留满足条件的元素)
var result ____
for _, val := range ____ {
if ____(val) {
result = append(result, ____)
}
}
return ____
}
func exercise4(vals []int, mapper func(int) int) []int {
fmt.Println("=== 练习4:映射函数 ===")
// 接收一个int切片和一个映射函数
// 返回映射后的切片(每个元素都经过mapper函数处理)
result := make([]int, ____)
for i, val := range ____ {
result[i] = ____(val)
}
return ____
}
func exercise5(vals []int, reducer func(int, int) int, initial int) int {
fmt.Println("=== 练习5:归约函数 ===")
// 接收一个int切片、一个归约函数和初始值
// 返回归约结果
// 例如:对切片求和、求积等
result := ____
for _, val := range ____ {
result = ____(result, val)
}
return ____
}
func exercise6() int {
fmt.Println("=== 练习6:函数组合 ===")
// 组合两个函数
// square函数:计算平方
// double函数:计算双倍
// 返回 double(square(3)) 的结果
// 即:先平方(9),再双倍(18)
square := func(n int) int { return ____ }
double := func(n int) int { return ____ }
compose := func(f, g func(int) int, x int) int {
return ____(____(x))
}
return compose(____, ____, 3)
}
func main() {
fmt.Println("=== 高阶函数 练习 ===\n")
平方 := func(n int) int { return n * n }
result1 := exercise1(平方, 4)
fmt.Printf("练习1结果: %d\n", result1)
double := exercise2()
result2 := double(5)
fmt.Printf("练习2结果: %d\n", result2)
nums1 := []int{1, 2, 3, 4, 5, 6}
isEven := func(n int) bool { return n%2 == 0 }
result3 := exercise3(nums1, isEven)
fmt.Printf("练习3结果: %v\n", result3)
nums2 := []int{1, 2, 3, 4, 5}
multiplyBy3 := func(n int) int { return n * 3 }
result4 := exercise4(nums2, multiplyBy3)
fmt.Printf("练习4结果: %v\n", result4)
nums3 := []int{1, 2, 3, 4, 5}
sum := func(acc, val int) int { return acc + val }
result5 := exercise5(nums3, sum, 0)
fmt.Printf("练习5结果: %d\n", result5)
result6 := exercise6()
fmt.Printf("练习6结果: %d\n", result6)
}
@@ -0,0 +1,117 @@
package main
import (
"bytes"
"os"
"strings"
"testing"
)
func captureOutput(f func()) string {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
f()
w.Close()
os.Stdout = old
var buf bytes.Buffer
buf.ReadFrom(r)
return buf.String()
}
func TestExercise1(t *testing.T) {
square := func(n int) int { return n * n }
result := exercise1(square, 4)
if result != 16 {
t.Errorf("期望 16, 实际: %d", result)
}
}
func TestExercise2(t *testing.T) {
double := exercise2()
result := double(5)
if result != 10 {
t.Errorf("期望 10, 实际: %d", result)
}
}
func TestExercise3(t *testing.T) {
nums := []int{1, 2, 3, 4, 5, 6}
isEven := func(n int) bool { return n%2 == 0 }
result := exercise3(nums, isEven)
if len(result) != 3 {
t.Errorf("期望长度 3, 实际: %d", len(result))
}
expected := []int{2, 4, 6}
for i, val := range expected {
if result[i] != val {
t.Errorf("索引 %d 期望 %d, 实际: %d", i, val, result[i])
}
}
}
func TestExercise4(t *testing.T) {
nums := []int{1, 2, 3, 4, 5}
multiplyBy3 := func(n int) int { return n * 3 }
result := exercise4(nums, multiplyBy3)
if len(result) != 5 {
t.Errorf("期望长度 5, 实际: %d", len(result))
}
expected := []int{3, 6, 9, 12, 15}
for i, val := range expected {
if result[i] != val {
t.Errorf("索引 %d 期望 %d, 实际: %d", i, val, result[i])
}
}
}
func TestExercise5(t *testing.T) {
nums := []int{1, 2, 3, 4, 5}
sum := func(acc, val int) int { return acc + val }
result := exercise5(nums, sum, 0)
if result != 15 {
t.Errorf("期望 15, 实际: %d", result)
}
product := func(acc, val int) int { return acc * val }
result = exercise5(nums, product, 1)
if result != 120 {
t.Errorf("期望 120, 实际: %d", result)
}
}
func TestExercise6(t *testing.T) {
result := exercise6()
if result != 18 {
t.Errorf("期望 18, 实际: %d", result)
}
}
func TestMainFunction(t *testing.T) {
output := captureOutput(main)
requiredSections := []string{
"=== 高阶函数 练习 ===",
"练习1:函数作为参数",
"练习2:函数作为返回值",
"练习3:过滤器函数",
"练习4:映射函数",
"练习5:归约函数",
"练习6:函数组合",
}
for _, section := range requiredSections {
if !strings.Contains(output, section) {
t.Errorf("期望输出包含 '%s'", section)
}
}
}
@@ -0,0 +1,44 @@
# 01-arrays - 数组
## 知识点讲解
### 数组定义
```go
var arr1 [5]int // 声明一个长度为5的int数组
var arr2 = [5]int{1, 2, 3, 4, 5} // 声明并初始化
arr3 := [5]int{1, 2, 3} // 部分初始化,其余为0
arr4 := [...]int{1, 2, 3, 4, 5} // 让编译器推断长度
```
### 访问和修改
```go
arr := [3]int{10, 20, 30}
fmt.Println(arr[0]) // 输出: 10
arr[1] = 100 // 修改元素
```
### 数组特点
- **固定长度**:创建后长度不可变
- **值类型**:数组赋值会拷贝整个数组
- **相同类型**:必须指定长度和元素类型
- **内存连续**:元素在内存中连续存储
### 重要提示
- Go中数组长度是类型的一部分,`[3]int` 和 `[4]int` 是不同的类型
- 数组作为参数传递时会拷贝整个数组,而不是引用
## 学习目标
完成本练习后,你将:
- ✅ 掌握数组的声明和初始化
- ✅ 学会数组的访问和修改
- ✅ 理解数组的值类型特性
- ✅ 了解数组的遍历方式
## 练习说明
打开 `main.go`,根据注释提示填充代码,完成后运行 `go test -v` 验证答案。
+95
View File
@@ -0,0 +1,95 @@
package main
import "fmt"
func exercise1() [5]int {
fmt.Println("=== 练习1:数组声明与初始化 ===")
// 声明一个长度为5的int数组
// 初始化所有元素为10
var arr ____
for i := 0; i < ____(arr); i++ {
arr[i] = ____
}
return ____
}
func exercise2() string {
fmt.Println("=== 练习2:数组访问与修改 ===")
// 修改数组元素并返回修改后的字符串表示
fruits := [4]string{"苹果", "香蕉", "橙子", "葡萄"}
fruits[____] = "芒果"
fmt.Printf("修改后的水果: %s\n", fruits)
return "芒果"
}
func exercise3() int {
fmt.Println("=== 练习3:数组求和 ===")
// 计算数组所有元素的和
numbers := [5]int{10, 20, 30, 40, 50}
sum := ____
for i := 0; i < ____(numbers); i++ {
sum += ____
}
return ____
}
func exercise4() [3]int {
fmt.Println("=== 练习4:数组拷贝 ===")
// 创建一个数组并拷贝到另一个数组
// 修改原数组,观察拷贝的数组是否变化
arr1 := [3]int{1, 2, 3}
arr2 := ____
arr1[0] = 99
return ____
}
func exercise5() int {
fmt.Println("=== 练习5:二维数组 ===")
// 计算二维数组的和
matrix := [3][2]int{{1, 2}, {3, 4}, {5, 6}}
sum := ____
for i := 0; i < ____(matrix); i++ {
for j := 0; j < ____(matrix[i]); j++ {
sum += ____
}
}
return ____
}
func main() {
fmt.Println("=== 数组 练习 ===\n")
result1 := exercise1()
fmt.Printf("练习1结果: %v\n", result1)
result2 := exercise2()
fmt.Printf("练习2结果: %s\n", result2)
result3 := exercise3()
fmt.Printf("练习3结果: %d\n", result3)
result4 := exercise4()
fmt.Printf("练习4结果: %v\n", result4)
result5 := exercise5()
fmt.Printf("练习5结果: %d\n", result5)
}
@@ -0,0 +1,79 @@
package main
import (
"bytes"
"os"
"strings"
"testing"
)
func captureOutput(f func()) string {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
f()
w.Close()
os.Stdout = old
var buf bytes.Buffer
buf.ReadFrom(r)
return buf.String()
}
func TestExercise1(t *testing.T) {
result := exercise1()
expected := [5]int{10, 10, 10, 10, 10}
if result != expected {
t.Errorf("期望 %v, 实际: %v", expected, result)
}
}
func TestExercise2(t *testing.T) {
result := exercise2()
if result != "芒果" {
t.Errorf("期望 '芒果', 实际: %s", result)
}
}
func TestExercise3(t *testing.T) {
result := exercise3()
if result != 150 {
t.Errorf("期望 150, 实际: %d", result)
}
}
func TestExercise4(t *testing.T) {
result := exercise4()
expected := [3]int{1, 2, 3}
if result != expected {
t.Errorf("期望 %v, 实际: %v", expected, result)
}
}
func TestExercise5(t *testing.T) {
result := exercise5()
if result != 21 {
t.Errorf("期望 21, 实际: %d", result)
}
}
func TestMainFunction(t *testing.T) {
output := captureOutput(main)
requiredSections := []string{
"=== 数组 练习 ===",
"练习1:数组声明与初始化",
"练习2:数组访问与修改",
"练习3:数组求和",
"练习4:数组拷贝",
"练习5:二维数组",
}
for _, section := range requiredSections {
if !strings.Contains(output, section) {
t.Errorf("期望输出包含 '%s'", section)
}
}
}
@@ -0,0 +1,53 @@
# 02-slices - 切片操作
## 知识点讲解
### 切片定义
```go
var s1 []int // 声明一个切片(nil切片)
s2 := []int{1, 2, 3, 4, 5} // 声明并初始化
s3 := make([]int, 5) // 创建长度为5的切片
s4 := make([]int, 5, 10) // 创建长度为5,容量为10的切片
```
### 从数组或切片创建
```go
arr := [5]int{1, 2, 3, 4, 5}
s1 := arr[1:3] // [2, 3]
s2 := arr[:3] // [1, 2, 3]
s3 := arr[2:] // [3, 4, 5]
```
### 添加元素
```go
s := []int{1, 2, 3}
s = append(s, 4) // [1, 2, 3, 4]
s = append(s, 5, 6) // [1, 2, 3, 4, 5, 6]
```
### 切片特性
- **动态长度**:可以自动扩容
- **引用类型**:切片指向底层数组
- **三个属性**:指针、长度、容量
- **浅拷贝**:切片赋值共享底层数组
### 重要提示
- 使用 `append` 时如果超出容量会创建新数组
- 切片切片操作不会复制元素,只是创建新的视图
## 学习目标
完成本练习后,你将:
- ✅ 掌握切片的声明和创建
- ✅ 学会使用 append 添加元素
- ✅ 理解切片的动态扩容
- ✅ 了解切片的底层结构
## 练习说明
打开 `main.go`,根据注释提示填充代码,完成后运行 `go test -v` 验证答案。
+96
View File
@@ -0,0 +1,96 @@
package main
import "fmt"
func exercise1() []int {
fmt.Println("=== 练习1:切片声明与初始化 ===")
// 创建一个长度为3的int切片,初始值为[1, 2, 3]
s := make([]int, ____)
s[0], s[1], s[2] = ____, ____, ____
return ____
}
func exercise2() int {
fmt.Println("=== 练习2:切片追加 ===")
// 向切片中添加元素并返回新长度
fruits := []string{"苹果", "香蕉"}
fruits = append(fruits, ____)
fruits = append(fruits, ____, ____)
return ____(fruits)
}
func exercise3() []int {
fmt.Println("=== 练习3:切片切片 ===")
// 从切片中提取子切片
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 提取索引2到6(不包含6)
sub1 := numbers[____:____]
// 提取前5个元素
sub2 := numbers[:____]
// 提取从第5个开始到末尾
sub3 := numbers[____:]
fmt.Printf("子切片1: %v\n", sub1)
fmt.Printf("子切片2: %v\n", sub2)
fmt.Printf("子切片3: %v\n", sub3)
return sub1
}
func exercise4() int {
fmt.Println("=== 练习4:切片拷贝 ===")
// 修改原始切片,观察使用copy复制的切片是否受影响
s1 := []int{1, 2, 3}
s2 := make([]int, ____(s1))
copy(____, s1)
s1[0] = 99
return ____
}
func exercise5() []int {
fmt.Println("=== 练习5:切片遍历 ===")
// 创建一个新切片,包含原切片中所有偶数
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var evens ____
for _, num := range ____ {
if num%2 == ____ {
evens = append(____, num)
}
}
return ____
}
func main() {
fmt.Println("=== 切片 练习 ===\n")
result1 := exercise1()
fmt.Printf("练习1结果: %v\n", result1)
result2 := exercise2()
fmt.Printf("练习2结果: %d\n", result2)
result3 := exercise3()
fmt.Printf("练习3结果: %v\n", result3)
result4 := exercise4()
fmt.Printf("练习4结果: %d\n", result4)
result5 := exercise5()
fmt.Printf("练习5结果: %v\n", result5)
}
@@ -0,0 +1,95 @@
package main
import (
"bytes"
"os"
"strings"
"testing"
)
func captureOutput(f func()) string {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
f()
w.Close()
os.Stdout = old
var buf bytes.Buffer
buf.ReadFrom(r)
return buf.String()
}
func TestExercise1(t *testing.T) {
result := exercise1()
expected := []int{1, 2, 3}
if len(result) != len(expected) {
t.Errorf("期望长度 %d, 实际: %d", len(expected), len(result))
}
for i, v := range expected {
if result[i] != v {
t.Errorf("索引 %d 期望 %d, 实际: %d", i, v, result[i])
}
}
}
func TestExercise2(t *testing.T) {
result := exercise2()
if result != 5 {
t.Errorf("期望长度 5, 实际: %d", result)
}
}
func TestExercise3(t *testing.T) {
result := exercise3()
expected := []int{3, 4, 5}
if len(result) != len(expected) {
t.Errorf("期望长度 %d, 实际: %d", len(expected), len(result))
}
for i, v := range expected {
if result[i] != v {
t.Errorf("索引 %d 期望 %d, 实际: %d", i, v, result[i])
}
}
}
func TestExercise4(t *testing.T) {
result := exercise4()
if result != 1 {
t.Errorf("期望 1, 实际: %d", result)
}
}
func TestExercise5(t *testing.T) {
result := exercise5()
expected := []int{2, 4, 6, 8, 10}
if len(result) != len(expected) {
t.Errorf("期望长度 %d, 实际: %d", len(expected), len(result))
}
for i, v := range expected {
if result[i] != v {
t.Errorf("索引 %d 期望 %d, 实际: %d", i, v, result[i])
}
}
}
func TestMainFunction(t *testing.T) {
output := captureOutput(main)
requiredSections := []string{
"=== 切片 练习 ===",
"练习1:切片声明与初始化",
"练习2:切片追加",
"练习3:切片切片",
"练习4:切片拷贝",
"练习5:切片遍历",
}
for _, section := range requiredSections {
if !strings.Contains(output, section) {
t.Errorf("期望输出包含 '%s'", section)
}
}
}
+60
View File
@@ -0,0 +1,60 @@
# 03-maps - map使用
## 知识点讲解
### map定义
```go
var m1 map[string]int // 声明一个map(nil map)
m2 := map[string]int{} // 声明并初始化空map
m3 := map[string]int{ // 声明并初始化
"张三": 90,
"李四": 85,
}
m4 := make(map[string]int) // 创建一个空map
```
### 添加和修改
```go
m := make(map[string]int)
m["key"] = 100 // 添加元素
m["key"] = 200 // 修改元素
```
### 读取和删除
```go
m := map[string]int{"a": 1, "b": 2}
value := m["a"] // 读取值
value, exists := m["c"] // 读取值,检查是否存在
delete(m, "b") // 删除元素
```
### 遍历map
```go
m := map[string]int{"a": 1, "b": 2, "c": 3}
for key, value := range m { // 遍历map
fmt.Printf("%s: %d\n", key, value)
}
```
### map特性
- **无序**:map中元素的顺序是不确定的
- **引用类型**:map赋值共享底层数据结构
- **零值**:未找到时对应类型的零值
- **并发安全**:多个goroutine同时读写需要加锁
## 学习目标
完成本练习后,你将:
- ✅ 掌握map的声明和初始化
- ✅ 学会添加、修改、删除元素
- ✅ 理解如何检查元素是否存在
- ✅ 掌握map的遍历方式
## 练习说明
打开 `main.go`,根据注释提示填充代码,完成后运行 `go test -v` 验证答案。
+112
View File
@@ -0,0 +1,112 @@
package main
import "fmt"
func exercise1() map[string]int {
fmt.Println("=== 练习1:map声明与初始化 ===")
// 创建一个map,键为string类型,值为int类型
// 添加"张三": 90, "李四": 85, "王五": 95
scores := ____
scores["张三"] = ____
scores["李四"] = ____
scores["王五"] = ____
return ____
}
func exercise2() string {
fmt.Println("=== 练习2:map读取与检查 ===")
// 读取map中的值并检查是否存在
scores := map[string]int{
"张三": 90,
"李四": 85,
}
score, exists := ____
if exists {
fmt.Printf("李四的分数是: %d\n", score)
}
return "85"
}
func exercise3() int {
fmt.Println("=== 练习3:map遍历 ===")
// 遍历map,计算所有分数的总和
scores := map[string]int{
"张三": 90,
"李四": 85,
"王五": 95,
"赵六": 88,
}
sum := ____
for ____, score := range ____ {
sum += ____
}
return ____
}
func exercise4() map[string]int {
fmt.Println("=== 练习4:map删除元素 ===")
// 删除map中的元素并返回删除后的map
scores := map[string]int{
"张三": 90,
"李四": 85,
"王五": 95,
}
____(scores, "李四")
return ____
}
func exercise5() []string {
fmt.Println("=== 练习5:map筛选 ===")
// 筛选出分数大于等于90的学生姓名
scores := map[string]int{
"张三": 90,
"李四": 85,
"王五": 95,
"赵六": 88,
"钱七": 92,
}
var topStudents ____
for name, score := range ____ {
if score >= ____ {
topStudents = append(____, ____)
}
}
return ____
}
func main() {
fmt.Println("=== map 练习 ===\n")
result1 := exercise1()
fmt.Printf("练习1结果: %v\n", result1)
result2 := exercise2()
fmt.Printf("练习2结果: %s\n", result2)
result3 := exercise3()
fmt.Printf("练习3结果: %d\n", result3)
result4 := exercise4()
fmt.Printf("练习4结果: %v\n", result4)
result5 := exercise5()
fmt.Printf("练习5结果: %v\n", result5)
}
@@ -0,0 +1,95 @@
package main
import (
"bytes"
"os"
"strings"
"testing"
)
func captureOutput(f func()) string {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
f()
w.Close()
os.Stdout = old
var buf bytes.Buffer
buf.ReadFrom(r)
return buf.String()
}
func TestExercise1(t *testing.T) {
result := exercise1()
if result["张三"] != 90 || result["李四"] != 85 || result["王五"] != 95 {
t.Errorf("期望值不正确: %v", result)
}
}
func TestExercise2(t *testing.T) {
result := exercise2()
if result != "85" {
t.Errorf("期望 '85', 实际: %s", result)
}
}
func TestExercise3(t *testing.T) {
result := exercise3()
if result != 358 {
t.Errorf("期望 358, 实际: %d", result)
}
}
func TestExercise4(t *testing.T) {
result := exercise4()
if _, exists := result["李四"]; exists {
t.Errorf("李四应该已被删除")
}
if result["张三"] != 90 {
t.Errorf("张三的分数应该是 90")
}
}
func TestExercise5(t *testing.T) {
result := exercise5()
expectedCount := 3
if len(result) != expectedCount {
t.Errorf("期望数量 %d, 实际: %d", expectedCount, len(result))
}
expectedNames := map[string]bool{"张三": false, "王五": false, "钱七": false}
for _, name := range result {
if _, found := expectedNames[name]; !found {
t.Errorf("意外的学生名: %s", name)
}
expectedNames[name] = true
}
for name, found := range expectedNames {
if !found {
t.Errorf("缺少学生名: %s", name)
}
}
}
func TestMainFunction(t *testing.T) {
output := captureOutput(main)
requiredSections := []string{
"=== map 练习 ===",
"练习1:map声明与初始化",
"练习2:map读取与检查",
"练习3:map遍历",
"练习4:map删除元素",
"练习5:map筛选",
}
for _, section := range requiredSections {
if !strings.Contains(output, section) {
t.Errorf("期望输出包含 '%s'", section)
}
}
}
@@ -0,0 +1,80 @@
# 04-structs - 结构体
## 知识点讲解
### 结构体定义
```go
type Person struct {
Name string
Age int
Address string
}
```
### 创建结构体
```go
// 方式1:按顺序赋值
p1 := Person{"张三", 20, "北京"}
// 方式2:指定字段名(推荐)
p2 := Person{
Name: "李四",
Age: 25,
Address: "上海",
}
// 方式3:使用new关键字,返回指针
p3 := new(Person)
p3.Name = "王五"
p3.Age = 30
```
### 结构体字段
```go
p := Person{Name: "张三", Age: 20}
p.Name = "李四" // 修改字段值
fmt.Println(p.Age) // 访问字段值
```
### 结构体方法
```go
func (p Person) Introduce() string {
return fmt.Sprintf("我是%s,今年%d岁", p.Name, p.Age)
}
```
### 匿名字段与嵌套
```go
type Address struct {
City string
Country string
}
type Person struct {
Name string
Address Address // 嵌套结构体
}
```
### 重要提示
- 结构体字段首字母大写表示公开,小写表示私有
- 结构体作为函数参数时默认是值传递
- 使用指针传递可以避免拷贝,提高性能
## 学习目标
完成本练习后,你将:
- ✅ 掌握结构体的定义和创建
- ✅ 学会访问和修改结构体字段
- ✅ 理解结构体指针的使用
- ✅ 掌握结构体的方法定义
## 练习说明
打开 `main.go`,根据注释提示填充代码,完成后运行 `go test -v` 验证答案。
+111
View File
@@ -0,0 +1,111 @@
package main
import "fmt"
type Person struct {
Name string
Age int
Score float64
}
func exercise1() Person {
fmt.Println("=== 练习1:结构体定义与创建 ===")
// 创建一个Person结构体实例
// 名字为"张三", 年龄为20, 分数为90.5
var p ____
p.Name = ____
p.Age = ____
p.Score = ____
return ____
}
func exercise2() Person {
fmt.Println("=== 练习2:结构体字段修改 ===")
// 修改结构体字段并返回
p := Person{Name: "李四", Age: 25, Score: 85.0}
p.____ = 26
p.____ = 88.5
return ____
}
type Book struct {
Title string
Author string
Price float64
}
func (b Book) GetInfo() string {
fmt.Println("=== 练习3:结构体方法 ===")
// 返回书籍的详细信息
// 格式: "《书名》- 作者, 价格 ¥XX.XX"
return fmt.Sprintf("《%s》-%s, 价格 ¥%.2f", ____, ____, ____)
}
func exercise4() *Book {
fmt.Println("=== 练习4:结构体指针 ===")
// 创建一个Book结构体指针
// 使用new创建,并设置字段的值
book := ____
book.Title = "Go语言圣经"
book.____ = "Alan Donovan"
book.____ = 99.0
return ____
}
type Student struct {
Name string
Grade int
}
type Teacher struct {
Name string
Subject string
}
func exercise5() interface{} {
fmt.Println("=== 练习5:结构体数组 ===")
// 创建一个结构体切片
// 包含2个学生和2个老师
students := []Student{
{Name: "张三", Grade: 1},
{Name: ____, Grade: 2},
}
teachers := []Teacher{
{Name: "王老师", Subject: "数学"},
{Name: ____, Subject: "英语"},
}
return append(students, teachers...)
}
func main() {
fmt.Println("=== 结构体 练习 ===\n")
result1 := exercise1()
fmt.Printf("练习1结果: %+v\n", result1)
result2 := exercise2()
fmt.Printf("练习2结果: %+v\n", result2)
result3 := exercise3()
fmt.Printf("练习3结果: %s\n", result3)
result4 := exercise4()
fmt.Printf("练习4结果: %+v\n", *result4)
result5 := exercise5()
fmt.Printf("练习5结果: %v (共%d个)\n", result5, len(result5.([]interface{})))
}
@@ -0,0 +1,85 @@
package main
import (
"bytes"
"os"
"strings"
"testing"
)
func captureOutput(f func()) string {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
f()
w.Close()
os.Stdout = old
var buf bytes.Buffer
buf.ReadFrom(r)
return buf.String()
}
func TestExercise1(t *testing.T) {
result := exercise1()
if result.Name != "张三" || result.Age != 20 || result.Score != 90.5 {
t.Errorf("期望值不正确: %+v", result)
}
}
func TestExercise2(t *testing.T) {
result := exercise2()
if result.Name != "李四" || result.Age != 26 || result.Score != 88.5 {
t.Errorf("期望值不正确: %+v", result)
}
}
func TestExercise3(t *testing.T) {
book := Book{Title: "Go语言实战", Author: "William Kennedy", Price: 59.9}
result := book.GetInfo()
expected := "《Go语言实战》-William Kennedy, 价格 ¥59.90"
if result != expected {
t.Errorf("期望 '%s', 实际: '%s'", expected, result)
}
}
func TestExercise4(t *testing.T) {
result := exercise4()
if result.Title != "Go语言圣经" || result.Author != "Alan Donovan" || result.Price != 99.0 {
t.Errorf("期望值不正确: %+v", *result)
}
}
func TestExercise5(t *testing.T) {
result := exercise5()
slice, ok := result.([]interface{})
if !ok {
t.Error("返回结果应该是切片")
return
}
if len(slice) != 4 {
t.Errorf("期望4个元素, 实际: %d", len(slice))
}
}
func TestMainFunction(t *testing.T) {
output := captureOutput(main)
requiredSections := []string{
"=== 结构体 练习 ===",
"练习1:结构体定义与创建",
"练习2:结构体字段修改",
"练习3:结构体方法",
"练习4:结构体指针",
"练习5:结构体数组",
}
for _, section := range requiredSections {
if !strings.Contains(output, section) {
t.Errorf("期望输出包含 '%s'", section)
}
}
}
@@ -0,0 +1,60 @@
# 01-pointers - 指针基础
## 知识点讲解
### 指针声明
```go
var p *int // 声明一个int类型的指针
x := 10
p = &x // p是x的地址
```
### 指针操作
```go
x := 10
p := &x // 取地址
fmt.Println(*p) // 解引用,输出10
*p = 20 // 通过指针修改值
fmt.Println(x) // 输出20
```
### 指针与函数
```go
function modifyValue(p *int) {
*p = 100 // 修改指针指向的值
}
x := 10
modifyValue(&x) // 传递地址
fmt.Println(x) // 输出100
```
### 零值
```go
var p *int // nil指针
fmt.Println(p == nil) // true
```
### 重要提示
- Go中有指针但没有指针运算
- 指针可以用于共享数据、避免拷贝
- 指针的零值是nil
- 解引用nil指针会panic
## 学习目标
完成本练习后,你将:
- ✅ 掌握指针的声明和创建
- ✅ 学会取地址和解引用操作
- ✅ 理解指针与函数参数的关系
- ✅ 了解nil指针的概念
## 练习说明
打开 `main.go`,根据注释提示填充代码,完成后运行 `go test -v` 验证答案。
@@ -0,0 +1,96 @@
package main
import "fmt"
func exercise1() int {
fmt.Println("=== 练习1:指针声明 ===")
// 声明一个int变量和一个指向它的指针
var x int = 10
var p ____ // 声明指针类型
p = ____ // p指向x的地址
fmt.Printf("x的值: %d, x的地址: %p\n", x, p)
fmt.Printf("*p的值: %d\n", ____)
return ____
}
func exercise2() int {
fmt.Println("=== 练习2:指针解引用 ===")
// 通过指针修改值
x := 20
p := ____
____ = 30
fmt.Println("修改后x的值:", x)
return ____
}
func modifyByPointer(p *int) {
// 通过指针修改值
____ = 100
}
func exercise3() int {
fmt.Println("=== 练习3:指针作为函数参数 ===")
// 使用指针作为函数参数
x := 50
____(&x)
if x != 100 {
fmt.Println("指针参数修改失败!")
}
return ____
}
func exercise4() bool {
fmt.Println("=== 练习4:指针零值 ===")
// 检查指针的零值
var p ____
fmt.Printf("未初始化的指针: %v, 是否为nil: %t\n", p, ____)
return ____
}
func exercise5() int {
fmt.Println("=== 练习5:指针与数组 ===")
// 使用指针访问数组元素
arr := [5]int{10, 20, 30, 40, 50}
p := ____
fmt.Printf("第一个元素: %d\n", ____)
fmt.Printf("第二个元素: %d\n", ____(p+1))
return arr[0]
}
func main() {
fmt.Println("=== 指针 练习 ===\n")
result1 := exercise1()
fmt.Printf("练习1结果: %d\n", result1)
result2 := exercise2()
fmt.Printf("练习2结果: %d\n", result2)
result3 := exercise3()
fmt.Printf("练习3结果: %d\n", result3)
result4 := exercise4()
fmt.Printf("练习4结果: %t\n", result4)
result5 := exercise5()
fmt.Printf("练习5结果: %d\n", result5)
}
@@ -0,0 +1,77 @@
package main
import (
"bytes"
"os"
"strings"
"testing"
)
func captureOutput(f func()) string {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
f()
w.Close()
os.Stdout = old
var buf bytes.Buffer
buf.ReadFrom(r)
return buf.String()
}
func TestExercise1(t *testing.T) {
result := exercise1()
if result != 10 {
t.Errorf("期望 10, 实际: %d", result)
}
}
func TestExercise2(t *testing.T) {
result := exercise2()
if result != 30 {
t.Errorf("期望 30, 实际: %d", result)
}
}
func TestExercise3(t *testing.T) {
result := exercise3()
if result != 100 {
t.Errorf("期望 100, 实际: %d", result)
}
}
func TestExercise4(t *testing.T) {
result := exercise4()
if !result {
t.Errorf("期望 true, 实际: %t", result)
}
}
func TestExercise5(t *testing.T) {
result := exercise5()
if result != 10 {
t.Errorf("期望 10, 实际: %d", result)
}
}
func TestMainFunction(t *testing.T) {
output := captureOutput(main)
requiredSections := []string{
"=== 指针 练习 ===",
"练习1:指针声明",
"练习2:指针解引用",
"练习3:指针作为函数参数",
"练习4:指针零值",
"练习5:指针与数组",
}
for _, section := range requiredSections {
if !strings.Contains(output, section) {
t.Errorf("期望输出包含 '%s'", section)
}
}
}
@@ -0,0 +1,63 @@
# 02-new-make - new vs make
## 知识点讲解
### new函数
```go
p := new(int) // 返回指向int类型零值的指针
fmt.Println(*p) // 输出: 0
*p = 10 // 可以通过指针修改值
```
**特点**:
- 返回指向类型的零值指针
- 适用于所有类型
- 内存已初始化为零值但不执行构造逻辑
### make函数
```go
// 创建切片
s := make([]int, 5) // [0, 0, 0, 0, 0]
s := make([]int, 3, 10) // 长度3, 容量10
// 创建map
m := make(map[string]int)
m["key"] = 100
// 创建channel
ch := make(chan int)
```
**特点**:
- 只用于创建引用类型(slice、map、channel)
- 返回的是初始化后的类型(非指针)
- 会执行类型的初始化逻辑
### 主要区别
| 特性 | new() | make() |
|------|-------|--------|
| 返回类型 | 指针 | 值 |
| 适用类型 | 所有类型 | slice, map, channel |
| 初始化 | 仅零值 | 完整初始化 |
| 常用场景 | 需要指针时 | 创建引用类型时 |
### 重要提示
- 使用 `new` 创建的指针指向零值
- 使用 `make` 创建的引用类型可以直接使用
- 不要用 `new` 创建切片、map或channel
## 学习目标
完成本练习后,你将:
- ✅ 理解new和make的区别
- ✅ 掌握new的使用场景
- ✅ 掌握make的使用场景
- ✅ 学会正确选择new或make
## 练习说明
打开 `main.go`,根据注释提示填充代码,完成后运行 `go test -v` 验证答案。
+102
View File
@@ -0,0 +1,102 @@
package main
import "fmt"
func exercise1() int {
fmt.Println("=== 练习1:new创建指针 ===")
// 使用new创建一个int类型的指针
p := ____ // new返回指向int零值的指针
fmt.Printf("new创建的指针: %v, 解引用值: %d\n", p, ____)
*p = 10
fmt.Printf("修改后的值: %d\n", ____)
return ____
}
func exercise2() []int {
fmt.Println("=== 练习2:make创建切片 ===")
// 使用make创建一个长度为5的int切片
s := ____
fmt.Printf("创建的切片: %v, 长度: %d\n", s, ____)
fmt.Printf("切片的第一个元素: %d\n", s[____])
return ____
}
func exercise3() map[string]int {
fmt.Println("=== 练习3:make创建map ===")
// 使用make创建一个map
m := ____
m["张三"] = 90
fmt.Printf("map内容: %v\n", ____)
return ____
}
func exercise4() int {
fmt.Println("=== 练习4:new vs make - 切片 ===")
// 分别使用new和make创建切片,观察区别
newSlice := new([]int) // 返回指针,指向nil切片
makeSlice := make([]int, 3) // 返回切片实例
fmt.Printf("new创建的切片: %v, 是否为nil: %t\n", ____, *newSlice == nil)
fmt.Printf("make创建的切片: %v, 是否为nil: %t\n", ____, makeSlice == nil)
if *newSlice == nil {
*newSlice = make([]int, 3)
____[0] = 100
}
return ____
}
func exercise5() string {
fmt.Println("=== 练习5:new vs make - 结构体 ===")
// 使用new创建结构体指针
type Person struct {
Name string
Age int
}
p := ____
fmt.Printf("new创建的结构体: %+v\n", ____)
p.Name = "张三"
p.Age = 20
fmt.Printf("修改后的结构体: %+v\n", ____)
return ____
}
func main() {
fmt.Println("=== new vs make 练习 ===\n")
result1 := exercise1()
fmt.Printf("练习1结果: %d\n", result1)
result2 := exercise2()
fmt.Printf("练习2结果: %v\n", result2)
result3 := exercise3()
fmt.Printf("练习3结果: %v\n", result3)
result4 := exercise4()
fmt.Printf("练习4结果: %d\n", result4)
result5 := exercise5()
fmt.Printf("练习5结果: %s\n", result5)
}
@@ -0,0 +1,77 @@
package main
import (
"bytes"
"os"
"strings"
"testing"
)
func captureOutput(f func()) string {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
f()
w.Close()
os.Stdout = old
var buf bytes.Buffer
buf.ReadFrom(r)
return buf.String()
}
func TestExercise1(t *testing.T) {
result := exercise1()
if result != 10 {
t.Errorf("期望 10, 实际: %d", result)
}
}
func TestExercise2(t *testing.T) {
result := exercise2()
if len(result) != 5 {
t.Errorf("期望长度 5, 实际: %d", len(result))
}
}
func TestExercise3(t *testing.T) {
result := exercise3()
if len(result) != 1 {
t.Errorf("期望长度 1, 实际: %d", len(result))
}
}
func TestExercise4(t *testing.T) {
result := exercise4()
if result != 100 {
t.Errorf("期望 100, 实际: %d", result)
}
}
func TestExercise5(t *testing.T) {
result := exercise5()
if result != "张三" {
t.Errorf("期望 '张三', 实际: %s", result)
}
}
func TestMainFunction(t *testing.T) {
output := captureOutput(main)
requiredSections := []string{
"=== new vs make 练习 ===",
"练习1:new创建指针",
"练习2:make创建切片",
"练习3:make创建map",
"练习4:new vs make - 切片",
"练习5:new vs make - 结构体",
}
for _, section := range requiredSections {
if !strings.Contains(output, section) {
t.Errorf("期望输出包含 '%s'", section)
}
}
}
@@ -0,0 +1,75 @@
# 03-value-pointer - 值传递vs指针传递
## 知识点讲解
### 值传递
```go
func modifyValue(x int) {
x = 100 // 修改的是函数内的副本
}
num := 10
modifyValue(num)
fmt.Println(num) // 输出: 10(原值不变)
```
### 指针传递
```go
func modifyPointer(p *int) {
*p = 100 // 修改的是指针指向的值
}
num := 10
modifyPointer(&num)
fmt.Println(num) // 输出: 100(原值被修改)
```
### 结构体的值传递
```go
type Person struct {
Name string
Age int
}
func modifyPersonValue(p Person) {
p.Name = "李四" // 修改副本
}
p := Person{Name: "张三", Age: 20}
modifyPersonValue(p)
fmt.Println(p.Name) // 输出: 张三(原值不变)
```
### 结构体的指针传递
```go
func modifyPersonPointer(p *Person) {
p.Name = "李四" // 修改原值
}
p := Person{Name: "张三", Age: 20}
modifyPersonPointer(&p)
fmt.Println(p.Name) // 输出: 李四(原值被修改)
```
### 重要提示
- Go中所有参数传递都是值传递
- 传递指针实际是拷贝指针地址,但通过指针可以修改原值
- 大结构体使用指针传递可以避免大量拷贝
- 切片、map等引用类型传递也具有类似指针的行为
## 学习目标
完成本练习后,你将:
- ✅ 理解值传递和指针传递的区别
- ✅ 掌握何时使用值传递
- ✅ 掌握何时使用指针传递
- ✅ 了解性能方面的考虑
## 练习说明
打开 `main.go`,根据注释提示填充代码,完成后运行 `go test -v` 验证答案。
@@ -0,0 +1,130 @@
package main
import "fmt"
func modifyByValue(x int) int {
// 值传递:修改函数内的副本
x = ____
return ____
}
func modifyByPointer(p *int) int {
// 指针传递:修改指针指向的值
____ = ____
return ____
}
type Counter struct {
Value int
}
func (c Counter) Increment() int {
// 值接收者:修改副本
c.Value += 1
return ____
}
func (c *Counter) IncrementPointer() int {
// 指针接收者:修改原值
c.Value += ____
return ____
}
func exercise1() int {
fmt.Println("=== 练习1:值传递 ===")
// 值传递不会修改原值
num := 10
result := modifyByValue(____)
fmt.Printf("原值: %d, 函数返回值: %d\n", ____, result)
return ____
}
func exercise2() int {
fmt.Println("=== 练习2:指针传递 ===")
// 指针传递会修改原值
num := 10
result := ____
fmt.Printf("原值: %d, 函数返回值: %d\n", ____, result)
return ____
}
func exercise3() int {
fmt.Println("=== 练习3:结构体值传递 ===")
// 值方法不会修改原结构体
counter := Counter{Value: 0}
for i := 0; i < 5; i++ {
counter.____()
}
fmt.Printf("原结构体值: %d\n", ____)
return ____
}
func exercise4() int {
fmt.Println("=== 练习4:结构体指针传递 ===")
// 指针方法会修改原结构体
counter := Counter{Value: 0}
for i := 0; i < 5; i++ {
counter.____()
}
fmt.Printf("原结构体值: %d\n", ____)
return ____
}
type SliceContainer struct {
Data []int
}
func (s *SliceContainer) Add(value int) {
// 指针方法可以修改切片内容
s.Data = append(____, ____)
}
func exercise5() []int {
fmt.Println("=== 练习5:切片与结构体 ===")
// 使用切片的结构体
container := SliceContainer{Data: []int{}}
container.Add(10)
container.Add(20)
container.Add(30)
fmt.Printf("切片内容: %v\n", ____)
return ____
}
func main() {
fmt.Println("=== 值传递 vs 指针传递 练习 ===\n")
result1 := exercise1()
fmt.Printf("练习1结果: %d\n", result1)
result2 := exercise2()
fmt.Printf("练习2结果: %d\n", result2)
result3 := exercise3()
fmt.Printf("练习3结果: %d\n", result3)
result4 := exercise4()
fmt.Printf("练习4结果: %d\n", result4)
result5 := exercise5()
fmt.Printf("练习5结果: %v\n", result5)
}
@@ -0,0 +1,83 @@
package main
import (
"bytes"
"os"
"strings"
"testing"
)
func captureOutput(f func()) string {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
f()
w.Close()
os.Stdout = old
var buf bytes.Buffer
buf.ReadFrom(r)
return buf.String()
}
func TestExercise1(t *testing.T) {
result := exercise1()
if result != 10 {
t.Errorf("期望 10, 实际: %d", result)
}
}
func TestExercise2(t *testing.T) {
result := exercise2()
if result != 100 {
t.Errorf("期望 100, 实际: %d", result)
}
}
func TestExercise3(t *testing.T) {
result := exercise3()
if result != 0 {
t.Errorf("期望 0, 实际: %d", result)
}
}
func TestExercise4(t *testing.T) {
result := exercise4()
if result != 5 {
t.Errorf("期望 5, 实际: %d", result)
}
}
func TestExercise5(t *testing.T) {
result := exercise5()
expected := []int{10, 20, 30}
if len(result) != len(expected) {
t.Errorf("期望长度 %d, 实际: %d", len(expected), len(result))
}
for i, val := range expected {
if result[i] != val {
t.Errorf("索引 %d 期望 %d, 实际: %d", i, val, result[i])
}
}
}
func TestMainFunction(t *testing.T) {
output := captureOutput(main)
requiredSections := []string{
"=== 值传递 vs 指针传递 练习 ===",
"练习1:值传递",
"练习2:指针传递",
"练习3:结构体值传递",
"练习4:结构体指针传递",
"练习5:切片与结构体",
}
for _, section := range requiredSections {
if !strings.Contains(output, section) {
t.Errorf("期望输出包含 '%s'", section)
}
}
}
+74
View File
@@ -0,0 +1,74 @@
# 01-interfaces - 接口定义与实现
## 知识点讲解
### 接口定义
```go
type Shape interface {
Area() float64
Perimeter() float64
}
```
### 接口实现
```go
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
```
**注意**:Go中接口是隐式实现的,不需要显式声明
### 多态
```go
func printShapeInfo(s Shape) {
fmt.Printf("面积: %.2f\n", s.Area())
fmt.Printf("周长: %.2f\n", s.Perimeter())
}
rect := Rectangle{Width: 10, Height: 5}
circle := Circle{Radius: 7}
printShapeInfo(rect) // 可以传递Rectangle
printShapeInfo(circle) // 也可以传递Circle
```
### 空接口
```go
var any interface{}
any = 10
any = "hello"
any = []int{1, 2, 3}
```
### 重要提示
- 接口定义一组方法签名
- 任何类型只要实现了接口的所有方法,就自动实现了该接口
- 空接口 `interface{}` 可以持有任何类型的值
- 接口类型可以包含零个或多个方法
## 学习目标
完成本练习后,你将:
- ✅ 掌握接口的定义方式
- ✅ 学会实现接口
- ✅ 理解多态的概念
- ✅ 了解空接口的用法
## 练习说明
打开 `main.go`,根据注释提示填充代码,完成后运行 `go test -v` 验证答案。
+155
View File
@@ -0,0 +1,155 @@
package main
import (
"fmt"
"math"
)
type Shape interface {
Area() float64
Perimeter() float64
}
type Rectangle struct {
Width float64
Height float64
}
type Circle struct {
Radius float64
}
func (r Rectangle) Area() float64 {
return ____
}
func (r Rectangle) Perimeter() float64 {
return ____
}
func (c Circle) Area() float64 {
return math.Pi * ____ * ____
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * ____
}
type ShapePrinter interface {
PrintInfo()
}
func (r Rectangle) PrintInfo() {
fmt.Printf("矩形 - 宽: %.2f, 高: %.2f\n", ____, ____)
}
func (c Circle) PrintInfo() {
fmt.Printf("圆形 - 半径: %.2f\n", ____)
}
func exercise1() float64 {
fmt.Println("=== 练习1:矩形接口实现 ===")
rect := Rectangle{Width: 10, Height: 5}
area := ____
perimeter := ____
fmt.Printf("矩形面积: %.2f, 周长: %.2f\n", area, perimeter)
return ____
}
func exercise2() string {
fmt.Println("=== 练习2:圆形接口实现 ===")
circle := Circle{Radius: 7}
area := ____
info := fmt.Sprintf("圆形面积: %.2f", area)
fmt.Println(____)
return ____
}
func exercise3() string {
fmt.Println("=== 练习3:多态 ===")
// 定义一个Shape类型的变量,分别指向Rectangle和Circle
var s Shape
s = ____
fmt.Printf("形状类型: %T, 面积: %.2f\n", s, s.____())
s = ____
fmt.Printf("形状类型: %T, 面积: %.2f\n", s, s.____())
return "多态演示完成"
}
func exercise4() interface{} {
fmt.Println("=== 练习4:空接口 ===")
// 空接口可以持有任何类型
var data interface{}
data = ____
fmt.Printf("数据: %v, 类型: %T\n", data, data)
data = ____
fmt.Printf("数据: %v, 类型: %T\n", data, data)
data = ____
fmt.Printf("数据: %v, 类型: %T\n", data, data)
return ____
}
type Animal interface {
Sound() string
}
type Dog struct{}
func (d Dog) Sound() string {
return "汪汪"
}
type Cat struct{}
func (c Cat) Sound() string {
return ____
}
func exercise5() string {
fmt.Println("=== 练习5:接口组合与使用 ===")
// 创建Animal切片,包含不同的动物
animals := []Animal{____, ____}
for _, animal := range ____ {
fmt.Printf("动物发出的声音: %s\n", animal.____())
}
return "动物接口演示完成"
}
func main() {
fmt.Println("=== 接口 练习 ===\n")
result1 := exercise1()
fmt.Printf("练习1结果: %.2f\n", result1)
result2 := exercise2()
fmt.Printf("练习2结果: %s\n", result2)
result3 := exercise3()
fmt.Printf("练习3结果: %s\n", result3)
result4 := exercise4()
fmt.Printf("练习4结果: %v (类型: %T)\n", result4, result4)
result5 := exercise5()
fmt.Printf("练习5结果: %s\n", result5)
}
+77
View File
@@ -0,0 +1,77 @@
package main
import (
"bytes"
"os"
"strings"
"testing"
)
func captureOutput(f func()) string {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
f()
w.Close()
os.Stdout = old
var buf bytes.Buffer
buf.ReadFrom(r)
return buf.String()
}
func TestExercise1(t *testing.T) {
result := exercise1()
if result != 50 {
t.Errorf("期望 50, 实际: %f", result)
}
}
func TestExercise2(t *testing.T) {
result := exercise2()
if !strings.Contains(result, "圆形面积:") {
t.Errorf("期望包含 '圆形面积:', 实际: %s", result)
}
}
func TestExercise3(t *testing.T) {
result := exercise3()
if result != "多态演示完成" {
t.Errorf("期望 '多态演示完成', 实际: %s", result)
}
}
func TestExercise4(t *testing.T) {
result := exercise4()
if result == nil {
t.Error("期望非nil值")
}
}
func TestExercise5(t *testing.T) {
result := exercise5()
if result != "动物接口演示完成" {
t.Errorf("期望 '动物接口演示完成', 实际: %s", result)
}
}
func TestMainFunction(t *testing.T) {
output := captureOutput(main)
requiredSections := []string{
"=== 接口 练习 ===",
"练习1:矩形接口实现",
"练习2:圆形接口实现",
"练习3:多态",
"练习4:空接口",
"练习5:接口组合与使用",
}
for _, section := range requiredSections {
if !strings.Contains(output, section) {
t.Errorf("期望输出包含 '%s'", section)
}
}
}
+73
View File
@@ -0,0 +1,73 @@
# 02-methods - 方法定义
## 知识点讲解
### 方法定义
```go
// 值接收者
func (p Person) GetName() string {
return p.Name
}
// 指针接收者
func (p *Person) SetName(name string) {
p.Name = name
}
```
### 值接收者 vs 指针接收者
```go
type Counter struct {
value int
}
func (c Counter) GetValue() int {
return c.value // 只读,不修改原值
}
func (c *Counter) Increment() {
c.value++ // 值读写,修改原值
}
```
### 方法选择
| 场景 | 接收者类型 |
|------|-----------|
| 方法不需要修改接收者 | 值接收者 |
| 方法需要修改接收者 | 指针接收者 |
| 接收者较大,避免拷贝 | 指针接收者 |
| 保持接口兼容性 | 值接收者 |
### 方法表达式与值
```go
c := Counter{value: 10}
// 方法调用
c.Increment()
// 方法表达式
f := (*Counter).Increment
f(&c)
```
### 重要提示
- 值接收者可以用于指针类型,反之则需要Go自动转换
- 方法集规则影响接口实现
- 选择接收者类型要考虑一致性
## 学习目标
完成本练习后,你将:
- ✅ 掌握方法的定义方式
- ✅ 理解值接收者和指针接收者的区别
- ✅ 学会为结构体定义方法
- ✅ 掌握方法的选择原则
## 练习说明
打开 `main.go`,根据注释提示填充代码,完成后运行 `go test -v` 验证答案。
+163
View File
@@ -0,0 +1,163 @@
package main
import "fmt"
type Person struct {
Name string
Age int
Email string
}
func (p Person) GetName() string {
return ____
}
func (p Person) GetAge() int {
return ____
}
func (p *Person) SetName(name string) {
____ = ____
}
func (p *Person) SetEmail(email string) {
____ = ____
}
func (p Person) GetInfo() string {
return fmt.Sprintf("%s, %d岁, %s", ____, ____, ____)
}
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return ____
}
func (r *Rectangle) Resize(width, height float64) {
____ = ____
____ = ____
}
type BankAccount struct {
Balance float64
Owner string
}
func (b *BankAccount) Deposit(amount float64) {
b.Balance += ____
}
func (b *BankAccount) Withdraw(amount float64) bool {
if b.Balance >= amount {
b.Balance -= ____
return ____
}
return false
}
func (b BankAccount) GetBalance() float64 {
return ____
}
func exercise1() string {
fmt.Println("=== 练习1:基础方法 ===")
person := Person{Name: "张三", Age: 25, Email: "zhangsan@example.com"}
name := ____
age := ____
person.SetName(____)
person.SetEmail(____)
fmt.Printf("姓名: %s, 年龄: %d\n", ____, person.Age)
return ____
}
func exercise2() float64 {
fmt.Println("=== 练习2:矩形方法 ===")
rect := Rectangle{Width: 10, Height: 5}
area := ____
fmt.Printf("矩形面积: %.2f\n", area)
rect.Resize(____, ____)
newArea := ____
fmt.Printf("调整后面积: %.2f\n", ____)
return ____
}
func exercise3() float64 {
fmt.Println("=== 练习3:银行账户方法 ===")
account := BankAccount{Balance: 1000, Owner: "张三"}
account.____(500)
fmt.Printf("存款后余额: %.2f\n", account.____())
success := account.____(300)
if success {
fmt.Printf("取款成功,余额: %.2f\n", account.____())
}
return ____
}
func exercise4() string {
fmt.Println("=== 练习4:方法链 ===")
// 使用方法链调用
person := Person{Name: "李四", Age: 30, Email: "lisi@example.com"}
person.SetName("李四(更新)").SetEmail("lisi_new@example.com")
return ____
}
func exercise5() int {
fmt.Println("=== 练习5:计算器方法 ===")
type Calculator struct {
value int
}
calc := Calculator{value: 0}
calc.setValue := func(v int) {
calc.value = v
}
calc.setValue(10)
fmt.Printf("计算器值: %d\n", calc.value)
return ____
}
func main() {
fmt.Println("=== 方法 练习 ===\n")
result1 := exercise1()
fmt.Printf("练习1结果: %s\n", result1)
result2 := exercise2()
fmt.Printf("练习2结果: %.2f\n", result2)
result3 := exercise3()
fmt.Printf("练习3结果: %.2f\n", result3)
result4 := exercise4()
fmt.Printf("练习4结果: %s\n", result4)
result5 := exercise5()
fmt.Printf("练习5结果: %d\n", result5)
}
+77
View File
@@ -0,0 +1,77 @@
package main
import (
"bytes"
"os"
"strings"
"testing"
)
func captureOutput(f func()) string {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
f()
w.Close()
os.Stdout = old
var buf bytes.Buffer
buf.ReadFrom(r)
return buf.String()
}
func TestExercise1(t *testing.T) {
result := exercise1()
if result != "张三(更新)" {
t.Errorf("期望 '张三(更新)', 实际: %s", result)
}
}
func TestExercise2(t *testing.T) {
result := exercise2()
if result != 80 {
t.Errorf("期望 80, 实际: %f", result)
}
}
func TestExercise3(t *testing.T) {
result := exercise3()
if result != 1200 {
t.Errorf("期望 1200, 实际: %f", result)
}
}
func TestExercise4(t *testing.T) {
result := exercise4()
if result != "李四(更新)" {
t.Errorf("期望 '李四(更新)', 实际: %s", result)
}
}
func TestExercise5(t *testing.T) {
result := exercise5()
if result != 10 {
t.Errorf("期望 10, 实际: %d", result)
}
}
func TestMainFunction(t *testing.T) {
output := captureOutput(main)
requiredSections := []string{
"=== 方法 练习 ===",
"练习1:基础方法",
"练习2:矩形方法",
"练习3:银行账户方法",
"练习4:方法链",
"练习5:计算器方法",
}
for _, section := range requiredSections {
if !strings.Contains(output, section) {
t.Errorf("期望输出包含 '%s'", section)
}
}
}
+93
View File
@@ -0,0 +1,93 @@
# 03-embedding - 组合与类型断言
## 知识点讲解
### 结构体嵌入(组合)
```go
type Address struct {
City string
Street string
}
type Person struct {
Name string
Address Address // 嵌套结构体
}
type Employee struct {
Person // 匿名嵌入
EmployeeID int
}
emp := Employee{
Person: Person{Name: "张三"},
}
fmt.Println(emp.Name) // 可以直接访问嵌入的字段
```
### 接口嵌入
```go
type Reader interface {
Read() string
}
type Writer interface {
Write(s string)
}
type ReadWriter interface {
Reader
Writer // 接口组合
}
```
### 类型断言
```go
var i interface{} = "hello"
// 方式1:标准断言
s := i.(string)
// 方式2:带检查
s, ok := i.(string)
if !ok {
fmt.Println("类型断言失败")
}
```
### 类型选择
```go
func printValue(v interface{}) {
switch v.(type) {
case int:
fmt.Println("整数:", v)
case string:
fmt.Println("字符串:", v)
default:
fmt.Println("未知类型")
}
}
```
### 重要提示
- Go使用组合而非继承
- 结构体嵌入可以实现类似继承的效果
- 类型断言若失败会panic,需谨慎使用
- 类型选择用于处理多种类型
## 学习目标
完成本练习后,你将:
- ✅ 掌握结构体嵌入的使用
- ✅ 理解接口嵌入的概念
- ✅ 学会使用类型断言
- ✅ 掌握类型选择的使用
## 练习说明
打开 `main.go`,根据注释提示填充代码,完成后运行 `go test -v` 验证答案。
+182
View File
@@ -0,0 +1,182 @@
package main
import "fmt"
type Address struct {
City string
Street string
}
type Person struct {
Name string
Age int
Address Address
}
type Employee struct {
Person
EmployeeID int
Department string
}
type Employee2 struct {
____
Salary float64
}
type Reader interface {
Read() string
}
type Writer interface {
Write(s string)
}
type File struct {
Name string
Content string
}
func (f File) Read() string {
return f.___
}
func (f *File) Write(s string) {
f.Content = ____
}
type ReadWriter interface {
____
____
}
func exercise1() string {
fmt.Println("=== 练习1:结构体嵌入 ===")
emp := Employee{
Person: Person{
Name: "张三",
Age: 30,
},
EmployeeID: 1001,
Department: "技术部",
}
fmt.Printf("员工姓名: %s, 年龄: %d\n", ____, emp.___)
return ____
}
func exercise2() string {
fmt.Println("=== 练习2:多层嵌入 ===")
address := Address{City: "北京", Street: "长安街"}
emp2 := Employee2{
Person: Person{
Name: "李四",
Address: ____,
},
Salary: 10000.0,
}
fmt.Printf("员工: %s, 城市: %s\n", emp2.___, emp2.___.City)
return ____
}
func exercise3() string {
fmt.Println("=== 练习3:接口实现 ===")
file := File{Name: "test.txt", Content: "初始内容"}
// 使用Read方法
content := file.____()
fmt.Printf("读取内容: %s\n", content)
// 使用Write方法
file.____("新内容")
fmt.Printf("写入后内容: %s\n", file.___)
return ___
}
func exercise4() string {
fmt.Println("=== 练习4:接口组合 ===")
file := &File{Name: "data.txt", "原始数据"}
var rw _____
rw.Write("修改后的数据")
result := rw.Read()
fmt.Printf("读写结果: %s\n", result)
return ____
}
func exercise5() string {
fmt.Println("=== 练习5:类型断言 ===")
var data interface{} = "Hello, Go!"
// 尝试断言为string
str, ok := data.____
if ok {
fmt.Printf("类型断言成功: %s\n", ____)
}
// 尝试断言为int
_, ok = data.____
if !ok {
fmt.Println("类型断言失败: 不是int类型")
}
return "类型断言演示完成"
}
func exercise6() string {
fmt.Println("=== 练习6:类型选择 ===")
values := []interface{}{10, "hello", 3.14, true}
for _, v := range ____ {
switch v.(type) {
case int:
fmt.Printf("整数: %d\n", ____)
case string:
fmt.Printf("字符串: %s\n", ____)
case float64:
fmt.Printf("浮点数: %.2f\n", ____)
case bool:
fmt.Printf("布尔值: %t\n", ____)
default:
fmt.Println("未知类型")
}
}
return "类型选择演示完成"
}
func main() {
fmt.Println("=== 组合与类型断言 练习 ===\n")
result1 := exercise1()
fmt.Printf("练习1结果: %s\n", result1)
result2 := exercise2()
fmt.Printf("练习2结果: %s\n", result2)
result3 := exercise3()
fmt.Printf("练习3结果: %s\n", result3)
result4 := exercise4()
fmt.Printf("练习4结果: %s\n", result4)
result5 := exercise5()
fmt.Printf("练习5结果: %s\n", result5)
result6 := exercise6()
fmt.Printf("练习6结果: %s\n", result6)
}
+85
View File
@@ -0,0 +1,85 @@
package main
import (
"bytes"
"os"
"strings"
"testing"
)
func captureOutput(f func()) string {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
f()
w.Close()
os.Stdout = old
var buf bytes.Buffer
buf.ReadFrom(r)
return buf.String()
}
func TestExercise1(t *testing.T) {
result := exercise1()
if result != "张三" {
t.Errorf("期望 '张三', 实际: %s", result)
}
}
func TestExercise2(t *testing.T) {
result := exercise2()
if result != "李四" {
t.Errorf("期望 '李四', 实际: %s", result)
}
}
func TestExercise3(t *testing.T) {
result := exercise3()
if result != "新内容" {
t.Errorf("期望 '新内容', 实际: %s", result)
}
}
func TestExercise4(t *testing.T) {
result := exercise4()
if result != "修改后的数据" {
t.Errorf("期望 '修改后的数据', 实际: %s", result)
}
}
func TestExercise5(t *testing.T) {
result := exercise5()
if result != "类型断言演示完成" {
t.Errorf("期望 '类型断言演示完成', 实际: %s", result)
}
}
func TestExercise6(t *testing.T) {
result := exercise6()
if result != "类型选择演示完成" {
t.Errorf("期望 '类型选择演示完成', 实际: %s", result)
}
}
func TestMainFunction(t *testing.T) {
output := captureOutput(main)
requiredSections := []string{
"=== 组合与类型断言 练习 ===",
"练习1:结构体嵌入",
"练习2:多层嵌入",
"练习3:接口实现",
"练习4:接口组合",
"练习5:类型断言",
"练习6:类型选择",
}
for _, section := range requiredSections {
if !strings.Contains(output, section) {
t.Errorf("期望输出包含 '%s'", section)
}
}
}