Go语言-->切片
Go 语言切片详细解释
1. 切片的基本概念
1.1 什么是切片
切片是 Go 语言中的一种动态数组,它是对底层数组的一个连续片段的引用。切片比数组更灵活,是 Go 中最常用的数据结构之一。
1.2 切片的内部结构
// Go 切片的内部结构(简化版)
type slice struct {array unsafe.Pointer // 指向底层数组的指针len int // 当前长度cap int // 容量
}
2. 切片的声明和初始化
2.1 基本声明方式
// 1. 声明一个空切片
var s []int // nil 切片,len=0, cap=0// 2. 使用 make 创建切片
s1 := make([]int, 5) // len=5, cap=5, 元素都是零值
s2 := make([]int, 3, 10) // len=3, cap=10// 3. 切片字面量
s3 := []int{1, 2, 3, 4, 5} // len=5, cap=5// 4. 从数组创建切片
arr := [5]int{1, 2, 3, 4, 5}
s4 := arr[1:4] // [2, 3, 4], len=3, cap=4
3. 切片的基本操作
3.1 访问和修改元素
s := []int{1, 2, 3, 4, 5}// 访问元素
fmt.Println(s[0]) // 1
fmt.Println(s[2]) // 3// 修改元素
s[1] = 10
fmt.Println(s) // [1, 10, 3, 4, 5]// 获取长度和容量
fmt.Println(len(s)) // 5
fmt.Println(cap(s)) // 5
3.2 切片操作
s := []int{1, 2, 3, 4, 5}// 切片操作 [start:end]
s1 := s[1:3] // [2, 3]
s2 := s[:3] // [1, 2, 3]
s3 := s[2:] // [3, 4, 5]
s4 := s[:] // [1, 2, 3, 4, 5] (完整拷贝)
4. 切片的动态操作
4.1 append 操作
// append 是切片最重要的操作
s := []int{1, 2, 3}
fmt.Printf("原始: len=%d, cap=%d, %v\n", len(s), cap(s), s)// 添加单个元素
s = append(s, 4)
fmt.Printf("添加4: len=%d, cap=%d, %v\n", len(s), cap(s), s)// 添加多个元素
s = append(s, 5, 6, 7)
fmt.Printf("添加5,6,7: len=%d, cap=%d, %v\n", len(s), cap(s), s)// 合并切片
s2 := []int{8, 9, 10}
s = append(s, s2...) // 注意三个点
fmt.Printf("合并切片: len=%d, cap=%d, %v\n", len(s), cap(s), s)
4.2 copy 操作
// copy 用于复制切片元素
src := []int{1, 2, 3, 4, 5}
dst := make([]int, 3)// 复制元素,返回实际复制的元素数量
n := copy(dst, src)
fmt.Printf("复制了 %d 个元素: %v\n", n, dst) // 复制了 3 个元素: [1 2 3]// 切片内部复制(删除元素的常用技巧)
s := []int{1, 2, 3, 4, 5}
// 删除索引为 2 的元素
copy(s[2:], s[3:])
s = s[:len(s)-1]
fmt.Println(s) // [1 2 4 5]
5. 切片的内存模型和共享
5.1 底层数组共享
// 多个切片可以共享同一个底层数组
original := []int{1, 2, 3, 4, 5}
slice1 := original[1:3] // [2, 3]
slice2 := original[2:4] // [3, 4]// 修改 slice1 会影响 original 和 slice2
slice1[1] = 99
fmt.Println("original:", original) // [1, 2, 99, 4, 5]
fmt.Println("slice1:", slice1) // [2, 99]
fmt.Println("slice2:", slice2) // [99, 4]
5.2 扩容机制
func demonstrateGrowth() {s := make([]int, 0, 2)fmt.Printf("初始: len=%d, cap=%d, ptr=%p\n", len(s), cap(s), &s)for i := 1; i <= 5; i++ {s = append(s, i)fmt.Printf("添加%d: len=%d, cap=%d, ptr=%p\n", i, len(s), cap(s), &s)}
}
// 输出示例:
// 初始: len=0, cap=2, ptr=0xc000010018
// 添加1: len=1, cap=2, ptr=0xc000010018
// 添加2: len=2, cap=2, ptr=0xc000010018
// 添加3: len=3, cap=4, ptr=0xc000010018 // 扩容了,但切片结构地址不变
// 添加4: len=4, cap=4, ptr=0xc000010018
// 添加5: len=5, cap=8, ptr=0xc000010018 // 再次扩容
6. 切片作为函数参数
6.1 传递机制详解
// 切片传递的是结构体副本,但共享底层数组
func modifySlice(s []int) {// 修改元素:影响原切片(共享底层数组)if len(s) > 0 {s[0] = 999}// 修改切片结构:不影响原切片(只修改了副本)s = append(s, 100)fmt.Println("函数内:", s)
}func modifySlicePtr(s *[]int) {// 通过指针修改:影响原切片if len(*s) > 0 {(*s)[0] = 888}// 通过指针修改切片结构:影响原切片*s = append(*s, 200)fmt.Println("函数内:", *s)
}func main() {original := []int{1, 2, 3}fmt.Println("调用前:", original)modifySlice(original)fmt.Println("调用后:", original) // [999, 2, 3] - 元素被修改,但没有新元素modifySlicePtr(&original)fmt.Println("指针调用后:", original) // [888, 2, 3, 200] - 完全被修改
}
7. 切片的高级用法
7.1 二维切片
// 创建二维切片
matrix := make([][]int, 3)
for i := range matrix {matrix[i] = make([]int, 4)
}// 或者直接初始化
matrix2 := [][]int{{1, 2, 3},{4, 5, 6},{7, 8, 9},
}
7.2 切片排序
import "sort"// 基本类型排序
nums := []int{3, 1, 4, 1, 5, 9}
sort.Ints(nums)
fmt.Println(nums) // [1, 1, 3, 4, 5, 9]// 自定义排序
type Person struct {Name stringAge int
}people := []Person{{"Alice", 30},{"Bob", 25},{"Charlie", 35},
}// 按年龄排序
sort.Slice(people, func(i, j int) bool {return people[i].Age < people[j].Age
})
7.3 切片去重
func removeDuplicates(slice []int) []int {seen := make(map[int]bool)result := []int{}for _, item := range slice {if !seen[item] {seen[item] = trueresult = append(result, item)}}return result
}
8. 性能优化建议
8.1 预分配容量
// ❌ 低效:频繁扩容
var result []int
for i := 0; i < 1000; i++ {result = append(result, i)
}// ✅ 高效:预分配容量
result := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {result = append(result, i)
}
8.2 避免内存泄漏
// ❌ 可能导致内存泄漏
func getFirstThree(large []int) []int {return large[:3] // 仍然引用整个大数组
}// ✅ 正确做法
func getFirstThree(large []int) []int {result := make([]int, 3)copy(result, large[:3])return result
}
9. 常见陷阱和注意事项
9.1 循环中的切片修改
// ❌ 错误:在循环中修改切片长度
slice := []int{1, 2, 3, 4, 5}
for i, v := range slice {if v%2 == 0 {slice = append(slice[:i], slice[i+1:]...) // 危险!}
}// ✅ 正确:从后往前删除或使用新切片
slice := []int{1, 2, 3, 4, 5}
for i := len(slice) - 1; i >= 0; i-- {if slice[i]%2 == 0 {slice = append(slice[:i], slice[i+1:]...)}
}
9.2 nil 切片 vs 空切片
var nilSlice []int // nil 切片
emptySlice := []int{} // 空切片
madeSlice := make([]int, 0) // 空切片fmt.Println(nilSlice == nil) // true
fmt.Println(emptySlice == nil) // false
fmt.Println(madeSlice == nil) // false// 但在使用上基本相同
fmt.Println(len(nilSlice)) // 0
fmt.Println(len(emptySlice)) // 0