假设我们正在创建一个在线游戏平台,需要跟踪玩家的得分。对于每个玩家,我们需要将他们的得分存储在该玩家的一致索引中。在这种情况下,可以使用数组, 数组是相同类型数据元素的集合,我们可以通过索引访问每个元素 。我们可以把数组想象成一排邮箱,每个索引代表一个门牌号。数组是最常见的变量形式之一, 在程序中使用。我们使用数组来完成以下任务:

  • 存储多条输入
  • 存储相关的值集合
  • 对数字列表执行数学运算

在世界各地,数十亿正在运行的程序都在使用数组!在本文中, 我们将介绍:

  • 创建数组
  • 访问数组值
  • 使用数组的长度

创建数组

在程序中使用数组,我们必须首先声明并命名它们。在 Go 中,有多种声明数组的方式。当我们在 Go 中声明一个变量时,编译器:

  • 位该变量在内存中查找空间
  • 将变量与名称关联

使用数组会使编译器的工作稍微复杂一些。当我们声明单个变量时,编译器需要为该数据类型之一找到足够的空间。当我们声明一个数组时,编译器需要为多个数据类型找到足够的空间。为了简化此过程,在 Go 中声明数组需要提供元素数量。声明后,除非声明新数组,否则无法更改此数量。编译器会根据数组类型乘以元素数量,找到足够的空间。

我们可以创建带有或不带有初始元素集的数组。当程序的其余部分将创建数组的内容时,我们使用不带初始元素集的数组。要创建不带初始元素集的数组,我们使用以下语法:

var playerScores [4]int
fmt.Println(playerScores)
// [0 0 0 0]

此语法创建一个包含 4 个元素的整数空数组。我们可以创建一个类似的数组,然后根据用户输入的值填充它。虽然空数组非常适合存储我们无法预测的数据,但有时我们已经知道数组中需要什么!

使用元素创建数组

想象一下,我们正在做一些数学作业,并且一遍又一遍地使用相同的三角形。我们可以声明两个数组,分别保存该三角形的边长和角度值。

triangleSides := [3]int{15, 26, 30}
triangleAngles := [...]int{30, 60, 90}

注意这两行之间的语法差异。当创建一个包含值的数组时,我们可以让编译器使用 ... 省略号语法自动确定其长度。现在我们有了一些值,但是如何使用它们来完成数学作业呢?,我们需要访问存储在数组中的值。

使用索引访问数组值

我们已经了解了如何创建包含初始值的数组。但是我们如何访问存储在数组中的值呢?虽然程序中有一个数字列表看起来不错,但我们需要访问这些值来进行有用的计算。我们需要使用或修改单个值来执行以下操作:

  • 求所有元素的总和(或其他值)
  • 更新特定元素的值
  • 在数组中搜索特定值

如果不能访问或更改值,数组就只是一个漂亮的列表!为了访问数组的元素,我们使用一种叫做索引的方法。如前所述,数组的每个元素都有一个索引。经常让新程序员感到困惑的一件事是数组开头的索引。Go 使用 0 作为数组的第一个索引,这意味着它将第一个元素存储在索引 0 处 。尝试使用索引 1 访问第一个元素可能很诱人,但这会访问第二个元素。让我们看一下如何访问学生姓名数组,定义如下:

students = [3]string{"Jill", "Fred", "Sasha"}
// Access the first element of the array
fmt.Println(students[0])
// Output: Jill
// Access the third element of the array
fmt.Println(students[2])
// Output: Sasha
// Store the second element into a variable
secondStudent := students[1]
// Print it
fmt.Println(secondStudent)
// Output: Fred

访问数组元素很有用,但我们经常需要更改数组中存储的值。修改数组值将是我们下一个的主题。

修改数组值

能够检索存储在数组中的值非常有用,但如果我们需要更改它们怎么办?在专业程序中,更改数组值是经常发生的事情。考虑一下:

  • 计数或计算一段时间内的值
  • 接收有关某条数据的新信息
  • 数组内的数据位置发生变化

所有这些场景都需要我们更改数组中初始存储的值。这样做的语法非常简单:

array[index] = value

其中 index 是数组中任意有效的索引,value 是任意表达式。假设我们有一个数组:

myArray := [4]int{10, 24, 5, 47}

假设我们决定将第三个元素设为 33 。我们可以使用以下代码更改该索引处的数组:

myArray[2] = 33

数组的内容现在为 {10, 24, 33, 47}, 我们可以使用此语法来修改 0 到数组长度之间的任何有效索引。接下来我们将了解切片, 这使我们能够为数组添加额外的长度!

切片简介

到目前为止,我们一直在使用数组,它们的大小是固定的。如果我们想在数组中存储不同数量的元素,就必须创建一个全新的数组。不过,Go 为我们提供了一个实用的替代方案。

切片是一种类似于数组的数据集合类型,但它可以改变大小。首先需要知道如何创建切片。创建切片的方法有很多种。我们可以从数组创建切片,也可以直接创建切片本身。我们先从创建切片本身开始。

// Each of the following creates an empty slice
var numberSlice []int
stringSlice := []string{}

// The following creates a slice with elements
names := []string{"Kathryn", "Martin", "Sasha", "Steven"}

虽然最后一个切片目前有四个元素,但我们可以继续使用函数添加元素。我们也可以获取一个数组,并基于该数组创建一个切片。修改切片仍然会更新原始数组。

array := [5]int{2, 5, 7, 1, 3}
// This is a slice of the whole array
sliceVersion := array[:]
fmt.Println(sliceVersion)
// [2 5 7 1 3]
// This is a slice containing the elements at indices 2, 3, and 4
partialSlice := array[2:5]
fmt.Println(partialSlice)
// [7 1 3]

切片的元素访问和修改方式与数组相同!既然我们已经知道如何使用数组,我们当然也知道使用切片:

var names = []string{"Kathryn", "Martin", "Sasha", "Steven"}
fmt.Println(names[1])
// Martin
names[3] = "Bishop"
fmt.Println(names[3])
// Bishop

长度和容量

为什么我们需要计算数组的长度?请描述一下这在哪些场景下会很有用。

len

len 是一个函数,它返回传递给它的数组或切片的长度:

favoriteThings := [2]string{"Raindrops on Roses", "Whiskers on Kittens"}
fmt.Println(len(favoriteThings))
// 2

fmt.Println(len(nastyThings))
// 3

len 用于循环,以及验证是否可以在数组或切片上使用索引。访问超出长度的元素会导致错误。数组只有长度,但当涉及到切片时,还有一个额外的因素需要考虑,那就是容量。

cap

切片是可调整大小的,因此存在以下区别:

  • 它的长度,它当前持有的元素数量
  • 它的容量 ,即在需要调整自身大小之前它可以容纳的元素数量。

可以通过 cap 函数访问切片的容量:

slice := []string{"Fido", "Fifi", "FruFru"}
// The slice begins at length 3 and capacity 3
fmt.Println(slice, len(slice), cap(slice))
// [Fido Fifi FruFru] 3 3
slice = append(slice, "FroFro")
// After appending an element when the slice is at capacity
// The slice will double in capacity, but increase its length by 1
fmt.Println(slice, len(slice), cap(slice))
// [Fido Fifi FruFru FroFro] 4 6

请注意在上面的例子中,当我们向已满容量的切片中添加一个元素时,发生了以下情况:

  • 仍然可以添加新元素
  • 长度增加以适应新元素
  • 容量增加了一倍。

所有这些都是通过切片自动实现的,而使用数组则无法实现!

向切片添加内容

到目前为止,我们已经讨论了数组和切片是什么,以及如何在基本层面上使用它们。然而,使用切片的一个重要方面是我们可以添加元素,并且切片会自动调整大小。但是,如何向切片中添加元素呢?Go 为我们提供了一个函数 append ,它处理添加和调整切片大小的所有逻辑:

books := []string{"Tom Sawyer", "Of Mice and Men"}
books = append(books, "Frankenstein")
books = append(books, "Dracula")
fmt.Println(books)
// [Tom Sawyer Of Mice and Men Frankenstein Dracula]

能够向切片中添加新元素非常重要。尤其是当数据随时间推移而来,且我们无法预测时。

函数中的数组和切片

所有这些数组和切片功能都很有用,但我们需要能够在 main 之外使用它们。接下来,我们将了解定义函数中使用它们. 我们可以将数组或切片作为参数传递给函数。要将数组参数传递给函数,我们需要提供本地名称、方括号和数据类型。切片参数和数组参数之间的区别在于是否声明元素数量:

func printFirstLastArray(array [4]int) {
  fmt.Println("First", array[0])
  fmt.Println("Last", array[3])
}

func printFirstLastSlice(slice []int) {
  length := len(slice)
  if (length > 0) {
      fmt.Println("First", slice[0])
      fmt.Println("Last", slice[length-1])
  }
}

由于 Go 是一种值传递语言,修改普通数组参数不会产生永久性改变。这在执行本地计算时很有用。

// Changes to the array will only be local to the function
func changeFirst(array [4]int, value int) {
  array[0] = value
}

为了保留更改,可以使用切片:

// Changes to the slice parameter will be permanent
func changeFirst(slice []int, value int) {
  if (len(slice) > 0) {
      slice[0] = value
  }
}

总结和回顾

在本文中,我们介绍了与使用数组和切片相关的概念和语法. 数组是大小固定且有序的列表,元素类型相同。数组适用于收集和访问多个相关值。数组和切片都是由相同数据类型的多个元素组成的集合。但是,切片可以调整大小以容纳更多元素,而数组则不能。数组的容量就是它的长度,这个长度是不能改变的。切片既有长度,也有容量,其中:

  • 切片的长度是它当前保存的元素数
  • 切片的容量是它在需要调整自身大小之前可以容纳的元素数量。