一、切片(slice)概念
在讲解切片(slice)之前,大家思考一下数组有什么问题?
var num [5]int = [5]int{1,2,3,4,5}
定义的num数组长度是5,表示只能存储5个整型数字,现在向数组num中追加一个数字,这时会出错。因为你已经定义死了。
针对以上两个问题,可以使用切片来进行解决。
切片与数组的区别: 切片与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大,所以可以将切片理解成“动态数组”,但是,它不是数组。
二、 切片与数组区别
通过定义,来比较一下切片与数组的区别。
/ 初始化切片
s := []int{1,2,3}
// 通过append函数向切片中追加数据
s = append(s,5,6,7)
fmt.Println(s)
append()函数,第一个参数表示向哪个切片追加数据,后面表示具体追加的数据。最终输出结果为
[1 2 3 5 6 7]
三、 切片其它定义方式
//声明切片和声明数组一样,只是少了长度,此为空(nil)切片
var s1 []int
//借助make函数, 格式 make\(切片类型, 长度, 容量\)
s := make([]int, 5, 10)
四、切片的长度与容量
长度是已经初始化的空间(以上切片s初始空间默认值都是0)。容量是已经开辟的空间,包括已经初始化的空间和空闲的空间。我们可以通过如下图来理解切片的长度与容量:
该切片的长度是5(存有数据,注意如果没有赋值,默认值都是0),容量是10,只不过有5个空闲区域。即使没有给切片s赋值,初始化的空间(长度)默认存储的数据都是0。
s := make([]int,5,8)
fmt.Println(s)
输出的结果是:
[0 0 0 0 0]
在使用make()函数定义切片时,一定要注意,切片长度要小于容量,例如:
// 以下是错误的
s := make([]int, 10, 5)
make()函数中的容量参数是可以省略掉的,如:
s := make([]int,10)
这时长度与容量是相等的,都是10.
GO语言提供了相应的函数来计算切片的长度与容量,示例如下:
s := make([]int,5,10)
fmt.Println("长度是",len(s))
fmt.Println("容量是",cap(s))
接下来给切片s赋值,可以通过下标的方式直接来进行赋值。如下所示:
s := make([]int,5,10)
s[0] = 1
s[1] = 2
也可以通过循环的方式来进行赋值。
s := make([]int,5,10)
for i:=0;i<len(s) ;i++ {
s[i] = i
}
在这里一定要注意,循环结束条件是小于切片的长度,而不是容量。因为,切片的长度是指的是初始化的空间。以下方式会出现异常错误
for i:=0;i<cap(s) ;i++ {
s[i] = i
}
给切片赋完值后,怎样将切片中的数据打印出来呢?
for i:=0;i<len(s) ;i++ {
fmt.Println(s[i])
}
或者使用range方式输出:
for _,v := range s {
fmt.Println(v)
}
四、 切片截取
首先说一下切片的截取操作,所谓截取就是从切片中获取指定的数据。
我们通过如下程序给大家解释一下:
//定义切片 并且完成初始化
s := []int{10,20,30,0,0}
//从切片s中截取数据
slice := s[0:3:5]
fmt.Println(slice)
以上程序输出结果:
[10 20 30]
其中s[0:3:5]是什么意思呢?我们来解释一下。每个位置的数字为s[low:high:max]:
现在将以上程序进行修改:
//定义切片 并且完成初始化
s := []int{10,20,30,40,50}
//从切片s中截取数据
slice := s[0:3:5]
fmt.Println(slice)
结果是:
[10 20 30]
因为起点还是0,也就是10开始,终点还是3也就是到30结束.长度是3,容量是5。
继续修改该程序:
//定义切片 并且完成初始化
s := []int{10,20,30,40,50}
//从切片s中截取数据
slice := s[0:4:5]
fmt.Println(slice)
结果是:
[10 20 30 40]
因为起点还是0,也就是10开始,终点还是4也就是到40结束。长度是4,容量是5。
继续修改该程序
//定义切片 并且完成初始化
s := []int{10,20,30,40,50}
//从切片s中截取数据
slice := s[1:4:5]
fmt.Println(slice)
slice切片结果是:
[20 30 40]
那么容量是多少呢?容量为4,通过第三个数减去第一个数(5-1)计算。
通过画图的方式来表示slice切片中的容量。
通过上面的图,可以发现切片s经过截取操作以后,将结果赋值给切片slice后,长度是3,容量是4,只不过有一块区域是空闲的。
以上就是关于切片的基本操作,这些操作在以后的开发过程中会经常用到,希望大家记住基本的规律。
接下来说,思考如下题,定义一个切片array,然后对该切片array进行截取操作(范围自定义),得到新的切片s6,并修改切片s6某个元素的值。代码如下:
s6切片的结果是:[2,3,4]因为是从下标为2的元素(包含)开始取,到下标为5的元素(不包含)结束,取出3个元素,也就是长度为3。
s6 = [2 3 888]
因为切除了234,然后现在0是2,1是3,2是4,然后把s6[2]也就是s6[4]赋值为888
接下来输出切片array的值:
输出的结果如下:
s6 = [2 3 888]
array = [0 1 2 3 888 5 6 7 8 9]
发现切片array中的值也发生了变化,也就是修改切片s6的值会影响到原切片array的值,下面通过画图的形式来说明其原因。
在这里重点要理解的是:s6 := array[2:5],将array切片中的array[2],array[3],array[4]截取作为新切片s6,实际上是切片s6指向了原切片array(在这里并不是为切片s6新建一块区域)。所以修改s6,也会影响到array。
结果是:s7 = [888 5 6 7 8]
下面也是通过画图的形式,来解释该程序的结果:
继续思考,现在在原有的程序中又加了一行,如下图所示:
最终,切片s7与原来切片array的值分别是多少?
结果所示:
s6 = [2 3 888]
s7 = [888 5 999 7 8]
array = [0 1 2 3 888 5 999 7 8 9]
在第一节中,已经给大家讲解过切片与数组很大的一个区别就是:切片的长度是不固定的,可以向已经定义的切片中追加数据。并且也给大家简单的演示过通过append的函数,在原切片的末尾添加元素。
arr := []int{1,2,3}
arr = append(arr,4) //追加一个数
arr = append(arr,5,6,7) //追加多个数
fmt.Println(arr)
如果容量不够用了,该怎么办呢?
例如有以下切片:
s:= make([]int, 5, 8)
定义了切片s,长度是5,容量是8,k。
s := make([]int,5,8)
fmt.Printf("len = %d,cap=%d\n",len(s),cap(s))
结果是:len = 5 cap = 8
并且前面我们讲解过,长度是指已经初始化的空间,现在切片s没有赋值,但是默认值为0
验证如下所示:
s := make([]int,5,8)
fmt.Printf("len = %d,cap=%d\n",len(s),cap(s))
fmt.Println(s)
结果是:
len = 5 cap = 8
[0 0 0 0 0]
现在开始通过append函数追加数据,如下所示:
s := make([]int,5,8)
s = append(s,1)
fmt.Println(s)
fmt.Printf("len = %d,cap=%d\n",len(s),cap(s))
输出结果是:
[0 0 0 0 0 1]
len = 6 cap = 8
从输出的结果上,我们完全能够体会到,append函数的作用是在末尾追加(直接在默认值后面追加数据),由于追加了一个元素,所以长度为6.
但是如果我们把程序修改成如下所示:
s := make([]int,5,8)
//s = append(s,1)
s[0] = 1
fmt.Println(s)
fmt.Printf("len = %d,cap=%d\n",len(s),cap(s))
输出结果是:
[1 0 0 0 0]
len = 5 cap = 8
由于s[0]=1是直接给下标为0的元素赋值,并不是追加,所以结果的长度不变。
下面我们继续通过append( )继续追加数据:
s := make([]int,5,8)
s = append(s,1)
s = append(s,2)
s = append(s,3)
fmt.Println(s)
fmt.Printf("len = %d,cap=%d\n",len(s),cap(s))
结果是:
[0 0 0 0 0 1 2 3]
len = 8 cap = 8
追加完成3个数据后,长度变为了8,与容量相同。
那么如果现在通过append( )函数,继续向切片s中继续追加一个数据,那么容量会变为多少呢?
代码如下:
s := make([]int,5,8)
s = append(s,1)
s = append(s,2)
s = append(s,3)
s = append(s,4)
fmt.Println(s)
fmt.Printf("len = %d,cap=%d\n",len(s),cap(s))
输出的结果是:
[0 0 0 0 0 1 2 3 4]
len = 9 cap = 16
追加完成一个数据后,长度变为9,大于创建切片s时的容量,所以切片s扩容,变为16.
那么切片的容量是否是以2倍容量来进行扩容的呢?
我们可以来验证一下:
输出结果是:
通过以上结果分析,发现是2倍的容量进行扩容。
但是我们修改一下循环条件看一下结果,将循环结束的条件修改的大一些,如下所示:
对应的结果:
通过以上的运行结果分析:当容量小于1024时是按照2倍容量扩容,当大于等于1024就不是按照2倍容量扩容。
针对切片操作常用的方法除了append()方法以外,还有copy方法。
基本语法:copy(切片1,切片2)
将第二个切片里面的元素,拷贝到第一个切片中。
下面通过一个案例,看一下该方法的使用:
上面案例中,将srcSlice中的元素拷贝到destSlice切片中。结果如下:
dst = [1 2 6 6 6]
通过以上结果可以分析出,直接将srcSlice切片中两个元素拷贝到dstSlice元素中相同的位置。而dstSlice原有的元素备替换掉。
src = [6 6]
通过以上两个程序得出如下结论:在进行拷贝时,拷贝的长度为两个slice中长度较小的长度值。
slice2 = [1 2 3]
slice1 = [5 4 3 4 5]
切片也可以作为函数参数,那么与数组作为函数参数有什么区别呢?
接下来通过一个案例,演示一下切片作为函数参数。
通过以上案例,发现在主函数main()中,定义了一个切片s,然后调用InitData()函数,将切片s作为实参传递到该函数中,并在InitData()函数中完成初始化,该函数并没有返回值,但是在主函数中直接打印切片s,发现能够输出对应的值。也就是在InitData()函数中对形参切片num赋值,影响到了main()函数中的切片s。
但是,大家仔细想一下,如果我们这里传递参数不是切片,而是数组,那么能否完成该操作呢?
那么我们将上面的程序,修改成以数组作为参数进行传递的形式:
发现以数组的形式作为参数,并不能完成我们的要求,所以切片作为函数实参与数组作为函数实参,进行传递时,传递的方式是不一样的。
在GO语言中,数组作为参数进行传递是值传递,而切片作为参数进行传递是引用传递。
建议:以后开发中使用切片来代替数组。