切片是一种复合数据类型,与数组相似,存放相同数据类型的元素,但数组的大小是固定的,而切片的大小可变,能够按需自动改变大小。切片是基于底层数组实现的,是对数组的抽象。切片很小,只有三个字段的数据结构:指向底层数组的指针、能访问的元素个数(即切片长度)和容许增加到的元素个数(即切片容量)。golang
使用内置函数建立空切片,形如:算法
s := make([]type, len, cap) // len 长度,cap 容量
复制代码
也能够只指定len
,那么切片的容量和长度是同样的。Go语言提供了内置函数len
、cap
分别返回切片的长度和容量。数组
// 声明一个长度为三、容量为5的整型切片
s1 := make([]int,3,5)
fmt.Println(len(s1),cap(s1)) // 输出:3 5
// 声明一个长度和容量都是5的字符串切片
s2 := make([]string,5)
fmt.Println(len(s2),cap(s2)) // 输出:5 5
复制代码
切片建立完成,若是不指定字面量的话,默认值就是数组的元素的零值。
切片的容量就是切片底层数组的大小,咱们只能访问切片长度范围内的元素,如第一节的图所示,长度为3的整型切片存入3个值后的结构,咱们只能访问到第3个元素,剩余的2个元素须要切片扩充之后才能够访问。因此,很明显的:容量>=长度,咱们不能建立长度大于容量的切片。数据结构
s1 := make([]int,5,3)
// 报错:len larger than cap in make([]int)
复制代码
使用字面量建立,就是指定了初始化的值app
s := []int{1,2,3,4,5} // 长度和容量都是5的整型切片
复制代码
有没有发现,这种建立方式与建立数组相似,只不过不用指定[]
的值,这时候切片的长度和容量是相等的,而且会根据指定的字面量推导出来。
区别:函数
// 建立大小为10的数组
s := [10]int{1,2,3,4,5}
// 建立切片
s := []int{1,2,3,4,5}
复制代码
咱们也能够只初始化某一个索引的值:学习
s := []int{4:1}
fmt.Println(len(s),cap(s)) // 输出:5 5
fmt.Println(s) // 输出:[0 0 0 0 1]
复制代码
指定了第5个元素为1
,其余元素初始化为0。ui
使用操做符[start:end]
,简写成[i:j]
,表示从索引i
,到索引j
结束,截取已有数组或者切片的任意部分,返回一个新的切片,新切片的值包含原切片的i
索引的值,可是不包含j
索引的值。i
、j
都是可选的,i
若是省略,默认是0,j
若是省略,默认是原切片或数组的长度。i
、j
都不能超过这个长度值。spa
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println("s[:]", s[:])
fmt.Println("s[2:]", s[2:])
fmt.Println("s[:4]", s[:4])
fmt.Println("s[2:4]", s[2:4])
复制代码
输出
你可能会有个疑问:截取得到的新切片的长度和容量怎么计算呢?咱们固然可使用内置函数len
、cap
直接得到,若是明白了怎么计算的,咱们处理问题就能够更驾轻就熟。
对底层数组大小为k
的切片执行[i,j]
操做以后得到的新切片的长度和容量是: 长度:j-i
容量:k-i
就拿上一个例子的s[2:4]
来讲,原切片底层数组大小是10,因此新切片的长度是4-2=2
,容量是10-2=8
。 可使用内置函数验证下:.net
s1 := s[2:4]
fmt.Println(len(s1),cap(s1)) // 输出:2 8
复制代码
上面是使用2个索引的方法建立切片,还可使用3个索引的方法,第3个用来限定新切片的容量,用法为slice[i:j:k]
。
s2 := s[2:4:8]
fmt.Println(s2) // 输出:[2 3]
复制代码
长度和容量如何计算:长度j-i
,容量k-i
。因此切片s2
的长度和容量分别是2
、6
。注意:k
不能超过原切片(数组)的长度,不然报错panic: runtime error: slice bounds out of range
。
例如上面的例子中,第三个索引值不能超过10。
咱们来看个例子:
s := []int{0, 1, 2, 3, 4, 5}
fmt.Println("before,s:",s)
s1 := s[1:4]
fmt.Println("before,s1:",s1)
s1[1] = 10
fmt.Println("after,s1:",s1)
fmt.Println("after,s:",s)
复制代码
输出:
before,s: [0 1 2 3 4 5]
before,s1: [1 2 3]
after,s1: [1 10 3]
after,s: [0 1 10 3 4 5]
复制代码
这个例子说明,原切片和新切片是基于同一个底层数组的,因此当修改的时候,底层数组的值就会被改变,原切片的值也随之改变了。对于基于数组的切片也同样的。
s
可以看到底层数组所有6个元素,而切片
s1
只能看到索引
1
及以后的所有元素,对于
s1
来讲,索引
1
以前的部分是不存在。
切片的使用方法与数组的使用方法相似,直接经过索引就能够获取、修改元素的值。
s := []int{1, 2, 3, 4, 5}
fmt.Println(s[1]) // 获取值 输出:2
s[1] = 10 // 修改值
fmt.Println(s) //输出:[1 10 3 4 5]
复制代码
只能访问切片长度范围内的元素,不然报错
s := []int{1, 2, 3, 4, 5}
s1 := s[2:3]
fmt.Println(s1[1])
复制代码
上面这个例子中,s1
的容量为3,长度为1,因此只能访问s1
第一个元素s1[0]
,访问s1[1]
就会报错:panic: runtime error: index out of range
与切片的容量相关联的元素只能用于增加切片,在使用这部分元素前,必须将其合并到切片的长度里。
相较于数组,使用切片的好处在于,能够按需增加,相似于动态数组。Go提供了内置append
函数,可以帮咱们处理切片增加的一些列细节,咱们只管使用就能够了。
函数原型:
func append(slice []Type, elems ...Type) []Type 复制代码
使用append
函数,须要一个被操做的切片和一个(多个)追加值,返回一个相同数据类型的新切片。
s := []int{1, 2, 3, 4, 5}
newS := s[2:4]
newS = append(newS, 50)
fmt.Println(s, newS)
fmt.Println(&s[2] == &newS[0])
复制代码
输出:
[1 2 3 4 50] [3 4 50]
true
复制代码
上面的例子中,截取得到一个长度为2,容量为3(可用容量为1)的新切片newS
,经过append
函数向切片newS
追加一个元素50。 追加元素50以前:
newS
与原切片
s
是共享底层数组的,当切片可用容量可以存下追加元素时,不会建立新的切片。
s := []int{1, 2, 3, 4, 5, 6, 7, 8}
s1 := s[2:4]
fmt.Printf("before -> s=%v\n", s)
fmt.Printf("before -> s1=%v\n", s1)
fmt.Printf("before -> len=%d, cap=%d\n", len(s1), cap(s1))
fmt.Println("&s[2] == &s1[0] is", &s[2] == &s1[0])
s1 = append(s1, 60, 70, 80, 90, 100, 110)
fmt.Printf("after -> s=%v\n", s)
fmt.Printf("after -> s1=%v\n", s1)
fmt.Printf("after -> len=%d, cap=%d\n", len(s1), cap(s1))
fmt.Println("&s[2] == &s1[0] is", &s[2] == &s1[0])
复制代码
输出:
before -> s=[1 2 3 4 5 6 7 8]
before -> s1=[3 4]
before -> len=2, cap=6
&s[2] == &s1[0] is true
after -> s=[1 2 3 4 5 6 7 8]
after -> s1=[3 4 60 70 80 90 100 110]
after -> len=8, cap=12
&s[2] == &s1[0] is false
复制代码
追加元素60、70、80、90、100和110以前:
append
函数会建立一个新的底层数组,将原数组的值复制到新数组里,再追加新的值,就不会影响原来的底层数组。
通常咱们在建立新切片的时候,最好要让新切片的长度和容量同样,这样咱们在追加操做的时候就会生成新的底层数组,和原有数组分离,就不会由于共用底层数组而引发奇怪问题,由于共用数组的时候修改内容,会影响多个切片。
append
函数会智能地增长底层数组的容量,目前的算法是:当数组容量<=1024
时,会成倍地增长;当超过1024
,增加因子变为1.25,也就是说每次会增长25%的容量。
Go提供了...
操做符,容许将一个切片追加到另外一个切片上:
s := []int{1, 2,3,4,5}
s1 := []int{6,7,8}
s = append(s,s1...)
fmt.Println(s,s1)
复制代码
输出:
[1 2 3 4 5 6 7 8] [6 7 8]
复制代码
使用for
循环迭代切片,配合len
函数使用:
s := []int{1, 2, 3, 4, 5}
for i:=0;i<len(s) ;i++ {
fmt.Printf("Index:%d,Value:%d\n",i,s[i])
}
复制代码
使用for range
迭代切片:
s := []int{1, 2, 3, 4, 5}
for i,v := range s {
fmt.Printf("Index:%d,Value:%d\n",i,v)
}
// 使用‘_’能够忽略返回值
s := []int{1, 2, 3, 4, 5}
for _,v := range s {
fmt.Printf("Value:%d\n",v)
}
复制代码
须要注意的是,range
返回的是切片元素的复制,而不是元素的引用。若是使用该值变量的地址做为指向每一个元素的指针,就会形成错误。
s := []int{1, 2, 3, 4, 5}
for i,v := range s {
fmt.Printf("v:%d,v_addr:%p,elem_addr:%p\n",v,&v,&s[i])
}
复制代码
输出:
v:1,v_addr:0xc000018058,elem_addr:0xc000016120
v:2,v_addr:0xc000018058,elem_addr:0xc000016128
v:3,v_addr:0xc000018058,elem_addr:0xc000016130
v:4,v_addr:0xc000018058,elem_addr:0xc000016138
v:5,v_addr:0xc000018058,elem_addr:0xc000016140
复制代码
能够看到,v
的地址老是相同的,由于迭代返回的变量在迭代过程当中根据切片依次赋值的新变量。 好了,今天先讲到这里,下一节,咱们再来讨论关于Slice
更多的用法!
原创文章,若需转载请注明出处!
欢迎扫码关注公众号「Golang来啦」或者移步 seekload.net ,查看更多精彩文章。
公众号「Golang来啦」给你准备了一份神秘学习大礼包,后台回复【电子书】领取!