Go - 4.数组和切片

目录

一.引言

二.定义

1.基础定义

2.引申理解

三.实战

1.估算切片的长度与容量

2.切片的切片长度与容量

四.拓展 

1.估算切片容量的增长

2.切片底层数组的替换

五.总结


一.引言

本文主要讨论 Go 语言的数组 array 类型和切片 slice 类型。主要从二者的使用方法,相同点也不同点进行讲解。

二.定义

1.基础定义

它们的共同点都属于集合类类型,并且,他们的值都可以用来存储某一种类型的元素 T。不过二者最大的不同点是:

        - 数组类型的值长度是固定的

        - 切片类型是可变长的

数组的长度在声明它的时候就必须给定,并且之后不会再改变。可以说,数组的长度是其类型的一部分。比如,[1]string 和 [2]string 就是两个不同的数组类型。

而切片的类型字面量中只有元素的类型,而没有长度。切片的长度可以自动地随着其中元素数量的增长而增长,但不会随着元素数量的减少而减小。

2.引申理解

我们其实可以把切片看做是对数组的一层简单的封装,因为在每个切片的底层数据结构中,一定会包含一个数组。数组可以被叫做切片的底层数组,而切片也可以被看作是对数组的某个连续片段的引用。

也正因为如此,Go 语言的切片类型属于引用类型,同属引用类型的还有字典类型、通道类型、函数类型等;而 Go 语言的数组类型则属于值类型,同属值类型的有基础数据类型以及结构体类型

注意,Go 语言里不存在像 Java 等编程语言中令人困惑的 “传值或传引用” 问题。在 Go 语言中,我们判断所谓的 “传值” 或者 “传引用” 只要看被传递的值的类型就好了。如果传递的值是引用类型的,那么就是 “传引用”。如果传递的值是值类型的,那么就是“传值”。从传递成本的角度讲,引用类型的值往往要比值类型的值低很多。我们在数组和切片之上都可以应用索引表达式,得到的都会是某个元素。我们在它们之上也都可以应用切片表达式,也都会得到一个新的切片。

我们通过调用内建函数 len,得到数组和切片的长度。通过调用内建函数 cap,我们可以得到它们的容量。

Tips:

数组的容量永远等于其长度,都是不可变的。切片的容量却不是这样,并且它的变化是有规律可寻的。

三.实战

1.估算切片的长度与容量

package main

import "fmt"

func main() {
  // 示例1。
  s1 := make([]int, 5)
  fmt.Printf("The length of s1: %d\n", len(s1))
  fmt.Printf("The capacity of s1: %d\n", cap(s1))
  fmt.Printf("The value of s1: %d\n", s1)
  s2 := make([]int, 5, 8)
  fmt.Printf("The length of s2: %d\n", len(s2))
  fmt.Printf("The capacity of s2: %d\n", cap(s2))
  fmt.Printf("The value of s2: %d\n", s2)
}

首先,我用内建函数 make 声明了一个 []int 类型的变量 s1。我传给 make 函数的第二个参数是 5,从而指明了该切片的长度。我用几乎同样的方式声明了切片 s2,只不过多传入了一个参数 8 以指明该切片的容量。现在,具体的问题是:切片s1和s2的容量都是多少?这道题的典型回答:切片 s1 和 s2 的容量分别是 5 和 8。

- 长度与容量的联系

s1 的长度我们声明为 5,因为我们没指明其容量,所以默认与长度一致,容量也是 5,而 s2 指明了容量等于 8。我们顺便通过 s2 再来明确下长度、容量以及它们的关系。我在初始化s2代表的切片时,同时也指定了它的长度和容量。

上面提到可以把切片看做是对数组的一层简单的封装,因为在每个切片的底层数据结构中,一定会包含一个数组。数组可以被叫做切片的底层数组,而切片也可以被看作是对数组的某个连续片段的引用。在这种情况下,切片的容量实际上代表了它的底层数组的长度,这里是8。(注意,切片的底层数组等同于我们前面讲到的数组,其长度不可变。)

2.切片的切片长度与容量

s3 := []int{1, 2, 3, 4, 5, 6, 7, 8}
s4 := s3[3:6]
fmt.Printf("The length of s4: %d\n", len(s4))
fmt.Printf("The capacity of s4: %d\n", cap(s4))
fmt.Printf("The value of s4: %d\n", s4)

切片语法与 python 很像,直接 [] + : 即可,注意这里也是左闭右开区间 [3, 6)。切片 s3 中有 8 个元素,分别是从 1 到 8 的整数。s3 的长度和容量都是 8。然后,我用切片表达式 s3[3:6] 初始化了切片 s4。问题是,这个 s4 的长度和容量分别是多少?

 [3:6] 其实是 [3, 6),这里 3 是起始索引,6 是结束索引不被包含,所以 s4 的长度是 6-3 = 3

再来看容量,切片的容量代表了它的底层数组的长度,但这仅限于使用 make 函数或者切片值字面量初始化切片的情况。 更通用的规则是:一个切片的容量可以被看作是透过这个窗口最多可以看到的底层数组中元素的个数。由于 s4 是通过在 s3 上施加切片操作得来的,所以 s3 的底层数组就是 s4 的底层数组。又因为,在底层数组不变的情况下,切片代表的窗口可以向右扩展,直至其底层数组的末尾。所以,s4 的容量就是其底层数组的长度 8, 减去上述切片表达式中的那个起始索引3,即 5,也就是其实索引后面的长度。

注意,切片代表的窗口是无法向左扩展的。也就是说,我们永远无法透过 s4 看到 s3 中最左边的那 3 个元素。这里可以把切片看作是一个滑动窗口,但是只能向右 ->

- 切片扩充

最后,顺便提一下把切片的窗口向右扩展到最大的方法。对于 s4 来说,切片表达式 s4[0:cap(s4)] 就可以做到。该表达式的结果值(即一个新的切片)会是 []int{4, 5, 6, 7, 8},其长度和容量都是 5。

	s5 := s4[0:cap(s4)]
	fmt.Printf("The length of s5: %d\n", len(s5))
	fmt.Printf("The capacity of s5: %d\n", cap(s5))
	fmt.Printf("The value of s5: %d\n", s5)

四.拓展 

1.估算切片容量的增长

一旦一个切片无法容纳更多的元素,Go 语言就会想办法扩容。但它并不会改变原来的切片,而是会生成一个容量更大的切片,然后将把原有的元素和新元素一并拷贝到新切片中。在一般的情况下,你可以简单地认为新切片的容量(以下简称新容量)将会是原切片容量(以下简称原容量)的 2 倍。

	s6 := make([]int, 0)
	fmt.Printf("The capacity of s6: %d\n", cap(s6))
	for i := 1; i <= 5; i++ {
		s6 = append(s6, i)
		fmt.Printf("s6(%d): len: %d, cap: %d\n", i, len(s6), cap(s6))
	}
	fmt.Println()

但是,当原切片的长度(以下简称原长度)大于或等于 1024 时,Go 语言将会以原容量的 1.5 倍作为新容量的基准(以下新容量基准)。新容量基准会被调整(不断地与 1.5 相乘),直到结果不小于原长度与要追加的元素数量之和(以下简称新长度)。最终,新容量往往会比新长度大一些,当然,相等也是可能的。

	s7 := make([]int, 1024)
	fmt.Printf("The capacity of s7: %d\n", cap(s7))
	s7e1 := append(s7, make([]int, 200)...)
	fmt.Printf("s7e1: len: %d, cap: %d\n", len(s7e1), cap(s7e1))
	s7e2 := append(s7, make([]int, 400)...)
	fmt.Printf("s7e2: len: %d, cap: %d\n", len(s7e2), cap(s7e2))
	s7e3 := append(s7, make([]int, 600)...)
	fmt.Printf("s7e3: len: %d, cap: %d\n", len(s7e3), cap(s7e3))
	fmt.Println()

另外,如果我们一次追加的元素过多,以至于使新长度比原容量的 2 倍还要大,那么新容量就会以新长度为基准。注意,与前面那种情况一样,最终的新容量在很多时候都要比新容量基准更大一些。更多细节可参见 runtime 包中 slice.go 文件里的 growslice 及相关函数的具体实现。

	s8 := make([]int, 10)
	fmt.Printf("The capacity of s8: %d\n", cap(s8))
	s8a := append(s8, make([]int, 11)...)
	fmt.Printf("s8a: len: %d, cap: %d\n", len(s8a), cap(s8a))
	s8b := append(s8a, make([]int, 23)...)
	fmt.Printf("s8b: len: %d, cap: %d\n", len(s8b), cap(s8b))
	s8c := append(s8b, make([]int, 45)...)
	fmt.Printf("s8c: len: %d, cap: %d\n", len(s8c), cap(s8c))

2.切片底层数组的替换

确切地说,一个切片的底层数组永远不会被替换。

虽然在扩容的时候 Go 语言一定会生成新的底层数组,但是它也同时生成了新的切片。它只是把新的切片作为了新底层数组的窗口,而没有对原切片,及其底层数组做任何改动。请记住,在无需扩容时,append 函数返回的是指向原底层数组的原切片,而在需要扩容时,append 函数返回的是指向新底层数组的新切片。所以,严格来讲,“扩容” 这个词用在这里虽然形象但并不合适。不过鉴于这种称呼已经用得很广泛了,我们也没必要另找新词了。

package main

import "fmt"

func main() {
	// 示例1。
	a1 := [7]int{1, 2, 3, 4, 5, 6, 7}
	fmt.Printf("a1: %v (len: %d, cap: %d)\n",
		a1, len(a1), cap(a1))
	s9 := a1[1:4]
	//s9[0] = 1
	fmt.Printf("s9: %v (len: %d, cap: %d)\n",
		s9, len(s9), cap(s9))
	for i := 1; i <= 5; i++ {
		s9 = append(s9, i)
		fmt.Printf("s9(%d): %v (len: %d, cap: %d)\n",
			i, s9, len(s9), cap(s9))
	}
	fmt.Printf("a1: %v (len: %d, cap: %d)\n",
		a1, len(a1), cap(a1))
	fmt.Println()

}

只要新长度不会超过切片的原容量,那么使用 append 函数对其追加元素的时候就不会引起扩容。这只会使紧邻切片窗口右边的(底层数组中的)元素被新的元素替换掉。 这里原始数组窗口为 2,3,4 右边的内容是 5,6,7,由于上面替换的原因,原始数组 a1 的 4,5,6 被替换为 1,2,3:

五.总结

 本文介绍了 Go 语言中的数组和切片类型,我们可以通过 make 的方式初始化数组,也可以通过直接初始化的方法传入元素,同时注意数组扩容期间切片的变化方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/721895.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++ 65 之 模版的局限性

#include <iostream> #include <cstring> using namespace std;class Students05{ public:string m_name;int m_age;Students05(string name, int age){this->m_name name;this->m_name age;} };// 两个值进行对比的函数 template<typename T> bool …

PCIe学习——重点提纲

PCIe学习-重点提纲 基础知识 计算机架构基础总线系统概述PCI vs PCI-X vs PCIe PCIe 概述 PCIe 的发展历史PCIe 与其他总线的对比PCIe 的优势和应用场景 PCIe 体系结构 PCIe 分层模型 物理层&#xff08;Physical Layer&#xff09;数据链路层&#xff08;Data Link Layer&…

在 KubeSphere 上快速安装和使用 KDP 云原生数据平台

作者简介&#xff1a;金津&#xff0c;智领云高级研发经理&#xff0c;华中科技大学计算机系硕士。加入智领云 8 余年&#xff0c;长期从事云原生、容器化编排领域研发工作&#xff0c;主导了智领云自研的 BDOS 应用云平台、云原生大数据平台 KDP 等产品的开发&#xff0c;并在…

[Java基本语法] 常量变量与运算符

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏:&#x1f355; Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm1001.2014.3001.5482 &#x1f9c0;线程与…

解决Java项目运行时错误:“Command line is too long”

在开发Java应用的过程中&#xff0c;你可能偶尔会遇到“Error running ‘Application’: Command line is too long”的问题。这是因为Java虚拟机&#xff08;JVM&#xff09;在启动时&#xff0c;如果传递给它的类路径&#xff08;classpath&#xff09;过长&#xff0c;超过了…

1080 MOOC期终成绩

solution 输出合格的学生信息&#xff0c;其中所谓合格需要同时满足 在线编程成绩>200总评成绩>60 总评计算方法为 当期中成绩>期末成绩时&#xff0c;总评期中成绩0.4期末成绩0.6否则&#xff0c;总评期末成绩 通过map建立学号和学生信息间的关联&#xff0c;否则直…

YOLOv10改进 | 注意力篇 | YOLOv10引入Polarized Self-Attention注意力机制

1. Polarized Self-Attention介绍 1.1 摘要:像素级回归可能是细粒度计算机视觉任务中最常见的问题,例如估计关键点热图和分割掩模。 这些回归问题非常具有挑战性,特别是因为它们需要在低计算开销的情况下对高分辨率输入/输出的长期依赖性进行建模,以估计高度非线性的像素语…

AI引领数字安全新纪元,下一代身份基础设施如何帮助企业破局?

近日&#xff0c;Open AI正式发布面向未来人机交互范式的全新大模型GPT-4o&#xff0c;具有文本、语音、图像三种模态的理解力&#xff0c;无疑代表着人工智能技术的又一重大跃进。 人工智能技术领域不断创新和发展&#xff0c;为各行各业带来巨大的生产变革和经济增长的同时&…

RockChip Android12 Settings二级菜单

一:概述 本文将针对Android12 Settings的二级菜单System进行说明。 二:System 1、Activity packages/apps/Settings/AndroidManifest.xml <activityandroid:name=".Settings$SystemDashboardActivity"android:label="@string/header_category_system&quo…

关于Hutool的模块使用说明方法

Hutool包含多个模块&#xff0c;每个模块针对特定的功能需求提供支持&#xff1a;• hutool-aop&#xff1a;JDK动态代理封装&#xff0c;为非IOC环境提供切面支持。• hutool-bloomFilter&#xff1a;布隆过滤器&#xff0c;提供基于Hash算法的实现。• hutool-cache&#xff…

Day 26:2288. 价格减免

Leetcode 2288. 价格减免 句子 是由若干个单词组成的字符串&#xff0c;单词之间用单个空格分隔&#xff0c;其中每个单词可以包含数字、小写字母、和美元符号 ‘$’ 。如果单词的形式为美元符号后跟着一个非负实数&#xff0c;那么这个单词就表示一个 价格 。 例如 “$100”、…

Corrupt JPEG data: 2 extraneous bytes before marker 0xd9

场景 异常&#xff1a;Corrupt JPEG data: 2 extraneous bytes before marker 0xd9 python语言&#xff0c;CV2读图像数据集&#xff0c;训练目标检测模型。在数据集分批送入模型训练过程中&#xff0c;出现大片图片异常情况。 &#xff08;建议直接去看修复图像方法二&…

做一个30点的四面体结构

这个结构有7层&#xff0c;每层有1&#xff0c;1&#xff0c;3&#xff0c;3&#xff0c;6&#xff0c;6&#xff0c;10个点&#xff0c;共30个点。 30个点的坐标是 {0.0,0.0,6.12372435695795}, {0.0,0.0,0.0}, {-2.8868,-5.0,-2.04124145231931}, {-2.8868,5.0,-2.0412414…

设计神器创作利器矢量图形编辑软件CorelDRAW2024重磅发布啦!

CorelDRAW2024&#xff0c;一款让你的创作无限可能的神器&#xff01;&#x1f3a8;✨ 你是否曾经在寻找一款功能强大、操作简便的设计软件&#xff1f;那么&#xff0c;让我来为你种草一款神奇的产品——CorelDRAW2024&#xff01;&#x1f389;&#x1f929; CorelDRAW全系列…

客户为先,自研为基 | YashanDB产品进化之旅

作为一款全自研的数据库产品&#xff0c;崖山数据库致力于构建自主可控的全栈数据库产品体系&#xff0c;以满足行业用户多元化的需求。 从第一行代码到完整的数据库产品体系&#xff0c;我们为何选择从0到1自研数据库&#xff1f;我们经历了哪些关键的产品节点&#xff1f;每个…

等保2.0对于物联网设备的漏洞管理还有哪些规定?

等保2.0针对物联网设备的漏洞管理&#xff0c;主要规定了以下几个方面&#xff1a; 1. 漏洞发现与识别&#xff1a;要求定期进行漏洞扫描和评估&#xff0c;利用专业的漏洞扫描工具和安全服务&#xff0c;及时发现物联网设备及其软件中的安全漏洞。这包括但不限于操作系统、应…

langchain教程-(1)Prompt模板

LangChain 的核心组件 模型 I/O 封装 LLMs&#xff1a;大语言模型Chat Models&#xff1a;一般基于 LLMs&#xff0c;但按对话结构重新封装PromptTemple&#xff1a;提示词模板OutputParser&#xff1a;解析输出 数据连接封装 Document Loaders&#xff1a;各种格式文件的加载…

扭蛋机小程序:深度探索虚拟寻宝之旅的乐趣

引言 扭蛋机小程序&#xff0c;这个融合了传统与创新的虚拟寻宝乐园&#xff0c;已经吸引了无数玩家的目光。在这个充满惊喜和挑战的虚拟世界里&#xff0c;每一个扭蛋都可能蕴藏着无尽的宝藏。本文将带您深入探索扭蛋机小程序的魅力所在&#xff0c;体验一场别开生面的虚拟寻…

查看服务器端口,如何查看服务器端口是多少并修改

查看服务器端口并修改内容是一个涉及网络管理和系统配置的专业任务。以下是一个详细的步骤说明&#xff0c;用于查看和修改服务器端口。 一、查看服务器端口 1. 使用命令行工具&#xff1a; - 对于Linux或Unix系统&#xff0c;可以使用netstat、lsof或ss等命令来查看端口状…

反馈型振荡器

目录 反馈型振荡器分类 基本工作原理 启动过程 “心脏”LC振荡 起振条件 平衡条件 稳定条件 互感耦合振荡器 电感三端LC振荡器 电容三端LC振荡器 串联改进电容三端式振荡器 并联改进电容三端式振荡器 相位平衡条件的判断准则 反馈型振荡器分类 基本工作原理 启动过…