Go数组切片小知识解析

一、题目

近日,在网上看到Go语言中关于数组与切片的一道思考题,题目是这样的:

package main

import (
    "fmt"
)

func main() {
    var numbers4 = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    myslice := numbers4[4:6:8]
    fmt.Printf("myslice为 %d, 其长度为: %d\n", myslice, len(myslice))

    myslice = myslice[:cap(myslice)]
    fmt.Printf("myslice的第四个元素为: %d", myslice[3])
}

为什么 myslice 的长度为2,却能访问到第四个元素

myslice为 [5 6], 其长度为: 2
myslice的第四个元素为: 8

二、分析解答

1. 什么是切片

切片(Slice)与数组一样,也是可以容纳若干类型相同的元素的容器。与数组不同的是,无法通过切片类型来确定其值的长度。每个切片值都会将数组作为其底层数据结构。我们也把这样的数组称为切片的底层数组。

切片是对数组的一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内(意思是这是个左闭右开的区间)

切片既是引用类型,也可理解为:它是在原数组上的一个重新定义开始/结束位置的特殊映射。什么意思呢?看下下面的代码:

package main

import "fmt"

func main() {
	//定义一个不确定容量的数组numbers
	var numbers = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	//将numbers下标为4的元素到下标为6之前的元素映射给切片myslice(左闭右开)
	myslice := numbers[4:6]
	//切片下标不与数组numbers一致,重新从0开始标记,但映射的内容其实一致
	fmt.Printf("myslice的第0个元素: %d => numbers的第4个元素:%d \n", myslice[0], numbers[4])
	fmt.Printf("myslice的第1个元素: %d => numbers的第5个元素:%d \n", myslice[1], numbers[5])
}

结果如下:

在这里插入图片描述


那如果按照这种说法,是不是切片myslice的第2个元素 就对应数组numbers的第6个元素?不妨试试:

fmt.Printf("myslice的第2个元素: %d => numbers的第6个元素:%d \n", myslice[2], numbers[6])

结果如下:

在这里插入图片描述

下标2超出了切片的范围。为什么呢?因为myslice := numbers[4:6]语句只是将 下标为4的元素到下标为6之前的元素(也就是5,6两个元素)赋予了切片myslice,切片就只有两个元素,长度为2,把切片当做一个数组来看,它肯定是获取不到超过它范围的元素的。


2. 切片的容量

在上面的测试结果中,笔者在这里发现了另一个问题:容量是数组numbers 从下标4到数组结束(9)的元素长度——6

fmt.Printf("myslice的长度为 %d,容量为 %d \n", len(myslice), cap(myslice))

//myslice的长度为 2,容量为 6

根据多次测试,笔者发现,切片的容量是根据切片位置和数组关系自动变化的:默认切片容量=切片开始位置数组结束位置(包含) 的元素个数。


那怎么样才能够自定义切片的容量呢?这里就用到思考题的一个切片小技巧:myslice = numbers4[4:6:8]

该语句的意思是,获取数组numbers的下标为4的元素到下标为6之前的元素(也就是5,6两个元素)赋予了切片myslice,同时,将切片myslice的容量设定为 下标为4的元素到下标为8之前的元素长度=4。下面来试试我们的理论是否是成立的。

package main

import "fmt"

func main() {
	//定义一个不确定容量的数组numbers
	var numbers = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	//将numbers下标为4的元素到下标为6之前的元素映射给切片myslice(左闭右开)
	myslice := numbers[4:6:8]
	//切片下标与数组numbers不一致,重新从0开始标记,但映射的内容其实一致
	fmt.Printf("myslice的长度为 %d,容量为 %d \n", len(myslice), cap(myslice))
}

//myslice的长度为 2,容量为 4

容量成功地变化了,那我们能够获取到我们所要的myslice下标为2的元素吗?

答案是 不行。容量仅仅表示数组/切片所能够容纳的元素数量,并不能够获取到不应存在的下标,它的范围还是由当前的长度=2决定的。


3. 切片的切片

难道就没有办法再通过切片去获取到原数组的下标为6的元素,获取到 myslice本不存在的下标2的元素了吗?

这里,思考题给了我们一个提示:切片的切片

myslice = myslice[:cap(myslice)]
//等价于myslice = myslice[:6]

将切片myslice再次进行切片,但是切片的范围[0,6) 却比切片的长度2更大。这样子能成功?实践出真理

package main

import "fmt"

func main() {
	//定义一个不确定容量的数组numbers
	var numbers = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	//将numbers下标为4的元素到下标为6之前的元素映射给切片myslice(左闭右开)
	myslice := numbers[4:6]
	//切片下标不与数组numbers一致,重新从0开始标记,但映射的内容其实一致
	fmt.Printf("myslice=%d,长度为 %d,容量为 %d \n", myslice, len(myslice), cap(myslice))

	//将myslice下标为0(即numbers下标为4)的元素到myslice下标为6(即numbers下标为10)之前的元素映射给切片myslice(左闭右开)
	myslice = myslice[:6]
	fmt.Printf("myslice=%d,长度为 %d,容量为 %d \n", myslice, len(myslice), cap(myslice))
}

结果如下:

在这里插入图片描述

结果已经很明显了,由于切片myslice映射了数组numbers,尽管它没有下标>=2的元素,但它依旧可以因为引用关系,当做是在数组numbers上进行切片操作,故而能够再次切片成功。

再次切片后,myslice值为[5 6 7 8 9 10]。无疑,肯定是能够获取到下标为2的元素(即原数组下标为6的元素)。

只要你明白了数组跟切片的这层关系,它还可以再无限次切片下去,这里就不再讲述。


4. 切片是数组的引用

Go中定义,切片是一个引用类型。那么是否意味着,切片其实是数组的一个引用变量?让我们再尝试一下,更改切片的值,是否会影响到原数组

package main

import "fmt"

func main() {
	//定义一个不确定容量的数组numbers
	var numbers = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
	//将numbers下标为4的元素到下标为6之前的元素映射给切片myslice(左闭右开)
	myslice := numbers[4:6]
	//切片下标不与数组numbers一致,重新从0开始标记,但映射的内容其实一致
	fmt.Printf("原myslice:%d \n", myslice)
	fmt.Printf("原myslice:%d \n", numbers)
	//更改了myslice下标为1的元素为5,映射更改了numbers下标为5的元素为5
	myslice[1] = 5
	fmt.Printf("更改myslice下标为1的元素为5 \n")
	fmt.Printf("更改myslice下标后myslice:%d \n", myslice)
	fmt.Printf("更改myslice下标后numbers:%d \n", numbers)

	//更改了numbers下标为4的元素为10,映射更改了myslice下标为0的元素为10
	numbers[4] = 10
	fmt.Printf("更改numbers下标为4的元素为10 \n")
	fmt.Printf("更改numbers下标后myslice:%d \n", myslice)
	fmt.Printf("更改numbers下标后numbers:%d \n", numbers)
}

结果如下:
在这里插入图片描述
从结果可知,切片确实是数组的引用类型,它们共用同一个内存空间,不管更改的是切片的值,还是数组的值,它们都是会相互影响的。


三、总结

  1. 切片是一个引用类型,可以认为它是在原数组上的一个重新定义开始/结束位置的特殊映射。你可以认为它也是一个数组,只是这个数组比较特殊些。
  2. 切片的长度为连续片段的元素个数,容量却默认是 连续片段开始位置(包含)数组结束位置(包含) 的元素个数。
  3. 切片的容量可以自定义,如numbers[ start : end : cap-end ]。决定的容量大小:连续片段开始位置start(包含)数组容量结束位置cap-end(不包含) 的元素个数。
  4. 切片可以再进行切片,相当于是 数组根据切片重定义了下标位置(映射切片的下标位置),根据切片的容量重定义了数组最大范围,而后根据该重定义后的数组进行切片。
  5. 切片引用数组,共用同一个内存空间,更改值会相互影响
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值