From 67e47dd5dd9ceb13523f6ecd64760a4b2a752dea Mon Sep 17 00:00:00 2001 From: openclaw Date: Mon, 23 Mar 2026 23:23:21 +0000 Subject: [PATCH] Add Chapter 3: Data Structures - Arrays, Slices, Maps, and Structs (Deep Dive) --- chapters/chapter-3-data-structures.md | 1063 +++++++++++++++++++++++++ chapters/chapter-3/go.mod | 3 + chapters/chapter-3/main.go | 366 +++++++++ 3 files changed, 1432 insertions(+) create mode 100644 chapters/chapter-3-data-structures.md create mode 100644 chapters/chapter-3/go.mod create mode 100644 chapters/chapter-3/main.go diff --git a/chapters/chapter-3-data-structures.md b/chapters/chapter-3-data-structures.md new file mode 100644 index 0000000..22dad78 --- /dev/null +++ b/chapters/chapter-3-data-structures.md @@ -0,0 +1,1063 @@ +# 第三章:数据结构详解 —— 数组、切片、映射与结构体 + +> **本章目标**:深入理解 Go 的四种核心数据结构,掌握数组与切片的本质区别、映射的底层实现、结构体的组合与嵌入,以及在实际开发中的最佳实践。 + +## 3.1 数组(Arrays):固定长度的序列 + +### 3.1.1 数组的基本概念 + +数组是**固定长度**的相同类型元素的集合。一旦声明,长度不可改变。 + +```go +package main + +import "fmt" + +func main() { + // 声明数组(长度是类型的一部分) + var arr1 [5]int // 5 个 int,初始化为 0 + var arr2 [3]string = {"A", "B", "C"} + + // 声明并初始化 + arr3 := [4]int{10, 20, 30, 40} + + // 部分初始化,剩余为 0 + arr4 := [5]int{1, 2} // [1 2 0 0 0] + + // 使用 ... 自动推断长度 + arr5 := [...]int{1, 2, 3, 4, 5} // [1 2 3 4 5] + + fmt.Printf("arr1: %v\n", arr1) + fmt.Printf("arr2: %v\n", arr2) + fmt.Printf("arr3: %v\n", arr3) + fmt.Printf("arr4: %v\n", arr4) + fmt.Printf("arr5: %v\n", arr5) +} +``` + +**深度解析**: +- 数组长度是**类型的一部分**:`[5]int` 和 `[10]int` 是**不同类型** +- 数组是**值类型**:赋值或传参时会**完整复制** +- 数组长度**不可变**,这是与切片的核心区别 + +### 3.1.2 数组的访问与遍历 + +```go +func arrayAccess() { + arr := [5]int{10, 20, 30, 40, 50} + + // 访问元素 + fmt.Println(arr[0]) // 10 + fmt.Println(arr[4]) // 50 + // fmt.Println(arr[5]) // panic: index out of range + + // 修改元素 + arr[1] = 99 + fmt.Println(arr) // [10 99 30 40 50] + + // 获取长度 + fmt.Println(len(arr)) // 5 + + // 遍历 + for i, v := range arr { + fmt.Printf("索引 %d: 值 %d\n", i, v) + } + + // 只遍历值 + for _, v := range arr { + fmt.Println(v) + } +} +``` + +### 3.1.3 多维数组 + +```go +func multiDimensionalArray() { + // 2x3 的二维数组 + var matrix [2][3]int + + // 初始化 + matrix = [2][3]int{ + {1, 2, 3}, + {4, 5, 6}, + } + + // 或者 + matrix := [2][3]int{ + {1, 2, 3}, + {4, 5, 6}, + } + + // 访问 + fmt.Println(matrix[0][1]) // 2 + fmt.Println(matrix[1][2]) // 6 + + // 遍历 + for i, row := range matrix { + for j, val := range row { + fmt.Printf("matrix[%d][%d] = %d\n", i, j, val) + } + } +} +``` + +### 3.1.4 数组的局限性 + +**问题 1:值复制性能问题** +```go +func arrayCopyDemo() { + arr := [10000]int{} + // 初始化... + + // 传参时会复制整个数组(性能差) + processArray(arr) +} + +func processArray(a [10000]int) { + // 这里接收的是副本 +} + +// 正确做法:传指针 +func processArrayPtr(a *[10000]int) { + // 只传递地址 +} +``` + +**问题 2:长度固定** +```go +arr := [5]int{1, 2, 3, 4, 5} +// arr[5] = 6 // 编译错误!数组长度固定 +``` + +**结论**:在实际开发中,**极少直接使用数组**,更多使用**切片(Slice)**。 + +--- + +## 3.2 切片(Slices):动态长度的视图 + +切片是 Go 中最常用的数据结构,它是**对数组的动态视图**,提供了灵活的长度和容量管理。 + +### 3.2.1 切片的底层结构 + +切片不是数组,它是一个**描述符**,包含三个字段: +- `ptr`:指向底层数组的指针 +- `len`:切片长度 +- `cap`:切片容量(从 ptr 开始到数组末尾的长度) + +```go +type SliceHeader struct { + Data uintptr // 指向底层数组 + Len int // 长度 + Cap int // 容量 +} +``` + +### 3.2.2 切片的创建 + +#### 方式 1:从数组创建 + +```go +func sliceFromArray() { + arr := [5]int{1, 2, 3, 4, 5} + + // 切片语法:arr[start:end] + s1 := arr[0:3] // [1 2 3], len=3, cap=5 + s2 := arr[1:] // [2 3 4 5], len=4, cap=4 + s3 := arr[:3] // [1 2 3], len=3, cap=5 + s4 := arr[:] // [1 2 3 4 5], len=5, cap=5 + + fmt.Printf("s1: %v, len=%d, cap=%d\n", s1, len(s1), cap(s1)) + fmt.Printf("s2: %v, len=%d, cap=%d\n", s2, len(s2), cap(s2)) + fmt.Printf("s3: %v, len=%d, cap=%d\n", s3, len(s3), cap(s3)) + fmt.Printf("s4: %v, len=%d, cap=%d\n", s4, len(s4), cap(s4)) + + // 修改切片会影响原数组 + s1[0] = 99 + fmt.Println(arr) // [99 2 3 4 5] +} +``` + +#### 方式 2:make 创建 + +```go +func sliceMake() { + // make([]T, length, capacity) + s1 := make([]int, 5) // len=5, cap=5, 元素为 0 + s2 := make([]int, 3, 10) // len=3, cap=10, 元素为 0 + + fmt.Printf("s1: %v, len=%d, cap=%d\n", s1, len(s1), cap(s1)) + fmt.Printf("s2: %v, len=%d, cap=%d\n", s2, len(s2), cap(s2)) + + // 直接初始化 + s3 := []int{1, 2, 3, 4, 5} // len=5, cap=5 + s4 := []string{"a", "b", "c"} // len=3, cap=3 +} +``` + +#### 方式 3:字面量创建 + +```go +func sliceLiteral() { + s := []int{1, 2, 3, 4, 5} + fmt.Printf("s: %v, len=%d, cap=%d\n", s, len(s), cap(s)) +} +``` + +### 3.2.3 切片的操作 + +#### append:追加元素 + +```go +func sliceAppend() { + s := []int{1, 2, 3} + + // 追加单个元素 + s = append(s, 4) + fmt.Println(s) // [1 2 3 4] + + // 追加多个元素 + s = append(s, 5, 6, 7) + fmt.Println(s) // [1 2 3 4 5 6 7] + + // 追加另一个切片 + s2 := []int{8, 9, 10} + s = append(s, s2...) // 必须加 ... 展开 + fmt.Println(s) // [1 2 3 4 5 6 7 8 9 10] + + // 容量变化 + s3 := make([]int, 0, 5) + fmt.Printf("初始:len=%d, cap=%d\n", len(s3), cap(s3)) + + for i := 1; i <= 10; i++ { + s3 = append(s3, i) + fmt.Printf("追加 %d: len=%d, cap=%d\n", i, len(s3), cap(s3)) + } +} +``` + +**深度解析**: +- `append` 会返回**新切片**,必须接收返回值 +- 当 `len < cap` 时,直接在底层数组追加 +- 当 `len == cap` 时,会**重新分配更大的数组**并复制 +- 扩容策略: + - 容量 < 1024:翻倍 + - 容量 >= 1024:增长约 25% + +#### copy:复制切片 + +```go +func sliceCopy() { + src := []int{1, 2, 3, 4, 5} + dst := make([]int, 3) + + n := copy(dst, src) // 复制 min(len(dst), len(src)) 个元素 + fmt.Printf("复制了 %d 个元素\n", n) + fmt.Printf("dst: %v\n", dst) // [1 2 3] + + // 从指定位置复制 + dst2 := make([]int, 5) + copy(dst2[1:], src) + fmt.Printf("dst2: %v\n", dst2) // [0 1 2 3 4] +} +``` + +#### 切片切片(Slicing the slice) + +```go +func sliceSlicing() { + s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + + s1 := s[2:5] // [3 4 5], len=3, cap=8 + s2 := s1[1:3] // [4 5], len=2, cap=6(从 s1[1] 开始,到原数组末尾) + + fmt.Printf("s: %v\n", s) + fmt.Printf("s1: %v, len=%d, cap=%d\n", s1, len(s1), cap(s1)) + fmt.Printf("s2: %v, len=%d, cap=%d\n", s2, len(s2), cap(s2)) + + // 修改 s2 会影响 s + s2[0] = 99 + fmt.Printf("s 修改后:%v\n", s) // [1 2 3 99 5 6 7 8 9 10] +} +``` + +**深度解析**: +- 切片的容量是从**当前起始位置到原数组末尾** +- 多次切片后,容量可能很大,导致内存无法释放 +- **最佳实践**:如果不再需要原数组,使用 `append` 创建新切片 + +```go +// 释放内存的正确方式 +func trimMemory(s []int) []int { + result := append([]int(nil), s[:3]...) // 创建新切片,只包含前 3 个元素 + return result +} +``` + +### 3.2.4 切片的常见陷阱 + +#### 陷阱 1:共享底层数组 + +```go +func sharedUnderlyingArray() { + s1 := []int{1, 2, 3, 4, 5} + s2 := s1[1:3] + + s2[0] = 99 + fmt.Println(s1) // [1 99 3 4 5] // s1 也被修改了! + fmt.Println(s2) // [99 3] +} +``` + +#### 陷阱 2:容量陷阱导致内存泄漏 + +```go +func capacityLeak() { + data := make([]int, 1000000) // 大数组 + // 处理数据... + + // 错误:只取前 10 个,但容量仍为 1000000 + small := data[:10] + // small 持有整个大数组的引用,内存无法释放 + + // 正确:创建新切片 + small := append([]int(nil), data[:10]...) + // 现在 data 可以被垃圾回收 +} +``` + +#### 陷阱 3:append 后的长度变化 + +```go +func appendLengthChange() { + s := make([]int, 3) + s[0], s[1], s[2] = 1, 2, 3 + + s = append(s, 4, 5, 6) + // s 现在是 [1 2 3 4 5 6], len=6 + + // 如果继续用原来的索引访问会 panic + // s[3] = 7 // 正确 + // s[10] = 8 // panic: index out of range +} +``` + +### 3.2.5 切片的高级用法 + +#### 使用切片作为函数参数 + +```go +func processSlice(s []int) { + // 修改会影响原切片(因为共享底层数组) + for i := range s { + s[i] *= 2 + } +} + +func main() { + s := []int{1, 2, 3} + processSlice(s) + fmt.Println(s) // [2 4 6] +} +``` + +#### 切片推导(Slice Comprehension) + +Go 没有像 Python 那样的列表推导式,但可以用循环实现: + +```go +func sliceComprehension() { + nums := []int{1, 2, 3, 4, 5} + + // 平方 + squares := make([]int, 0, len(nums)) + for _, n := range nums { + squares = append(squares, n*n) + } + fmt.Println(squares) // [1 4 9 16 25] + + // 过滤偶数 + evens := make([]int, 0) + for _, n := range nums { + if n%2 == 0 { + evens = append(evens, n) + } + } + fmt.Println(evens) // [2 4] +} +``` + +--- + +## 3.3 映射(Maps):键值对的集合 + +映射是 Go 中的**哈希表**实现,提供 O(1) 的平均查找时间。 + +### 3.3.1 映射的创建与初始化 + +```go +func mapCreation() { + // 方式 1:make 创建 + m1 := make(map[string]int) + m1["one"] = 1 + m1["two"] = 2 + + // 方式 2:字面量初始化 + m2 := map[string]int{ + "one": 1, + "two": 2, + "three": 3, + } + + // 方式 3:空映射 + m3 := map[string]int{} + + // nil 映射(不能写入) + var m4 map[string]int + // m4["five"] = 5 // panic: assignment to entry in nil map + + fmt.Printf("m1: %v\n", m1) + fmt.Printf("m2: %v\n", m2) + fmt.Printf("m3: %v\n", m3) +} +``` + +### 3.3.2 映射的基本操作 + +```go +func mapOperations() { + m := map[string]int{ + "apple": 5, + "banana": 3, + "orange": 8, + } + + // 读取 + fmt.Println(m["apple"]) // 5 + + // 写入 + m["grape"] = 10 + m["apple"] = 7 // 更新 + + // 删除 + delete(m, "banana") + + // 检查键是否存在(重要!) + value, exists := m["orange"] + if exists { + fmt.Printf("orange 存在,价格:%d\n", value) + } + + // 读取不存在的键,返回零值 + fmt.Println(m["mango"]) // 0(int 的零值) + + // 遍历(无序!) + for fruit, price := range m { + fmt.Printf("%s: %d\n", fruit, price) + } + + // 只遍历键 + for fruit := range m { + fmt.Println(fruit) + } + + // 只遍历值 + for _, price := range m { + fmt.Println(price) + } +} +``` + +### 3.3.3 映射的底层原理 + +**深度解析**: +- 映射是**引用类型**,底层指向一个 `hmap` 结构 +- 映射是**无序**的,每次遍历顺序可能不同(故意设计,防止依赖顺序) +- 映射**不是并发安全**的,多线程读写会 panic +- 映射的容量会自动增长,但**不会自动缩小** + +```go +// 并发安全的映射 +func concurrentMap() { + var m sync.Map // 或使用 sync.RWMutex 保护普通 map + + m.Store("key", "value") + val, _ := m.Load("key") + fmt.Println(val) +} +``` + +### 3.3.4 映射的常见陷阱 + +#### 陷阱 1:遍历无序 + +```go +func mapUnordered() { + m := map[int]string{ + 1: "one", + 2: "two", + 3: "three", + } + + for i := 0; i < 5; i++ { + for k, v := range m { + fmt.Printf("%d:%s ", k, v) + } + fmt.Println() + } + // 每次输出顺序都不同! +} +``` + +#### 陷阱 2:nil 映射 + +```go +func mapNil() { + var m map[string]int // nil 映射 + + // 读取没问题(返回零值) + fmt.Println(m["key"]) // 0 + + // 写入会 panic + // m["key"] = 1 // panic: assignment to entry in nil map + + // 正确做法 + m = make(map[string]int) + m["key"] = 1 +} +``` + +#### 陷阱 3:映射作为函数参数 + +```go +func modifyMap(m map[string]int) { + m["new"] = 100 // 修改会影响原映射 +} + +func main() { + m := make(map[string]int) + modifyMap(m) + fmt.Println(m) // map[new:100] +} +``` + +### 3.3.5 高级用法:嵌套映射 + +```go +func nestedMap() { + // 映射的映射 + scores := map[string]map[string]int{ + "Alice": { + "math": 95, + "english": 88, + }, + "Bob": { + "math": 92, + "english": 90, + }, + } + + // 访问 + fmt.Println(scores["Alice"]["math"]) // 95 + + // 动态创建 + if scores["Charlie"] == nil { + scores["Charlie"] = make(map[string]int) + } + scores["Charlie"]["math"] = 85 + + // 遍历 + for name, subjects := range scores { + fmt.Printf("%s:\n", name) + for subject, score := range subjects { + fmt.Printf(" %s: %d\n", subject, score) + } + } +} +``` + +--- + +## 3.4 结构体(Structs):自定义类型 + +结构体是 Go 中**组合数据**的方式,类似其他语言的类(但没有继承)。 + +### 3.4.1 结构体的定义与初始化 + +```go +func structDefinition() { + // 定义结构体 + type Person struct { + Name string + Age int + Email string + } + + // 方式 1:字面量初始化(推荐) + p1 := Person{ + Name: "Alice", + Age: 25, + Email: "alice@example.com", + } + + // 方式 2:位置初始化(不推荐,易错) + p2 := Person{"Bob", 30, "bob@example.com"} + + // 方式 3:部分初始化 + p3 := Person{Name: "Charlie"} // Age=0, Email="" + + // 方式 4:new(返回指针) + p4 := new(Person) + p4.Name = "David" + p4.Age = 28 + + // 方式 5:取地址 + p5 := &Person{Name: "Eve", Age: 22} + + fmt.Printf("p1: %+v\n", p1) + fmt.Printf("p2: %+v\n", p2) + fmt.Printf("p3: %+v\n", p3) + fmt.Printf("p4: %+v\n", *p4) + fmt.Printf("p5: %+v\n", *p5) +} +``` + +### 3.4.2 结构体字段访问 + +```go +func structAccess() { + type Point struct { + X float64 + Y float64 + } + + p := Point{X: 10.5, Y: 20.3} + + // 值访问 + fmt.Println(p.X) // 10.5 + p.X = 15.0 + + // 指针访问(自动解引用) + ptr := &p + fmt.Println(ptr.X) // 15.0(不需要 ptr->X) + ptr.Y = 25.0 + + fmt.Printf("p: %+v\n", p) // {X:15 Y:25} +} +``` + +### 3.4.3 结构体方法 + +```go +func structMethods() { + type Rectangle struct { + Width float64 + Height float64 + } + + // 值接收者方法 + func (r Rectangle) Area() float64 { + return r.Width * r.Height + } + + // 指针接收者方法(可以修改结构体) + func (r *Rectangle) Scale(factor float64) { + r.Width *= factor + r.Height *= factor + } + + rect := Rectangle{Width: 10, Height: 5} + fmt.Printf("面积:%.2f\n", rect.Area()) // 50.00 + + rect.Scale(2.0) + fmt.Printf("缩放后面积:%.2f\n", rect.Area()) // 200.00 +} +``` + +**深度解析**: +- **值接收者**:方法接收结构体的副本,不能修改原结构体 +- **指针接收者**:方法接收结构体的指针,可以修改原结构体 +- 如果方法需要修改接收者,必须使用**指针接收者** +- 如果方法只读取,可以使用**值接收者**(小结构体)或**指针接收者**(大结构体,避免复制) + +### 3.4.4 结构体嵌入(组合) + +Go 没有继承,但通过**嵌入**实现类似功能。 + +```go +func structEmbedding() { + // 基础结构体 + type Person struct { + Name string + Age int + } + + func (p Person) SayHello() { + fmt.Printf("你好,我是 %s\n", p.Name) + } + + // 嵌入结构体 + type Employee struct { + Person // 匿名嵌入 + ID int + Salary float64 + } + + e := Employee{ + Person: Person{Name: "Alice", Age: 30}, + ID: 1001, + Salary: 50000, + } + + // 直接访问嵌入字段 + fmt.Println(e.Name) // "Alice"(等价于 e.Person.Name) + fmt.Println(e.Age) // 30 + + // 调用嵌入方法 + e.SayHello() // "你好,我是 Alice" + + // 方法重写 + type Manager struct { + Employee + TeamSize int + } + + // Manager 也继承了 SayHello 方法 + m := Manager{ + Employee: Employee{ + Person: Person{Name: "Bob", Age: 40}, + ID: 2001, + Salary: 80000, + }, + TeamSize: 10, + } + + m.SayHello() // "你好,我是 Bob" +} +``` + +### 3.4.5 结构体标签(Tags) + +结构体标签用于元数据,常用于 JSON、数据库映射等。 + +```go +func structTags() { + type User struct { + ID int `json:"id" db:"user_id"` + Name string `json:"name" validate:"required"` + Email string `json:"email,omitempty"` + Password string `json:"-"` // 忽略此字段 + Age int `json:"age" validate:"min=18"` + } + + u := User{ + ID: 1, + Name: "Alice", + Email: "alice@example.com", + Password: "secret123", + Age: 25, + } + + // JSON 序列化 + jsonData, _ := json.Marshal(u) + fmt.Println(string(jsonData)) + // 输出:{"id":1,"name":"Alice","email":"alice@example.com","age":25} + // Password 被忽略(json:"-") + // Email 即使为空也会输出(没有 omitempty 时) +} +``` + +### 3.4.6 结构体比较 + +```go +func structComparison() { + type Point struct { + X int + Y int + } + + p1 := Point{X: 1, Y: 2} + p2 := Point{X: 1, Y: 2} + p3 := Point{X: 1, Y: 3} + + fmt.Println(p1 == p2) // true + fmt.Println(p1 == p3) // false + + // 包含不可比较字段的结构体不能比较 + type Data struct { + Name string + List []int // 切片不可比较 + } + + // d1 := Data{Name: "a", List: []int{1}} + // d2 := Data{Name: "a", List: []int{1}} + // fmt.Println(d1 == d2) // 编译错误! +} +``` + +--- + +## 3.5 深度实践:综合案例 + +### 3.5.1 学生管理系统 + +```go +package main + +import ( + "encoding/json" + "fmt" + "sort" +) + +// 学生结构体 +type Student struct { + ID int `json:"id"` + Name string `json:"name"` + Age int `json:"age"` + Score float64 `json:"score"` +} + +// 学生管理系统 +type StudentManager struct { + students map[int]*Student + nextID int +} + +// 创建管理系统 +func NewStudentManager() *StudentManager { + return &StudentManager{ + students: make(map[int]*Student), + nextID: 1, + } +} + +// 添加学生 +func (sm *StudentManager) AddStudent(name string, age int, score float64) int { + id := sm.nextID + sm.nextID++ + + sm.students[id] = &Student{ + ID: id, + Name: name, + Age: age, + Score: score, + } + + return id +} + +// 获取学生 +func (sm *StudentManager) GetStudent(id int) *Student { + return sm.students[id] +} + +// 更新学生 +func (sm *StudentManager) UpdateStudent(id int, name string, age int, score float64) bool { + if student, exists := sm.students[id]; exists { + student.Name = name + student.Age = age + student.Score = score + return true + } + return false +} + +// 删除学生 +func (sm *StudentManager) DeleteStudent(id int) bool { + if _, exists := sm.students[id]; exists { + delete(sm.students, id) + return true + } + return false +} + +// 获取所有学生 +func (sm *StudentManager) GetAllStudents() []*Student { + students := make([]*Student, 0, len(sm.students)) + for _, s := range sm.students { + students = append(students, s) + } + return students +} + +// 按分数排序 +func (sm *StudentManager) SortByScore(descending bool) []*Student { + students := sm.GetAllStudents() + sort.Slice(students, func(i, j int) bool { + if descending { + return students[i].Score > students[j].Score + } + return students[i].Score < students[j].Score + }) + return students +} + +// 导出为 JSON +func (sm *StudentManager) ExportJSON() (string, error) { + data, err := json.MarshalIndent(sm.students, "", " ") + if err != nil { + return "", err + } + return string(data), nil +} + +func main() { + manager := NewStudentManager() + + // 添加学生 + id1 := manager.AddStudent("Alice", 20, 95.5) + id2 := manager.AddStudent("Bob", 22, 88.0) + id3 := manager.AddStudent("Charlie", 21, 92.5) + + fmt.Printf("添加学生 ID: %d, %d, %d\n", id1, id2, id3) + + // 获取学生 + student := manager.GetStudent(id1) + fmt.Printf("学生: %+v\n", student) + + // 更新学生 + manager.UpdateStudent(id1, "Alice", 21, 97.0) + fmt.Printf("更新后: %+v\n", manager.GetStudent(id1)) + + // 删除学生 + manager.DeleteStudent(id2) + fmt.Printf("删除后学生数量:%d\n", len(manager.GetAllStudents())) + + // 排序 + sorted := manager.SortByScore(true) + fmt.Println("\n按分数降序排列:") + for _, s := range sorted { + fmt.Printf(" %s: %.1f\n", s.Name, s.Score) + } + + // 导出 JSON + jsonStr, _ := manager.ExportJSON() + fmt.Println("\nJSON 导出:") + fmt.Println(jsonStr) +} +``` + +### 3.5.2 切片性能优化对比 + +```go +func slicePerformance() { + // 预分配容量(推荐) + start := time.Now() + s1 := make([]int, 0, 10000) + for i := 0; i < 10000; i++ { + s1 = append(s1, i) + } + fmt.Printf("预分配容量耗时:%v\n", time.Since(start)) + + // 不预分配容量(慢) + start = time.Now() + s2 := make([]int, 0) + for i := 0; i < 10000; i++ { + s2 = append(s2, i) + } + fmt.Printf("不预分配容量耗时:%v\n", time.Since(start)) + + // 输出:预分配通常快 2-3 倍 +} +``` + +--- + +## 3.6 常见陷阱与最佳实践 + +### 3.6.1 数组 vs 切片 + +| 特性 | 数组 | 切片 | +|------|------|------| +| 长度 | 固定 | 动态 | +| 类型 | 长度是类型一部分 | 无长度限制 | +| 传参 | 完整复制 | 只传描述符 | +| 使用场景 | 固定大小、性能敏感 | 通用、推荐 | + +**最佳实践**:99% 的场景使用切片,数组仅在特殊场景(如性能极致优化、固定大小缓冲区)使用。 + +### 3.6.2 切片内存泄漏 + +```go +// 错误:切片持有大数组引用 +func badPractice(data []byte) []byte { + return data[:10] // 持有整个 data 的引用 +} + +// 正确:创建新切片 +func goodPractice(data []byte) []byte { + result := make([]byte, 10) + copy(result, data[:10]) + return result +} +// 或者 +func goodPractice2(data []byte) []byte { + return append([]byte(nil), data[:10]...) +} +``` + +### 3.6.3 映射并发安全 + +```go +// 错误:并发读写 +var m = make(map[string]int) + +func worker() { + for i := 0; i < 1000; i++ { + m["key"] = i // panic: concurrent map writes + } +} + +// 正确:使用 sync.Mutex +var ( + m = make(map[string]int) + mu sync.RWMutex +) + +func workerSafe() { + for i := 0; i < 1000; i++ { + mu.Lock() + m["key"] = i + mu.Unlock() + } +} + +// 或者使用 sync.Map +var sm sync.Map + +func workerSyncMap() { + for i := 0; i < 1000; i++ { + sm.Store("key", i) + } +} +``` + +### 3.6.4 结构体最佳实践 + +1. **使用指针接收者**:除非结构体很小且不需要修改 +2. **避免嵌入指针**:优先嵌入值,除非需要 nil 语义 +3. **使用标签**:为 JSON、数据库等添加元数据 +4. **导出字段**:首字母大写才能被外部访问 +5. **组合优于继承**:通过嵌入实现代码复用 + +--- + +## 3.7 课后练习 + +1. **切片操作**:实现一个函数,移除切片中的重复元素 +2. **映射统计**:统计字符串中每个字符出现的次数 +3. **结构体链式调用**:为结构体实现链式调用方法 +4. **性能优化**:对比预分配容量和不预分配的切片性能差异 +5. **并发安全**:实现一个线程安全的计数器(使用映射) +6. **综合项目**:实现一个简单的待办事项管理器(支持增删改查、排序、导出) + +## 3.8 下一步 + +完成本章后,你将进入第四章:**函数与接口**,深入学习 Go 的函数式编程特性、闭包、 defer、panic/recover 以及接口的底层实现。 + +--- + +**代码仓库位置**:https://giter.top/openclaw/test/tree/main/chapters/chapter-3 + +**下一章预告**:闭包的高级用法、defer 的执行顺序、接口的空接口与类型断言、接口底层实现 diff --git a/chapters/chapter-3/go.mod b/chapters/chapter-3/go.mod new file mode 100644 index 0000000..9ff1861 --- /dev/null +++ b/chapters/chapter-3/go.mod @@ -0,0 +1,3 @@ +module go-tutorial + +go 1.21 diff --git a/chapters/chapter-3/main.go b/chapters/chapter-3/main.go new file mode 100644 index 0000000..7e9a1c7 --- /dev/null +++ b/chapters/chapter-3/main.go @@ -0,0 +1,366 @@ +package main + +import ( + "encoding/json" + "fmt" + "sort" + "sync" + "time" +) + +// 3.1 数组示例 +func arrayExamples() { + fmt.Println("=== 数组示例 ===") + + // 声明数组 + var arr1 [5]int + arr2 := [3]string{"A", "B", "C"} + arr3 := [4]int{10, 20, 30, 40} + arr4 := [5]int{1, 2} // 部分初始化 + arr5 := [...]int{1, 2, 3, 4, 5} + + fmt.Printf("arr1: %v\n", arr1) + fmt.Printf("arr2: %v\n", arr2) + fmt.Printf("arr3: %v\n", arr3) + fmt.Printf("arr4: %v\n", arr4) + fmt.Printf("arr5: %v\n", arr5) + + // 访问和修改 + arr1[0] = 99 + fmt.Printf("修改后 arr1: %v\n", arr1) + + // 多维数组 + matrix := [2][3]int{ + {1, 2, 3}, + {4, 5, 6}, + } + fmt.Printf("matrix[0][1]: %d\n", matrix[0][1]) +} + +// 3.2 切片示例 +func sliceExamples() { + fmt.Println("\n=== 切片示例 ===") + + // 从数组创建 + arr := [5]int{1, 2, 3, 4, 5} + s1 := arr[0:3] + s2 := arr[1:] + s3 := arr[:3] + s4 := arr[:] + + fmt.Printf("s1: %v, len=%d, cap=%d\n", s1, len(s1), cap(s1)) + fmt.Printf("s2: %v, len=%d, cap=%d\n", s2, len(s2), cap(s2)) + fmt.Printf("s3: %v, len=%d, cap=%d\n", s3, len(s3), cap(s3)) + fmt.Printf("s4: %v, len=%d, cap=%d\n", s4, len(s4), cap(s4)) + + // make 创建 + s5 := make([]int, 5) + s6 := make([]int, 3, 10) + fmt.Printf("s5: %v, len=%d, cap=%d\n", s5, len(s5), cap(s5)) + fmt.Printf("s6: %v, len=%d, cap=%d\n", s6, len(s6), cap(s6)) + + // append + s7 := []int{1, 2, 3} + s7 = append(s7, 4) + s7 = append(s7, 5, 6, 7) + s8 := []int{8, 9, 10} + s7 = append(s7, s8...) + fmt.Printf("s7 after append: %v\n", s7) + + // 容量变化演示 + fmt.Println("\n容量变化:") + s9 := make([]int, 0, 5) + for i := 1; i <= 10; i++ { + s9 = append(s9, i) + fmt.Printf("追加 %d: len=%d, cap=%d\n", i, len(s9), cap(s9)) + } + + // copy + src := []int{1, 2, 3, 4, 5} + dst := make([]int, 3) + n := copy(dst, src) + fmt.Printf("\n复制了 %d 个元素,dst: %v\n", n, dst) + + // 共享底层数组陷阱 + s10 := []int{1, 2, 3, 4, 5} + s11 := s10[1:3] + s11[0] = 99 + fmt.Printf("\n共享数组陷阱:\n") + fmt.Printf("s10: %v\n", s10) + fmt.Printf("s11: %v\n", s11) + + // 释放内存的正确方式 + fmt.Println("\n释放内存:") + data := make([]int, 1000) + small := append([]int(nil), data[:10]...) + fmt.Printf("small cap: %d (原 data cap: %d)\n", cap(small), cap(data)) +} + +// 3.3 映射示例 +func mapExamples() { + fmt.Println("\n=== 映射示例 ===") + + // 创建映射 + m1 := make(map[string]int) + m1["one"] = 1 + m1["two"] = 2 + + m2 := map[string]int{ + "one": 1, + "two": 2, + "three": 3, + } + + fmt.Printf("m1: %v\n", m1) + fmt.Printf("m2: %v\n", m2) + + // 读取和检查存在 + value, exists := m2["two"] + fmt.Printf("two 存在: %v, 值: %d\n", exists, value) + + value, exists = m2["four"] + fmt.Printf("four 存在: %v, 值: %d\n", exists, value) + + // 删除 + delete(m2, "one") + fmt.Printf("删除 one 后: %v\n", m2) + + // 遍历(无序) + fmt.Println("\n遍历映射:") + for k, v := range m2 { + fmt.Printf("%s: %d\n", k, v) + } + + // nil 映射 + var m3 map[string]int + fmt.Printf("\nnil 映射读取: %d\n", m3["key"]) + // m3["key"] = 1 // panic! + + // 嵌套映射 + scores := map[string]map[string]int{ + "Alice": {"math": 95, "english": 88}, + "Bob": {"math": 92, "english": 90}, + } + fmt.Printf("\nAlice 数学成绩: %d\n", scores["Alice"]["math"]) + + // 动态创建 + if scores["Charlie"] == nil { + scores["Charlie"] = make(map[string]int) + } + scores["Charlie"]["math"] = 85 + fmt.Printf("Charlie 数学成绩: %d\n", scores["Charlie"]["math"]) +} + +// 3.4 结构体示例 +func structExamples() { + fmt.Println("\n=== 结构体示例 ===") + + // 定义结构体 + type Person struct { + Name string + Age int + Email string + } + + // 初始化 + p1 := Person{Name: "Alice", Age: 25, Email: "alice@example.com"} + p2 := Person{"Bob", 30, "bob@example.com"} + p3 := Person{Name: "Charlie"} + p4 := &Person{Name: "David", Age: 28} + + fmt.Printf("p1: %+v\n", p1) + fmt.Printf("p2: %+v\n", p2) + fmt.Printf("p3: %+v\n", p3) + fmt.Printf("p4: %+v\n", *p4) + + // 方法 + type Rectangle struct { + Width float64 + Height float64 + } + + rect := Rectangle{Width: 10, Height: 5} + + // 定义方法(在函数外定义) + area := rect.Width * rect.Height + fmt.Printf("矩形面积: %.2f\n", area) + + // 嵌入 + type Employee struct { + Person + ID int + Salary float64 + } + + e := Employee{ + Person: Person{Name: "Eve", Age: 35}, + ID: 1001, + Salary: 50000, + } + + fmt.Printf("员工姓名: %s\n", e.Name) // 直接访问嵌入字段 + fmt.Printf("员工 ID: %d\n", e.ID) + + // 结构体标签 + type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email,omitempty"` + Password string `json:"-"` + } + + user := User{ID: 1, Name: "Alice", Email: "alice@example.com", Password: "secret"} + jsonData, _ := json.Marshal(user) + fmt.Printf("JSON: %s\n", jsonData) +} + +// 3.5 学生管理系统 +type Student struct { + ID int `json:"id"` + Name string `json:"name"` + Age int `json:"age"` + Score float64 `json:"score"` +} + +type StudentManager struct { + students map[int]*Student + nextID int +} + +func NewStudentManager() *StudentManager { + return &StudentManager{ + students: make(map[int]*Student), + nextID: 1, + } +} + +func (sm *StudentManager) AddStudent(name string, age int, score float64) int { + id := sm.nextID + sm.nextID++ + sm.students[id] = &Student{ID: id, Name: name, Age: age, Score: score} + return id +} + +func (sm *StudentManager) GetStudent(id int) *Student { + return sm.students[id] +} + +func (sm *StudentManager) UpdateStudent(id int, name string, age int, score float64) bool { + if student, exists := sm.students[id]; exists { + student.Name = name + student.Age = age + student.Score = score + return true + } + return false +} + +func (sm *StudentManager) DeleteStudent(id int) bool { + if _, exists := sm.students[id]; exists { + delete(sm.students, id) + return true + } + return false +} + +func (sm *StudentManager) GetAllStudents() []*Student { + students := make([]*Student, 0, len(sm.students)) + for _, s := range sm.students { + students = append(students, s) + } + return students +} + +func (sm *StudentManager) SortByScore(descending bool) []*Student { + students := sm.GetAllStudents() + sort.Slice(students, func(i, j int) bool { + if descending { + return students[i].Score > students[j].Score + } + return students[i].Score < students[j].Score + }) + return students +} + +func studentManagerDemo() { + fmt.Println("\n=== 学生管理系统 ===") + + manager := NewStudentManager() + + id1 := manager.AddStudent("Alice", 20, 95.5) + id2 := manager.AddStudent("Bob", 22, 88.0) + id3 := manager.AddStudent("Charlie", 21, 92.5) + + fmt.Printf("添加学生 ID: %d, %d, %d\n", id1, id2, id3) + + manager.UpdateStudent(id1, "Alice", 21, 97.0) + fmt.Printf("更新后 Alice: %+v\n", manager.GetStudent(id1)) + + manager.DeleteStudent(id2) + fmt.Printf("删除后学生数量: %d\n", len(manager.GetAllStudents())) + + sorted := manager.SortByScore(true) + fmt.Println("\n按分数降序排列:") + for _, s := range sorted { + fmt.Printf(" %s: %.1f\n", s.Name, s.Score) + } + + jsonStr, _ := json.MarshalIndent(manager.students, "", " ") + fmt.Println("\nJSON 导出:") + fmt.Println(string(jsonStr)) +} + +// 3.6 性能对比 +func slicePerformance() { + fmt.Println("\n=== 切片性能对比 ===") + + // 预分配容量 + start := time.Now() + s1 := make([]int, 0, 10000) + for i := 0; i < 10000; i++ { + s1 = append(s1, i) + } + fmt.Printf("预分配容量耗时: %v\n", time.Since(start)) + + // 不预分配容量 + start = time.Now() + s2 := make([]int, 0) + for i := 0; i < 10000; i++ { + s2 = append(s2, i) + } + fmt.Printf("不预分配容量耗时: %v\n", time.Since(start)) +} + +// 3.7 并发安全映射 +func concurrentMapDemo() { + fmt.Println("\n=== 并发安全映射 ===") + + var m = make(map[string]int) + var mu sync.RWMutex + + var wg sync.WaitGroup + + // 写操作 + for i := 0; i < 100; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + mu.Lock() + m[fmt.Sprintf("key%d", id%10)] = id + mu.Unlock() + }(i) + } + + wg.Wait() + fmt.Printf("最终映射大小: %d\n", len(m)) +} + +func main() { + arrayExamples() + sliceExamples() + mapExamples() + structExamples() + studentManagerDemo() + slicePerformance() + concurrentMapDemo() +}