文末有面经共享群

在 Go 语言的丰富数据类型中,数组和切片是处理有序数据集合的强大工具,它们允许开发者以连续的内存块来存储和管理相同类型的多个元素。无论是在处理大量数据时的性能优化,还是在实现算法时对数据结构的需求,数组和切片都扮演着至关重要的角色。

Go 语言中的数组

在 Go 语言中,数组是一种基本的数据结构,用于存储具有相同类型元素的集合。数组与切片(slice)不同,数组的长度是固定的,一旦声明就不可改变,并且是数组类型定义的一部分,这意味着数组的类型不仅包括元素的类型,还包括其长度。

定义

定义数组时,必须指定元素的类型和数组的长度。例如,[3]bool 表示一个布尔类型的数组,长度为 3;[4]int 表示一个整型的数组,长度为 4。

var a1 [3]bool
var a2 [4]int fmt.Printf("a1:%T\na2:%T\n", a1, a2)

打印结果如下:

数组初始化

默认值

定义数组时如果不进行初始化,默认元素就是零值:bool 类型的 false、整型和浮点类型的 0、字符串的空串" "。

var a1 [3]bool
var a2 [4]int fmt.Println(a1, a2)

打印结果如下:

初始化方式 1:在大括号中定义好和长度一致的值

最简单的初始化方式:

var a1 [3]bool
a1 = [3]bool{true,false,false}
fmt.Println(a1)

打印结果如下:

初始化方式 2:根据初始值自动判断数组的长度

在中括号中写明长度,当定义的数值个数比长度小时,会用默认值补齐,比如 0、false、""。

a8 := [10]int{0, 1, 2, 3, 4, 5, 6, 7}  //7后面会用0补齐
fmt.Println(a8)

打印结果: [0 1 2 3 4 5 6 7 0 0]。

[...]的用法:[...]设置数组长度时,会根据初始值自动判断数组的长度。

aa := [...]int{0, 1, 2, 3, 4, 5, 6, 7} //[...]根据初始值自动判断数组的长度
fmt.Println(aa)

打印结果:[0 1 2 3 4 5 6 7]。

初始化方式 3:根据索引初始化

指定索引对应的值,未指定索引的值会用默认值填充,比如 0、false、""。

a3 := [5]int{0: 1, 4: 2} //根据索引初始化
fmt.Println(a3)

打印结果:[1 0 0 0 2]。

取值

遍历数组

你可以使用一个标准的 for 循环来遍历数组的每个元素。这种方法需要你手动管理索引变量,并使用它来访问数组中的元素。

For i 循环遍历数组:

citys := [...]string{"北京", "上海", "深圳"} //索引从0到2
// 根据索引遍历
for i := 0; i < len(citys); i++ {
fmt.Println(citys[i])
}

打印结果如下:

for range 是一种更简洁的遍历数组或切片的语法。for range 循环会自动生成索引和元素值,使得遍历过程更加简单。

citys := [...]string{"北京", "上海", "深圳"} //索引从0到2
for i, city := range citys {
fmt.Printf("key值:%d 城市为:%v\n", i, city)
}

打印结果如下:

多维数组

定义

我们以二维数组举例,比如定义 [[1 2 3][4 5 6]] 这样的二维数组,要怎么定义呢?

示例如下:

  1. 下面代码中的第一个长度单位[2],表示二维数组的有几个元素;

  2. 第二个长度单位[3],表示子集数组中有几个元素;

  3. 初始化的时候:变量 = 数组类型{}。

//定义多维数组
var a11 [2][3]int //初始化多维数组
a11 = [2][3]int{
[3]int{1, 2, 3},
[3]int{4, 5, 6}, //注意:最后这个也要加逗号分隔
} fmt.Println(a11)

打印结果如下:

取值

多维数组的遍历:

//定义多维数组
var a11 [2][3]int //初始化多维数组
a11 = [2][3]int{
[3]int{1, 2, 3},
[3]int{4, 5, 6}, //注意:最后这个也要加逗号分隔
} //双重for range遍历取值
for _, v1 := range a11 {
fmt.Println(v1)
for _, v2 := range v1 {
fmt.Println(v2)
}
}

打印结果如下:

数组特点:值类型不是引用类型

我们发现把 b 1 赋值给 b 2,再修改 b 2 的值,b 1 的值并没有改变,这是数组和切片最大的区别,建议你再对比学习一下切片的知识点。

b1 := [3]int{1, 2, 3}
b2 := b1
b2[0] = 100
fmt.Println(b1,b2)

打印结果如下:

总结:说明 Go 的数组是值类型,不是引用类型 b2:=b1 的操作,给 b 2 开辟了新的内存空间,而不是引用 b 1 的内存地址。

数组实战

求数组 cArray[1,3,5,7,8]所有元素之和:

cArray := [...]int{1, 3, 5, 7, 8}
r := 0
for _, i2 := range cArray {
r += i2
}
fmt.Printf("相加结果为:%v", r)

打印结果:相加结果为:24

求出 cArray 数组中,和为 8 的下标,比如[0 3]和[1 2]:

for i := 0; i < len(cArray); i++ {
for j := 0; j < i; j++ {
if cArray[i]+cArray[j] == 8 {
fmt.Printf("符合的下标为:%v,%v \n", j, i)
}
}
}

打印结果如下:

Go 语言中的切片

切片区别于数组,是引用类型,不是值类型。数组是固定长度的,而切片长度是可变的,我的理解是:切片是对数组一个片段的引用。

定义

切片(slice)是一种灵活的序列类型,它基于数组;与数组不同,切片的长度是可变的,并且不包含在类型定义中。例如,[]int 表示一个存放整数类型元素的切片,[]string 表示一个存放字符串类型元素的切片。

var s1 []int
var s2 []string
fmt.Println(s1, s2)
fmt.Println(s1 == nil) //true 为空 没有开辟内存空间
fmt.Println(s2 == nil) //true

打印结果如下:

解析:这意味着 s1s2 目前不指向任何实际的内存空间,它们是“空”的切片。在 Go 语言中,当切片被声明但未初始化时,它们的默认值就是 nilnil 是一个表示“无”或“空”的特殊值,对于切片来说,它表示切片没有底层数组,也没有长度和容量。因此,尝试访问或修改一个 nil 切片的元素将会导致运行时错误。

声明并初始化

我们可以在声明的同时初始化:

var s1 = []int{1, 2, 3}
var s2 = []string{"北苑", "长阳", "望京"}
fmt.Println(s1, s2)
fmt.Println(s1 == nil) //false
fmt.Println(s2 == nil) //false

打印结果如下:

解析:初始化成功,s1s2 的值都不等于 nil

长度和容量

分别使用 len ()、cap () 获得切片的长度和容量。

fmt.Printf("len(s1):%d cap(s1):%d\n", len(s1), cap(s1))
fmt.Printf("len(s2):%d cap(s2):%d\n", len(s2), cap(s2))

打印结果如下:

解析:和我们预期的一致,长度和容量都为 3。

由数组得到切片

开篇我已经提到数组和切片的关系,这里再进一步讲一下:

  1. 切片的本质是操作数组,只是数组是固定长度的,而切片的长度可变的;

  2. 切片是引用类型,可以理解为引用数组的一个片段;而数组是值类型,把数组 A 赋值给数组 B,会为数组 B 开辟新的内存空间,修改数组 B 的值并不会影响数组 A;

  3. 而切片作为引用类型,指向同一个内存地址,是会互相影响的。

//定义一个数组
a1 := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
s3 := a1[0:4] //基于一个数组切割 [0:4]左包含 右不包含 即为[1,2,3,4]
fmt.Println(s3)

打印结果如下:

注意:a1[0:4] 是一个切片表达式,它基于数组 a1 创建了一个新的切片,包含了数组中索引从 03 的元素,上面示例中就是{1, 2, 3, 4}。

更多切割方式举例

切片提供了多种切割方式,这些方式允许你灵活地操作和访问数组或其他切片的部分元素。以下是一些常见的切片切割方式的例子:

a1 := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
s4 := a1[2:4] //[3 4]
s5 := a1[:4] //[1 2 3 4]
s6 := a1[2:] //[3 4 5 6 7 8 9]
s7 := a1[:] //[1 2 3 4 5 6 7 8 9]
fmt.Println(s4)
fmt.Println(s5)
fmt.Println(s6)
fmt.Println(s7)

打印结果如下:

解析:切片操作 s4 从索引 2 开始到索引 4(左闭右开),s5 默认从索引 0 开始截取,s6 截取到最后一个元素,而 s7 省略了索引,表示获取全部元素,所有这些切片操作都遵循左包含右不包含的原则。

切片的长度和容量

切片的长度很好理解,就是元素的个数。

切片的容量我们重点理解一下:在切片引用的底层数组中从切片的第一个元素到数组最后一个元素的长度就是切片的容量****。

我来画个图:

我们再看下面这个例子就很好理解了:

a1 := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}

s5 := a1[:4] //[1 2 3 4]
s6 := a1[2:] //[3 4 5 6 7 8 9]
s7 := a1[:] //[1 2 3 4 5 6 7 8 9] fmt.Printf("len(s5):%d cap(s5):%d\n", len(s5), cap(s5)) //4 9
fmt.Printf("len(s6):%d cap(s6):%d\n", len(s6), cap(s6)) //7 7
fmt.Printf("len(s7):%d cap(s7):%d\n", len(s7), cap(s7)) //9 9

打印结果如下:

解析:a 1 是数组长度为 9,容量也为 9,值是从 1~9。

S 5/s 6/s 7 都是切割数组 a 1 得到的切片。

S 5 的长度为 4,因为只有 1 2 3 4 这 4 个元素,容量为 9,因为 s 5 切片是从数组起始位置开始切割的:第一个元素是 1,而 s 5 底层数组 a 1 最后一个元素是 9,1~9 共 9 个元素,所以 s 5 的容量为 9。

S 6 的长度为 7,因为 s 6 的元素是 3~9 这 7 个元素;容量也为 7,因为 s 5 的底层数组最后一个元素是 9,3~9 共 7 个元素,所以 s 6 的容量为 7。

S 7 更好理解了,长度和容量都是 9,小伙伴们自己理解一下。

切片再切片

我们可以对切片进行再切片操作,比如,针对上面的数据再次切片进行测试:

s8 :=s6[3:]
//s8的值为:6 7 8 9
fmt.Printf("len(s8):%d cap(s8):%d\n", len(s8), cap(s8)) //4 4

打印结果如下:

解析:我们知道可以对切片进行再次切片就可以,至于长度和容器大家搞明白上面的例子,这个输出结果就是意料之中的了。

切片特点:引用类型不是值类型

我们举个例子来证明切片是引用类型:

//定义数组
a1 := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
//由数组切割成切片s6
s6 := a1[2:] //[3 4 5 6 7 8 9]
//切片再次切片,赋值给s8
s8 :=s6[3:] //[6 7 8 9]
//修改原始数组,把下标为2的值由3改为333
a1[2] = 333
//打印s6,发现s6中的3也变成了333
fmt.Println("s6:", s6) //[333 4 5 6 7 8 9]
//因为s8基于s6切片而成,我们测试一下切片再切片的引用传的
fmt.Println("s8:", s8) //[6 7 8 9]
//我们把原始数组下标为5的值由6改为666
a1[5] = 666
//打印s8切片,得到结果6也变成了666
fmt.Println("s8:", s8) //[666 7 8 9]

打印结果如下:

解析:由此我们可以明确的知道切片是引用类型,当底层数组改变时,不管是切片,还是切片再切片,值都会改变。因为他们使用的是一个内存块,引用的一个内存地址。

总结

Go 语言的数组和切片各有特点:

  1. 数组适合于长度固定且已知的场景,而切片则提供了更大的灵活性,特别是在处理动态大小的数据集合时;

  2. 数组作为值类型,在赋值和函数传递时会复制数据,保证了数据的独立性;而切片作为引用类型,多个切片变量可能指向同一个底层数组,因此在操作时要特别注意对底层数据的影响。

欢迎关注

我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。

没准能让你能刷到自己意向公司的最新面试题呢。

感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:【博客园】。

Go 必知必会:探索 Go 语言中的数组和切片深入理解顺序集合的更多相关文章

  1. Go语言中底层数组和切片的关系以及数组扩容规则

    Go语言中底层数组和切片的关系以及数组扩容规则 demo package main import ( "fmt" ) func main() { // 声明一个底层数组,长度为10 ...

  2. 闻道Go语言,6月龄必知必会

    大家好,我是马甲哥, 学习新知识, 我的策略是模仿-->归纳--->举一反三, 在同程倒腾Go语言一年有余,本次记录<闻道Go语言,6月龄必知必会>,形式是同我的主力语言C#做 ...

  3. 2015 前端[JS]工程师必知必会

    2015 前端[JS]工程师必知必会 本文摘自:http://zhuanlan.zhihu.com/FrontendMagazine/20002850 ,因为好东东西暂时没看懂,所以暂时保留下来,供以 ...

  4. [ 学习路线 ] 2015 前端(JS)工程师必知必会 (2)

    http://segmentfault.com/a/1190000002678515?utm_source=Weibo&utm_medium=shareLink&utm_campaig ...

  5. makefile 必知必会

    Makefile 必知必会 Makefile的根本任务是根据规则生成目标文件. 规则 一条规则包含三个:目标文件,目标文件依赖的文件,更新(或生成)目标文件的命令. 规则: <目标文件>: ...

  6. 《MySQL必知必会》[01] 基本查询

    <MySQL必知必会>(点击查看详情) 1.写在前面的话 这本书是一本MySQL的经典入门书籍,小小的一本,也受到众多网友推荐.之前自己学习的时候是啃的清华大学出版社的计算机系列教材< ...

  7. mysql必知必会系列(一)

    mysql必知必会系列是本人在读<mysql必知必会>中的笔记,方便自己以后查看. MySQL. Oracle以及Microsoft SQL Server等数据库是基于客户机-服务器的数据 ...

  8. mysql必知必会

    春节放假没事,找了本电子书mysql必知必会敲了下.用的工具是有道笔记的markdown文档类型. 下面是根据大纲已经敲完的章节,可复制到有道笔记的查看,更美观. # 第一章 了解SQL## 什么是S ...

  9. python网络爬虫,知识储备,简单爬虫的必知必会,【核心】

    知识储备,简单爬虫的必知必会,[核心] 一.实验说明 1. 环境登录 无需密码自动登录,系统用户名shiyanlou 2. 环境介绍 本实验环境采用带桌面的Ubuntu Linux环境,实验中会用到桌 ...

  10. Android程序员必知必会的网络通信传输层协议——UDP和TCP

    1.点评 互联网发展至今已经高度发达,而对于互联网应用(尤其即时通讯技术这一块)的开发者来说,网络编程是基础中的基础,只有更好地理解相关基础知识,对于应用层的开发才能做到游刃有余. 对于Android ...

随机推荐

  1. manage.py“Couldn't import Django”报错的问题解决

    问题分析: 在pyharm中项目可以正常运行但是在终端 终端输入python manage.py runserver首次测试项目时,出现了无法引用Django的错误. Traceback (most ...

  2. 使用Eclipse开发Vue——CodeMix够智能

    使用Eclipse开发Vue--CodeMix够智能 Eclipse的CodeMix插件允许您访问 VS Code和Code OSS扩展社区,以及 Webclipse 1.x 功能. Vue.js是构 ...

  3. Session的默认保存路径

    在php.ini里的配置session.save_path是注释掉的,那么Seesion保存的路径在不同类型操作系统保存在什么位置? Linux:/tmp 或 /var/lib/php/session ...

  4. MViT:性能杠杠的多尺度ViT | ICCV 2021

    论文提出了多尺度视觉Transformer模型MViT,将多尺度层级特征的基本概念与Transformer模型联系起来,在逐层扩展特征复杂度同时降低特征的分辨率.在视频识别和图像分类的任务中,MViT ...

  5. android常用布局基础学习

    总结:可水平放置可垂直放置也可穿插使用,默认为水平 <!--我在第一次使用权重的时候忽视了本线性布局中的宽度与高度,如果要使用权重,请将线性布局的最初大小设置为match_parent,否则不会 ...

  6. Microsoft Dynamics CRM 插件被限制2分钟超时解决方案

    背景: 在隔离模式"沙箱"中运行的插件或自定义工作流活动将有2分钟的硬限制.如果你的插件很复杂,需要超过2分钟,有一些解决方法. CRM on premise (本地版) 选择插件 ...

  7. VScode配置PHP开发环境

    vscode配置php开发环境 使用vscode配置PHP开发环境,首先要下载vscode, vscode下载 下载完vscode之后,可以在扩展里面下载中文版本 将vscode下载完之后,需要下载x ...

  8. 一文带你了解CAP的全部特性,你学会了吗?

    目录 前言 消息发布 携带消息头 设置消息前缀 原生支持的延迟消息 并行发布消息 事务消息 事务消息发送 事务消息消费 事务补偿 消息处理 序列化 过滤器 消息重试 多线程处理 自动恢复/重连 分布式 ...

  9. Fiddler使用界面介绍-底部状态栏

    底部状态栏 1.Capturing抓包状态 Capturing:Fiddler正在抓包 空白:Fiddler停止抓包 2.All Processes抓取进程类型 All Processes:抓取所有进 ...

  10. 7、Git之Github操作

    7.1.注册Github账号 7.1.1.访问官网 Github 官网:https://github.com/ 先访问GitHub的官网首页,点击 sign in (登录),跳转到登录页. 7.1.2 ...