一文了解 Go 的复合数据类型(数组、切片 Slice、Map)

时间:2022-11-24 22:58:27

耐心和持久胜过激烈和*。

前言

上一篇文章 ​​一文熟悉 Go 的基础语法和基本数据类型 ​​,讲解了 Go 的基础语法和基本数据类型,本篇文章将对 Go 的复合数据类型(数组、切片 Slice、Map)进行介绍。

数组

数组是由特定元素组成的固定长度的序列,元素可以是Go 的原生类型(如整形、字符串型和浮点型等)和自定义类型。一个数组可以包含零个或多个元素。通过数组的下标索引可以高效访问和修改每个元素的值,索引从 0 开始,到数组长度 - 1 结束。

数组的创建方式

  • 第一种
import "fmt"

func main() {
var arr [5]int
fmt.Printf("%d, %d, %d, %d, %d\n", arr[0], arr[1], arr[2], arr[3], arr[4]) //0, 0, 0, 0, 0
arr[0] = 1
fmt.Println(arr[0]) // 1
}

通过隐式的方式初始化一个长度为 5 的 ​​int​​​ 类型数组,数组下标索引从 0 开始,上面输出的值为 ​​0, 0, 0, 0, 0​​​,如果初始化数组的时候,不带初始值,那么默认情况下,数组里的每个元素都会被初始化为对应数据类型的默认值,​​int​​​ 类型的默认值为 ​​0​​。通过下标索引可以直接访问元素的值和修改元素的值。

  • 第二种
import "fmt"

func main() {
var arr [5]int = [5]int{1}
var arr2 = [5]int{1, 2, 3, 4, 5}
fmt.Println(arr) // [1 0 0 0 0]
fmt.Println(arr2) // [1 2 3 4 5]
}

显式初始化数组时,可以使用数组字面值语法初始化一个元素或多个元素。

  • 第三种
import "fmt"

func main() {
var arr = [...]int{1, 2, 3, 4}
fmt.Println(arr) // [1 2 3 4]
fmt.Printf("%T\n", arr) // [4]int
}

初始化数组时,如果长度的位置出现 ​​...​​ 而不是数字,则表示数组的长度是根据初始值元素的个数去计算的。

  • 第四种
import "fmt"

func main() {
var arr = [...]int{5: 5}
fmt.Println(arr) // [0 0 0 0 0 5]
}

初始化数组时,通过 ​​index: value​​ 的形式对某个位置的元素进行初始化,其他位置的元素为默认值。

数组的遍历

  • 普通 for 循环
import "fmt"

func main() {
var arr = [5]int{1, 2, 3, 4, 5}
for i := 0; i < len(arr); i++ {
fmt.Printf("索引:%d, 值:%d\n", i, arr[i])
}
}

输出结果:

索引:0, 值:1
索引:1, 值:2
索引:2, 值:3
索引:3, 值:4
索引:4, 值:5
  • for-range 循环
import "fmt"

func main() {
var arr = [5]int{1, 2, 3, 4, 5}
for index, value := range arr {
fmt.Printf("索引:%d, 值:%d\n", index, value)
}
}

​index​​​ 为数组的下标索引,​​value​​ 为元素值。

输出结果:

索引:0, 值:1
索引:1, 值:2
索引:2, 值:3
索引:3, 值:4
索引:4, 值:5

Slice 切片

  • 切片和数组长得很像,但它们各有各的特点。由于数组的长度是固定的这个限制,在使用 Go 的过程中很少直接使用数组,而是使用切片 ​​slice​​,它是一个动态的序列,程序运行时可以对它动态添加元素。
  • 切片的数据结构如下所示
type slice struct {
array unsafe.Pointer
len int
cap int
}

我们可以看到,切片包含三个字段:

  • ​array​​: 指向底层数组的指针;
  • ​len​​: 切片的长度,即切片中当前元素的个数;
  • ​cap​​​: 底层数组的长度,也是切片的最大容量,​​cap​​​ 的值永远大于等于 ​​len​​ 的值。

切片的创建方式

  • 声明切片
import "fmt"

func main() {
var arr []int
fmt.Printf("长度:%d\n", len(arr))
fmt.Printf("容量:%d\n", cap(arr))
fmt.Println(arr)
}

以上的创建方式只是声明切片,并未初始化,​​arr​​​ 的值为 ​​nil​​。

  • 声明切片并初始化
import "fmt"

func main() {
var arr = []int{1, 2, 3, 4, 5}
fmt.Printf("长度:%d\n", len(arr)) // 5
fmt.Printf("容量:%d\n", cap(arr)) // 5
fmt.Println(arr) // [1 2 3 4 5]
}
  • 通过 ​​make​​ 函数来创建切片
import "fmt"

func main() {
/*
第一个参数 -> type 切片的类型
第二个参数 -> len 切片的长度
第三个参数 -> cap 切片的容量
*/
arr := make([]int, 2, 5)
fmt.Printf("长度:%d\n", len(arr)) // 2
fmt.Printf("容量:%d\n", cap(arr)) // 5
/*
第一个参数 -> type 切片的类型
第二个参数 -> len & cap 切片的长度和容量
*/
arr2 := make([]int, 5)
fmt.Printf("长度:%d\n", len(arr2)) // 5
fmt.Printf("容量:%d\n", cap(arr2)) // 5
}

通过 ​​make​​​ 函数创建切片时,使用 ​​make([]int, 2, 5)​​​ 的形式,指定了切片的长度为 2,容量为 5;如果使用 ​​make([]int, 5)​​ 这种形式,不指定容量,那么容量就等于切片的长度。

  • 基于存在的数组创建切片
import "fmt"

func main() {
arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
sl := arr[2:4]
fmt.Println(sl) // [3 4]
}

采用 ​​array[low : high]​​​ 语法基于一个已存在的数组创建切片,这种方式被称为数组的切片化。直接修改 ​​sl​​​ 的元素值会影响 ​​arr​​​ 的元素值,因为 ​​sl​​​ 的底层数组是指向 ​​arr​​ 的。

切片的遍历

  • 普通 for 循环
import "fmt"

func main() {
var arr = []int{1, 2, 3, 4, 5}
for i := 0; i < len(arr); i++ {
fmt.Printf("索引:%d, 值:%d\n", i, arr[i])
}
}

输出结果:

索引:0, 值:1
索引:1, 值:2
索引:2, 值:3
索引:3, 值:4
索引:4, 值:5
  • for-range 循环
import "fmt"

func main() {
var arr = []int{1, 2, 3, 4, 5}
for index, value := range arr {
fmt.Printf("索引:%d, 值:%d\n", index, value)
}
}

​index​​​ 为数组的下标索引,​​value​​ 为元素值。

输出结果:

索引:0, 值:1
索引:1, 值:2
索引:2, 值:3
索引:3, 值:4
索引:4, 值:5

向切片追加元素

使用 ​​append​​ 函数可以想切片追加元素

import "fmt"

func main() {
var arr = []string{"a", "b", "c", "d", "e"}
fmt.Println(arr) // [a b c d e]
arr = append(arr, "f")
fmt.Println(arr) // [a b c d e f]
}

追加的元素被放置在切片的尾部

Map

一文了解 Go 的复合数据类型(数组、切片 Slice、Map)

  • ​Map​​ 表示的是一组无序的键值对( ​​key → value​​​ ),在 Go 中的形式为 ​​map[key_type]value_type​​。
  • ​key​​​ 和 ​​value​​​ 可以是同一种类型 ​​map[int]int​​​,也可以不是同一种类型 ​​map[string]int​​。
  • ​map​​​ 中对 ​​value​​​ 的类型没有限制,但是对 ​​key​​​ 却有限制,想要作为 ​​map​​​ 的 ​​key​​,必须满足以下条件:
  • key 的类型必须支持 ​​==​​​ 和 ​​!=​​​ 比较操作符 例如 ​​​int​​​ 类型的 ​​a​​​ 和 ​​b​​​ 两个变量,是支持 ​​ a == b​​​ 和 ​​a != b​​​ 操作的,而 ​​Go​​​ 语言中 ​​Slice​​​、​​map​​​、​​function​​​ 复合类型,是不支持 ​​T == T​​​ 和 ​​T != T​​​操作的,只支持 ​​T == nil​​ 的判空操作。

Map 的创建方式

  • 错误的创建方式
func main() {
var m map[string]string
m["name"] = "chenmingyong"
}

只声明而未初始化,直接使用 ​​m​​​ 则会报错 ​​m​​​ 为 ​​nil​​。

  • 使用复合字面值初始化 ​​map​​ 类型变量
import "fmt"

func main() {
m := map[string]string{}
m["name"] = "chenmingyong"
fmt.Println(m["name"]) // chenmingyong
}
  • 使用复合字面值显式初始化 ​​map​​ 类型变量
import "fmt"

func main() {
m := map[string]string{
"name": "chenmingyong",
}
fmt.Println(m["name"]) // chenmingyong
}
  • 使用 ​​make​​​ 创建 ​​map​​ 类型变量
func main() {
m1 := make(map[string]string) // 不指定容量,默认会给一个初始值
m2 := make(map[string]string, 5) // 指定容量为 5
}

如果不指定 ​​map​​ 的容量,默认会给一个初始值。

Map 的基本操作

插入和修改

func main() {
m := make(map[string]string)
// 新增键值对
m["name"] = "chenmingyong"
fmt.Println(m["name"]) // chenmingyong
// 修改 value
m["name"] = "cmy"
fmt.Println(m["name"]) // cmy
}

通过 ​​m[key] = value​​​ 的形式对 ​​map​​ 进行插入和修改操作。

删除

import "fmt"

func main() {
m := make(map[string]string)
// 新增键值对
m["name"] = "chenmingyong"
fmt.Println(m["name"]) // chenmingyong
delete(m, "name")
fmt.Println(m["name"]) // ""
}

通过 ​​delete(map, key)​​​ 方法,对 ​​map​​ 里面的键值对进行删除。

查找操作

import "fmt"

func main() {
m := make(map[string]string)
m["name"] = "chenmingyong"
value, ok := m["name"]
fmt.Println(ok, value) // true chenmingyong
value2, ok2 := m["age"]
fmt.Println(ok2, value2) // false
}

使用 ​​comma ok​​​ 惯用法对 ​​map​​​ 进行键查找和键值读取操作,第一个变量接收 ​​value​​​ 的值,第二个变量用于判断 ​​key​​​ 是否存在,类型为 ​​bool​​​,若 ​​key​​​ 不存在,​​value​​​ 的值为对应 ​​key​​ 类型的默认值。

遍历操作

import "fmt"

func main() {
m := make(map[string]string)
m["name"] = "chenmingyong"
m["addr"] = "china"
for key, value := range m {
fmt.Println(key, value)
}
}

通过 ​​for-range​​​ 的方式遍历,​​map​​ 也仅仅支持这种方式的遍历。

删除操作

  • 1、通过遍历,逐个删除
import "fmt"

func main() {
m := make(map[string]string)
m["name"] = "chenmingyong"
m["addr"] = "china"
for key, _ := range m {
delete(m, key)
}
fmt.Println(len(m)) // 0
}
  • 2、将 ​​map​​​ 变量指向一个新的 ​​map​​​,旧的 ​​map​​​ 将会被 ​​gc​​ 回收
func main() {
m := make(map[string]string)
m["name"] = "chenmingyong"
m["addr"] = "china"

m = make(map[string]string)
fmt.Println(len(m)) // 0
}

小结

本文对数组、Slice 切片和 Map 的定义和相关操作进行了介绍,后续文章会对 ​​Slice​​​ 切片和 ​​Map​​ 的底层原理进行详细介绍。

如果本文对你有帮助,欢迎点赞收藏加关注,如果本文有错误的地方,欢迎指出!