GO学习笔记——切片的概念(11)

本文深入探讨了Go语言中的切片(Slice),包括其定义、灵活的使用方式、底层实现原理以及与数组的关系。通过实例展示了切片如何在不拥有数据的情况下,作为数组的视图工作,并讨论了切片的越界问题和注意事项。
摘要由CSDN通过智能技术生成

数组是不可以调整大小的,所以数组使用起来不够灵活。因此GO语言使用了Slice(切片),切片是数组建立的一种方便、灵活且功能强大的包装。

正因为数组不可以调整大小,使用不够灵活这个缺点,所以在GO语言开发中,用的更多的是Silce,下面来进入Slice的学习。


Slice的定义

先来看一下slice的一个简单的定义

func main() {
	arr := [...]int{0,1,2,3,4,5,6}
	s := arr[2:6]    
	fmt.Println(s)
}

看一下输出结果

[2 3 4 5]

这边解释一下,切片赋值的那一句话,[2:6]的意思是,从arr数组的下标2开始,到arr数组的下标(6-1)也就是5结束,注意这里是一个半开半闭的区间,左边是闭,右边是开,所以打印slice的输出结果也就是2,3,4,5。

下面来看一下一些灵活的定义

func main() {
	arr := [...]int{0,1,2,3,4,5,6}
	s := arr[2:6]	//从下标2到下标5
	s1 := arr[2:]	//从下标2一直到最后,也就是下标6
	s2 := arr[:6]	//从下标0到下标5
	s3 := arr[:]	//等价于整个数组
 	fmt.Println(s)
 	fmt.Println(s1)
 	fmt.Println(s2)
 	fmt.Println(s3)
}

输出结果

[2 3 4 5]
[2 3 4 5 6]
[0 1 2 3 4 5]
[0 1 2 3 4 5 6]

另外,在Slice之上还可以创建Slice

func main() {
	arr := []int{0,1,2,3,4,5,6}
	s1 := arr[:]
	fmt.Println(s1)
	s1 = s1[2:]
	fmt.Println(s1)
	s1 = arr[:4]
	fmt.Println(s1)
}

输出结果

[0 1 2 3 4 5 6]
[2 3 4 5 6]
[0 1 2 3]

另一种创建方式

func main() {
	s1 := []int{0,1,2,3,4,5,6}    
	fmt.Println(s1)
}

上面的创建方式相当于下面这样,s1其实就是整个数组的映射,只是在创建数组的同时,返回的是对整个数组的Slice

func main() {
	arr := []int{0,1,2,3,4,5,6}
	s1 := arr[:]
	fmt.Println(s1)
}

Slice的原理

Slice本身其实不拥有任何数据,它其实相当于一个视图,映射了数组的一部分,对视图(Slice)的修改会直接映射到原来的数组上。

所以说,要有Slice,必须得先有一个数组,因为Slice只是数组的一个反映而已.

来看一个简单的例子

//注意函数参数写成[],就表示这个参数是一个Slice,而不是数组
func modifySlice(s []int){
	s[0] = 100
}

func main() {
	arr := [...]int{0,1,2,3,4,5,6}
	s := arr[:6]	//从下标2到下标5
	fmt.Println(s)
	modifySlice(s)
	fmt.Print("After modifySlice:")
 	fmt.Println(s)
	fmt.Println(arr)
}

输出结果

[0 1 2 3 4 5]
After modifySlice:[100 1 2 3 4 5]
[100 1 2 3 4 5 6]

我们在函数内部将Slice修改了,修改了之后,因为Slice是数组的一层视图,因此实际上也就是改了数组本身,我们在最后一行打印了这个数组,发现数组本身也被改变了,另外数组改了之后,Slice也就随之改变了。

所以,如果函数的参数传的是一个Slice,那么这就相当于一个引用传递,对Slice的修改会改变数组的本身。这样我们就不用像之前那样,传一个数组的指针进去了。

 

当一个数组的多个Slice同时修改该数组时,会怎么样?

func main() {
	arr := [...]int{0,1,2,3,4,5,6}
	s1 := arr[:]
	s2 := arr[:]

	s1[0] = 100
	fmt.Println(arr)
	s2[1] = 200
	fmt.Println(arr)
}

输出结果

[100 1 2 3 4 5 6]
[100 200 2 3 4 5 6]

所以,当多个Slice共享同一个数组的时候,每个Slice所做的修改都会映射在原数组中。

Slice的越界问题

先上代码

func main() {
	arr := []int{0,1,2,3,4,5,6}
	s1 := arr[2:6]
	s2 := s1[3:5]
	fmt.Println(s1)
	fmt.Println(s2)
}

s1我们知道,是[2,3,4,5],但是s2究竟是什么呢?

它从s1的下标3开始,也就是5,到下标4结束,但是s1中没有下标为4的元素,那么s2会不会报错呢?如果不报错,那么输出的是什么呢?

输出结果

[2 3 4 5]
[5 6]

注意,这里我们就可以发现了。虽然在s1中没有下标为4的元素,但是这么说,原数组中其实还是有这个元素的存在的。还是画个图来理解一下吧。

 看上图,s1虽然只是映射了[2,3,4,5]这四个元素,但是其实后面的6那个元素在数组底层还是存在的。因为本身s1和s2都只是视图而已,但不包含实际的数据,所以s2虽然超出了s1的界限,但是还是没有超过原数组的界限。正因为如此,这也是Slice的一个灵活之处,只要没有超过原数组的界限,这就是可以的,不会报错。

但是一旦超过了原数组的界限,就会报错了,比如改一下数据

func main() {
	arr := []int{0,1,2,3,4,5,6}
	s1 := arr[2:6]
	s2 := s1[3:6]    //这里从[3:5]改成了[3:6]
	fmt.Println(s1)
	fmt.Println(s2)
}

这样就报错了

panic: runtime error: slice bounds out of range

Slice的底层实现

有了上面的越界问题,再来看看Slice的底层实现究竟是怎么样的。

它内部实现了三个变量:ptr,len,capacity

  • ptr表示Slice对应的数组的下标,如在之前的例子中,s1的ptr就是指向下标为2的元素
  • len表示这个Slice的长度,指在Slice定义时候给的大小
  • capacity表示这个Slice的容量,也就是数组最后一个元素到ptr指向的元素的距离大小

如果学过C++的人很好理解,这和vector中的三个指针实现起来差不多,begin,end和last,下面来画一张图然后结合代码来看看

我们以s1为例

如上,ptr就是指向下标为2的元素,len就是s1[2:6]这个Slice的长度,capacity就是数组最后一个元素的下标减到ptr指向的元素的下标。

对于s1而言,它有四个元素,下标分别为0,1,2,3,那么如果访问s1[4]会出现什么问题呢?按之前的理解,s1[4]在原数组上其实是指下标为6的那个元素。

func main() {
	arr := []int{0,1,2,3,4,5,6}
	s1 := arr[2:6]
	s2 := s1[3:5]
	s1[4] = 4	//这里访问s1[4]
	fmt.Println(s1)
	fmt.Println(s2)
}

编译器会报错

panic: runtime error: index out of range

所以,这里就知道了,如果要访问这个Slice,那么下标最多不可以超过Slice的 len - 1 这个大小,这和我们平时的理解是一样的,有4个元素,那么最高下标是3,访问下标为4就是越界了。

所以,要将这部分和上面定义s2时做区别,在访问Slice的时候,下标不可以超过 len - 1;但是用一个Slice定义另一个Slice的时候,Slice是可以进行扩展的,只要不超过原数组的界限,就可以定义成功。

另外,Slice不可以向前扩展,所以用一些Slice定义另一个Slice的时候,新定义的Slice的ptr不可能比原来的Slice的ptr要小。

 

总结一下Slice使用的注意点

  • Slice不可以向前扩展
  • s[i]使用时不可以超越len(s)
  • Slice向后扩展不可以超过底层数组cap(s)

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值