介绍 Go 的数组和切片

学习在 Go 中使用数组和切片存储数据的优缺点,以及为什么其中一个比另一个更好
Traffic lights at night

图片来自于: 
carrotmadman6. Modified by Opensource.com. CC BY-SA 2.0

数组

数组是编程语言中最流行的数据结构之一,主要有两个原因:它们简单易懂,并且可以存储许多不同类型的数据。

你可以声明一个名为 anArray 的 Go 数组,它存储了四个整数:

anArray := [4]int{-1, 2, 0, -4}

数组的大小应该在类型之前声明,类型应该在元素之前定义。len() 函数可以帮助你找出任何数组的长度。上面的数组的大小是 4。

如果您熟悉其他编程语言,您可能会尝试使用 for 循环访问数组中的所有元素。然而,正如您将在下面看到的,Go 的range 关键字允许您以一种更优雅的方式访问数组或片中的所有元素。

最后,介绍如何定义二维数组:

twoD := [3][3]int{
    {1, 2, 3},
    {6, 7, 8},
    {10, 11, 12}}

arrays.go 源文件解释了如何使用 Go 的数组。arrays.go 最重要的代码是:

for i := 0; i < len(twoD); i++ {
        k := twoD[i]
        for j := 0; j < len(k); j++ {
                fmt.Print(k[j], " ")
        }
        fmt.Println()
}

for _, a := range twoD {
        for _, j := range a {
                fmt.Print(j, " ")
        }
        fmt.Println()
}

这展示了如何使用 for 循环和 range 关键字遍历数组的元素。arrays.go 的其余代码。展示了如何将数组作为参数传递到函数中。

下面是 arrays.go 的输出:

$ go run arrays.go
Before change(): [-1 2 0 -4]
After change(): [-1 2 0 -4]
1 2 3
6 7 8
10 11 12
1 2 3
6 7 8
10 11 12

这个输出说明,在函数退出后,您对函数内的数组所做的更改将丢失。

数组的缺点

Go 数组有很多缺点,这将使您重新考虑在 Go 项目中使用它们。首先,你不能在定义一个数组后改变它的大小,这意味着Go 数组不是动态的。简单地说,如果需要将一个元素添加到一个没有任何剩余空间的数组中,则需要创建一个更大的数组,并将旧数组中的所有元素复制到新数组中。其次,当您将数组作为参数传递给函数时,实际上传递的是数组的一个副本,这意味着您对函数内部数组所做的任何更改都将在函数退出后丢失。最后,将一个大数组传递给一个函数可能非常慢,这主要是因为 Go 必须创建一个数组的副本。

Go 切片可以解决所有的问题。

切片

Go 切片类似于没有缺点的 Go 数组。首先,可以使用 append() 函数向现有切片添加一个元素。此外,Go 切片是使用数组在内部实现的,这意味着 Go 对每个切片使用一个底层数组。

切片具有一个 容量 属性和一个 长度 属性,它们并不总是相同的。切片的长度与元素数量相同的数组的长度相同,可以使用 len() 函数找到它。切片的容量是当前分配给切片的空间,可以使用 cap() 函数找到它。

切片的大小是动态的,如果切片的容量耗尽(这意味着当您试图向数组添加另一个元素时,数组的当前长度与它的容量相同), Go 会自动将当前容量加倍,为更多元素腾出空间,并将请求的元素添加到数组中。

此外,切片是通过对函数的引用传递的,这意味着实际传递给函数的是切片变量的内存地址,在函数退出后,对函数内部切片的任何修改都不会丢失。因此,将一个大的切片传递给一个函数要比将一个具有相同数量元素的数组传递给同一个函数要快得多。这是因为 Go 不必复制切片——它只传递切片变量的内存地址。

Go 切片在 slice.go 中有说明,包含以下代码:

package main

import (
        "fmt"
)

func negative(x []int) {
        for i, k := range x {
                x[i] = -k
        }
}

func printSlice(x []int) {
        for _, number := range x {
                fmt.Printf("%d ", number)
        }
        fmt.Println()
}

func main() {
        s := []int{0, 14, 5, 0, 7, 19}
        printSlice(s)
        negative(s)
        printSlice(s)

        fmt.Printf("Before. Cap: %d, length: %d\n", cap(s), len(s))
        s = append(s, -100)
        fmt.Printf("After. Cap: %d, length: %d\n", cap(s), len(s))
        printSlice(s)

        anotherSlice := make([]int, 4)
        fmt.Printf("A new slice with 4 elements: ")
        printSlice(anotherSlice)
}

切片定义和数组定义之间最大的区别是,您不需要指定切片的大小,切片的大小是由您想要放入其中的元素数量决定的。 另外,append() 函数允许您向现有的切片添加一个元素——注意,即使切片的容量允许您向该切片添加一个元素,它的长度也不会改变,除非您调用 append()printSlice() 函数是一个辅助函数,用于打印其切片参数的元素,而 negative()函数则处理其切片参数的所有元素。

slice.go 的输出是:

$ go run slice.go
0 14 5 0 7 19
0 -14 -5 0 -7 -19
Before. Cap: 6, length: 6
After. Cap: 12, length: 7
0 -14 -5 0 -7 -19 -100
A new slice with 4 elements: 0 0 0 0

请注意,当您创建一个新的切片并为给定数量的元素分配内存空间时,Go 将自动将所有元素初始化为其类型的 0 值,在本例中为 0。

切片中引用数组

Go 允许您使用 [:] 符号引用一个带有切片的现有数组。在这种情况下,您对切片函数所做的任何更改都将传播到数组中——这在 refArray.go 中得到了说明。请记住,[:] 符号并不创建数组的副本,只是对它的引用。

refArray.go 最有趣的部分是:

func main() {
        anArray := [5]int{-1, 2, -3, 4, -5}
        refAnArray := anArray[:]

        fmt.Println("Array:", anArray)
        printSlice(refAnArray)
        negative(refAnArray)
        fmt.Println("Array:", anArray)
}

refArray.go 输出是:

$ go run refArray.go
Array: [-1 2 -3 4 -5]
-1 2 -3 4 -5
Array: [1 -2 3 -4 5]

因此,anArray 数组的元素会因为切片对它的引用而改变。

总结

尽管 Go 同时支持数组和切片,但你现在应该很清楚,您很可能会使用切片,因为它们比 Go 数组更通用、更强大。只有少数情况下需要使用数组而不是切片。最明显的一种情况是,您完全确定需要存储固定数量的元素。

你可以找到本文的 Go 代码 arrays.goslice.go, 和 refArray.go 在 GitHub.

译自opensource

本作品采用《CC 协议》,转载必须注明作者和本文链接
最初的时候也是最苦的时候,最苦的时候也是最酷的时候。
讨论数量: 4
xianyunyehe

@lovecn 数组一般只用作那种固定长度的,而且不修改的,比如每个星期的七天,固定,基本上只会读取,
切片和数组里面的元素都是同一类型,你如果需要append 不同的类型,那你就得自己转换,但是也有万能的interface{} 不建议,不推荐使用

slice1 := make([]interface{},0)
slice1 = append(slice1,"111")
slice1 = append(slice1,2)
4年前 评论

对初学很有帮助 :+1:

4年前 评论
xianyunyehe

可以把切片理解成

type Slice struct {
  arr []interfase
  len int
  cap int 
}
4年前 评论

这么说数组几乎用不上,还有切片里都是同一个类型,如果要append一个字符串怎么办?

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!