当前位置: 首页 > article >正文

Go基础学习05-数组和切片关系深度解析

切片和数组的联系

数组(array)和切片(slice)都属于集合类的类型,它们的值也都可以用来存储某一种类型的值(或者说元素)。数组和切片最重要的不同在于:

  • 数组类型的值的长度是固定的,而切片类型的值的长度是可变长的。 数组的长度在声明它的时候就必须给定,并且在之后不会再改变。可以说,数组的长度是其类型的一部分。
  • 切片的类型字面量中只有其元素的类型,而没有其长度。切片的长度可以自动地随着其中元素数量的增长而增长,但不会随着元素数量的减少而减少。
  • 切片属于引用类型(切片、通道、函数),数组属于值类型(基础数据类型、结构体类型)。

数组和切片的关系:

把切片看作是对数组的一层简单的封装(在每个切片的底层数据结构中,一定包含一个数组);
数组可以看作切片的底层数据结构,切片也可以看作是对数组的某个连续片段的引用。

切片的容量和长度的关系:

示例代码1:通过make初始化切片

	slice1 := make([]int, 5)
	slice2 := make([]int, 5, 8)
	fmt.Printf("slice1 len is : %d, cap is : %d\n", len(slice1), cap(slice1))
	fmt.Printf("slice2 len is : %d, cap is : %d\n", len(slice2), cap(slice2))

上述代码运行结果:

slice1 len is : 5, cap is : 5
slice2 len is : 5, cap is : 8

当我们使用make函数初始化切片时,如果不指明那个量,那么切片的容量就会和长度一致。如果使用make函数初始化切片时,指明了切片的容量,那么切片的容量就是此时指明的数据,不一定等于切片的长度。通过make函数初始化的切片,其容量代表了切片的底层数组的长度,上述的5和8代表底层数组的长度,数组的长度不可改变。 既然切片底层数据结构数组的长度不可改变,那么当切片容量不足时需要扩容时如果扩?正确的答案:切片容量不足时,会开辟一块更大的空间(足够存储扩容后的所有元素),随后将切片原有元素复制到新的空间,并将新添加的元素追加到后面,随后再将新的引用传递给切片

示例代码2:使用切片表达式基于某个数组或切片生成新切片

 	slice3 := []int{1, 2, 3, 4, 5, 6, 7, 8}
	slice4 := slice3[3:6]
	fmt.Printf("slice3 elements is : %d\n", slice3)
	fmt.Printf("slice4 elements is : %d\n", slice4)
	fmt.Printf("slice3 len is : %d, cap is : %d\n", len(slice3), cap(slice3))
	fmt.Printf("slice4 len is : %d, cap is : %d\n", len(slice4), cap(slice4))

上述代码执行结果:

slice3 elements is : [1 2 3 4 5 6 7 8]
slice4 elements is : [4 5 6]
slice3 len is : 8, cap is : 8
slice4 len is : 3, cap is : 5

提醒读者特意关注一下第四行的输出结果,可以查看一下是否符合预期。
slice4 := slice3[3 : 6]表示透过切片能看到底层数据结构数组的数据范围,左开右闭。所以表示的就是slice3中元素的索引范围从3到5(不包含6),所以slice4此时输出的元素是456。
此时对于slice4的长度计算就是结束索引 - 起始索引:6-3=3。所以len(slice4)就是3。为什么cap(slice4) = 5,而不是3呢?

使用切片表达式基于数组或者切片生成新的切片,其本质和原数组或者旧切片共用相同的底层数据结构,可以将新切片看作在旧切片的基础上的一个封装,一个拥有左右边界的窗口,左边界是切片表达式的左值,右边界是切片表达式的右值(右边界不能大于原有切片的最大容量,否则panic)。slice4的底层数组就是slice3的底层数组,又因为,在底层数组不变的情况下,切片代表的窗口可以向右扩展,直至其底层数组的末尾。所以,slice4的容量就是其底层数组的长度8减去上述切片表达式中的那个起始索引3,即5。
切片代表的窗口是无法向左扩展的,也就是说,我们永远无法透过slice4看到slice3中最左边的那三个元素

如何估算切片容量的增长

一旦一个切片无法容纳更多的元素,Go 语言就会想办法扩容。但它并不会改变原来的切片,而是会生成一个容量更大的切片,然后将把原有的元素和新元素一并拷贝到新切片中。

  • 在一般的情况下,你可以简单地认为新切片的容量(以下简称新容量)将会是原切片容量(以下
    简称原容量)的 2 倍。
  • 但是,当原切片的长度(以下简称原长度)大于或等于1024时,Go 语言将会以原容量的1.25
    倍作为新容量的基准(以下新容量基准)。新容量基准会被调整(不断地与1.25相乘),直到结果不小于原长度与要追加的元素数量之和(以下简称新长度)。最终,新容量往往会比新长度大一些,当然,相等也是可能的。
  • 此外,如果我们一次追加的元素过多,以至于使新长度比原容量的 2 倍还要大,那么新容量就
    会以新长度为基准。

关于切片底层数组替换的思考

一个切片的底层数组永远不会被替换。虽然在扩容的时候 Go 语言一定会生成新的底层数组,但是它也同时生成了新的切片。它是把新的切片作为了新底层数组的窗口,而没有对原切片及其底层数组做任何改动。

  • 在无需扩容时,append函数返回的是指向原底层数组的新切片,而在需要扩容时,append函数返回的是指向新底层数组的新切片。
  • 只要新长度不会超过切片的原容量,那么使用append函数对其追加元素的时候就不会引起扩容。这只会使紧邻切片窗口右边的(底层数组中的)元素被新的元素替换掉。
    代码示例:
	slice3 := []int{1, 2, 3, 4, 5, 6, 7, 8}
	slice4 := slice3[3:7]
	fmt.Printf("slice3 elements is : %d\n", slice3)
	fmt.Printf("slice4 elements is : %d\n", slice4)
	slice4 = append(slice4, 10)
	fmt.Printf("slice3 elements is : %d\n", slice3)
	fmt.Printf("slice4 elements is : %d\n", slice4)

运行结果:

slice3 elements is : [1 2 3 4 5 6 7 8]
slice4 elements is : [4 5 6 7]
slice3 elements is : [1 2 3 4 5 6 7 10]
slice4 elements is : [4 5 6 7 10]


http://www.kler.cn/a/325266.html

相关文章:

  • 【论文阅读】主动推理:作为感知行为的理论
  • FPGA开发-逻辑分析仪的应用-数字频率计的设计
  • 如何在 Ubuntu 上安装 Emby 媒体服务器
  • 基于 CentOS7.6 的 Docker 下载常用的容器(MySQLRedisMongoDB),解决拉取容器镜像失败问题
  • 【AtCoder】Beginner Contest 380-C.Move Segment
  • w039基于Web足球青训俱乐部管理后台系统开发
  • 主数据管理的误区有哪些?
  • 数据结构:二叉树的遍历和线索二叉树
  • 创建数据/采集数据+从PI数据到PC+实时UI+To PLC
  • 基于Vue3组件封装的技巧分享
  • 基于PHP+MySQL组合开发地方门户分类信息网站源码系统 带完整的安装代码包以及搭建部署教程
  • 【数据结构-栈】力扣1441. 用栈操作构建数组
  • Linux防火墙-nat表
  • 828华为云征文 | 使用 Memtester 对华为云 X 实例进行内存性能测试
  • 深入探讨AI 神经网络:类型、特点与创新应用
  • AGI interior designer丨OPENAIGC开发者大赛高校组AI创作力奖
  • C++【类和对象】(取地址运算符重载与实现Date类)
  • 无人机之物流货运篇
  • PDCA优化任务流程
  • OpenCV图像文件读写(2) 检查 OpenCV 是否支持某种图像格式的写入功能函数haveImageWriter()的使用
  • 画个心,写个花!Python Turtle库带你玩转创意绘图!
  • bluefs _flush_range allocated: osd用空间但是显示ceph_bluefs_db_used_bytes is 100%
  • 【国庆要来了】基于Leaflet的旅游路线WebGIS可视化实践
  • 240924-通过服务器代理ip地址及port端口wget等下载文件
  • 如何判断IP有没有被污染过
  • 产品管理 - 互联网产品(3) : 迭代管理