切片的结构
切片的底层结构:
type SliceHeader struct {Data uintptr // 指向底层数组的指针 Len int //长度Cap int //空间容量
}
切片的初始化
1 通过数组或者已有的slice创建新的slice
1.1 使用数组创建切片
通过数组的一部分来初始化切片。
array := [10]int{0,1, 2, 3, 4, 5,6,7,8,9}
slice := a[5:7]
Slice将与原数组共用一部分内存。
1.2 通过slice创建新的切片对象
x := []int{2, 3, 5, 7, 11}
y := x[1:3]
x: 长度len=5 cap=5 data指针指向长度为5的底层数组结构。
y 长度为2 cap为y底层原始数组结构第一个元素位置到的最后一个容量空间位置的长度即5-1=4
data为指针指向底层原始数组结构第1个元素的地址(索引从0开始)
图示如下:
2,使用make函数
make函数是Go中用于分配和初始化内置类型的内置函数,也可以用来创建切片。
slice := make([]int, 5, 10) // 创建一个长度为5、容量为10的int类型切片
长度为5,即可以使用下标slice[0] ~ slice[4]来操作里面的元素,capacity为10,表示后续向slice添加新的元素时可以不必重新分配内存,直接使用预留内存即可。
3,切片字面量
直接使用切片字面量创建切片。
s := []int{1, 2, 3} // 直接初始化一个切片
4,较特殊的切片:
nil 切片
var a []int
未初始化的切片默认值为nil
空切片
var b = []int{}
也叫零值切片。和 nil 不相等, 一般用来表示一个空的集合。len 和 cap 都为 0。空切片在内部拥有一个非nil的、零长度的底层数组
在判断一个切片是否为空时,一般通过 len 获取切片的长度来判断,一般很少将切片和 nil 值做直接的比较
数组与切片
数组截取元素生成切片,共用底层元素存储底层数组结构
func main() {data := [...]int{0, 1, 2, 3, 4, 5}s := data[2:4]s[0] += 100s[1] += 200fmt.Println(s)fmt.Println(data)
}
打印结果:
[102 203]
[0 1 102 203 4 5]
切片赋值
data := [...]int{0, 1, 2, 3, 4, 5}s2 := data[:]fmt.Println("s2是:", s2)s3 := s2fmt.Println("s3是:", s3)s3[0] = 1000 + s3[0]fmt.Println("s3是:", s3)fmt.Println("s2是:", s2)fmt.Println("data是:", data)
打印结果:
s3是: [1000 1 2 3 4 5]
s2是: [1000 1 2 3 4 5]
data是: [1000 1 2 3 4 5]
切片参数
函数参数是切片时,是引用传递,函数内对切片的改动影响源切片
func main(){sl := []int{1, 2, 3, 4, 5, 6, 7}PrintElements(sl)fmt.Printf("slice a : %v\n", sl)}func PrintElements(sl []int) {fmt.Println("-------------")for i, v := range sl {fmt.Println(v)sl[i] = v + 1}
}
打印结果:
-------------
1
2
3
4
5
6
7
slice a : [2 3 4 5 6 7 8]
切片容量
切片扩容
扩容实际上是重新分配一块更大的内存,将原Slice数据拷贝进新Slice,然后返回新Slice,扩容后再将数据追加进去。
例如,当向一个capacity为5,且length也为5的Slice再次追加1个元素时,就会发生扩容,如下图所示:
扩容操作只关心容量,会把原Slice数据拷贝到新Slice,追加数据由append在扩容结束后完成。上图可见,扩容后新的Slice长度仍然是5,但容量由5提升到了10,原Slice的数据也都拷贝到了新Slice指向的数组中。
扩容容量的选择遵循以下规则:
- 如果原Slice容量小于1024,则新Slice容量将扩大为原来的2倍;
- 如果原Slice容量大于等于1024,则新Slice容量将扩大为原来的1.25倍;
使用append()向Slice添加一个元素的实现步骤如下:
- 假如Slice容量够用,则将新元素追加进去,Slice.len++,返回原Slice
- 原Slice容量不够,则将Slice先扩容,扩容后得到新Slice
- 将新元素追加进新Slice,Slice.len++,返回新的Slice。
参考:slice-地鼠文档
切片字面量创建的切片
slice := []int{1, 2, 3, 4, 5, 6, 7, 8}
当你使用字面量来创建切片时,切片的初始容量(capacity)通常等于其长度(length)。这意味着,例子slice := []int{1, 2, 3, 4, 5, 6, 7, 8}
,切片的长度和容量都是8。
slice := []int{1, 2, 3, 4, 5, 6, 7, 8}fmt.Print("cap:", cap(slice))fmt.Print("len:", len(slice))
cap: 8
len: 8
扩容:
slice := []int{1, 2, 3, 4, 5, 6, 7, 8}fmt.Println("cap:", cap(slice))fmt.Println("len:", len(slice))fmt.Println("----------append后扩容----------")slice2 := append(slice, 0) // 切片扩展 1 个空间fmt.Println("cap:", cap(slice2))fmt.Println("len:", len(slice2))fmt.Println(&slice[0] == &slice2[0])
打印结构
cap: 8
len: 8
----------append后扩容----------
cap: 16
len: 9
false
append元素后容量不够使用,扩容为原来的两倍
make创建的切片
make创建的切片,类型申明之后,第一个数值是长度,第二个数值是容量
slice2 := make([]int, 3, 7)fmt.Println("cap:", cap(slice2))fmt.Println("len:", len(slice2))
cap: 7
len: 3
append元素后,容量不够就会扩容为之前的两倍容量。随之是地址的改动。
slice2 := make([]int, 3, 7)fmt.Println("cap:", cap(slice2))fmt.Println("len:", len(slice2))slice3 := append(slice2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // 切片扩展 1 个空间fmt.Println("----------append后扩容----------")fmt.Println("cap:", cap(slice3))fmt.Println("len:", len(slice3))fmt.Println(&slice2[0] == &slice3[0])
cap: 7
len: 3
----------append后扩容----------
cap: 14
len: 13
false
切片操作
1,append操作
使用append函数,生成新的切片
append函数返回的slice跟原slice是独立的slice,互不影响
var a = []int{1, 2, 3}fmt.Printf("slice a : %v\n", a)var b = []int{4, 5, 6}fmt.Printf("slice b : %v\n", b)c := append(a, b...)fmt.Printf("slice c : %v\n", c)b = append(b, 7)fmt.Printf("slice b after apend 7: %v\n", b)a = append(a, 7)fmt.Printf("slice a after apend 7: %v\n", a)fmt.Printf("slice c : %v\n", c)c = append(c, 7)fmt.Printf("slice c after apend 7: %v\n", c)
打印结果:
slice a : [1 2 3]
slice b : [4 5 6]
slice c : [1 2 3 4 5 6]
slice b after apend 7: [4 5 6 7]
slice a after apend 7: [1 2 3 7]
slice c : [1 2 3 4 5 6]
slice c after apend 7: [1 2 3 4 5 6 7]
其中b...是使用...展开运算符将slice展开作为单独元素传递给函数使用
以上apend操作是在末尾添加,还可以在头部添加
s := []int{2, 3, 4, 5, 6, 7}s = append([]int{1}, s...)fmt.Println("s是:", s)s = append([]int{-1, -2}, s...)fmt.Println("s是:", s)
s是: [1 2 3 4 5 6 7]
s是: [-1 -2 1 2 3 4 5 6 7]
2,切片copy
copy(dst,src)将src内容复制给dst切片
src := []int{1, 2, 3}dst := make([]int, 3)copy(dst, src) // 现在dst是[1, 2, 3]的拷贝fmt.Println("dst是:", dst)
copy复制后两个切片是独立的
src := []int{1, 2, 3}dst := make([]int, 3)copy(dst, src) // 现在dst是[1, 2, 3]的拷贝fmt.Println("dst是:", dst)dst = append(dst, 4)fmt.Println("dst是:", dst)fmt.Println("src是:", src)
dst是: [1 2 3]
dst是: [1 2 3 4]
src是: [1 2 3]
copy与append实现高效 添加元素
a = append(a, 0) // 切片扩展 1 个空间copy(a[i+1:], a[i:]) // a[i:] 向后移动 1 个位置a[i] = x // 设置新添加的元素
具体实现:
a := []int{1, 2, 3, 4, 5, 6, 7, 8}a = append(a, 0) // 切片扩展 1 个空间copy(a[4+1:], a[4:]) // a[i:] 向后移动 1 个位置a[4] = 88 // 设置新添加的元素fmt.Println("a是:", a)
打印:
a是: [1 2 3 4 88 5 6 7 8]
3,切片删除
从末尾删除
slice5 := []int{1, 2, 3, 4, 5, 6, 7, 8}new_slice5 := slice5[:len(slice5)-1]fmt.Println("new_slice5:", new_slice5)new_slice5 = slice5[:len(slice5)-5]fmt.Println("new_slice5:", new_slice5)
new_slice5: [1 2 3 4 5 6 7]
new_slice5: [1 2 3]
从头部删除
slice5 = slice5[1:] //删除头部第1个元素
slice5 = slice[5:] //删除头部第N个元素
使用append删除
append删除元素非常灵活,可以删除头部,中间,尾部元素。注意若是希望地址不变,需要将append的结果赋值回去
删除头部元素
slice5 = append(slice5[:0], slice5[3:]...) //删除头部元素fmt.Println("slice5:", slice5)
slice5: [4 5 6 7 8]
删除中间元素
slice5 = append(slice5[:3], slice5[5:]...) //删除中间两个元素fmt.Println("slice5:", slice5)
slice5: [1 2 3 6 7 8]
删除尾部元素
slice5 = append(slice5[:0], slice5[0:len(slice5)-3]...) //删除尾部元素fmt.Println("slice5:", slice5)
slice5: [1 2 3 4 5]
以上便是对slice的学习总结,若是不当之处,烦请指出。后面有新的学习领悟,会继续添加。