5

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

 1 year ago
source link: https://blog.51cto.com/u_15878295/5885161
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

精选 原创

耐心和持久胜过激烈和狂热。

上一篇文章 ​ ​一文熟悉 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]
}

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

一文了解 Go 的复合数据类型(数组、切片 Slice、Map)_Go基础
  • ​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​​ 的底层原理进行详细介绍。

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK