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 共享内存,也就不存在数据共享问题了。

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

相关推荐
风象南34 分钟前
我把大脑开源给了AI
人工智能·后端
橙序员小站5 小时前
Agent Skill 是什么?一文讲透 Agent Skill 的设计与实现
前端·后端
怒放吧德德5 小时前
Netty 4.2 入门指南:从概念到第一个程序
java·后端·netty
雨中飘荡的记忆7 小时前
大流量下库存扣减的数据库瓶颈:Redis分片缓存解决方案
java·redis·后端
开心就好20258 小时前
UniApp开发应用多平台上架全流程:H5小程序iOS和Android
后端·ios
悟空码字8 小时前
告别“屎山代码”:AI 代码整洁器让老项目重获新生
后端·aigc·ai编程
小码哥_常9 小时前
大厂不宠@Transactional,背后藏着啥秘密?
后端
奋斗小强9 小时前
内存危机突围战:从原理辨析到线上实战,彻底搞懂 OOM 与内存泄漏
后端
小码哥_常9 小时前
Spring Boot接口防抖秘籍:告别“手抖”,守护数据一致性
后端
心之语歌9 小时前
基于注解+拦截器的API动态路由实现方案
java·后端