一文了解 Go 的复合数据类型(数组、切片 Slice、Map)
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.
一文了解 Go 的复合数据类型(数组、切片 Slice、Map)
精选 原创耐心和持久胜过激烈和狂热。
上一篇文章 一文熟悉 Go 的基础语法和基本数据类型 ,讲解了 Go 的基础语法和基本数据类型,本篇文章将对 Go 的复合数据类型(数组、切片 Slice、Map)进行介绍。
数组是由特定元素组成的固定长度的序列,元素可以是Go 的原生类型(如整形、字符串型和浮点型等)和自定义类型。一个数组可以包含零个或多个元素。通过数组的下标索引可以高效访问和修改每个元素的值,索引从 0 开始,到数组长度 - 1 结束。
数组的创建方式
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
。通过下标索引可以直接访问元素的值和修改元素的值。
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]
}
显式初始化数组时,可以使用数组字面值语法初始化一个元素或多个元素。
func main() {
var arr = [...]int{1, 2, 3, 4}
fmt.Println(arr) // [1 2 3 4]
fmt.Printf("%T\n", arr) // [4]int
}
初始化数组时,如果长度的位置出现 ...
而不是数字,则表示数组的长度是根据初始值元素的个数去计算的。
func main() {
var arr = [...]int{5: 5}
fmt.Println(arr) // [0 0 0 0 0 5]
}
初始化数组时,通过 index: value
的形式对某个位置的元素进行初始化,其他位置的元素为默认值。
数组的遍历
- 普通 for 循环
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])
}
}
输出结果:
索引:1, 值:2
索引:2, 值:3
索引:3, 值:4
索引:4, 值:5
- for-range 循环
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
为元素值。
输出结果:
索引:1, 值:2
索引:2, 值:3
索引:3, 值:4
索引:4, 值:5
Slice 切片
- 切片和数组长得很像,但它们各有各的特点。由于数组的长度是固定的这个限制,在使用 Go 的过程中很少直接使用数组,而是使用切片
slice
,它是一个动态的序列,程序运行时可以对它动态添加元素。 - 切片的数据结构如下所示
array unsafe.Pointer
len int
cap int
}
我们可以看到,切片包含三个字段:
-
array
: 指向底层数组的指针; -
len
: 切片的长度,即切片中当前元素的个数; -
cap
: 底层数组的长度,也是切片的最大容量,cap
的值永远大于等于 len
的值。
切片的创建方式
func main() {
var arr []int
fmt.Printf("长度:%d\n", len(arr))
fmt.Printf("容量:%d\n", cap(arr))
fmt.Println(arr)
}
以上的创建方式只是声明切片,并未初始化,arr
的值为 nil
。
- 声明切片并初始化
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
函数来创建切片
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)
这种形式,不指定容量,那么容量就等于切片的长度。
- 基于存在的数组创建切片
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 循环
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])
}
}
输出结果:
索引:1, 值:2
索引:2, 值:3
索引:3, 值:4
索引:4, 值:5
- for-range 循环
func main() {
var arr = []int{1, 2, 3, 4, 5}
for index, value := range arr {
fmt.Printf("索引:%d, 值:%d\n", index, value)
}
}
index
为数组的下标索引,value
为元素值。
输出结果:
索引:1, 值:2
索引:2, 值:3
索引:3, 值:4
索引:4, 值:5
向切片追加元素
使用 append
函数可以想切片追加元素
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
表示的是一组无序的键值对( 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 的创建方式
- 错误的创建方式
var m map[string]string
m["name"] = "chenmingyong"
}
只声明而未初始化,直接使用 m
则会报错 m
为 nil
。
- 使用复合字面值初始化
map
类型变量
func main() {
m := map[string]string{}
m["name"] = "chenmingyong"
fmt.Println(m["name"]) // chenmingyong
}
- 使用复合字面值显式初始化
map
类型变量
func main() {
m := map[string]string{
"name": "chenmingyong",
}
fmt.Println(m["name"]) // chenmingyong
}
- 使用
make
创建 map
类型变量
m1 := make(map[string]string) // 不指定容量,默认会给一个初始值
m2 := make(map[string]string, 5) // 指定容量为 5
}
如果不指定 map
的容量,默认会给一个初始值。
Map 的基本操作
插入和修改
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
进行插入和修改操作。
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
里面的键值对进行删除。
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
类型的默认值。
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、通过遍历,逐个删除
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
回收
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
的底层原理进行详细介绍。
如果本文对你有帮助,欢迎点赞收藏加关注,如果本文有错误的地方,欢迎指出!
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK