go语言基础(二):切片

切片的定义

切片的基本定义初始化如下:

	// 定义空切片
	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语言中,数组作为参数进行传递是值传递,而切片作为参数进行传递是引用传递。

什么是值传递?什么是引用传递?

值传递:方法调用时,实参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参数的值

引用传递:也称为传地址。函数调用时,实际参数的引用(地址,而不是参数的值)被传递给函数中相对应的形式参数(实参与形参指向了同一块存储区域),在函数执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一彡十

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值