golang快速入门:数组和切片

能治愈的都不是伤痛,不能治愈的才是心底的烙印

数组

数组是所有语言编程中最常用的数据结构之一,Go 语言也不例外。在 Go 语言中,数组是固定长度的、同一类型的数据集合。数组中包含的每个数据项被称为数组元素,一个数组包含的元素个数被称为数组的长度。

  1. 声明及初始化

在 Go 语言中,你可以通过 [] 来标识数组类型。数组的元素必须全部为同一类型,声明数组是必须制定长度,以下是一些常见的数组声明方法:

  1. 数组元素的访问和设置

获取数组元素涉及到数组元素的索引下标,可以使用数组下标来访问 Go 数组中的元素,数组下标默认从 0 开始,len(arr)-1 表示最后一个元素的下标:

数组除了支持通过下标访问对应索引的元素值之外,还可以通过下标设置对应索引位置的元素值:

nameList[0] = "lilei"

  1. 多维数组

多维数组的操作与一维数组一样,只不过每个元素可能是个数组,数组嵌套。

  1. 数组类型的不足

由于数组类型变量一旦声明后长度就固定了,这意味着我们将不能动态添加元素到数组,如果要这么做的话,需要先创建一个容量更大的数组,然后把老数组的元素都拷贝过来,最后再添加新的元素,如果数组尺寸很大的话,势必会影响程序性能。

切片

数组的长度在定义之后无法修改,数组长度是数组类型本身的一部分,是数组的一个内置常量,因此我们无法在数组上做动态的元素增删操作。

显然这种数据结构无法完全满足开发者的日常开发需求,Go 语言提供了切片(slice)来弥补数组的不足,切片一个最强大的功能就是支持对元素做动态增删操作。

切片的创建

切片与数组最大的不同在于,切片的类型字面量中只有元素的类型,没有长度。建切片的方法主要有三种 ------ 基于数组、直接创建。

  1. 基于数组

切片可以基于一个已存在的数组创建。从这个层面来说,数组可以看作是切片的底层数组,而切片则可以看作是数组某个连续片段的引用。切片可以只使用数组的一部分元素或者整个数组来创建,甚至可以创建一个比所基于的数组还要大的切片

Go 语言支持通过 array[start:end] 这样的方式基于数组生成一个切片,start 表示切片在数组中的下标起点,end 表示切片在数组中的下标终点,两者之间的元素就是切片初始化后的元素集合,遵守左闭右开的规则

  1. 直接创建

Go 语言提供的内置函数 make() 可以用于灵活地创建切片。

所有未初始化的切片,会填充元素类型对应的零值。

事实上,使用直接创建的方式来创建切片时,Go 底层还是会有一个匿名数组被创建出来,然后调用基于数组创建切片的方式返回切片,只是上层不需要关心这个匿名数组的操作而已。所以,最终切片都是基于数组创建的,切片可以看做是操作数组的指针。

动态增加元素

切片比数组更强大之处在于支持动态增加元素,甚至可以在容量不足的情况下自动扩容。在切片类型中,元素个数和实际可分配的存储空间是两个不同的值,元素的个数即切片的实际长度,而可分配的存储空间就是切片的容量。

一个切片的容量初始值根据创建方式的不同而不同:

  • 对于基于数组和切片创建的切片而言,默认容量是从切片起始索引到对应底层数组的结尾索引
  • 对于通过内置 make 函数创建的切片而言,在没有指定容量参数的情况下,默认容量和切片长度一致

所以,通常一个切片的长度值小于等于其容量值,我们可以通过 Go 语言内置的 cap() 函数和 len() 函数来获取某个切片的容量和实际长度:

我们可以通过 append() 函数向切片追加新元素:

函数 append() 的第二个参数是一个不定参数,我们可以按自己需求添加若干个元素(大于等于 1 个),甚至直接将一个切片追加到另一个切片的末尾:

自动扩容

如果追加的元素个数超出切片的默认容量,则底层会自动进行扩容:

新切片的长度变成了 11,容量变成了 20,需要注意的是 append() 函数并不会改变原来的切片,而是会生成一个容量更大的切片,然后把原有的元素和新元素一并拷贝到新切片中。

默认情况下,扩容后新切片的容量将会是原切片容量的 2 倍,如果还不足以容纳新元素,则按照同样的操作继续扩容,直到新容量不小于原长度与要追加的元素数量之和。但是,当原切片的长度大于或等于 1024 时,Go 语言将会以原容量的 1.25 倍作为新容量的基准。

因此,如果事先能预估切片的容量并在初始化时合理地设置容量值,可以大幅降低切片内部重新分配内存和搬送内存块的操作次数,从而提高程序性能。

内容复制

切片类型还支持 Go 语言的另一个内置函数 copy(),用于将元素从一个切片复制到另一个切片。如果两个切片不一样大,就会按其中较小的那个切片的元素个数进行复制。

数据共享问题

切片底层是基于数组实现的。对应的结构体对象如下所示:

在结构体中使用指针存在不同实例的数据共享问题,我们看个例子:

可以看到,slice2 是基于 slice1 创建的,它们的数组指针指向了同一个数组,因此,修改 slice2 元素会同步到 slice1,因为修改的是同一份内存数据,这就是数据共享问题。

要解决这个问题,可以这么做:

可以看到,虽然 slice2 是基于 slice1 创建的,但是修改 slice2 不会再同步到 slice1,因为 append 函数会重新分配新的内存,然后将结果赋值给 slice1,这样一来,slice2 会和老的 slice1 共享同一个底层数组内存,不再和新的 slice1 共享内存,也就不存在数据共享问题了。

但是这里有个需要注意的地方,就是一定要重新分配内存空间,如果没有重新分配,依然存在数据共享问题。

相关推荐
customer0830 分钟前
【开源免费】基于SpringBoot+Vue.JS个人博客系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
qq_4592384934 分钟前
SpringBoot整合Redis和Redision锁
spring boot·redis·后端
灰色人生qwer38 分钟前
SpringBoot 项目配置日志输出
java·spring boot·后端
阿华的代码王国1 小时前
【从0做项目】Java搜索引擎(6)& 正则表达式鲨疯了&优化正文解析
java·后端·搜索引擎·正则表达式·java项目·从0到1做项目
EQUINOX11 小时前
lab4 CSAPP:Cachelab
java·后端·spring
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS打卡健康评测系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
一小路一1 小时前
Go Web 开发基础:从入门到实战
服务器·前端·后端·面试·golang
(; ̄ェ ̄)。2 小时前
在Nodejs中使用kafka(三)offset偏移量控制策略,数据保存策略
分布式·后端·kafka·node.js
paterWang2 小时前
基于SpringBoot的驾校报名小程序系统设计与实现(源码+文档)
spring boot·后端·小程序
苏生Susheng4 小时前
【SpringBoot整合系列】Kafka的各种模式及Spring Boot整合的使用基础案例
java·spring boot·后端·spring·kafka·消息队列·并发