本章介绍 Go 程序的基本组成部分:数组、切片和映射,它们也是常用的数据类型。

4.1 数组 Arrays

数组是由相同数据类型的数据项组成的长度固定、顺序固定的集合,数据项是数组的元素,数组的长度也是数组类型的组成部分。在编程中,数组通常用来对数据进行逻辑分组。

Go 中创建数组,初始化时必须声明长度和数据类型:

var blog [2]string

释义:

  • 使用 var 关键字定义名为 blog 的数组

  • 数组的长度为2

  • 数组里存储 string 的数据类型

为 blog 数组中的元素分配字符串:

blog[0] = "https://www.aiops.red"
blog[1] = "sretech"

变量名后 [] 里的数字是数组元素的索引,表示分配的值在数组中的位置。索引从0开始,因此要访问数组的第一个元素,写法:blog[0];访问第二个元素:blog[1]。

Go 语言的索引形式采用了左闭右开区间,这和大多数编程语言相同。如果我们要访问 blog 里的所有元素,可以使用 blog[0:2]的写法,这种写法包括索引0处的元素,不包括索引2处的元素,这就叫左闭右开。比如 arrays[m:n],0 ≤ m ≤ n ≤ len(s),包含 n-m 个元素。

使用变量名和索引值打印对应数组元素的值:

fmt.Println(blog[0])
fmt.Println(blog[1])

仅使用变量名打印数组中的所有值:

fmt.Println(blog)

完整代码示例:

package main

import "fmt"

func main() {
 var blog [2]string
   blog[0] = "https://www.aiops.red"
   blog[1] = "sretech"
   fmt.Println(blog[0])
   fmt.Println(blog[1])
   fmt.Println(blog)
}

数组在定义时,必须指定其长度,且添加到该数组的元素数量不能超出数组的长度,否则会编译错误。

4.1.1 二维数组

数组中的元素也可以是数组,称为二维数组:

arrays := [3][2]int{{1, 2}, {3, 4}, {5, 6}}

完整代码示例:

package main

import "fmt"

func main() {
   arrays := [3][2]int{{1, 2}, {3, 4}, {5, 6}}
   fmt.Println(arrays)
   fmt.Println(arrays[1])
   fmt.Println(arrays[1][1])
}

4.2 切片 Slices

数组虽是 Go 的重要组成部分,但在 Go 中使用切片的情况更为常见。切片可以看作是基础数组的连续段,按顺序提供对数组元素的访问。既然这样,为什么会存在切片,而不是直接使用数组呢?

因为 Go 中使用数组有一些限制,在介绍数组部分的示例中,我们不能向 blog 数组添加超过其长度的元素,并且 blog 的数组长度是固定的。切片比数组更加灵活,可以向切片添加元素、删除元素和复制元素,并且切片的长度是可扩容的。可以把切片看做是对数组的封装,既保留的数组的完整功能,又更易于使用。

声明一个长度为2,数据类型是 string 的空切片:

var blog = make([]string, 2)

释义:

  • 使用 var 关键字对变量 blog 进行初始化

  • 用 Go 内置的 make 函数初始化一个切片,第一个参数代表切片的数据类型,第二个参数代表切片的长度。此示例中,切片包含了两个字符串类型的元素。

  • 切片被分配给 blog 变量

在初始化切片后,可以使用和数组相同的赋值方法:

blog[0] = "https://www.aiops.red"
blog[1] = "sretech"

打印切片的方式也和打印数组相同:

fmt.Println(blog[0])
fmt.Println(blog[1])

到目前为止,切片看起来和数组相似。但是切片的不同之处在于添加和删除元素的功能。

4.2.1 向切片添加元素

Go 提高了内置的 append 函数作为扩展切片长度的一种方式:

blog = append(blog, "https://golang.google.cn")
fmt.Println(blog[2])

必要时可以使用 append 调整切片大小,且无需考虑实现的复杂性。此示例将 blog 切片的大小从两个元素扩展为三个元素,并将 https://golang.google.cn 分配给索引2的元素:

package main

import "fmt"

func main() {
    var blog = make([]string, 2)

    blog[0] = "https://www.aiops.red"
    blog[1] = "sretech"

    blog = append(blog, "https://golang.google.cn")

    fmt.Println(blog)
}

append 函数是可变参函数,可以通过 append 函数将多个值附加给切片。

blog := append(blog, "https://golang.google.cn", "golang.aiops.red", "gitlab.com")

这会调整 blog 切片的大小,并按顺序将值分配给新创建的元素。

完整示例:

package main

import "fmt"

func main() {
    var blog = make([]string, 2)

    blog[0] = "https://www.aiops.red"
    blog[1] = "sretech"

    blog = append(blog, "https://golang.google.cn", "golang.aiops.red", "gitlab.com")

    fmt.Println(blog)
}

4.2.2 删除切片中的元素

append 也可用来删除切片中的元素,以下示例将删除索引2处的元素:

blog = append(blog[:2], blog[3:]...)

检查 blog 元素删除前后的长度,以确定它已正确的调整了大小。

package main

import "fmt"

func main() {
    var blog = make([]string, 2)

    blog[0] = "https://www.aiops.red"
    blog[1] = "sretech"

    blog = append(blog, "https://golang.google.cn", "golang.aiops.red", "gitlab.com")

    fmt.Println(blog)

    blog = append(blog[0:2], blog[3:]...)

    fmt.Println(blog)
}

4.2.3 从切片复制元素

使用内置的 copy 函数,可以从切片中复制全部或部分元素。要将一个切片复制到另一个切片,两个切片的数据类型必须相同:

package main

import "fmt"

func main() {
    var blog = make([]string, 3)

    blog[0] = "aiops.red"
    blog[1] = "sretech"
    blog[2] = "golang.org"

    var blogChinese = make([]string, 2)
    copy(blogChinese, blog)
    fmt.Println(blog)
    fmt.Println(blogChinese)
}

因为新切片的长度是2,copy 函数将 blog 切片中的前两个元素复制到新切片中,如果修改了一个切片中的元素,则对其他切片没有影响。也可以将单个元素或一系列元素复制到新切片中。在以下示例中,将从索引1处开始复制元素:

copy(blogChinese, blog[1:])

4.2.4 nil 切片与空切片

定义 nil 切片:

var nilSlice []int

定义空切片:

var emptySlice []int = []int{}

在 Go 中, 空切片和 nil 切片在定义上虽然不同,但操作上没有区别。

4.2.5 切片的长度和容量

在定义切片时,可以指定其长度及容量:

var blog = make([]string, 2, 8)

定义了长度为2,容量是8的切片。

可以通过内置函数 len()查看切片的长度,cap()查看切片的容量。

4.3 映射 Maps

数组和切片是可以用索引取值的有序元素集合。映射是由键而不是索引取值的无序元素组。如果你有其他编程经验,可以把映射理解为字典、散列或关联数组。因为可以直接通过键检索数据,所以映射可以非常有效的查找信息片段。简单的说,映射就是由键值对组成的一种无序的数据结构,通过 key 操作 value。

出始化一个空映射:

var players = make(map[string]int)

释义:

  • var 关键字初始化 players 变量

  • 使用内置的 make 函数初始化一个映射,键数据类型是 string,值数据类型是 int

  • 空映射分配给 players 变量

将键值对分配给空映射 players:

players["Curry"] = 30
players["James"] = 23
players["Kobe"] = 24

变量后的方括号里是键名,等号右侧是键对应的值。

在映射中,使用键作为索引,查找对应的值:

fmt.Println(players["Kobe"])

和数组、切片一样,要想打印映射中的所有键值对,只需映射名即可:

fmt.Println(players)

可以将元素动态添加到映射中,而无需调整其大小。这是 Go 的一个功能展现,Go 语言的行为更像是像 Ruby 或 Python 这样的动态编程语言,而不是类似 C 的语言。

但如果你清楚的知道,你需要使用多大容量的映射时,还是建议你在初始化映射时指定其容量大小。因为切片和映射都是自增长度的存储,在每次自增时,会分配新的内存空间,然后拷贝数据。这样会有消耗。在初始化时把容量初始化到我们需要的大小,会有效提高性能:

players := mmake(map[string]int, 3) // 3 就是初始化 players 时指定的容量

map 可以使用 len 函数求长度,但不能使用 cap 函数计算容量。

4.3.1 增加映射的元素

通过键赋值,如果键存在则是更新值的操作,如果键不存在,则是增加元素操作:

players["KD"] = 35

4.3.2 从映射中删除元素

delete 内置函数可以从映射中删除元素:

delete(players, "Kobe")
package main

import "fmt"

func main() {
    var players = make(map[string]int)

    players["Curry"] = 35
    players["James"] = 23
    players["Kobe"] = 24

    fmt.Println(players["Curry"])
    fmt.Println(players["Kobe"])
    fmt.Println(players)

    delete(players, "Kobe")

    fmt.Println(players)
}

4.3.3 遍历映射中的元素

使用 for range 语句,对映射进行遍历:

// for range
for key, value := range players {
   fmt.Println(key, value)
}

for key := range players {
   fmt.Println(key, players[key])
}

4.3.4 映射中的 nil 与空值

在映射中,nil 不能进行赋值操作,所以在初始化映射的时候,尽量不要定义 nil 映射,而是定义空映射。

var nilMap map[string]string
//panic: assignment to entry in nil map

var emptyMap = make(map[string]int)

4.3.5 映射与工厂模式

工厂模式是设计模式最常用的方式之一,Go 中,函数是一等公民,可以作为参数传递。

m := map[int]func(num int)int{}
// m 的 key 是 int 类型,值是一个传入参数为 int,返回值为 int 的函数类型

m[1] = func(num int) int {return num}
m[1] = func(num int) int {return num * num}
m[1] = func(num int) int {return num * num * num}
fmt.Println(m[1](2), m[2](2), m[3](2))

4.4 总结

通过本章的学习,你可以在 Go 中定义数组、切片和映射了。并且应该也理解了,除非特定需求,尽量使用切片而非数组。虽然本章更多的是理论性的,而非实践性的,但数组、切片和映射是 Go 编程的基本结构块,理解它们,对以后的编程大有裨益。

文档更新时间: 2021-11-08 09:36   作者:admin