06.Go语言-容器类型

2022-09-04 10:09:28 浏览数 (1)

第 6 章 容器类型

6.1 数组

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

6.1.1 数组声明

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

代码语言:go复制
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 %Tn", arr1)
	fmt.Printf("type of arr2 is %Tn", 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 函数将返回数组中元素的个数,即数组的长度。

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

6.1.3 数组遍历

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

代码语言:go复制
func showArr() {
	arr := [...]string{"Go语言极简一本通", "Go语言微服务架构核心22讲", "从0到Go语言微服务架构师"}
	for index, value := range arr {
		fmt.Printf("arr[%d]=%sn", index, value)
	}

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

6.1.4 数组是值类型

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

代码语言:go复制
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 类型元素的切片。

代码语言:go复制
// 声明整型切片
var numList []int

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

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

代码语言:go复制
numList := make([]int, 3, 5)

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

代码语言:go复制
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 的长度和容量。

代码语言:go复制
	s := make([]string, 3, 5)
	fmt.Println(len(s)) // 3
	fmt.Println(cap(s)) // 5

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

代码语言:go复制
    s := make([]int, 3, 5)
	fmt.Println(s[10]) //panic: runtime error: index out of range [10] with length 3

Tips:

  • 由于 slice 是引用类型,所以你不对它进行赋值的话,它的默认值是 nil
代码语言:go复制
  var numList []int
  fmt.Println(numList == nil) // true
  • 切片之间不能比较,因此我们不能使用 == 操作符来判断两个 slice 是否含有全部相等元素。特别注意,如果你需要测试一个 slice 是否是空的,使用 len(s) == 0 来判断,而不应该用 s == nil 来判断。

6.2.3 切片元素的修改

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

代码语言:go复制
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 的个数是可变的。这些类型的函数被称为可变函数。

代码语言:go复制
  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 多维切片

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

代码语言:go复制
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)

代码语言:go复制
// 创建一个键类型为 string 值类型为 int 名为 scores 的 map
scores := make(map[string]int)
steps := make(map[string]string)

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

代码语言:go复制
var steps2 map[string]string = map[string]string{
		"第一步": "Go语言极简一本通",
		"第二步": "Go语言微服务架构师核心22讲",
		"第三步": "从0到Go语言微服务架构师",
}
fmt.Println(steps2)

或者

代码语言:go复制
steps3 := map[string]string{
		"第一步": "Go语言极简一本通",
		"第二步": "Go语言微服务架构师核心22讲",
		"第三步": "从0到Go语言微服务架构师",
}
fmt.Println(steps3)

6.3.2 Map 操作

  • 添加元素
代码语言:go复制
  // 可以使用 `map[key] = value` 向 map 添加元素。
  steps3["第四步"] = "总监"
  • 更新元素
代码语言:go复制

// 若 key 已存在,使用 mapkey = value 可以直接更新对应 key 的 value 值。

steps3"第四步" = "CTO"

代码语言:txt复制
  • 获取元素
代码语言:txt复制
  // 直接使用 map[key] 即可获取对应 key 的 value 值,如果 key不存在,会返回其 value 类型的零值。
  fmt.Println(steps3["第四步"] )
  • 删除元素
代码语言:txt复制
  //使用 delete(map, key)可以删除 map 中的对应 key 键值对,如果 key 不存在,delete 函数会静默处理,不会报错。
  delete(steps3, "第四步")
  • 判断 key 是否存在
代码语言:txt复制
  // 如果我们想知道 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
代码语言:txt复制
  // 遍历 map 中所有的元素需要用 for range 循环。
  for key, value := range steps3 {
      fmt.Printf("key: %s, value: %dn", key, value)
  }
  • 获取 map 长度
代码语言:txt复制
  // 使用 len 函数可以获取 map 长度
  func createMap() {
    	//...
       fmt.Println(len(steps3))    // 4
  }

6.3.3 map 是引用类型

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

代码语言:go复制
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 作为函数参数传递时也会发生同样的情况。

0 人点赞