[译] Golang 中的数组和切片指南(及其差别)

首先,很容易看到数组和切片好像是同一个东西:表示列表的数据结构。然而,它们实际上彼此彻底不一样。git

在这篇文章中,咱们将探讨他们在 Go 中的差别和实现。github

数组

数组是固定的数据列表。这里的重点是固定的,由于一旦设置了数组的长度,它就没法更改。golang

咱们举一个声明了四个整数的数组的例子:数组

arr := [4]int{3, 2, 5, 4}
复制代码

长度和类型

咱们在上面的例子中定义的 arr 变量的类型是 [4] int,它是一个大小为 4 的数组。这里须要注意的是,4 包含在类型定义中。数据结构

这意味着两个不一样长度的数组其实是两个不一样的类型。因此不能将不一样长度的数组视为一种类型,也不能将其中一个的值分配给另外一个:app

longerArr := [5]int{5, 7, 1, 2, 0}

longerArr = arr
// 会抛出编译错误

longerArr == arr
// 会抛出编译错误
复制代码

我发现考虑数组的一个好方法就是结构体。若是咱们能够构造数组等价的结构体,它可能看起来像这样:ide

// 长度为 4 的数组的等价结构体
type int4 struct {
  e0 int
  e1 int
  e2 int
  e3 int
}

// 长度为 5 的数组的等价结构体
type int5 struct {
  e0 int
  e1 int
  e2 int
  e3 int
  e5 int
}

arr := int4{3, 2, 5, 4}
longerArr := int5{5, 7, 1, 2, 0}
复制代码

不建议执行此操做,但这是一个很好的方法来了解为什么不一样长度的数组是彻底不一样的类型。函数

内存表示

数组存储为指定类型的 n 块的序列:post

初始化数组类型的变量后,将当即分配此内存。ui

引用传递

在 Go 中,没有引用传递。一切都是经过值传递的。若是将数组的值分配给另外一个变量,则会复制整个值。

若是只想将“引用”传递给数组,可使用指针:

在内存分配和函数中,数组其实是一种很是简单的数据类型,其工做方式与结构体相同。

切片

咱们能够将切片视为基于数组的高级实现。

在 Go 中实现了切片,以涵盖开发人员在处理列表时面临的一些很是常见的需求,例如须要动态修改大小。

声明切片几乎与声明数组相同,除了须要必须省略长度的说明符:

slice := []int{4, 5, 3}
复制代码

仅仅看代码的话,切片和数组看起来很是类似,但实际上在实现和使用方面存在显著差别。

内存表示

切片的分配方式与数组不一样,其实是修改过的指针。每一个切片包含三条信息:

  1. 指向数据序列的指针
  2. 长度:表示当前包含的元素总数。
  3. 容量:即配置的内存位置总数。

而后,能够为彼此的值分配不一样长度的切片。它们的类型相同,指针,长度和容量都在变化:

slice1 := []int{6, 1, 2}
slice2 := []int{9, 3}

// 能够将任何长度的切片分配给其余切片
复制代码

与数组不一样,切片在初始化期间不分配数据块的内存。实际上,切片用 nil 值初始化。

引用传递

将切片分配给另外一个变量时,仍然按值传递。这里的值仅指代指针,长度和容量,而不是元素自己占用的内存。(译者注:这里作了一个实验能够更清晰地了解这个过程)

增长新元素

要向切片添加元素,一般使用 append 函数。

nums := []int{8, 0}
nums = append(nums, 8)
复制代码

在内部,这会将指定的值分配给新元素,并返回一个新的切片。这个新切片的长度增长了 1。(译者注:关于切片的扩容分析能够参考煎鱼stefno 的博客)

这就是为何常常建议建立一个预先指定长度和容量的切片(特别是若是你很清楚它的大小多是多少):

arr := make([]int, 0, 5)
// 这将建立一个长度为 0 且容量为 5 的切片
复制代码

数组和切片的使用场景

数组和切片是彻底不一样的,所以,它们的用例也是彻底不一样的。

咱们来看一下开源项目和 Go 标准库中的一些例子,看看它们怎么使用的。

例子 1:UUID

UUID 是 128 位数据,一般用来惟一标记对象或实体。一般以短划线分隔的十六进制值表示:

e39bdaf4-710d-42ea-a29b-58c368b0c53c
复制代码

Google 的 UUID 库 中,UUID 表示为 16 字节的数组:

type UUID [16]byte
复制代码

这是有意义的,由于咱们知道 UUID 是由 128 位(16 字节)组成的。咱们不会在 UUID 中添加或删除任何字节,所以使用数组来表示会更好。

例子 2:整数排序

在下一个示例中,咱们将查看排序标准库中的 sort.Ints 函数:

s := []int{5, 2, 6, 3, 1, 4} // unsorted
sort.Ints(s)
fmt.Println(s)
// [1 2 3 4 5 6]
复制代码

sort.Ints 函数接受一个整数列表并将它们排序。这里选切片有两个缘由:

  1. 未指定整数的数量(能够有任意数量的整数进行排序)。
  2. 这些数字须要原地排序。使用数组会将整个整数列表做为值传递,所以该函数只会对它的副本进行排序,而不是对传递给它的值。(译者注:由于不一样长度的数组是不一样的类型,因此 sort.Ints 规定了入参类型为切片,这也是一个比较重要的缘由)

结论

如今咱们已经介绍了数组和切片之间的关键差别及其用例,这里有一些提示能够决定哪一种结构更合适:

  1. 若是实体由一组固定长度的非空项组成:使用数组。
  2. 须要对列表进行添加或删除元素时,请使用切片。
  3. 若是列表能够包含任意数量的元素,请使用切片。
  4. 你会以某种方式修改列表吗?若是是,则使用切片

咱们能够看到,切片涵盖了在 Go 中建立应用程序的大多数场景。尽管如此,数组确实有它们的位置,而且在须要它们时很是有用。

大家有更好的例子吗?若是有任何关于你更喜欢切片胜于数组(反之亦然)的例子?请在下面的评论中告诉咱们👇

相关文章
相关标签/搜索