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

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.mrgr.cn/news/36207.html

相关文章:

  • s3c2440——ADC模数转换器,Linux驱动编程——u-boot
  • Windows C++:MoveFile、MoveFileEx、MoveFileWithProgress、CopyFile、CopyFileEx。
  • 分布式数据库——HBase基本操作
  • 【MySQL】基础入门篇
  • 防抖和节流的区别
  • Go项目初始化与依赖包引入指南
  • 2024年测评分享7款帮人写论文的AI网站
  • 图(graph.cpp)(回归)
  • 单词记忆的化境:用思想的流水去淹没坚硬的石块
  • mysql知识梳理
  • YOLO-World
  • Vscode Run Code Py中文乱码问题
  • 汽车零部件开发流程关键阶段
  • 【9.模块化开发和代码重用之——头文件、动静态库】
  • python - 在linux上编译py文件为【.so】文件部署项目运行
  • 通信工程学习:什么是VPN虚拟专用网络
  • 828华为云征文|使用Flexus X实例集成ES搜索引擎
  • 认知世界的经济学读书笔记
  • 车间调度 | 利用遗传算法(GA)求解混合流水车间调度问题(Hybrid flow-shop scheduling problem, HFSP)
  • C语言导航 1.2编程工具