程序员

Golang的Slice重要小知识

作者:admin 2021-06-22 我要评论

学习过程中对Slice的体会 基础很重要 面试题 可能出现的面试题 在这里我就简单的分享一下我学习Slice的体会与困惑作为一名即将上岸Golang开发的学子需要99%的汗...

在说正事之前,我要推荐一个福利:你还在原价购买阿里云、腾讯云、华为云服务器吗?那太亏啦!来这里,新购、升级、续费都打折,能够为您省60%的钱呢!2核4G企业级云服务器低至69元/年,点击进去看看吧>>>)

学习过程中对Slice的体会

在这里我就简单的分享一下我学习Slice的体会与困惑,作为一名即将上岸Golang开发的学子,需要99%的汗水。

基础很重要

首先我们得清楚什么是切片Slice,它是一个动态数组,是一个引用类型,切片的长度可以变化的,并且它就是一个结构体,分别含有三个字段:指向底层数组的首地址、切片长度len、切片容量cap。然后怎么初始化、怎么使用,怎么声明,怎么遍历以及怎么计算当前的cap这些就不必要说了,基础知识,但是强调一下:对于s[low : high]这种格式的表达式, 如果s是数组或者字符串, 则0 <= low <= high <= len(s)。如果s是切片, 则0 <= low <= high <= cap(s)。还有一种我后面才知道的,刚开始没学到,就是声明切片时:s := arr[0:3:3],最后一个数可以声明该切片的容量,但是3必须小于cap(arr),并且s的当前容量也是3 - 0 = 3。

面试题

看代码

func main() {
 arr := []int{1, 2, 3, 4, 5, 6, 7, 8}
 s1 := arr[2:5] // 此时切片的长度为3, 容量为6
 s2 := s1[2:4] //  因为s1容量为6,所以4仍然属于合法的index
 fmt.Println(len(s1), cap(s1), len(s2), cap(s2)) // 输出:3 6 2 4
 fmt.Println(s1, s2) // 又因为未发生扩容行为,所以s1,s2公用底层数组, 故最后输出[3 4 5] [5 6]
}

具体的输出结果以及想法写在了上面的注释里,这里比较容易困惑的是为什么s2会有一个6,那是因为s2和s1指向底层的同一个数组,并且s1的容量是6,切s1切到4明显可以切,所以切的还是同一个arr数组,自然5后面就是6了。



再看一段代码

s := []int{2}
fmt.Println(s[1:])

这里会输出什么?空[ ] 还是报错超出可取范围?首先我们来分析一下,对于s切片很明显是一个len为1,cap也为1的切片,也就是说,根据官方文档来说:切片可以切这个范围0 <= low <= high <= cap(s),也就是说切片的low可以为len长度,并且[1:]表示切下标为1到后面所有,后面所有也就是len长度,所以就是这样切[len:len],对于这种切法我们很清楚我们得到的切片是不包含后面这个下标的元素的,所有这样切出来就是[ ]空切片,也就是说s[1:]切出一个空切片。但是如果我们进行[len+1:]来切的话就很明显报错了,超出了可以切的范围~



继续

arr := []int{1, 2, 3, 4, 5, 6, 7, 8}
s1 := arr[2:5:7]
s1 = append(s1, 999)
fmt.Println(s1, arr)

这里又会输出什么呢?
答案是:[3 4 5 999] [1 2 3 4 5 999 7 8]
首先对arr切片进行切片得到的s1是通过arr[2:5:7]进行的,那么这个7就是取原多少容量,目前取7,然后再减去从第2下标开始切,所以s1的容量就是5~,那么s1 = [3,4,5],然后再通过append动态添加新元素到s1后面,但是s1是和arr指向相同的一个底层数组,所以呢,s1的5的后面一位是有数值的并且是6,当append之后就被覆盖掉了,动态添加到了5后面,也就是6 -> 999,同时它改变了底层数组的数据,所以指向的相同底层数组的arr也会出现相同的改变。



杀手锏

a := []int{1, 2, 3, 4, 5}
	b := a[0:2]
	c := a[0:3:3]
	b = append(b, 1)
	c = append(c, 1)

	fmt.Println("b:", b, "cap", cap(b))
	fmt.Println("c:", c, "cap", cap(c))
	fmt.Println("a:", a, "cap", cap(a))
	fmt.Println("c:", c[0:6])
	/*输出结果
		b: [1 2 1] cap 5  
		c: [1 2 1 1] cap 6	
		a: [1 2 1 4 5] cap 5 
		c: [1 2 1 1 0 0]
	*/

刚开始基础不扎实的时候,感觉很懵很乱
首先b切了a,并且从index=0开始切,所以b=[1 2],容量是5
c切了a,并且也是从index=0开始切,但是声明了容量是3,所以c的容量是3-0=3 ; c=[1 2 3]。
然后开始分别给b和c append一个数,由于b的容量是5所以append时,len=2,完全有足够的空间可以放,所以不会发生扩容,所以b的第三个数是1,b=[1 2 1],并且同时改动了和a指向相同的底层数组的数据,所以a会输出[1 2 1 4 5];这里重点在c,c的容量是3,并且已经满了,所以append的时候放不下了,这时候就会splice开始扩容,按照小于1024个字节就扩容一倍,所以c的容量就变成了6,是怎么计算的?3个元素int类型按照64位计算机就是8个字节,8 * 3 * 2 = 48;然后48 / 8 = 6,所以分配了6个int大小容量的新的底层数组,并且把数据copy到了新底层数组然后就可以成功append进去了。最后再让c切片指向新的底层数组,这也就是为什么c输出的结果是[1 2 1 1 0 0 ]因为后面开辟出来的数组默认零值,并且本来是在append之前原来的底层数组的3就已经被替换成了1,所以会有这样的结果。


以上就是我学习遇到的面试题~

可能出现的面试题

接下来就是我自己琢磨切片的容量的扩容情况
上代码

package main

import "fmt"

func main() {
	s1 := make([]byte, 1)        //任何低于8个字节的扩容后都是8个字节,不是简单的扩一倍。前提是放的下。
	fmt.Println(s1, "", cap(s1)) //1
	s1 = append(s1, 2)
	fmt.Println(s1, "", cap(s1)) //8
	//在append多个并且超出8就是16

	s4 := []rune("hello")
	fmt.Println(s4, "", cap(s4)) //5
	s4 = append(s4, 'h', 'r')    //rune = int32 是4个字节
	fmt.Println(s4, "", cap(s4)) //12   按内存分配的规格,分配5*4*2=40 => 直接给48个字节的内存 (特殊)按8 16 32 48 64分配,不只是单纯的扩容一倍

	s2 := []int{1, 2}
	s2 = append(s2, 3)   //int 8 个字节 8 * 2 *2 = 32  32 / 8 = 4 所以这个时候cap=4
	s2 = append(s2, 4)   //由于容量是4,目前只有3个,所以还放得下; 所以这个时候仍然cap=4
	s2 = append(s2, 5)   //由于目前4个容量已满, 需要扩容当前容量,按一倍 =>  8 * 4 * 2 = 64 ;总共需要64字节大小的内存; 64 / 8 = 8 个int大小的容量 所以下面输出8
	fmt.Println("=", cap(s2)) //输出8

	s3 := []int{1, 2, 3, 4}
	fmt.Println(s3, "", cap(s3))   //输出4
	s3 = append(s3, 3, 3, 3, 3, 3) //  4*8*2=64 64/8 = 8不够同时放入5个数,所以就按(4+1)*8*2=80个字节 80 / 8 = 10 个int容量  (特殊)
	fmt.Println("=", cap(s3))      //输出10

	s5 := []string{"hello"}
	fmt.Println(cap(s5))     //输出1
	s5 = append(s5, "world")   //string是16个字节单位 16*2 = 32  32/16 = 2
	fmt.Println(cap(s5))    //输出2
	s5 = append(s5, "!")   //16 * 2 * 2 = 64  64 /16 = 4
	fmt.Println(cap(s5))    //输出4
	s5 = append(s5, "渺", "!", "!", "!", "!", "!") //当同时append多个数据超出 容量*2 的时候 ,容量按 按一倍扩容后超出多少个加多少个16字节的内存  (特殊)
	fmt.Println(cap(s5))  //扩容一倍=8,不够放还差1个,所以输出9,append再加cap也再加
}

以上就是我自己测试琢磨并在注释写出了它的扩容规则,注意我这里说的扩容规则是我自己在看不到append()源码的情况下多次测试并且认为的。不同数据类型会有不同的扩容规则,比如int32和int64不同~对此有啥意见的可以讨论讨论,顺便我也可以学习学习各位大牛的想法,新手上路,多多指教。

;原文链接:https://blog.csdn.net/S_FMX/article/details/115608669

版权声明:本文转载自网络,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。本站转载出于传播更多优秀技术知识之目的,如有侵权请联系QQ/微信:153890879删除

相关文章
  • 阿里巴巴DevOps实践指南(八)| 以特性

    阿里巴巴DevOps实践指南(八)| 以特性

  • 阿里巴巴DevOps实践指南(五)| 业务驱

    阿里巴巴DevOps实践指南(五)| 业务驱

  • RISC-V工具链简介

    RISC-V工具链简介

  • 变局时代:RISC-V处理器架构的技术演变

    变局时代:RISC-V处理器架构的技术演变

腾讯云代理商
海外云服务器