7.切片
1.切片基础
1.切片的定义
在 Go 语言中,切片(slice) 是一种拥有相同类型元素的可变长度的序列,类似于动态数组。
- 动态长度:切片的长度是可变的,适合需要频繁改变长度的数据结构。
- 引用类型:切片是对底层数组的引用,因此修改切片内容会影响到其引用的底层数组。
- 组成部分:切片包含一个指向底层数组的指针、长度(len)、以及容量(cap)。
- 自动扩容:当切片追加元素并超出容量时,会自动生成一个新的底层数组并复制数据到新的数组中。
定义切片的基本语法
var name []T
// name:表示切片的变量名。
// T:表示切片中元素的类型。
var nums []int
:声明了一个存储整数类型的切片,变量名为nums
。var names []string
:声明了一个存储字符串类型的切片,变量名为names
。
在 Go 语言中,字符串切片(slice of strings) 是一种引用类型,这意味着切片本身并不存储数据,而是引用底层数组中的数据。这种引用特性使得切片在赋值、传递函数参数或使用 =
赋值符号时,都只复制引用,不会复制数据本身。
package main
import "fmt"
func main() {
// 切片是引用类型,不支持直接比较,只能和 nil 比较
var a []string // 声明一个字符串切片
fmt.Println(a) // 输出:[]
fmt.Println(a == nil) // 输出:true
var b []int // 声明一个整数切片并初始化
fmt.Println(b) // 输出:[]
fmt.Println(b == nil) // 输出:false
var c = []bool{false, true} // 声明一个布尔切片并初始化
fmt.Println(c) // 输出:[false true]
fmt.Println(c == nil) // 输出:false
}
区别 nil
切片 和 空切片
var a []string
:这是一个nil
切片,因为它只是声明了但没有进行初始化,也没有为它分配底层数组。a
直接被赋值为nil
,所以a == nil
结果为true
。var b []int
:虽然看起来和a
的声明方式相同,但整数类型的切片b
在这种情况下默认被初始化为空切片(长度和容量都是0
),而且它有一个指向的空的底层数组,所以b == nil
结果为false
。c := []bool{false, true}
:显然,c
已经通过字面量初始化,因此也不是nil
,c == nil
结果为false
。
切片之间不能直接用
==
操作符比较两个切片是否相等,唯一能进行的比较是与nil
的比较。一个
nil
切片的长度和容量都是0
,但长度和容量为0
的切片不一定是nil
。
package main
import "fmt"
func main() {
var slice = []int{1, 2, 3}
slice2 := slice
slice[0] = 3
fmt.Println(slice, slice2)
}
切片 slice
被初始化为 [1, 2, 3]
。
slice2 := slice
使得 slice2
和 slice
指向同一个底层数组。
修改 slice[0]
的值会同时反映在 slice2
上,因为它们共享同一个底层数组。因此,输出结果为 [3, 2, 3] [3, 2, 3]
。
2. 切片的本质
切片的本质就是对底层数组的封装,它包含了三个信息; 底层数组的指针、切片的长度(len)和切片的容量(cap)
举个例子,现在有一个数组 a := [8] int {0,1,2,3,4,5,6,7}
, 切片 s1 := a [5]
,相应示意图如下。
切片 s2 := a[3:6]
,相应示意图如下
[3:6]
:切片的下界和上界,创建的新切片会包含从下标 3
到下标 5
的元素,但不包含下标 6
处的元素。
- **下标
3
:是开始位置,包含该位置的元素。 - **下标
6
:是结束位置,不包含该位置的元素。
** 左闭右开区间规则**
一致性
半开区间 [start:end]
设计能够使操作更加一致。例如,当 start == end
时,结果总是一个空切片,不管 start
和 end
是什么值,这让代码更容易理解和预测。
例如:
s := a[3:3]
// 结果是一个空切片,因为 3 == 3
这种情况在逻辑上很容易理解。如果是闭区间设计,这种情况下则可能引起混乱。