切片(Slice)是一个拥有相同类型元素的可变长度的序列,他是基于数组做的一层分装,有点类似于Java中的集合框架。相较于数组,他更加灵活,可自动扩容 切片是一个引用类型,他的内部结构包括**地址**、**长度**和**容量**。切片一般用于快速地操作一块数据集合。
切片(Slice)是一个拥有相同类型元素的可变长度的序列,他是基于数组做的一层分装,有点类似于Java中的集合框架。相较于数组,他更加灵活,可自动扩容
切片是一个引用类型,他的内部结构包括地址、长度和容量。切片一般用于快速地操作一块数据集合。
定义切片
var s1 = []int // 声明了一个空的int类型的切片 此时:s1 = nil
var s2 = []int {1, 2, 3} // 声明一个int类型的切片,并赋值1,2,3 此时:s2 != nil
除了直接定义一个切片,还可以用通过一个数组截取一个切片,使用arr[start:end]
的形式,需要注意的是和其他语言一样,注意区间范围是{%span blue, 左必右开%}的
// 通过一个数组截取一个切片
arr := [...]int{1, 2, 3, 45, 6}
s3 := arr[0:4] // 截取arr数组中下标从0到3的元素作为一个切片赋值给s3
s4 := arr[1:] // 从下标1开始到len(arr)
s5 := arr[:4] // 从开始到下标为4
s6 := ass[:] // 从下标0开始到len(arr)
也可以对一个已有的切片进行切片得到一个新的切片(禁止套娃!)
var s1 = []string{"我", "好", "帅", "啊"}
var s2 = s1[:3] // ["我" "好" "帅"]
切片的长度和容量
每一个切片都拥有自己的长度和容量,我们可以使用len()
函数来求切片的长度,使用cap()
函数求一个切片的容量
{% noteblock cyan %}
{% p subtitle, 什么是容量? %}
切片的容量是指其底层封装的数数组从切片的第一个元素开始到最后的数组的长度。
{% endnoteblock %}
切片的长度就是切片中元素的个数
arr := [...]int{1, 2, 3, 4, 5, 6, 7}
s1 := arr[0:5]
s2 := arr[2:]
fmt.Printf("len(s1): %v; cap(s1): %v\n", len(s1), cap(s1)) // len(s1): 5; cap(s1): 7
fmt.Printf("len(s2): %v; cap(s2): %v\n", len(s2), cap(s2)) // len(s2): 5; cap(s2): 5
使用make()函数创建切片
语法
make(类型, 长度, 容量)
s1 := make([]int, 4) // 会创建一个int类型,长度为4,容量为4的切片
s2 := make([]string, 2, 8) // s2是一个string类型,长度为2,容量为8的切片
切片与切片之间不能进行比较
切片是引用类型,他是指向一片内存地址,就算切片中的值都相同,他们也不能进行比较。
一个nil
值得切片的底层没有数组,他的容量和长度都为0
silce1 := []int
{%span red, 他的值就是nil,他的长度和容量都是0%}
silce2 := []int{}
{%span green, 这个切片不是nil,他是一个长度和容量都是0的切片 %}
{%note done, 所以在判断一个切片是否为空的使用应该使用len(silce)0,不能用silcenil %}
切片的复制
s1 := make([]int, 4, 10)
s2 := s1
for i := 0; i < len(s1); i++ {
s1[i] = i
}
s2[2] = 100
for _, v := range s1 {
fmt.Print(v, "\t")
}
fmt.Println()
for _, v := range s2 {
fmt.Print(v, "\t")
}
由于切片是引用类型,所以当他进行复制操作的时候,实质上是将底层指向的数组复制给了另一个切片,他们就指向了同一块内存空间,类似于Linux中的软连接。
数组是一个值类型,值类型的复制就是本质上的复制。
切片的扩容
要对切片进行扩容,需要调用append()
方法
package main
import "fmt"
func main() {
sli := make([]int, 6)
for i := 0; i < len(sli); i++ {
sli[i] = i + 1
}
fmt.Printf("len(sli): %v; cap(sli):%v\n", len(sli), cap(sli))
sli = append(sli, 8)
fmt.Printf("len(sli): %v; cap(sli):%v\n", len(sli), cap(sli))
}
go语言切片在进行append()
扩容时会先判断底层数组的容量够不够,如果容量足够,就直接进行扩容,如果底层的数组容量不够,那么就需要进行扩容!
切片的扩容操作会首先吧底层的数组的容量进行增长,然后形成一个新的数组,再复制原来的数组中的值进入新的数组,最后在新的数组中插入新值。所以在进行append()
的时候需要一个变量去接收append()
返回的新的数组,一般情况下用原切片来接收。
如何增长:
- 首先判断,如果新申请的容量大于原来容量的2倍,那么最终容量就是新申请的容量
- 否则进行判断,如果原来切片的容量小于1024,那么最终容量就是原来容量的2倍
- 如果原来切片的容量大于1024,那么最终容量就是原来的1.25倍
- 荣国最终容量计算值溢出,则最终容量就是新申请的切片的容量
切片的扩容还和切片中存储的类型有关
append()
方法还可以批量添加,但是注意{%span red, 只能添加另一个切片中的值%}
func main() {
sli := make([]int, 6)
for i := 0; i < len(sli); i++ {
sli[i] = i + 1
}
fmt.Printf("len(sli): %v; cap(sli):%v\n", len(sli), cap(sli))
sli = append(sli, 8)
fmt.Printf("len(sli): %v; cap(sli):%v\n", len(sli), cap(sli))
addSli := []int{24, 35, 12, 31, 53, 62, 21}
sli = append(sli, addSli...) // ...表示将切片addSli拆开
fmt.Printf("len(sli): %v; cap(sli):%v\n", len(sli), cap(sli))
}
copy()方法
a1 := []int{1, 2, 3}
a2 := a1
fmt.Printf("a1=%v,a2=%v\n", a1, a2)
a3 := make([]int, 3, 10)
copy(a3, a2)
fmt.Printf("a3=%v\n", a3)
copy()
方法可以将一个切片的底层数组复制给另一个元素