介绍
在 Go 中, arrays 和 slices 是由元素排序序组成的 数据结构 这些数据收集非常适合在想要使用多个相关值时使用。
虽然 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" |
---|---|---|---|
0 | 1 | 2 | 3 |
第一个元素,字符串蓝色珊瑚
,从索引0
开始,而片段在索引3
结束,元素elkhorn珊瑚
。
由于片段或数组中的每个元素都有相应的索引号码,我们可以以与其他序列数据类型相同的方式访问和操纵它们。
现在我们可以通过引用其索引号称来命名片段的离散元素:
1fmt.Println(coral[1])
1[secondary_label Output]
2staghorn coral
这个片段的索引数字从0-3
到0-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 coral
和antipathes
。
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
,4
和5
的索引:
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 中编码系列。