Files
golang/golang-learning/04-data-structures/02-slices.go
2025-08-24 01:15:18 +08:00

672 lines
18 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
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. 大切片的内存泄漏问题
*/