第 6 章 容器类型

6.1 数组

数组 是一个由 固定长度 的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,因此在 Go 语言中很少直接使用数组。和数组对应的类型是 slice(切片) ,它是可以动态的增长和收缩的序列, slice 功能也更灵活,下面我们再讨论 slice

6.1.1 数组声明

可以使用 [n]Type 来声明一个数组。其中 n 表示数组中元素的数量, Type 表示每个元素的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package main

import "fmt"

func test01() {
// 声明时没有指定数组元素的值, 默认为零值
var arr [5]int
fmt.Println(arr)

arr[0] = 1
arr[1] = 2
arr[2] = 3
fmt.Println(arr)
}

func test02() {
// 直接在声明时对数组进行初始化
var arr1 = [5]int{15, 20, 25, 30, 35}
fmt.Println(arr1)

// 使用短声明
arr2 := [5]int{15, 20, 25, 30, 35}
fmt.Println(arr2)

// 部分初始化, 未初始化的为零值
arr3 := [5]int{15, 20} // [15 20 0 0 0]
fmt.Println(arr3)

// 可以通过指定索引,方便地对数组某几个元素赋值
arr4 := [5]int{1: 100, 4: 200}
fmt.Println(arr4) // [0 100 0 0 200]

// 直接使用 ... 让编译器为我们计算该数组的长度
arr5 := [...]int{15, 20, 25, 30, 35, 40}
fmt.Println(arr5)
}

func test03() {
// 特别注意数组的长度是类型的一部分,所以 [3]int 和 [5]int 是不同的类型
arr1 := [3]int{15, 20, 25}
arr2 := [5]int{15, 20, 25, 30, 35}
fmt.Printf("type of arr1 is %T\n", arr1)
fmt.Printf("type of arr2 is %T\n", arr2)
}

func test04() {
// 定义多维数组
arr := [3][2]string{
{"1", "Go语言极简一本通"},
{"2", "Go语言微服务架构核心22讲"},
{"3", "从0到Go语言微服务架构师"}}
fmt.Println(arr) // [[15 20] [25 22] [25 22]]
}

func main() {
test01()
test02()
test03()
test04()
}

6.1.2 数组长度

使用内置的 len 函数将返回数组中元素的个数,即数组的长度。

1
2
3
4
func arrLength() {
arr := [...]string{"Go语言极简一本通", "Go语言微服务架构核心22讲", "从0到Go语言微服务架构师"}
fmt.Println("数组的长度是:", len(arr)) //数组的长度是: 3
}

6.1.3 数组遍历

使用 for range 循环可以获取数组每个索引以及索引上对应的元素。

1
2
3
4
5
6
7
8
9
10
func showArr() {
arr := [...]string{"Go语言极简一本通", "Go语言微服务架构核心22讲", "从0到Go语言微服务架构师"}
for index, value := range arr {
fmt.Printf("arr[%d]=%s\n", index, value)
}

for _, value := range arr {
fmt.Printf("value=%s\n", value)
}
}

6.1.4 数组是值类型

Go 中的数组是值类型而不是引用类型。当数组赋值给一个新的变量时,该变量会得到一个原始数组的一个副本。如果对新变量进行更改,不会影响原始数组。

1
2
3
4
5
6
7
func arrByValue() {
arr := [...]string{"Go语言极简一本通", "Go语言微服务架构核心22讲", "从0到Go语言微服务架构师"}
copy := arr
copy[0] = "Golang"
fmt.Println(arr)
fmt.Println(copy)
}

6.2 切片(Slice)

切片是对数组的一个连续片段的引用,所以切片是一个引用类型。切片 本身不拥有任何数据,它们只是对现有数组的引用,每个切片值都会将数组作为其底层的数据结构。slice 的语法和数组很像,只是没有固定长度而已。

6.2.1 创建切片

使用 []Type 可以创建一个带有 Type 类型元素的切片。

1
2
3
4
5
// 声明整型切片
var numList []int

// 声明一个空切片
var numListEmpty = []int{}

你也可以使用 make 函数构造一个切片,格式为 make([]Type, size, cap)

1
numList := make([]int, 3, 5)

当然,我们可以通过对数组进行片段截取创建一个切片。

1
2
3
4
arr := [5]string{"Go语言极简一本通", "Go语言微服务架构核心22讲", "从0到Go语言微服务架构师", "微服务", "分布式"}
var s1 = arr[1:4]
fmt.Println(arr) // [Go语言极简一本通 Go语言微服务架构核心22讲 从0到Go语言微服务架构师 微服务 分布式]
fmt.Println(s1) // [Go语言微服务架构核心22讲 从0到Go语言微服务架构师 微服务]

6.2.2 切片的长度和容量

一个 slice 由三个部分构成:指针长度容量 。指针指向第一个 slice 元素对应的底层数组元素的地址,要注意的是 slice 的第一个元素并不一定就是数组的第一个元素。长度对应 slice 中元素的数目;长度不能超过容量,容量一般是从 slice 的开始位置到底层数据的结尾位置。简单的讲,容量就是从创建切片索引开始的底层数组中的元素个数,而长度是切片中的元素个数。

内置的 lencap 函数分别返回 slice 的长度和容量。

1
2
3
s := make([]string, 3, 5)
fmt.Println(len(s)) // 3
fmt.Println(cap(s)) // 5

如果切片操作超出上限将导致一个 panic 异常。

1
2
s := make([]int, 3, 5)
fmt.Println(s[10]) //panic: runtime error: index out of range [10] with length 3

Tips:

  • 由于 slice 是引用类型,所以你不对它进行赋值的话,它的默认值是 nil

    1
    2
    var numList []int
    fmt.Println(numList == nil) // true
  • 切片之间不能比较,因此我们不能使用 == 操作符来判断两个 slice 是否含有全部相等元素。特别注意,如果你需要测试一个 slice 是否是空的,使用 len(s) == 0 来判断,而不应该用 s == nil 来判断。

6.2.3 切片元素的修改

切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中。

1
2
3
4
5
6
7
8
9
10
func modifySlice() {
var arr = [...]string{"Go语言极简一本通", "Go语言微服务架构核心22讲", "从0到Go语言微服务架构师"}
s := arr[:] //[0:len(arr)]
fmt.Println(arr) //[Go语言极简一本通 Go语言微服务架构核心22讲 从0到Go语言微服务架构师]
fmt.Println(s) //[Go语言极简一本通 Go语言微服务架构核心22讲 从0到Go语言微服务架构师]

s[0] = "Go语言"
fmt.Println(arr) //[Go语言 Go语言微服务架构核心22讲 从0到Go语言微服务架构师]
fmt.Println(s) //[Go语言 Go语言微服务架构核心22讲 从0到Go语言微服务架构师]
}

这里的 arr[:] 没有填入起始值和结束值,默认就是 0len(arr)

6.2.4 追加切片元素

使用 append 可以将新元素追加到切片上。append 函数的定义是 func append(slice []Type, elems ...Type) []Type 。其中 elems ...Type 在函数定义中表示该函数接受参数 elems 的个数是可变的。这些类型的函数被称为可变函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  func appendSliceData() {
s := []string{"Go语言极简一本通"}
fmt.Println(s)
fmt.Println(cap(s))

s = append(s, "Go语言微服务架构核心22讲")
fmt.Println(s)
fmt.Println(cap(s))

s = append(s, "从0到Go语言微服务架构师", "分布式")
fmt.Println(s)
fmt.Println(cap(s))

s = append(s, []string{"微服务", "分布式锁"}...)
fmt.Println(s)
fmt.Println(cap(s))
}

当新的元素被添加到切片时,如果容量不足,会创建一个新的数组。现有数组的元素被复制到这个新数组中,并返回新的引用。现在新切片的容量是旧切片的两倍。

6.2.5 多维切片

类似于数组,切片也可以有多个维度。

1
2
3
4
5
6
7
8
func mSlice() {
numList := [][]string{
{"1", "Go语言极简一本通"},
{"2", "Go语言微服务架构核心22讲"},
{"3", "从0到Go语言微服务架构师"},
}
fmt.Println(numList)
}

6.3 Map

在 Go 语言中,map 是散列表(哈希表)的引用。它是一个拥有键值对元素的无序集合,在这个集合中,键是唯一的,可以通过键来获取、更新或移除操作。无论这个散列表有多大,这些操作基本上是通过常量时间完成的。所有可比较的类型,如 整型字符串 等,都可以作为 key

6.3.1 创建 Map

使用 make 函数传入键和值的类型,可以创建 map 。具体语法为 make(map[KeyType]ValueType)

1
2
3
// 创建一个键类型为 string 值类型为 int 名为 scores 的 map
scores := make(map[string]int)
steps := make(map[string]string)

我们也可以用 map 字面值的语法创建 map ,同时还可以指定一些最初的 key/value :

1
2
3
4
5
6
var steps2 map[string]string = map[string]string{
"第一步": "Go语言极简一本通",
"第二步": "Go语言微服务架构师核心22讲",
"第三步": "从0到Go语言微服务架构师",
}
fmt.Println(steps2)

或者

1
2
3
4
5
6
steps3 := map[string]string{
"第一步": "Go语言极简一本通",
"第二步": "Go语言微服务架构师核心22讲",
"第三步": "从0到Go语言微服务架构师",
}
fmt.Println(steps3)

6.3.2 Map 操作

  • 添加元素

    1
    2
    // 可以使用 `map[key] = value` 向 map 添加元素。
    steps3["第四步"] = "总监"
  • 更新元素

    1
    2
    // 若 key 已存在,使用 map[key] = value 可以直接更新对应 key 的 value 值。
    steps3["第四步"] = "CTO"
  • 获取元素

    1
    2
    // 直接使用 map[key] 即可获取对应 key 的 value 值,如果 key不存在,会返回其 value 类型的零值。
    fmt.Println(steps3["第四步"] )
  • 删除元素

    1
    2
    //使用 delete(map, key)可以删除 map 中的对应 key 键值对,如果 key 不存在,delete 函数会静默处理,不会报错。
    delete(steps3, "第四步")
  • 判断 key 是否存在

    1
    2
    3
    4
    5
    6
    7
    8
    // 如果我们想知道 map 中的某个 key 是否存在,可以使用下面的语法:value, ok := map[key]
    v3, ok := steps3["第三步"]
    fmt.Println(ok)
    fmt.Println(v3)

    v4, ok := steps3["第四步"]
    fmt.Println(ok)
    fmt.Println(v4)

    这个语句说明 map 的下标读取可以返回两个值,第一个值为当前 keyvalue 值,第二个值表示对应的 key 是否存在,若存在 oktrue ,若不存在,则 okfalse

  • 遍历 map

    1
    2
    3
    4
    // 遍历 map 中所有的元素需要用 for range 循环。
    for key, value := range steps3 {
    fmt.Printf("key: %s, value: %s\n", key, value)
    }
  • 获取 map 长度

    1
    2
    3
    4
    5
    // 使用 len 函数可以获取 map 长度
    func createMap() {
    //...
    fmt.Println(len(steps3)) // 4
    }

6.3.3 map 是引用类型

map 被赋值为一个新变量的时候,它们指向同一个内部数据结构。因此,改变其中一个变量,就会影响到另一变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func mapByReference() {
steps4 := map[string]string{
"第一步": "Go语言极简一本通",
"第二步": "Go语言微服务架构师核心22讲",
"第三步": "从0到Go语言微服务架构师",
}
fmt.Println("steps4: ", steps4)
// steps4: map[第一步:Go语言极简一本通 第三步:从0到Go语言微服务架构师 第二步:Go语言微服务架构师核心22讲]
newSteps4 := steps4
newSteps4["第一步"] = "Go语言极简一本通-222"
newSteps4["第二步"] = "Go语言微服务架构师核心22讲-222"
newSteps4["第三步"] = "从0到Go语言微服务架构师-222"
fmt.Println("steps4: ", steps4)
// steps4: map[第一步:Go语言极简一本通-222 第三步:从0到Go语言微服务架构师-222 第二步:Go语言微服务架构师核心22讲-222]
fmt.Println("newSteps4: ", newSteps4)
// newSteps4: map[第一步:Go语言极简一本通-222 第三步:从0到Go语言微服务架构师-222 第二步:Go语言微服务架构师核心22讲-222]
}

map 作为函数参数传递时也会发生同样的情况。

如何学习Go语言微服务,快速步入架构师

从0到Go语言微服务架构师-海报 从0到Go语言微服务架构师
添加微信 公众号更多内容
wechat gzh