在Go语言中,数组和切片是两种重要的线性数据结构。数组长度固定,值类型;切片则是动态数组,引用类型,是实际开发中最常用的序列类型。理解二者之间的关系和底层实现,是高效使用Go语言的关键。
数组是具有固定长度、存储相同类型元素的序列。长度是数组类型的一部分,例如 [3]int 和 [4]int 是不同的类型。
package main
import "fmt"
func main() {
// 方式1:声明后赋值(零值数组)
var arr1 [3]int // [0,0,0]
arr1[0] = 10
// 方式2:字面量初始化
arr2 := [3]int{1, 2, 3}
// 方式3:自动推断长度(...)
arr3 := [...]int{4, 5, 6, 7} // 长度由初始化元素个数决定
// 方式4:指定索引初始化
arr4 := [5]int{0: 10, 3: 40} // [10,0,0,40,0]
fmt.Println(arr1, arr2, arr3, arr4)
}
// 传统for
for i := 0; i < len(arr2); i++ {
fmt.Println(arr2[i])
}
// range遍历
for index, value := range arr3 {
fmt.Printf("索引%d: 值%d\n", index, value)
}
数组在赋值或传参时会发生完整拷贝,对新数组的修改不影响原数组。
original := [3]int{1, 2, 3}
copyArr := original
copyArr[0] = 100
fmt.Println(original) // [1 2 3],原数组不变
fmt.Println(copyArr) // [100 2 3]
// 函数传参也是拷贝
func modify(arr [3]int) {
arr[0] = 99
}
// 调用后 original 仍为 [1,2,3]
var matrix [2][3]int = [2][3]int{
{1, 2, 3},
{4, 5, 6},
}
fmt.Println(matrix[1][2]) // 6
切片是对底层数组一个连续片段的引用,长度可变,是Go中最常用的序列类型。切片本身不存储数据,操作切片可能会影响底层数组。
// 从数组切割
arr := [5]int{1,2,3,4,5}
s1 := arr[1:4] // [2,3,4],包含索引1~3
// 字面量创建
s2 := []int{10,20,30} // 长度=容量=3
// 使用 make 创建(指定长度和容量)
s3 := make([]int, 5, 10) // 长度5,容量10,元素零值
s4 := make([]int, 3) // 长度=容量=3
切片包含三个字段:指向底层数组的指针、长度(len)和容量(cap)。长度是切片中元素个数,容量是从切片起始位置到底层数组末尾的元素个数。
numbers := []int{1,2,3,4,5,6}
s := numbers[1:4] // 元素: [2,3,4]
fmt.Println(len(s)) // 3
fmt.Println(cap(s)) // 5 (从索引1到末尾共5个元素)
// 重新切片可以扩展切片(不超过容量)
s = s[:cap(s)] // 扩展到长度为5: [2,3,4,5,6]
多个切片可以共享同一个底层数组,修改会互相影响。
a := []int{1,2,3}
b := a // b 和 a 指向同一底层数组
b[0] = 99
fmt.Println(a) // [99,2,3]
使用 append 向切片添加元素,如果容量不足,会自动扩容(通常以2倍容量增长)。
var s []int // nil 切片
s = append(s, 1) // 追加1个元素
s = append(s, 2, 3, 4) // 追加多个
s = append(s, []int{5,6}...) // 解包追加
fmt.Println(s) // [1 2 3 4 5 6]
// 查看扩容过程
var slice []int
for i := 0; i < 10; i++ {
slice = append(slice, i)
fmt.Printf("len=%d cap=%d\n", len(slice), cap(slice))
}
copy(dst, src) 将源切片元素复制到目标切片,返回复制的元素个数。
src := []int{1,2,3}
dst := make([]int, 2)
n := copy(dst, src) // dst=[1,2], n=2
// 复制全部元素
dst2 := make([]int, len(src))
copy(dst2, src)
利用append和切片操作实现删除。
// 删除索引 i 的元素
a := []int{0,1,2,3,4,5}
i := 2
a = append(a[:i], a[i+1:]...) // 结果: [0,1,3,4,5]
// 删除首部元素
a = a[1:]
// 删除尾部元素
a = a[:len(a)-1]
nil 切片未分配底层数组,空切片已分配但长度为0。二者在大多数情况下(如range、len、append)表现一致。
var s1 []int // nil 切片
s2 := []int{} // 空切片(非nil)
fmt.Println(s1 == nil) // true
fmt.Println(s2 == nil) // false
// 但两者都可直接append
| 特性 | 数组 | 切片 |
|---|---|---|
| 长度 | 固定,编译时确定 | 动态,运行时可变 |
| 类型定义 | [n]T | []T |
| 值/引用 | 值类型(赋值拷贝) | 引用类型(共享底层) |
| 内存分配 | 栈上或静态区 | 底层数组在堆上 |
| 作为参数 | 整个数组拷贝(大数组低效) | 只拷贝切片头(指针/len/cap)高效 |
| 零值 | 各个元素零值 | nil(未make时) |
package main
import "fmt"
func main() {
// 数组转切片
arr := [5]int{1,2,3,4,5}
slice := arr[:] // 得到包含所有元素的切片
// 过滤函数:返回新切片
filter := func(s []int, fn func(int) bool) []int {
result := make([]int, 0, len(s))
for _, v := range s {
if fn(v) {
result = append(result, v)
}
}
return result
}
even := filter(slice, func(v int) bool { return v%2 == 0 })
fmt.Println("偶数:", even) // [2,4]
// 去重示例
nums := []int{1,2,2,3,4,4,5}
unique := []int{}
seen := make(map[int]bool)
for _, v := range nums {
if !seen[v] {
seen[v] = true
unique = append(unique, v)
}
}
fmt.Println("去重后:", unique) // [1,2,3,4,5]
}