了解 Go 中的数组和切片

介绍

在 Go 中, arraysslices 是由元素排序序组成的 数据结构 这些数据收集非常适合在想要使用多个相关值时使用。

虽然 Go 中的数组和片段都是元素的排序序列,但两者之间存在显著差异。在 Go 中,一个 array 是一个 数据结构 组成的元素的排序序序列,其容量在创建时已定义。

鉴于这些差异,有特定情况下,你会使用一个,而不是另一个. 如果你是新的Go,确定什么时候使用它们可能令人困惑:虽然切片的多功能性使它们在大多数情况下更合适的选择,但有具体的例子,在其中数组可以优化你的程序的性能。

本文将详细涵盖数组和片段,这将为您提供必要的信息,以便在选择这些数据类型时做出合适的选择。此外,您将审查最常见的方式来声明和使用两个数组和片段。

拉拉斯

数据结构只需要分配一次内存,而不是一个可变长度的数据结构,该结构必须动态分配内存,以便在未来可以变得更大或更小。虽然固定长度的数组可以使它们变得有些僵硬,但一次性内存分配可以增加程序的速度和性能。

定义一个 Array

数组被定义为将数组的大小声明为[ ],其次是元素的数据类型。在Go中,数组必须有所有元素相同的 数据类型.在数据类型之后,您可以声明数组元素的个别值为{ }

以下是声明数组的一般方案:

1[capacity]data_type{element_values}

<$>[注] 注: 重要的是要记住,每个新数组的声明都会产生一个不同的类型。

如果您没有声明数组元素的值,默认值为零,这意味着数组元素将是空的。

例如,下列数组有三个尚未具有值的整数元素:

1var numbers [3]int

如果您打印了数字,您将收到以下输出:

1[secondary_label Output]
2[0 0 0]

如果您想在创建数组时分配元素的值,请将这些值放置在弯曲的支架中。

1[4]string{"blue coral", "staghorn coral", "pillar coral", "elkhorn coral"}

您可以将数组存储在变量中并打印出来:

1coral := [4]string{"blue coral", "staghorn coral", "pillar coral", "elkhorn coral"}
2fmt.Println(coral)

使用前面的行运行一个程序将为您提供以下输出:

1[secondary_label Output]
2[blue coral staghorn coral pillar coral elkhorn coral]

请注意,在打印时,数组中的元素之间没有划分,这使得很难知道一个元素在哪里结束,另一个元素在哪里开始,因此有时有助于使用代替fmt.Printf函数,该函数可以在打印到屏幕之前格式化字符串。

1fmt.Printf("%q\n", coral)

这将导致如下:

1[secondary_label Output]
2["blue coral" "staghorn coral" "pillar coral" "elkhorn coral"]

现在每一个项目都被引用了. 词汇 n 指示格式器在末尾添加一行返回。

有了如何声明数组及其组成的一般想法,您现在可以学习如何用索引号指定数组中的元素。

索引阵列(和片段)

一个数组中的每个元素(以及一个片段)可以通过索引单独调用,每个元素都与一个索引数相匹配,这是一个从索引数0开始和计数的int值。

我们将在以下示例中使用一个数组,但您也可以使用一个片段,因为它们在索引两者的方式上是相同的。

对于数组珊瑚,索引分布看起来如下:

"blue coral""staghorn coral""pillar coral""elkhorn coral"
0123

第一个元素,字符串蓝色珊瑚,从索引0开始,而片段在索引3结束,元素elkhorn珊瑚

由于片段或数组中的每个元素都有相应的索引号码,我们可以以与其他序列数据类型相同的方式访问和操纵它们。

现在我们可以通过引用其索引号称来命名片段的离散元素:

1fmt.Println(coral[1])
1[secondary_label Output]
2staghorn coral

这个片段的索引数字从0-30-3,如上表所示,因此,要单独调用任何一个元素,我们会提到索引数字如下:

1coral[0] = "blue coral"
2coral[1] = "staghorn coral"
3coral[2] = "pillar coral"
4coral[3] = "elkhorn coral"

如果我们将数组称为珊瑚,其索引数大于3,它将不适用:

1fmt.Println(coral[18])
1[secondary_label Output]
2panic: runtime error: index out of range

在索引数组或片段时,您必须始终使用一个正数,与允许您使用负数向后索引的某些语言不同,在Go中这样做会导致错误:

1fmt.Println(coral[-1])
1[secondary_label Output]
2invalid array index -1 (index must be non-negative)

我们可以将数组或片段中的字符串元素连接到其他字符串中,使用+运算符:

1fmt.Println("Sammy loves " + coral[0])
1[secondary_label Output]
2Sammy loves blue coral

我们能够将索引数0的字符串元素与字符串Sammy loves相连。

使用与数组或片段中的元素相匹配的索引号码,我们可以密切访问每个元素并与这些元素一起工作。

变更元素

我们可以使用索引来改变数组或片段中的元素,通过设置一个等于不同的值的索引编号元素,这使我们能够更大地控制我们片段和片段中的数据,并允许我们程序性地操纵单个元素。

如果我们要将元素的字符串值从数组珊瑚的索引1珊瑚更改为珊瑚,我们可以这样做:

1coral[1] = "foliose coral"

现在,当我们打印珊瑚时,数组将不同:

1fmt.Printf("%q\n", coral)
1[secondary_label Output]
2["blue coral" "foliose coral" "pillar coral" "elkhorn coral"]

现在,您已经知道如何操作一个数组或片段的个别元素,让我们看看一些功能,这些功能在使用收集数据类型时可以提供更多的灵活性。

用「len()」计数元素

在Go中,‘len()’是一个内置的函数,用于帮助您处理数组和切片,就像串一样,您可以使用‘len()’来计算数组或切片的长度,并将数组或切片作为参数。

例如,要找出珊瑚数组中有多少元素,你会使用:

1len(coral)

如果您打印了珊瑚数组的长度,您将收到以下输出:

1[secondary_label Output]
24

这给出了int数据类型的4数组的长度,这是正确的,因为coral数组有四个项目:

1coral := [4]string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral"}

如果您创建了包含更多元素的整数组,您也可以使用len()函数:

1numbers := [13]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
2fmt.Println(len(numbers))

这将导致以下产出:

1[secondary_label Output]
213

虽然这些示例数组具有相对较少的项目,但在确定在非常大的数组中有多少元素时,len()函数尤其有用。

接下来,我们将介绍如何将元素添加到收集数据类型,并展示由于数组的固定长度,添加这些静态数据类型会导致错误。

添加附加( )的元素

append() 是 Go 中的内置方法,该方法将元素添加到集合数据类型中,但是,这种方法在与数组一起使用时不会起作用. 如前所述,数组与片段不同的主要方式是,数组的大小无法更改。

让我们来考虑你的珊瑚阵列:

1coral := [4]string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral"}

假设您想将项黑色珊瑚添加到这个数组中,如果您试图通过键入数组使用附加()函数:

1coral = append(coral, "black coral")

您将收到一个错误作为您的输出:

1[secondary_label Output]
2first argument to append must be slice; have [4]string

要修复此问题,让我们了解更多关于片段数据类型,如何定义片段,以及如何从数组转换为片段。

切片

一个 slice 是 Go 中的一个数据类型,它是元素的 _mutable,或可变的,顺序序序列。由于片段的大小是可变的,使用它们时有更大的灵活性;当处理可能需要在未来扩展或合并的数据集时,使用片段将确保您的代码在尝试操纵集合的长度时不会出现错误。在大多数情况下,这种可变性值得可能的内存重新分配,有时与片段相比,需要使用片段。

定义一个片段

切片被定义为通过声明数据类型,以一个空集的方块([])和圆形块({})之间的元素列表为先,你会注意到,与需要在支架之间声明特定长度的int的数组不同,一个支架之间没有任何元素,代表其可变长度。

让我们创建一个包含字符串数据类型的元素的片段:

1seaCreatures := []string{"shark", "cuttlefish", "squid", "mantis shrimp", "anemone"}

当我们打印片段时,我们可以看到片段中的元素:

1fmt.Printf("%q\n", seaCreatures)

这将导致如下:

1[secondary_label Output]
2["shark" "cuttlefish" "squid" "mantis shrimp" "anemone"]

如果您想创建一定长度的片段而没有填充集合的元素,您可以使用内置的 make() 函数:

1oceans := make([]string, 3)

如果你打印了这个片段,你会得到:

1[secondary_label Output]
2["" "" ""]

如果您想为特定容量预先分配内存,您可以在第三个参数中传输到 make():

1oceans := make([]string, 3, 5)

这将产生一个3的长度和5元素的预分配容量的零切片。

但是,这还没有解决我们以前在珊瑚数组中遇到的错误。要使用珊瑚中的附件()函数,你首先需要学习如何切割数组的部分。

通过使用索引数来确定开始和终点,您可以调用一个数组内的值的子部分。这被称为 slicing 数组,您可以通过创建由一个列分隔的索引数组,以 `[first_index:second_index] 的形式。

假设您只想打印珊瑚数组的中间元素,而不是第一个和最后一个元素。

1fmt.Println(coral[1:3])

运行此行程式将产生如下结果:

1[secondary_label Output]
2[foliose coral pillar coral]

在创建片段时,如在[1:3]中,第一个数字是片段开始的地方(包括),第二个数字是第一个数字和您想要获取的元素总数的总和:

1array[starting_index : (starting_index + length_of_slice)]

在这种情况下,您将第二个元素(或索引1)称为起点,并将总共称为两个元素。

1array[1 : (1 + 2)]

这就是你如何到达这个笔记本:

1coral[1:3]

如果要将数组的开始或结束设置为片段的开始或结束点,则可以将数组(first_index:second_index)语法中的一个数字放弃,例如,如果要打印数组的头三个项目珊瑚,即蓝珊瑚,珊瑚柱珊瑚,则可以这样做:

1fmt.Println(coral[:3])

这将打印:

1[secondary_label Output]
2[blue coral foliose coral pillar coral]

这打印了数组的开始,停止在索引3之前。

要包含一个数组末尾的所有项目,你会扭转语法:

1fmt.Println(coral[1:])

这将给出以下片段:

1[secondary_label Output]
2[foliose coral pillar coral elkhorn coral]

本节讨论了通过切除子部分来调用数组的个别部分,接下来,您将学习如何使用切割来将整个数组转换为切片。

从一个 Array 转换为一个 Slice

如果你创建一个数组,并决定你需要它具有可变长度,你可以将其转换为片段. 要将一个数组转换为片段,使用你在本教程的 ** 切割数组成片段**步骤中学到的切割过程,除非这次选择整个片段,通过忽略两个索引数字来确定终点:

1coral[:]

请记住,你不能将变量珊瑚转换为一个片段本身,因为一旦一个变量被定义在Go中,它的类型无法更改。

1coralSlice := coral[:]

如果您打印了coralSlice,您将收到以下输出:

1[secondary_label Output]
2[blue coral foliose coral pillar coral elkhorn coral]

现在,尝试添加黑色珊瑚元素,如在数组部分中,使用附件()与新转换的片段:

1coralSlice = append(coralSlice, "black coral")
2fmt.Printf("%q\n", coralSlice)

这将输出与添加元素的切片:

1[secondary_label Output]
2["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral"]

我们也可以在单个附件()语句中添加多个元素:

1coralSlice = append(coralSlice, "antipathes", "leptopsammia")
1[secondary_label Output]
2["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral" "antipathes" "leptopsammia"]

要将两个片段合并在一起,您可以使用附加(),但您必须使用...扩展语法扩展第二个参数到附加:

1moreCoral := []string{"massive coral", "soft coral"}
2coralSlice = append(coralSlice, moreCoral...)
1[secondary_label Output]
2["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral" "antipathes" "leptopsammia" "massive coral" "soft coral"]

现在你已经学会了如何将一个元素附加到你的片段,我们将看看如何删除一个。

从片段中删除一个元素

与其他语言不同,Go不提供任何内置的功能来从片段中删除一个元素。

要删除一个元素,您必须切除该元素之前的元素,切除该元素之后的元素,然后将这两个新片段连接在一起,而没有您想要删除的元素。

如果「i」是要移除元素的索引,则此过程的格式将如下:

1slice = append(slice[:i], slice[i+1:]...)

coralSlice中,让我们删除项目elkhorn coral这个项目位于3的索引位置。

1coralSlice := []string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral", "black coral", "antipathes", "leptopsammia", "massive coral", "soft coral"}
2
3coralSlice = append(coralSlice[:3], coralSlice[4:]...)
4
5fmt.Printf("%q\n", coralSlice)
1[secondary_label Output]
2["blue coral" "foliose coral" "pillar coral" "black coral" "antipathes" "leptopsammia" "massive coral" "soft coral"]

现在在索引位置3的元素,字符串elkhorn珊瑚,已不在我们的片段coralSlice

我们也可以用同样的方法删除一个范围,假设我们不仅想删除elkhorn coral,而且也可以删除black coralantipathes

1coralSlice := []string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral", "black coral", "antipathes", "leptopsammia", "massive coral", "soft coral"}
2
3coralSlice = append(coralSlice[:3], coralSlice[6:]...)
4
5fmt.Printf("%q\n", coralSlice)

此代码将从片段中提取3,45的索引:

1[secondary_label Output]
2["blue coral" "foliose coral" "pillar coral" "leptopsammia" "massive coral" "soft coral"]

现在你知道如何从片段中添加和删除元素,让我们看看如何衡量片段在任何特定时间可以存储的数据量。

cap()来衡量片段的容量

由于片段具有可变长度,因此len()方法并不是确定该数据类型的尺寸的最佳选择,相反,您可以使用cap()函数来了解片段的容量。

<$>[注] 注: 由于数组的长度和容量总是相同的,所以 cap() 函数不会在数组上工作。

cap()的常见用途是创建具有预设元素数量的片段,然后在程序上填充这些元素,从而避免使用append()添加元素超过当前分配的容量可能发生的潜在不必要的分配。

我们可以使用附加()在循环中这样做,或者我们可以先分配切片,并使用cap()循环通过填充值。

首先,我们可以使用append()来查看:

1numbers := []int{}
2for i := 0; i < 4; i++ {
3    numbers = append(numbers, i)
4}
5fmt.Println(numbers)
1[secondary_label Output]
2[0 1 2 3]

在这个例子中,我们创建了一个片段,然后创建了一个循环,它会重复四次。每个重复都将循环变量i的当前值附加到片段的索引中。但是,这可能会导致不必要的内存分配,这可能会减慢您的程序。当您添加到空片段时,每次你呼叫添加时,该程序会检查片段的容量。如果添加的元素使片段超过这个容量,该程序会分配额外的内存来计算它。

现在,让我们通过预分配一定的长度/容量来填充切片而不使用 append():

1numbers := make([]int, 4)
2for i := 0; i < cap(numbers); i++ {
3    numbers[i] = i
4}
5
6fmt.Println(numbers)
1[secondary_label Output]
2[0 1 2 3]

在本示例中,我们使用make()来创建一个片段,并让它预分配4元素,然后在循环中使用cap()函数来迭代每个零元素,填充每个元素,直到它达到预分配容量。

虽然附件()cap()策略在功能上均等,但cap()示例避免使用附件()函数需要的任何额外的内存分配。

构建多维式切割

您还可以将由其他片段组成的片段定义为元素,每个片段列表都包含在母片的较大的片段内。 类似这些片段的集合被称为多维片段。

让我们来看看下面的多维片段:

1seaNames := [][]string{{"shark", "octopus", "squid", "mantis shrimp"}, {"Sammy", "Jesse", "Drew", "Jamie"}}

要访问这个片段中的一个元素,我们将不得不使用多个索引,一个用于构造的每个维度:

1fmt.Println(seaNames[1][0])
2fmt.Println(seaNames[0][0])

在前面的代码中,我们首先在索引1中识别片段的索引0,然后在索引0中识别片段的索引0

1[secondary_label Output]
2Sammy
3shark

以下是其余个别元素的索引值:

1seaNames[0][0] = "shark"
2seaNames[0][1] = "octopus"
3seaNames[0][2] = "squid"
4seaNames[0][3] = "mantis shrimp"
5
6seaNames[1][0] = "Sammy"
7seaNames[1][1] = "Jesse"
8seaNames[1][2] = "Drew"
9seaNames[1][3] = "Jamie"

在使用多维切片时,重要的是要记住,您需要参考多个索引号码才能访问相关嵌入切片中的特定元素。

结论

在本教程中,您了解了在Go中使用数组和片段的基本知识,您通过了多个练习来证明数组是如何在长度上固定的,而片段在长度上是可变的,并发现这种差异如何影响这些数据结构的局势用途。

要继续研究 Go 中的数据结构,请参阅我们关于 理解 Go 中的地图的文章,或探索整个 如何在 Go 中编码系列。

Published At
Categories with 技术
Tagged with
comments powered by Disqus