

在本文中,我将为您介绍Go语言的数据类型之一——slice切片类型~

一. 切片
由于数组长度的不可改变性,导致在一些特定情况下使用数组显得不合适。于是,Go提供了一种基于对数组的连续片段的引用类型的数据类型——slice切片类型。Go中切片是引用类型,它是对数组的一种引用。
它的底层是基于数组的,具有动态可扩展性,即它的长度是不固定的,可以随时根据用户的使用自动扩充容量。
切片内部结构有三部分:切片地址、大小及容量。
■ ■■■■
Go 语言定义切片有两种方式:可以使用make函数来创建;使用类似数组的形式定义切片;通过从一个数组或一个切片产生一个新的切片。
下面介绍第一种,使用make的方式。
// 通过make函数声明slice:var slice_name []slice_type = make([]slice_element_type, length, capacity)// 等价于slice_name := make([]slice_type, length, capacity)
其中,slice_name为切片的名称,slice_element_type为切片中的元素的数据类型([]slice_element_type才是切片的类型),length为切片的长度,capacity为切片的容量。eg:
package mainimport "fmt"func main() {// 声明切片slice1var slice1 []string = make([]string, 0)fmt.Println(slice1)fmt.Println(len(slice1), cap(slice1))// 声明切片slice2var slice2 []string = make([]string, 2, 3)fmt.Println(slice2)fmt.Println(len(slice2), cap(slice2))}/*[]0 0[ ]2 3*/
第二种,通过定义类似数组的形式。只不过数组是直接指定需要长度,切片不指定长度,但是它的底层是基于数据创建的。
// 声明一个未指定大小的数组来定义切片var slice_name []slice_element_type
与make一致,slice_name为切片的名称,slice_element_type为切片中元素的类型,整个[]slice_element_type才是切片的类型。eg:
package mainimport "fmt"func main() {// 声明切片slice1var slice1 []stringfmt.Println(slice1)// 打印是否为空fmt.Println(slice1 == nil)}/*[]true*/
第三种,通过从一个数组或一个切片产生一个新的切片。由于切片是指向一段连续内存空间区域的,所以,这段空间可以是来自数组,也可以切片本身。从一段连续内存空间区域产生切片是常见的操作,格式如下:
数组[开始位置 : 结束位置]slice[开始位置 : 结束位置]
其中,slice为目标数组或切片,开始位置为目标数组或切片的开始索引,结束位置为目标数组或切片的结束索引。以下为使用数组生成切片例子:
package mainimport "fmt"func main() {// 声明并初始化一个数组var usernames = [5]string{"xiaoer","zhangsan","lisi","wangwu","zhaoliu"}fmt.Println(usernames)// 用数组生成切片usernames1 := usernames[1:4]fmt.Println(usernames1)fmt.Println(len(usernames1), cap(usernames1))}/*[xiaoer zhangsan lisi wangwu zhaoliu][zhangsan lisi wangwu]3 4*/
■ ■■■■
下面通过代码示例展示如何初始化切片,eg:
package mainimport "fmt"func main() {// 切片初始化方式一:先定义一个数组,通过数组创建切片作初始化// 声明并初始化一个数组var usernames = [5]string{"xiaoer","zhangsan","lisi","wangwu","zhaoliu"}fmt.Println(usernames)fmt.Println("---------------------")// 用数组生成切片,初始化// 从usernames的索引为1的元素开始,到索引4结束,不包括索引4的元素usernames1 := usernames[1:4]fmt.Println(usernames1)fmt.Println(len(usernames1), cap(usernames1))fmt.Println("---------------------")// 从usernames的index=0的元素开始,到index=4为止,不包括index=4usernames2 := usernames[:4]fmt.Println(usernames2)fmt.Println(len(usernames2), cap(usernames2))fmt.Println("---------------------")// 从usernames index=4开始,到末尾元素,不包括末尾元素usernames3 := usernames[4:]fmt.Println(usernames3)fmt.Println(len(usernames3), cap(usernames3))fmt.Println("---------------------")// 初始化usernames4,是数组usernames的引用,包含usernames全部元素usernames4 := usernames[:]fmt.Println(usernames4)fmt.Println(len(usernames4), cap(usernames4))fmt.Println("---------------------")// 重置切片(注意:这种方式不是“空”的切片,所谓空的,指的是长度和容量都为0)// 把切片的开始和结束位置都设为 0 时,生成的切片将变空usernames5 := usernames[0:0]fmt.Println(usernames5)fmt.Println(len(usernames5), cap(usernames5))fmt.Println(usernames5 == nil)fmt.Println("---------------------")// 方式二:使用make函数初始化切片// []int 标识为其元素类型为 int 的切片。scores := make([]int, 4, 5)fmt.Println(scores)fmt.Println(len(scores), cap(scores))}
打印结果如下:
[xiaoer zhangsan lisi wangwu zhaoliu]---------------------[zhangsan lisi wangwu]3 4---------------------[xiaoer zhangsan lisi wangwu]4 5---------------------[zhaoliu]1 1---------------------[xiaoer zhangsan lisi wangwu zhaoliu]5 5---------------------[]0 5false---------------------[0 0 0 0]4 5

■ ■■■■

二. 操作
使用Go语言的内建函数append可以为切片动态添加元素,append函数的第一个参数为切片,后面的参数可以是一个元素,可以是多个元素,也可以是一个切片:
package mainimport "fmt"func main() {var a []intfmt.Println(a, len(a), cap(a))a = append(a, 1) // 追加1个元素fmt.Println(a, len(a), cap(a))a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式fmt.Println(a, len(a), cap(a))a = append(a, []int{1, 2, 3}...) // 追加一个切片, 切片需要解包fmt.Println(a, len(a), cap(a))}/*[] 0 0[1] 1 1[1 1 2 3] 4 4[1 1 2 3 1 2 3] 7 8*/
如果空间不足以容纳足够多的元素,切片就会进行“扩容”,此时新切片的长度会发生改变。切片在扩容时,容量的扩展规律是按容量的 2 倍数进行扩充,例如 1、2、4、8、16……,eg:
package mainimport "fmt"func main() {// 数组a := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}fmt.Println(a)// 切片初始化s := a[:2:3]fmt.Println(s, len(s), cap(s))// apppend追加2个元素,超出了容量,于是// 重新分配底层数组s = append(s, 100, 200)fmt.Println(s, len(s), cap(s))}/*[0 1 2 3 4 5 6 7 8 9][0 1] 2 3[0 1 100 200] 4 6*/
再看一个例子,有助于更好的理解切片是怎么扩容的,eg:
package mainimport "fmt"func main() {// 声明切片var a []int// 向切片中追加元素for i := 0; i < 10; i++ {a = append(a, i)fmt.Println("len=", len(a), " cap=", cap(a), " a=", a)}}
打印结果:
len= 1 cap= 1 a= [0]len= 2 cap= 2 a= [0 1]len= 3 cap= 4 a= [0 1 2]len= 4 cap= 4 a= [0 1 2 3]len= 5 cap= 8 a= [0 1 2 3 4]len= 6 cap= 8 a= [0 1 2 3 4 5]len= 7 cap= 8 a= [0 1 2 3 4 5 6]len= 8 cap= 8 a= [0 1 2 3 4 5 6 7]len= 9 cap= 16 a= [0 1 2 3 4 5 6 7 8]len= 10 cap= 16 a= [0 1 2 3 4 5 6 7 8 9]
分析打印结果,可以看出,在追加元素时,会看之前的切片的容量是否能够容纳这次添加的元素,若不能,就会生成一个容量是原来2倍的新切片。
■ ■■■■
2. copy函数切片拷贝
在Go中,可以使用copy() 可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。格式:
copy(destSlice, srcSlice []T) int
将srcSlice切片复制到destSlice切片,目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致,copy() 函数的返回值表示实际发生复制的元素个数。
下面的通过一个例子展示将一个切片复制到另一个切片中,eg:
package mainimport "fmt"func main() {// 切片aa := []int{0, 1, 2, 3, 4, 5}fmt.Println(a) // [0 1 2 3 4 5]// 切片bb := []int{6, 7, 8, 9}fmt.Println(b) // [6 7 8 9]fmt.Println("-----------")// // 将a拷贝到b// n := copy(b, a) // 将a的前四个元素复制到b// fmt.Println(n) // 4// fmt.Println(b) // [0 1 2 3]// 将b拷贝到an = copy(a, b) // 将b复制到a的前四个位置fmt.Println(n) // 4fmt.Println(a) // [6 7 8 9 4 5]}
■ ■■■■
3. 切片遍历
与遍历数组一样,可以用for循环遍历,也可以使用for range遍历。
package mainimport "fmt"func main() {a := make([]int, 10)for i := 0; i < 10; i++ {a[i] = i}// forfor i := 0; i < len(a); i++ {fmt.Println(a[i])}fmt.Println("-------")// for rangefor _, v := range a {fmt.Println(v)}}

■ ■■■■

三、总结

长按二维码关注我们吧
不要错过






