This commit is contained in:
2025-08-24 01:15:18 +08:00
parent e51feb1296
commit ec23aa5c26
6 changed files with 3631 additions and 3 deletions

View File

@@ -0,0 +1,672 @@
/*
02-slices.go - Go 语言切片详解
学习目标:
1. 理解切片的概念和内部结构
2. 掌握切片的创建和初始化
3. 学会切片的各种操作
4. 了解切片的扩容机制
5. 掌握切片的实际应用场景
知识点:
- 切片的定义和特性
- 切片的创建方式
- 切片的操作append、copy、切片表达式
- 切片的内部结构(指针、长度、容量)
- 切片的扩容机制
- 切片的内存管理
*/
package main
import "fmt"
func main() {
fmt.Println("=== Go 语言切片详解 ===\n")
// 演示切片的基本概念
demonstrateBasicSlices()
// 演示切片的创建方式
demonstrateSliceCreation()
// 演示切片的基本操作
demonstrateSliceOperations()
// 演示切片的内部结构
demonstrateSliceInternals()
// 演示切片的扩容机制
demonstrateSliceGrowth()
// 演示切片的高级操作
demonstrateAdvancedSliceOperations()
// 演示切片的实际应用
demonstratePracticalApplications()
}
// demonstrateBasicSlices 演示切片的基本概念
func demonstrateBasicSlices() {
fmt.Println("1. 切片的基本概念:")
// 切片的基本特性
fmt.Printf(" 切片的基本特性:\n")
fmt.Printf(" - 动态数组,长度可变\n")
fmt.Printf(" - 引用类型,指向底层数组\n")
fmt.Printf(" - 有长度(len)和容量(cap)两个属性\n")
fmt.Printf(" - 零值是 nil\n")
fmt.Printf(" - 不能直接比较,只能与 nil 比较\n")
// 基本切片示例
fmt.Printf(" 基本切片示例:\n")
var slice []int // 声明切片,零值为 nil
fmt.Printf(" 声明切片: %v\n", slice)
fmt.Printf(" 切片长度: %d\n", len(slice))
fmt.Printf(" 切片容量: %d\n", cap(slice))
fmt.Printf(" 是否为 nil: %t\n", slice == nil)
// 初始化切片
numbers := []int{1, 2, 3, 4, 5}
fmt.Printf(" 初始化切片: %v\n", numbers)
fmt.Printf(" 长度: %d, 容量: %d\n", len(numbers), cap(numbers))
// 切片元素访问
fmt.Printf(" 第一个元素: %d\n", numbers[0])
fmt.Printf(" 最后一个元素: %d\n", numbers[len(numbers)-1])
fmt.Println()
}
// demonstrateSliceCreation 演示切片的创建方式
func demonstrateSliceCreation() {
fmt.Println("2. 切片的创建方式:")
// 方式1: 字面量创建
fmt.Printf(" 方式1 - 字面量创建:\n")
slice1 := []string{"Go", "Python", "Java"}
fmt.Printf(" slice1: %v (len=%d, cap=%d)\n", slice1, len(slice1), cap(slice1))
// 方式2: make 函数创建
fmt.Printf(" 方式2 - make 函数创建:\n")
slice2 := make([]int, 5) // 长度为5容量为5
slice3 := make([]int, 3, 10) // 长度为3容量为10
fmt.Printf(" slice2: %v (len=%d, cap=%d)\n", slice2, len(slice2), cap(slice2))
fmt.Printf(" slice3: %v (len=%d, cap=%d)\n", slice3, len(slice3), cap(slice3))
// 方式3: 从数组创建
fmt.Printf(" 方式3 - 从数组创建:\n")
arr := [6]int{1, 2, 3, 4, 5, 6}
slice4 := arr[:] // 全部元素
slice5 := arr[1:4] // 索引1到3
slice6 := arr[:3] // 开头到索引2
slice7 := arr[2:] // 索引2到结尾
fmt.Printf(" 原数组: %v\n", arr)
fmt.Printf(" arr[:]: %v (len=%d, cap=%d)\n", slice4, len(slice4), cap(slice4))
fmt.Printf(" arr[1:4]: %v (len=%d, cap=%d)\n", slice5, len(slice5), cap(slice5))
fmt.Printf(" arr[:3]: %v (len=%d, cap=%d)\n", slice6, len(slice6), cap(slice6))
fmt.Printf(" arr[2:]: %v (len=%d, cap=%d)\n", slice7, len(slice7), cap(slice7))
// 方式4: 从切片创建
fmt.Printf(" 方式4 - 从切片创建:\n")
original := []int{10, 20, 30, 40, 50}
slice8 := original[1:4]
slice9 := slice8[1:3]
fmt.Printf(" 原切片: %v\n", original)
fmt.Printf(" original[1:4]: %v (len=%d, cap=%d)\n", slice8, len(slice8), cap(slice8))
fmt.Printf(" slice8[1:3]: %v (len=%d, cap=%d)\n", slice9, len(slice9), cap(slice9))
// 方式5: 三索引切片表达式
fmt.Printf(" 方式5 - 三索引切片表达式:\n")
data := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
slice10 := data[2:5:7] // [low:high:max] 限制容量
fmt.Printf(" data[2:5:7]: %v (len=%d, cap=%d)\n", slice10, len(slice10), cap(slice10))
fmt.Println()
}// de
monstrateSliceOperations 演示切片的基本操作
func demonstrateSliceOperations() {
fmt.Println("3. 切片的基本操作:")
// append 操作
fmt.Printf(" append 操作:\n")
var fruits []string
fmt.Printf(" 初始切片: %v (len=%d, cap=%d)\n", fruits, len(fruits), cap(fruits))
// 添加单个元素
fruits = append(fruits, "apple")
fmt.Printf(" 添加 apple: %v (len=%d, cap=%d)\n", fruits, len(fruits), cap(fruits))
// 添加多个元素
fruits = append(fruits, "banana", "cherry")
fmt.Printf(" 添加多个: %v (len=%d, cap=%d)\n", fruits, len(fruits), cap(fruits))
// 添加另一个切片
moreFruits := []string{"date", "elderberry"}
fruits = append(fruits, moreFruits...)
fmt.Printf(" 添加切片: %v (len=%d, cap=%d)\n", fruits, len(fruits), cap(fruits))
// copy 操作
fmt.Printf(" copy 操作:\n")
source := []int{1, 2, 3, 4, 5}
dest := make([]int, 3)
n := copy(dest, source)
fmt.Printf(" 源切片: %v\n", source)
fmt.Printf(" 目标切片: %v\n", dest)
fmt.Printf(" 复制了 %d 个元素\n", n)
// copy 的特殊用法
numbers := []int{1, 2, 3, 4, 5, 6}
copy(numbers[2:], numbers[1:]) // 向右移动元素
fmt.Printf(" 移动后: %v\n", numbers)
// 切片遍历
fmt.Printf(" 切片遍历:\n")
colors := []string{"red", "green", "blue", "yellow"}
// 使用索引遍历
fmt.Printf(" 使用索引遍历:\n")
for i := 0; i < len(colors); i++ {
fmt.Printf(" colors[%d] = %s\n", i, colors[i])
}
// 使用 range 遍历
fmt.Printf(" 使用 range 遍历:\n")
for index, color := range colors {
fmt.Printf(" colors[%d] = %s\n", index, color)
}
// 切片修改
fmt.Printf(" 切片修改:\n")
nums := []int{10, 20, 30, 40, 50}
fmt.Printf(" 原切片: %v\n", nums)
nums[2] = 99
fmt.Printf(" 修改索引2: %v\n", nums)
// 批量修改
for i := range nums {
nums[i] *= 2
}
fmt.Printf(" 全部乘以2: %v\n", nums)
fmt.Println()
}
// demonstrateSliceInternals 演示切片的内部结构
func demonstrateSliceInternals() {
fmt.Println("4. 切片的内部结构:")
fmt.Printf(" 切片内部结构包含三个字段:\n")
fmt.Printf(" - 指针:指向底层数组的指针\n")
fmt.Printf(" - 长度:切片中元素的个数\n")
fmt.Printf(" - 容量:从切片起始位置到底层数组末尾的元素个数\n")
// 演示切片和底层数组的关系
fmt.Printf(" 切片和底层数组的关系:\n")
arr := [8]int{0, 1, 2, 3, 4, 5, 6, 7}
slice := arr[2:6]
fmt.Printf(" 原数组: %v\n", arr)
fmt.Printf(" 切片 arr[2:6]: %v (len=%d, cap=%d)\n", slice, len(slice), cap(slice))
// 修改切片影响底层数组
slice[1] = 99
fmt.Printf(" 修改切片后:\n")
fmt.Printf(" 数组: %v\n", arr)
fmt.Printf(" 切片: %v\n", slice)
// 多个切片共享底层数组
fmt.Printf(" 多个切片共享底层数组:\n")
slice1 := arr[1:4]
slice2 := arr[3:7]
fmt.Printf(" slice1 (arr[1:4]): %v\n", slice1)
fmt.Printf(" slice2 (arr[3:7]): %v\n", slice2)
slice1[2] = 888 // 修改 arr[3]
fmt.Printf(" 修改 slice1[2] 后:\n")
fmt.Printf(" slice1: %v\n", slice1)
fmt.Printf(" slice2: %v (第一个元素也变了)\n", slice2)
fmt.Printf(" 数组: %v\n", arr)
// 切片的容量计算
fmt.Printf(" 切片的容量计算:\n")
data := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := data[2:5] // 从索引2开始容量是到数组末尾
s2 := data[2:5:7] // 限制容量到索引7
fmt.Printf(" data[2:5]: %v (len=%d, cap=%d)\n", s1, len(s1), cap(s1))
fmt.Printf(" data[2:5:7]: %v (len=%d, cap=%d)\n", s2, len(s2), cap(s2))
fmt.Println()
}
// demonstrateSliceGrowth 演示切片的扩容机制
func demonstrateSliceGrowth() {
fmt.Println("5. 切片的扩容机制:")
fmt.Printf(" 切片扩容规则:\n")
fmt.Printf(" - 当 append 时容量不足,会创建新的底层数组\n")
fmt.Printf(" - 新容量通常是原容量的2倍小切片\n")
fmt.Printf(" - 大切片的扩容策略更复杂\n")
// 演示扩容过程
fmt.Printf(" 扩容过程演示:\n")
var slice []int
for i := 0; i < 10; i++ {
oldCap := cap(slice)
slice = append(slice, i)
newCap := cap(slice)
if newCap != oldCap {
fmt.Printf(" 扩容: 长度 %d, 容量 %d -> %d\n", len(slice), oldCap, newCap)
}
}
fmt.Printf(" 最终切片: %v (len=%d, cap=%d)\n", slice, len(slice), cap(slice))
// 预分配容量避免频繁扩容
fmt.Printf(" 预分配容量优化:\n")
// 不好的做法:频繁扩容
var badSlice []int
for i := 0; i < 1000; i++ {
badSlice = append(badSlice, i)
}
fmt.Printf(" 频繁扩容结果: len=%d, cap=%d\n", len(badSlice), cap(badSlice))
// 好的做法:预分配容量
goodSlice := make([]int, 0, 1000) // 预分配容量1000
for i := 0; i < 1000; i++ {
goodSlice = append(goodSlice, i)
}
fmt.Printf(" 预分配结果: len=%d, cap=%d\n", len(goodSlice), cap(goodSlice))
// 扩容时的内存重新分配
fmt.Printf(" 扩容时的内存重新分配:\n")
original := []int{1, 2, 3}
fmt.Printf(" 原切片: %v (cap=%d)\n", original, cap(original))
// 创建基于原切片的新切片
derived := original[1:2]
fmt.Printf(" 派生切片: %v (cap=%d)\n", derived, cap(derived))
// 扩容派生切片
derived = append(derived, 99, 100, 101, 102)
fmt.Printf(" 扩容后派生切片: %v (cap=%d)\n", derived, cap(derived))
fmt.Printf(" 原切片: %v (未受影响)\n", original)
fmt.Println()
}
// demonstrateAdvancedSliceOperations 演示切片的高级操作
func demonstrateAdvancedSliceOperations() {
fmt.Println("6. 切片的高级操作:")
// 切片删除元素
fmt.Printf(" 切片删除元素:\n")
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Printf(" 原切片: %v\n", numbers)
// 删除索引为3的元素
index := 3
numbers = append(numbers[:index], numbers[index+1:]...)
fmt.Printf(" 删除索引3: %v\n", numbers)
// 删除多个元素索引1到3
start, end := 1, 4
numbers = append(numbers[:start], numbers[end:]...)
fmt.Printf(" 删除索引1-3: %v\n", numbers)
// 切片插入元素
fmt.Printf(" 切片插入元素:\n")
data := []int{1, 2, 5, 6}
fmt.Printf(" 原切片: %v\n", data)
// 在索引2插入元素3和4
insertIndex := 2
insertValues := []int{3, 4}
// 方法1使用 append 和切片操作
data = append(data[:insertIndex], append(insertValues, data[insertIndex:]...)...)
fmt.Printf(" 插入3,4: %v\n", data)
// 切片反转
fmt.Printf(" 切片反转:\n")
original := []string{"a", "b", "c", "d", "e"}
reversed := make([]string, len(original))
for i, v := range original {
reversed[len(original)-1-i] = v
}
fmt.Printf(" 原切片: %v\n", original)
fmt.Printf(" 反转后: %v\n", reversed)
// 就地反转
toReverse := []int{1, 2, 3, 4, 5}
fmt.Printf(" 就地反转前: %v\n", toReverse)
for i, j := 0, len(toReverse)-1; i < j; i, j = i+1, j-1 {
toReverse[i], toReverse[j] = toReverse[j], toReverse[i]
}
fmt.Printf(" 就地反转后: %v\n", toReverse)
// 切片去重
fmt.Printf(" 切片去重:\n")
withDuplicates := []int{1, 2, 2, 3, 3, 3, 4, 5, 5}
unique := removeDuplicates(withDuplicates)
fmt.Printf(" 有重复: %v\n", withDuplicates)
fmt.Printf(" 去重后: %v\n", unique)
// 切片过滤
fmt.Printf(" 切片过滤:\n")
allNumbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
evenNumbers := filter(allNumbers, func(n int) bool { return n%2 == 0 })
fmt.Printf(" 所有数字: %v\n", allNumbers)
fmt.Printf(" 偶数: %v\n", evenNumbers)
fmt.Println()
}/
/ demonstratePracticalApplications 演示切片的实际应用
func demonstratePracticalApplications() {
fmt.Println("7. 切片的实际应用:")
// 应用1: 动态数组
fmt.Printf(" 应用1 - 动态数组(购物车):\n")
var cart []string
// 添加商品
cart = append(cart, "笔记本电脑", "鼠标", "键盘")
fmt.Printf(" 购物车: %v\n", cart)
// 移除商品(移除鼠标)
for i, item := range cart {
if item == "鼠标" {
cart = append(cart[:i], cart[i+1:]...)
break
}
}
fmt.Printf(" 移除鼠标后: %v\n", cart)
// 应用2: 缓冲区和队列
fmt.Printf(" 应用2 - 简单队列实现:\n")
queue := NewQueue()
// 入队
queue.Enqueue("任务1")
queue.Enqueue("任务2")
queue.Enqueue("任务3")
fmt.Printf(" 队列状态: %v\n", queue.items)
// 出队
item := queue.Dequeue()
fmt.Printf(" 出队: %s\n", item)
fmt.Printf(" 队列状态: %v\n", queue.items)
// 应用3: 栈实现
fmt.Printf(" 应用3 - 简单栈实现:\n")
stack := NewStack()
// 入栈
stack.Push(10)
stack.Push(20)
stack.Push(30)
fmt.Printf(" 栈状态: %v\n", stack.items)
// 出栈
value := stack.Pop()
fmt.Printf(" 出栈: %d\n", value)
fmt.Printf(" 栈状态: %v\n", stack.items)
// 应用4: 数据处理管道
fmt.Printf(" 应用4 - 数据处理管道:\n")
rawData := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 管道处理:过滤偶数 -> 平方 -> 求和
evenNumbers := filter(rawData, func(n int) bool { return n%2 == 0 })
squares := mapInts(evenNumbers, func(n int) int { return n * n })
sum := reduce(squares, 0, func(acc, n int) int { return acc + n })
fmt.Printf(" 原始数据: %v\n", rawData)
fmt.Printf(" 偶数: %v\n", evenNumbers)
fmt.Printf(" 平方: %v\n", squares)
fmt.Printf(" 求和: %d\n", sum)
// 应用5: 分页处理
fmt.Printf(" 应用5 - 分页处理:\n")
allItems := []string{
"item1", "item2", "item3", "item4", "item5",
"item6", "item7", "item8", "item9", "item10",
"item11", "item12", "item13", "item14", "item15",
}
pageSize := 5
totalPages := (len(allItems) + pageSize - 1) / pageSize
for page := 0; page < totalPages; page++ {
start := page * pageSize
end := start + pageSize
if end > len(allItems) {
end = len(allItems)
}
pageItems := allItems[start:end]
fmt.Printf(" 第%d页: %v\n", page+1, pageItems)
}
// 应用6: 批处理
fmt.Printf(" 应用6 - 批处理:\n")
tasks := []string{
"task1", "task2", "task3", "task4", "task5",
"task6", "task7", "task8", "task9", "task10",
}
batchSize := 3
for i := 0; i < len(tasks); i += batchSize {
end := i + batchSize
if end > len(tasks) {
end = len(tasks)
}
batch := tasks[i:end]
fmt.Printf(" 处理批次: %v\n", batch)
// 这里可以并发处理这个批次
}
// 应用7: 滑动窗口
fmt.Printf(" 应用7 - 滑动窗口(移动平均):\n")
prices := []float64{10.5, 11.2, 10.8, 12.1, 11.9, 12.5, 11.8, 12.3, 13.0, 12.7}
windowSize := 3
fmt.Printf(" 价格序列: %v\n", prices)
fmt.Printf(" %d日移动平均:\n", windowSize)
for i := 0; i <= len(prices)-windowSize; i++ {
window := prices[i : i+windowSize]
average := 0.0
for _, price := range window {
average += price
}
average /= float64(len(window))
fmt.Printf(" 第%d-%d日: %.2f\n", i+1, i+windowSize, average)
}
fmt.Println()
}
// ========== 辅助函数和类型 ==========
// 去重函数
func removeDuplicates(slice []int) []int {
seen := make(map[int]bool)
var result []int
for _, v := range slice {
if !seen[v] {
seen[v] = true
result = append(result, v)
}
}
return result
}
// 过滤函数
func filter(slice []int, predicate func(int) bool) []int {
var result []int
for _, v := range slice {
if predicate(v) {
result = append(result, v)
}
}
return result
}
// 映射函数
func mapInts(slice []int, mapper func(int) int) []int {
result := make([]int, len(slice))
for i, v := range slice {
result[i] = mapper(v)
}
return result
}
// 归约函数
func reduce(slice []int, initial int, reducer func(int, int) int) int {
result := initial
for _, v := range slice {
result = reducer(result, v)
}
return result
}
// 简单队列实现
type Queue struct {
items []string
}
func NewQueue() *Queue {
return &Queue{items: make([]string, 0)}
}
func (q *Queue) Enqueue(item string) {
q.items = append(q.items, item)
}
func (q *Queue) Dequeue() string {
if len(q.items) == 0 {
return ""
}
item := q.items[0]
q.items = q.items[1:]
return item
}
func (q *Queue) IsEmpty() bool {
return len(q.items) == 0
}
// 简单栈实现
type Stack struct {
items []int
}
func NewStack() *Stack {
return &Stack{items: make([]int, 0)}
}
func (s *Stack) Push(item int) {
s.items = append(s.items, item)
}
func (s *Stack) Pop() int {
if len(s.items) == 0 {
return 0
}
index := len(s.items) - 1
item := s.items[index]
s.items = s.items[:index]
return item
}
func (s *Stack) IsEmpty() bool {
return len(s.items) == 0
}
func (s *Stack) Peek() int {
if len(s.items) == 0 {
return 0
}
return s.items[len(s.items)-1]
}
/*
运行这个程序:
go run 02-slices.go
学习要点:
1. 切片是动态数组,长度可变,是引用类型
2. 切片有长度(len)和容量(cap)两个属性
3. 切片的零值是 nil不能直接比较
4. append 操作可能触发扩容,创建新的底层数组
5. 多个切片可以共享同一个底层数组
切片的内部结构:
1. 指针:指向底层数组的指针
2. 长度:切片中元素的个数
3. 容量:从切片起始位置到底层数组末尾的元素个数
创建切片的方式:
1. 字面量:[]Type{values...}
2. make函数make([]Type, len, cap)
3. 从数组/切片array[start:end]
4. 三索引表达式slice[start:end:cap]
切片操作:
1. append添加元素可能扩容
2. copy复制元素
3. 切片表达式:获取子切片
4. 遍历for range 或索引循环
扩容机制:
1. 容量不足时创建新数组
2. 小切片通常扩容为原来的2倍
3. 大切片扩容策略更复杂
4. 预分配容量可以避免频繁扩容
最佳实践:
1. 预分配容量避免频繁扩容
2. 使用三索引表达式限制容量
3. 注意切片共享底层数组的问题
4. 大切片传参考虑性能影响
5. 及时释放不需要的切片引用
常见应用场景:
1. 动态数组和列表
2. 栈和队列实现
3. 缓冲区和管道
4. 数据处理和过滤
5. 分页和批处理
6. 滑动窗口算法
注意事项:
1. 切片是引用类型,修改会影响底层数组
2. 切片扩容会创建新数组,断开与原数组的联系
3. 空切片和nil切片的区别
4. 切片越界会panic
5. 大切片的内存泄漏问题
*/