Go语言的切片(slice)是一个灵活且强大的数据结构。相比数组,切片的长度可以动态变化,更适合用于处理动态数据。切片是基于数组构建的抽象,为开发者提供了更高效的内存管理和数据操作手段。
一、切片的概念和结构
切片其实是对底层数组的一个引用。它包含三个核心信息:
- 指向底层数组的指针(Pointer) - 指向切片的第一个元素(在底层数组中的位置)。
- 切片的长度(Length) - 当前切片中实际包含的元素数。
- 切片的容量(Capacity) - 从切片的起始位置到底层数组末尾的元素总数。
切片在Go语言中被设计为动态扩展的数据结构,通过共享底层数组实现快速操作和高效的内存管理。
二、切片的创建方式
切片有多种创建方式,具体如下:
1. 从数组创建切片
arr := [5]int{10, 20, 30, 40, 50}
s := arr[1:4] // 从arr数组的第1到第3个元素创建一个切片
fmt.Println(s) // 输出:[20 30 40]
在上例中,arr[1:4]
的含义是从数组的索引1(包含)到索引4(不包含)生成一个切片。切片len(s)
为3,cap(s)
为4。
2. 通过make
函数创建切片
make
函数可以创建一个指定长度和容量的切片。其格式为:make([]Type, length, capacity)
。
s := make([]int, 3, 5) // 创建一个长度为3,容量为5的切片
fmt.Println(s) // 输出:[0 0 0]
fmt.Println(len(s)) // 输出:3
fmt.Println(cap(s)) // 输出:5
如果省略容量参数,则容量和长度相同。
3. 直接使用字面量创建切片
s := []int{1, 2, 3} // 创建一个包含元素1, 2, 3的切片,长度和容量都为3
fmt.Println(s) // 输出:[1 2 3]
三、切片的属性
通过内置函数len
和cap
可以分别获取切片的长度和容量。
s := []int{1, 2, 3, 4, 5}
fmt.Println("长度:", len(s)) // 输出:5
fmt.Println("容量:", cap(s)) // 输出:5
当我们基于一个切片或数组进行“切片操作”时,容量会从新的切片起始位置算起,到底层数组的末尾。
四、切片的常用操作
1. 切片的追加操作(append)
append
是一个用于向切片追加元素的内置函数。若切片容量不足时,append
会创建一个新的底层数组,将旧数据复制过去,然后再追加新元素。
s := []int{1, 2, 3}
s = append(s, 4, 5) // 追加元素4, 5
fmt.Println(s) // 输出:[1 2 3 4 5]
fmt.Println(len(s)) // 输出:5
fmt.Println(cap(s)) // 如果之前容量为3,现在可能变成6
2. 切片的切片(子切片)
切片也可以再次“切片”,生成一个新的子切片。子切片共享原切片的底层数组,因此对数据的更改会影响彼此。
s := []int{1, 2, 3, 4, 5}
sub := s[1:4] // 创建从索引1到索引3的子切片
fmt.Println(sub) // 输出:[2 3 4]sub[0] = 20 // 修改sub的第一个元素
fmt.Println(s) // 输出:[1 20 3 4 5],s也被修改
五、切片的扩容机制
切片的底层数组容量是有限的,如果超出容量,Go语言会自动扩容。扩容时,Go会创建一个新的、较大的底层数组,并将旧数组的数据拷贝到新数组中。扩容的策略通常是成倍扩展容量,但并不保证每次都是相同倍数。
例如,当容量不够时,以下代码展示了扩容的现象:
s := make([]int, 2, 2)
s[0] = 1
s[1] = 2
fmt.Println(len(s), cap(s)) // 输出:2 2s = append(s, 3) // 追加一个元素
fmt.Println(len(s), cap(s)) // 输出:3 4,容量翻倍
在上例中,append
使切片的容量从2变为4。
六、切片的拷贝(copy函数)
copy
函数用于将一个切片的内容复制到另一个切片中,格式为:copy(dst, src)
。它会根据目标切片的长度,复制最多与dst
长度相同的元素数。
src := []int{1, 2, 3}
dst := make([]int, 2)
copy(dst, src) // 只会复制src的前两个元素到dst中
fmt.Println(dst) // 输出:[1 2]
七、切片是引用类型的注意事项
切片是引用类型,因此多个切片可能共享同一个底层数组。这种情况下,一个切片的修改可能会影响另一个切片的内容。为了避免这种问题,可以在切片上创建一个副本。
original := []int{1, 2, 3}
copySlice := make([]int, len(original))
copy(copySlice, original) // 将original内容复制到copySlice
copySlice[0] = 100
fmt.Println(original) // 输出:[1 2 3]
fmt.Println(copySlice) // 输出:[100 2 3]
八、示例代码总结
以下代码展示了切片的创建、追加、切片操作和拷贝:
package main
import "fmt"func main() {// 基于数组创建切片arr := [5]int{1, 2, 3, 4, 5}s := arr[1:4]fmt.Println("切片内容:", s)fmt.Println("切片长度:", len(s))fmt.Println("切片容量:", cap(s))// 追加元素s = append(s, 6)fmt.Println("追加后切片:", s)// 创建切片副本copySlice := make([]int, len(s))copy(copySlice, s)fmt.Println("副本切片:", copySlice)// 子切片修改影响原切片subSlice := s[1:3]subSlice[0] = 100fmt.Println("修改子切片后,原切片:", s)
}
以上代码展示了创建切片、追加元素、复制切片,以及子切片修改对原切片的影响等操作。