【Go】--数组和切片
文章目录
- Go 语言数组与切片
- 数组和切片
- 数组 (Array)
- 定义
- 语法结构
- 特性
- 常见操作
- 切片 (Slice)
- 定义
- 语法结构
- 内部结构
- 常见操作
- 切片扩容机制
- 数组与切片的区别与联系
- 主要区别
- 联系
- 相互转换示例
- 使用场景
- 数组的使用场景
- 切片的使用场景
- 高级特性
- 切片的内存优化
- 多维切片
- 注意事项
- 数组注意事项
- 切片注意事项
- ALL
Go 语言数组与切片
数组和切片
数组和切片是 Go 语言中处理集合数据的核心数据结构。数组提供固定大小的存储,切片提供动态灵活的接口。理解它们的区别、联系和适用场景,对于编写高效、正确的 Go 程序至关重要。
- 数组:适合固定大小、性能敏感的场景
- 切片:适合动态大小、需要灵活操作的场景
- 选择原则:根据数据大小是否固定、是否需要动态调整来选择
数组 (Array)
定义
数组是具有固定长度的相同类型元素的序列。数组的长度是类型的一部分,因此不同长度的数组是不同的类型。
语法结构
// 声明语法
var arrayName [length]elementType// 初始化语法
var arrayName [length]elementType = [length]elementType{value1, value2, ..., valueN}
arrayName := [length]elementType{value1, value2, ..., valueN}// 自动推断长度
arrayName := [...]elementType{value1, value2, ..., valueN}
特性
- 固定长度:数组长度在编译时确定,不可改变
- 值类型:数组是值类型,赋值和传参会复制整个数组
- 类型包含长度:
[3]int
和[5]int
是不同的类型 - 内存连续:数组元素在内存中连续存储
package mainimport "fmt"func main() {// 声明数组var arr1 [3]intfmt.Println("arr1:", arr1) // [0 0 0]// 初始化数组var arr2 [3]int = [3]int{1, 2, 3}arr3 := [3]int{4, 5, 6}fmt.Println("arr2:", arr2) // [1 2 3]fmt.Println("arr3:", arr3) // [4 5 6]// 自动推断长度arr4 := [...]int{7, 8, 9, 10}fmt.Println("arr4:", arr4) // [7 8 9 10]fmt.Println("arr4 长度:", len(arr4)) // 4// 指定索引初始化arr5 := [5]int{1: 10, 3: 30}fmt.Println("arr5:", arr5) // [0 10 0 30 0]// 多维数组var matrix [2][3]int = [2][3]int{{1, 2, 3},{4, 5, 6},}fmt.Println("matrix:", matrix) // [[1 2 3] [4 5 6]]
}
常见操作
package mainimport "fmt"func main() {arr := [5]int{10, 20, 30, 40, 50}// 访问元素fmt.Println("第一个元素:", arr[0]) // 10fmt.Println("最后一个元素:", arr[len(arr)-1]) // 50// 修改元素arr[0] = 100fmt.Println("修改后:", arr) // [100 20 30 40 50]// 遍历数组fmt.Println("遍历数组:")for i := 0; i < len(arr); i++ {fmt.Printf("arr[%d] = %d\n", i, arr[i])}// 使用 range 遍历fmt.Println("使用 range 遍历:")for index, value := range arr {fmt.Printf("索引: %d, 值: %d\n", index, value)}// 数组比较(只有相同长度和类型的数组可以比较)arr1 := [3]int{1, 2, 3}arr2 := [3]int{1, 2, 3}arr3 := [3]int{1, 2, 4}fmt.Println("arr1 == arr2:", arr1 == arr2) // truefmt.Println("arr1 == arr3:", arr1 == arr3) // false
}
切片 (Slice)
定义
切片是对数组的抽象,提供了更灵活、更强大的序列接口。切片是引用类型,底层引用一个数组。
语法结构
// 声明切片
var sliceName []elementType// 使用 make 创建
sliceName := make([]elementType, length, capacity)// 字面量创建
sliceName := []elementType{value1, value2, ..., valueN}// 从数组创建
sliceName := arrayName[start:end]
内部结构
切片包含三个组件:
- 指针:指向底层数组的起始位置
- 长度:切片中元素的数量
- 容量:从切片起始位置到底层数组末尾的元素数量
package mainimport "fmt"func main() {// 声明切片var slice1 []intfmt.Println("slice1:", slice1) // []fmt.Println("slice1 是否为 nil:", slice1 == nil) // true// 使用 make 创建切片slice2 := make([]int, 3, 5)fmt.Println("slice2:", slice2) // [0 0 0]fmt.Println("长度:", len(slice2)) // 3fmt.Println("容量:", cap(slice2)) // 5// 字面量创建slice3 := []int{1, 2, 3, 4, 5}fmt.Println("slice3:", slice3) // [1 2 3 4 5]// 从数组创建切片arr := [5]int{10, 20, 30, 40, 50}slice4 := arr[1:4] // 包含索引1,不包含索引4fmt.Println("slice4:", slice4) // [20 30 40]// 修改切片会影响底层数组slice4[0] = 200fmt.Println("修改后数组:", arr) // [10 200 30 40 50]fmt.Println("修改后切片:", slice4) // [200 30 40]
}
常见操作
package mainimport "fmt"func main() {// 创建切片slice := []int{1, 2, 3}// 添加元素slice = append(slice, 4)fmt.Println("添加后:", slice) // [1 2 3 4]// 添加多个元素slice = append(slice, 5, 6, 7)fmt.Println("添加多个后:", slice) // [1 2 3 4 5 6 7]// 合并切片slice2 := []int{8, 9, 10}slice = append(slice, slice2...)fmt.Println("合并后:", slice) // [1 2 3 4 5 6 7 8 9 10]// 复制切片slice3 := make([]int, len(slice))copy(slice3, slice)fmt.Println("复制后:", slice3) // [1 2 3 4 5 6 7 8 9 10]// 修改复制后的切片不会影响原切片slice3[0] = 100fmt.Println("原切片:", slice) // [1 2 3 4 5 6 7 8 9 10]fmt.Println("新切片:", slice3) // [100 2 3 4 5 6 7 8 9 10]// 切片操作fmt.Println("前3个元素:", slice[:3]) // [1 2 3]fmt.Println("从第3个开始:", slice[3:]) // [4 5 6 7 8 9 10]fmt.Println("第2-5个元素:", slice[2:5]) // [3 4 5]// 删除元素(删除索引2的元素)slice = append(slice[:2], slice[3:]...)fmt.Println("删除后:", slice) // [1 2 4 5 6 7 8 9 10]
}
切片扩容机制
package mainimport "fmt"func main() {slice := make([]int, 0, 2)fmt.Printf("初始 - 长度: %d, 容量: %d\n", len(slice), cap(slice))for i := 1; i <= 10; i++ {slice = append(slice, i)fmt.Printf("添加 %d - 长度: %d, 容量: %d\n", i, len(slice), cap(slice))}/* 输出示例:初始 - 长度: 0, 容量: 2添加 1 - 长度: 1, 容量: 2添加 2 - 长度: 2, 容量: 2添加 3 - 长度: 3, 容量: 4 (扩容)添加 4 - 长度: 4, 容量: 4添加 5 - 长度: 5, 容量: 8 (扩容)添加 6 - 长度: 6, 容量: 8添加 7 - 长度: 7, 容量: 8添加 8 - 长度: 8, 容量: 8添加 9 - 长度: 9, 容量: 16 (扩容)添加 10 - 长度: 10, 容量: 16*/
}
数组与切片的区别与联系
主要区别
特性 | 数组 (Array) | 切片 (Slice) |
---|---|---|
长度 | 固定,编译时确定 | 动态,运行时可变 |
类型 | 值类型 | 引用类型 |
传递 | 值传递(复制整个数组) | 引用传递(传递切片头) |
比较 | 可以比较(相同类型) | 不能直接比较 |
声明 | var arr [3]int | var slice []int |
零值 | 对应类型的零值数组 | nil |
联系
- 底层关系:切片基于数组实现,切片是数组的视图
- 内存共享:多个切片可以共享同一个底层数组
- 相互转换:数组可以转换为切片,但切片不能直接转换为数组
相互转换示例
package mainimport "fmt"func main() {// 数组转切片arr := [5]int{1, 2, 3, 4, 5}slice := arr[:] // 整个数组转为切片fmt.Println("数组转切片:", slice) // [1 2 3 4 5]// 切片转数组(需要确保长度匹配)if len(slice) == 5 {var newArr [5]intcopy(newArr[:], slice) // 将切片内容复制到数组fmt.Println("切片转数组:", newArr) // [1 2 3 4 5]}// Go 1.17+ 支持直接转换// newArr := (*[5]int)(slice) // 注意:需要长度匹配
}
使用场景
数组的使用场景
- 固定大小的集合:如月份、星期等固定长度的数据
- 性能敏感场景:数组在栈上分配,访问速度快
- 内存布局控制:需要精确控制内存布局时
- 作为切片底层:为切片提供底层存储
package mainimport "fmt"func main() {// 固定大小的数据集合daysOfWeek := [7]string{"周一", "周二", "周三", "周四", "周五", "周六", "周日"}// 矩阵运算var matrix1 [2][2]int = [2][2]int{{1, 2}, {3, 4}}var matrix2 [2][2]int = [2][2]int{{5, 6}, {7, 8}}// 性能敏感的场景var buffer [1024]byte // 固定大小的缓冲区fmt.Println("星期:", daysOfWeek)fmt.Println("矩阵1:", matrix1)fmt.Println("矩阵2:", matrix2)fmt.Println("缓冲区大小:", len(buffer))
}
切片的使用场景
- 动态集合:大小不确定的数据集合
- 函数参数:作为函数参数传递集合数据
- 数据流处理:处理来自网络、文件的数据流
- 字符串处理:字符串本质上是字节切片
package mainimport ("fmt""strings"
)func processData(data []int) []int {result := make([]int, 0, len(data))for _, value := range data {if value%2 == 0 {result = append(result, value*2)}}return result
}func main() {// 动态数据处理data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}processed := processData(data)fmt.Println("处理后的数据:", processed) // [4 8 12 16 20]// 字符串处理(字符串可视为只读的字节切片)str := "Hello, 世界"bytes := []byte(str)runes := []rune(str)fmt.Println("字符串:", str)fmt.Println("字节切片:", bytes)fmt.Println("字符切片:", runes)fmt.Println("字符数量:", len(runes)) // 8 (Hello, 和 世界)// 字符串分割words := strings.Split("Go is a programming language", " ")fmt.Println("分割后的单词:", words)
}
高级特性
切片的内存优化
package mainimport "fmt"func main() {// 预分配容量避免频繁扩容slice := make([]int, 0, 1000) // 预分配足够容量for i := 0; i < 1000; i++ {slice = append(slice, i)}fmt.Printf("最终长度: %d, 容量: %d\n", len(slice), cap(slice))// 切片截取可能导致内存泄漏bigSlice := make([]int, 10000)smallSlice := bigSlice[:10] // smallSlice 仍然引用整个 bigSlice// 正确的做法:复制需要的部分properSlice := make([]int, 10)copy(properSlice, bigSlice[:10])fmt.Printf("smallSlice 容量: %d\n", cap(smallSlice)) // 10000fmt.Printf("properSlice 容量: %d\n", cap(properSlice)) // 10
}
多维切片
package mainimport "fmt"func main() {// 创建二维切片matrix := make([][]int, 3)for i := range matrix {matrix[i] = make([]int, 3)for j := range matrix[i] {matrix[i][j] = i*3 + j + 1}}fmt.Println("二维切片:")for i, row := range matrix {fmt.Printf("第%d行: %v\n", i, row)}// 不规则多维切片irregular := [][]int{{1},{2, 3},{4, 5, 6},}fmt.Println("\n不规则二维切片:")for i, row := range irregular {fmt.Printf("第%d行: %v\n", i, row)}
}
注意事项
数组注意事项
- 长度是类型的一部分:不同长度的数组不能相互赋值
- 值传递开销:大数组作为参数传递时会产生复制开销
- 零值初始化:声明但未初始化的数组会用零值填充
package mainimport "fmt"func main() {// 错误:不同长度的数组不能赋值// var arr1 [3]int// var arr2 [5]int// arr1 = arr2 // 编译错误// 正确:使用相同类型的数组var arr3 [3]int = [3]int{1, 2, 3}var arr4 [3]intarr4 = arr3 // 正确fmt.Println("arr4:", arr4)
}
切片注意事项
- nil 切片与空切片:nil 切片没有底层数组,空切片有底层数组但长度为0
- 切片共享底层数组:修改切片可能影响其他共享同一底层数组的切片
- append 的副作用:append 可能返回新的切片引用
- 容量与长度的区别:容量 >= 长度
package mainimport "fmt"func main() {// nil 切片 vs 空切片var nilSlice []int // nil 切片emptySlice := []int{} // 空切片emptySlice2 := make([]int, 0) // 空切片fmt.Printf("nilSlice: len=%d, cap=%d, nil=%t\n", len(nilSlice), cap(nilSlice), nilSlice == nil) // len=0, cap=0, nil=truefmt.Printf("emptySlice: len=%d, cap=%d, nil=%t\n", len(emptySlice), cap(emptySlice), emptySlice == nil) // len=0, cap=0, nil=falsefmt.Printf("emptySlice2: len=%d, cap=%d, nil=%t\n", len(emptySlice2), cap(emptySlice2), emptySlice2 == nil) // len=0, cap=0, nil=false// 切片共享问题arr := [5]int{1, 2, 3, 4, 5}slice1 := arr[1:4] // [2, 3, 4]slice2 := slice1[0:2] // [2, 3]slice2[0] = 200fmt.Println("arr:", arr) // [1 200 3 4 5]fmt.Println("slice1:", slice1) // [200 3 4]fmt.Println("slice2:", slice2) // [200 3]// append 的副作用slice3 := make([]int, 2, 3)slice3[0], slice3[1] = 1, 2slice4 := append(slice3, 3)slice4[0] = 100fmt.Println("slice3:", slice3) // [1 2] (未受影响,因为容量足够)fmt.Println("slice4:", slice4) // [100 2 3]slice5 := append(slice3, 3, 4) // 超出容量,创建新底层数组slice5[0] = 200fmt.Println("slice3:", slice3) // [1 2] (未受影响)fmt.Println("slice5:", slice5) // [200 2 3 4]
}
ALL
- 预分配容量:使用
make([]T, 0, capacity)
预分配足够容量 - 避免不必要的复制:对于大切片,考虑使用指针或引用
- 使用数组作为切片底层:对于固定大小的数据,先创建数组再转为切片
- 批量操作:使用
copy
函数进行批量复制