【Go语言基础【17】】切片:一种动态数组
文章目录
- 零、概述
- 一、切片基础
- 1、切片的结构
- 2、切片的创建方式
- 3、切片的操作与扩容
- 二、切片的拷贝与共享内存
- 三、切片作为函数参数
Go语言的切片(slice)是一种动态数组,提供了灵活、高效的元素序列操作。它基于底层数组实现,通过长度和容量的分离设计,支持动态扩容和内存优化。
零、概述
切片 vs 数组
特性 | 切片(slice) | 数组(array) |
---|---|---|
类型 | 引用类型 | 值类型 |
长度 | 动态可变 | 固定不可变 |
传递方式 | 传递指针副本,修改影响原数据 | 传递值拷贝,修改不影响原数据 |
创建方式 | make([]T, len, cap) 或[]T{...} | [n]T{...} 或[...]T{...} |
内存占用 | 包含指针、长度、容量 | 仅包含元素 |
性能优化原理:
- 预分配容量:若已知元素数量,使用
make([]T, 0, capacity)
避免频繁扩容。- 避免内存泄漏:切片保留对底层数组的引用,若仅需部分元素,建议
copy
到新切片后释放原数组。- 大容量切片谨慎扩容:大切片扩容可能导致内存浪费,可通过
copy
手动控制扩容策略。
一、切片基础
1、切片的结构
切片是一个引用类型,底层结构包含三个字段:
type slice struct {array unsafe.Pointer // 指向底层数组的指针len int // 当前切片的长度cap int // 底层数组的容量
}
- 长度(len):切片中实际存在的元素数量,可通过
len()
函数获取。 - 容量(cap):底层数组的总长度,可通过
cap()
函数获取。 - 指针(array):指向底层数组的起始位置,决定了切片的起始元素。
示例:创建切片
s := make([]int, 3, 5) // 创建长度为3、容量为5的切片
// 底层数组: [0, 0, 0, 0, 0]
// 切片结构: {array: 指向数组, len: 3, cap: 5}
2、切片的创建方式
a. 使用make
函数
s1 := make([]int, 3) // 长度=容量=3,初始值为0
s2 := make([]int, 3, 5) // 长度=3,容量=5
b. 使用切片字面量
s3 := []int{1, 2, 3} // 长度=容量=3,初始值为[1,2,3]
s4 := []int{3: 100} // 索引3的值为100,长度=容量=4,初始值为[0,0,0,100]
c. 从数组或切片派生
arr := [5]int{1, 2, 3, 4, 5}
s5 := arr[1:3] // 从数组创建切片,[2,3],长度=2,容量=4
s6 := s5[0:2] // 从切片创建切片,[2,3],长度=2,容量=4
3、切片的操作与扩容
a. 访问元素
通过索引访问,范围为0
到len(s)-1
:
s := []int{1, 2, 3}
fmt.Println(s[0]) // 输出: 1
b. 基于切片创建切片
通过指定[low:high:max]
创建新切片:
low
:起始索引(包含)。high
:结束索引(不包含),新切片长度为high - low
。max
:容量上限,新切片容量为max - low
(可选,默认等于原切片容量)。
s := []int{10, 20, 30, 40, 50}
s1 := s[1:3] // 长度=2,容量=4,元素=[20,30]
s2 := s[1:3:3] // 长度=2,容量=2,元素=[20,30]
c. nil切片与空切片
var nilSlice []int // nil切片,array=nil,len=0,cap=0
emptySlice := make([]int, 0) // 空切片,array指向空数组,len=0,cap=0
- nil切片:未初始化,
array
为nil
。 - 空切片:已初始化,但长度为0,
array
指向底层数组。
d. 动态扩容:append
函数
s := make([]int, 0, 2) // 容量=2
s = append(s, 1, 2) // 追加元素,容量足够,直接添加
s = append(s, 3) // 容量不足,触发扩容(通常翻倍)
扩容规则:
- 容量
<1000
时,通常翻倍。- 容量
≥1000
时,按1.25倍增长(具体实现可能随版本变化)。
e、多维切片
切片的元素可以是另一个切片,形成多维结构:
matrix := [][]int{{1, 2, 3},{4, 5, 6},
}fmt.Println(matrix[0][1]) // 输出: 2
注意:多维切片的每个内层切片是独立的,可拥有不同长度。
二、切片的拷贝与共享内存
- 浅拷贝(共享底层数组)
s1 := []int{1, 2, 3}
s2 := s1[0:2] // s2与s1共享底层数组
s1[0] = 100 // 修改s1会影响s2
fmt.Println(s2[0]) // 输出: 100
- 深拷贝(独立内存)
使用copy
函数:
s1 := []int{1, 2, 3}
s2 := make([]int, len(s1))
copy(s2, s1) // 复制元素到新切片
s1[0] = 100 // 修改s1不影响s2
fmt.Println(s2[0]) // 输出: 1
三、切片作为函数参数
切片作为参数传递时,传递的是切片的值拷贝(即array
、len
、cap
的副本),但由于array
是指针,函数内可修改原切片的元素:
func modify(s []int) {s[0] = 100 // 修改底层数组的元素
}func main() {s := []int{1, 2, 3}modify(s)fmt.Println(s[0]) // 输出: 100
}
若需在函数内扩容并影响原切片,需返回新切片:
func appendElement(s []int) []int {return append(s, 4)
}s := []int{1, 2, 3}
s = appendElement(s) // 需接收返回值以更新原切片