切片的定义
切片的基本定义初始化如下:
// 定义空切片
a := []int{}
// 切片第二种定义方式:
var s1 []int //声明切片和声明数组一样,只是少了长度,此为空(nil)切片
// 切片第三中定义方式,通过make( )函数实现
//借助make函数, 格式 make(切片类型, 长度, 容量)
s := make([]int, 5, 10)
什么是切片的长度与容量?
长度是已经初始化的空间(以上切片s初始空间默认值都是0)。容量是已经开辟的空间,包括已经初始化的空间和空闲的空间。
我们可以通过如下图来理解切片的长度与容量:
该切片的长度是5(存有数据,注意如果没有赋值,默认值都是0),容量是10,只不过有5个空闲区域。
即使没有给切片s赋值,初始化的空间(长度)默认存储的数据都是0。
演示如下:
a := make([]int, 5, 8)
// [0 0 0 0 0]
fmt.Println(a)
在使用make( )函数定义切片时,一定要注意,切片长度要小于容量,例如: s := make([]int, 10, 5)是错误的。
**make( )**函数中的容量参数是可以省略掉的,如:s := make([]int, 10),这时长度与容量是相等的,都是10.
GO语言提供了相应的函数来计算切片的长度与容量:len(),cap()
a := make([]int, 5, 8)
// 长度是: 5
// 容量是: 8
fmt.Println("长度是:", len(a))
fmt.Println("容量是:", cap(a))
切片截取
package main
import "fmt"
func main() {
a := []int{10, 20, 30, 0, 0}
// 从切片中截取数据 a[low:high:max]
slice := a[0:3:5]
// [10 20 30]
fmt.Println(slice)
// 3
// 5
fmt.Println(len(slice))
fmt.Println(cap(slice))
}
a[low:high:max]:第一个数(low)表示下标的起点(从该位置开始截取),如果low取值为0表示从第一个元素开始截取,也就是对应的切片s中的10
第二个数(high)表示取到哪结束,也就是下标的终点(不包含该位置),3表示取出下标是0,1,2的数据(10,20,30),不包括下标为3的数据,那么也就是说取出的数据长度是3. 可以根据公式:3-0 计算(len=high-low),也就是第二个数减去第一个数,差就是数据长度。在这里可以将长度理解成取出的数据的个数。
第三个数用来计算容量,所谓容量:是指切片目前可容纳的最多元素个数。通过公式5-0计算(cap=max-low),也就是第三个数据减去第一个数。该案例中容量为5。
继续修改该程序:
package main
import "fmt"
func main() {
a := []int{10, 20, 30, 40, 50}
// 从切片中截取数据 a[low:high:max]
slice := a[1:4:5]
// [20 30 40]
fmt.Println(slice)
// 3
// 4
fmt.Println(len(slice))
fmt.Println(cap(slice))
}
那么容量是多少呢?容量为4,通过第三个数减去第一个数(5-1)计算。
通过上面的图,可以发现切片s经过截取操作以后,将结果赋值给切片slice后,长度是3,容量是4,只不过有一块区域是空闲的。
关于切片的截取还有其它的操作,如下所示:
操作 | 含义 |
---|---|
s[n] | 切片s中索引位置为n的项 |
s[:] | 从切片s的索引位置0到len(s)-1处所获得的切片 |
s[low:] | 从切片s的索引位置low到len(s)-1处所获得的切片 |
s[:high] | 从切片s的索引位置0到high处所获得的切片,len=high |
s[low:high] | 从切片s的索引位置low到high处所获得的切片,len=high-low |
s[low:high:max] | 从切片s的索引位置low到high处所获得的切片,len=high-low,cap=max-low |
len(s) | 切片s的长度,总是<=cap(s) |
cap(s) | 切片s的容量,总是>=len(s) |
(1) s[n]
a := []int{10, 20, 30, 40, 50}
// 从切片中截取数据 s[n]
slice := a[2]
// 30
fmt.Println(slice)
(2) s[:]
a := []int{10, 20, 30, 40, 50}
// 从切片中截取数据 s[:]
slice := a[:]
// [10 20 30 40 50]
fmt.Println(slice)
//5
//5
fmt.Println(len(slice))
fmt.Println(cap(slice))
(3)s[low:]
a := []int{10, 20, 30, 40, 50}
// 从切片中截取数据 s[low:]
slice := a[2:]
// [30 40 50]
fmt.Println(slice)
//3
//3
fmt.Println(len(slice))
fmt.Println(cap(slice))
容量为3,分析:
a[2:]说明index(也即low)为2,且没有设置high和max,而a本来的容量为5,所以a[2:]的max=5。a[2:]的容量是:max-index=5-2=3
(4)s[:high]
a := []int{10, 20, 30, 40, 50}
// 从切片中截取数据 s[:high]
slice := a[:2]
// [10 20]
fmt.Println(slice)
//2
//5
fmt.Println(len(slice))
fmt.Println(cap(slice))
容量为5分析:
a[:2]说明high为2,且没有设置index和max,由上得index=0,max=5。a[2:]的容量是:max-index=5-0=5
(5)s[low:high]
a := []int{10, 20, 30, 40, 50}
// 从切片中截取数据 s[low:high]
slice := a[2:3]
// [30]
fmt.Println(slice)
//1
//3
fmt.Println(len(slice))
fmt.Println(cap(slice))
容量为3分析:
a[2:3]说明index=2,high=3,且没有设置max,由上max=5。a[2:3]的容量是:max-index=5-2=3
思考题
接下来说,思考如下题,定义一个切片a,然后对该切片a进行截取操作(范围自定义),得到新的切片slice, 并修改切片slice某个元素的值。代码如下:
a := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// 从切片中截取数据 s[low:high]
slice := a[2:5]
// [2 3 4]
fmt.Println(slice)
slice****切片的结果是:[2,3,4] 因为是从下标为2的元素(包含)开始取,到下标为5的元素(不包含)结束,取出3个元素,也就是长度为3。
现在将程序进行如下修改:
a := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// 从切片中截取数据 s[low:high]
slice := a[2:5]
slice[2] = 888
//slice= [2 3 888]
//a= [0 1 2 3 888 5 6 7 8 9]
fmt.Println("slice=", slice)
fmt.Println("a=", a)
输出的结果如下:
slice= [2 3 888]
a= [0 1 2 3 888 5 6 7 8 9]
发现切片array中的值也发生了变化,也就是修改切片slice的值会影响到原切片a的值,下面通过画图的形式来说明其原因。
创建了切片a,然后基于截取得到了slice: slice := a[2:5]
slice指向了a切片,对应的值为 2,3,4
然后执行 slice[2] = 888
对应的slice[2]的值变为了888,当然slice的值也由4变为了888
下面继续修改上面的程序,创建一个新的切片:
a := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
slice := a[2:5]
slice[2] = 888
slice2 := slice[2:7]
//slice2= [888 5 6 7 8]
//slice= [2 3 888]
//a= [0 1 2 3 888 5 6 7 8 9]
fmt.Println("slice2=", slice2)
fmt.Println("slice=", slice)
fmt.Println("a=", a)
上面程序的值为:
slice2= [888 5 6 7 8]
slice= [2 3 888]
a= [0 1 2 3 888 5 6 7 8 9]
主要是关注slice2,他是基于slice创建的又一个切片,其实很容易理解 第一个值为888。应为slice[2:],slice的索引为2的值正是888。但是为啥slice的长度为3的情况下,还输出了大于他长度的值嘞?这是因为slice指向了a,在slice[:7]满足不了slice后,他输出了a中的值给slice2。于是得到slice2= [888 5 6 7 8]
继续思考, 现在在原有的程序中又加了一行slice2[2] = 999,如下所示:
a := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
slice := a[2:5]
slice[2] = 888
slice2 := slice[2:7]
slice2[2] = 999
// 888 5 999 8
fmt.Println("slice2=", slice2)
// 2 3 888
fmt.Println("slice=", slice)
// 0 1 2 3 888 5 999 7 8 9
fmt.Println("a=", a)
结果如下:
slice2= [888 5 999 7 8]
slice= [2 3 888]
a= [0 1 2 3 888 5 999 7 8 9]
根据上一个的推断,其实则会个也比较好理解。追本溯源,怎么来的就怎么回去。既然slice2除888是基于a得到的,那么修改slice2的值,只要不修改888,那么都会改变a的值。slice[2]对应a[6],所以slice2= [888 5 999 7 8]、a= [0 1 2 3 888 5 999 7 8 9]
继续思考:以上案例中,slice切片和slice2切片的容量分别是多少?
cap(slice):8
cap(slice2): 6
append函数
append函数是在原切片的末尾添加元素,如下:
package main
import "fmt"
func main() {
a := []int{0, 1, 2, 3, 4, 5}
//[0 1 2 3 4 5 4 5 6]
a = append(a, 4, 5, 6)
fmt.Println(a)
//[0 1 2 3 4 5 4 5 6 5 6 7]
a = append(a, 5, 6, 7)
fmt.Println(a)
}
问题:可能有同学会问,如果容量不够用了,该怎么办呢?
例如有下面一个程序:
a := make([]int, 5, 8)
fmt.Println(a)
fmt.Printf("len = %d, cap = %d\n", len(a), cap(a))
定义了一个切片a,初始化它的长度为5,容量为8。上面程序的输出如下:
[0 0 0 0 0]
len = 5, cap = 8
在上面的基础上,追加一个数:3
a := make([]int, 5, 8)
fmt.Println(a)
fmt.Printf("len = %d, cap = %d\n", len(a), cap(a))
a = append(a,3)
fmt.Println(a)
fmt.Printf("len = %d, cap = %d\n", len(a), cap(a))
输出如下:
[0 0 0 0 0]
len = 5, cap = 8
[0 0 0 0 0 3]
len = 6, cap = 8
从输出的结果上,我们完全能够体会到,append函数的作用是在末尾追加(直接在默认值后面追加数据),由于追加了一个元素,所以长度为6.
下面我们继续通过append( )继续追加数据:
a := make([]int, 5, 8)
fmt.Println(a)
fmt.Printf("len = %d, cap = %d\n", len(a), cap(a))
a = append(a,3)
a = append(a,4)
a = append(a,5)
fmt.Println(a)
fmt.Printf("len = %d, cap = %d\n", len(a), cap(a))
输出如下:
[0 0 0 0 0]
len = 5, cap = 8
[0 0 0 0 0 3 4 5]
len = 8, cap = 8
追加完成3个数据后,长度变为了8,与容量相同。
那么如果现在通过append( )函数,继续向切片s中继续追加一个数据,那么容量会变为多少呢?
代码如下:
a := make([]int, 5, 8)
fmt.Println(a)
fmt.Printf("len = %d, cap = %d\n", len(a), cap(a))
a = append(a,3)
a = append(a,4)
a = append(a,5)
a = append(a,6)
fmt.Println(a)
fmt.Printf("len = %d, cap = %d\n", len(a), cap(a))
输出如下:
[0 0 0 0 0]
len = 5, cap = 8
[0 0 0 0 0 3 4 5 6]
len = 9, cap = 16
追加完成一个数据后,长度变为9,大于创建切片s时的容量,所以切片s扩容,变为16.
那么切片的容量是否是以2倍容量来进行扩容的呢?
我们可以来验证一下:
package main
import "fmt"
func main() {
a := make([]int, 0, 1)
oldCap := cap(a)
for i := 0; i < 200000; i++ {
a = append(a, i)
newCap := cap(a)
if oldCap < newCap {
fmt.Printf("cap: %d ===> %d\n", oldCap, newCap)
oldCap = newCap
}
}
}
输出如下:
cap: 1 ===> 2
cap: 2 ===> 4
cap: 4 ===> 8
cap: 8 ===> 16
cap: 16 ===> 32
cap: 32 ===> 64
cap: 64 ===> 128
cap: 128 ===> 256
cap: 256 ===> 512
cap: 512 ===> 1024
cap: 1024 ===> 1280
cap: 1280 ===> 1696
cap: 1696 ===> 2304
cap: 2304 ===> 3072
cap: 3072 ===> 4096
cap: 4096 ===> 5120
cap: 5120 ===> 7168
cap: 7168 ===> 9216
cap: 9216 ===> 12288
cap: 12288 ===> 15360
cap: 15360 ===> 19456
cap: 19456 ===> 24576
cap: 24576 ===> 30720
cap: 30720 ===> 38912
cap: 38912 ===> 49152
cap: 49152 ===> 61440
cap: 61440 ===> 76800
cap: 76800 ===> 96256
cap: 96256 ===> 120832
cap: 120832 ===> 151552
cap: 151552 ===> 189440
cap: 189440 ===> 237568
通过以上的运行结果分析:当容量小于1024时是按照2倍容量扩容,当大于等于1024是不是按照2倍容量扩容。
copy函数
针对切片操作常用的方法除了append( )方法以外,还有copy方法.
基本语法:copy(切片1,切片2)
将第二个切片里面的元素,拷贝到第一个切片中。
下面通过一个案例,看一下该方法的使用:
package main
import "fmt"
func main() {
a := make([]int, 4)
b := []int{2, 3, 3, 3}
fmt.Println(a)
// 要赋值的slice放前面
copy(a, b)
fmt.Println(a)
}
输出如下:
[0 0 0 0]
[2 3 3 3]
上面要注意的是:a := make([]int, 4),这个不可以设为 a := make([]int, 0),如下:
a := make([]int, 0)
b := []int{2, 3, 3, 3}
fmt.Println(a)
// 要赋值的slice放前面
copy(a, b)
fmt.Println(a)
输出如下:
[]
[]
切片作为函数参数
切片也可以作为函数参数,那么与数组作为函数参数有什么区别呢?
接下来通过一个案例,演示一下切片作为函数参数。
package main
import "fmt"
func main() {
a := make([]int, 10)
initData(a)
fmt.Println(a)
}
func initData(a []int) {
for i := 0; i < len(a); i++ {
a[i] = i
}
}
输出如下:
[0 1 2 3 4 5 6 7 8 9]
通过以上案例,发现在主函数main( )中,定义了一个切片a,然后调用InitData( )函数,将切片a作为实参传递到该函数中,并在initData( )函数中完成初始化,该函数并没有返回值,但是在主函数中直接打印切片a,发现能够输出对应的值。也就是在initData( )函数中对形参切片num赋值,影响到了main( )函数中的切片a.
但是,大家仔细想一下,如果我们这里传递参数不是切片,而是数组,那么能否完成该操作呢?
答案是不能,这个我们之前是测试过的。
在GO语言中,数组作为参数进行传递是值传递,而切片作为参数进行传递是引用传递。
什么是值传递?什么是引用传递?
值传递:方法调用时,实参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参数的值
引用传递:也称为传地址。函数调用时,实际参数的引用(地址,而不是参数的值)被传递给函数中相对应的形式参数(实参与形参指向了同一块存储区域),在函数执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。
输出如下:
```go
[0 0 0 0]
[2 3 3 3]
上面要注意的是:a := make([]int, 4),这个不可以设为 a := make([]int, 0),如下:
a := make([]int, 0)
b := []int{2, 3, 3, 3}
fmt.Println(a)
// 要赋值的slice放前面
copy(a, b)
fmt.Println(a)
输出如下:
[]
[]
切片作为函数参数
切片也可以作为函数参数,那么与数组作为函数参数有什么区别呢?
接下来通过一个案例,演示一下切片作为函数参数。
package main
import "fmt"
func main() {
a := make([]int, 10)
initData(a)
fmt.Println(a)
}
func initData(a []int) {
for i := 0; i < len(a); i++ {
a[i] = i
}
}
输出如下:
[0 1 2 3 4 5 6 7 8 9]
通过以上案例,发现在主函数main( )中,定义了一个切片a,然后调用InitData( )函数,将切片a作为实参传递到该函数中,并在initData( )函数中完成初始化,该函数并没有返回值,但是在主函数中直接打印切片a,发现能够输出对应的值。也就是在initData( )函数中对形参切片num赋值,影响到了main( )函数中的切片a.
但是,大家仔细想一下,如果我们这里传递参数不是切片,而是数组,那么能否完成该操作呢?
答案是不能,这个我们之前是测试过的。
在GO语言中,数组作为参数进行传递是值传递,而切片作为参数进行传递是引用传递。
什么是值传递?什么是引用传递?
值传递:方法调用时,实参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参数的值
引用传递:也称为传地址。函数调用时,实际参数的引用(地址,而不是参数的值)被传递给函数中相对应的形式参数(实参与形参指向了同一块存储区域),在函数执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。