一、数组类型
1. 数组简介和定义
数组是同一种数据类型元素的集合。 在 Go 语言中,数组从声明时长度就已确定且长度不能修改。声明一个数组的基本语法如下:
var 数组变量名 [元素数量]元素数据类型
需要注意的是长度是数组类型的一部分,因此即便两个数组中存储的元素类型是一样的,但是两个数组的长度不一样,那么这两个数组是两种类型的数据。
package main
import "fmt"
func main() {
var a [3]int
var b [4] int
fmt.Printf("a的数据类型:%T\n", a) // a的数据类型:[3]int
fmt.Printf("b的数据类型:%T\n", b) // b的数据类型:[4]int
}
2. 数组的初始化
数组类型是值类型,定义时就会被自动分配内存空间(即有长度),且对于每一个元素,如果没有进行初始化,默认使用元素数据类型的0值作为默认值。
package main
import "fmt"
func main() {
var a [3]int
var s [3]bool
fmt.Printf("a的类型:%T,a的值:%v\n", a, a) // a的类型:[3]int,a的值:[0 0 0]
fmt.Printf("s的类型:%T,s的值:%v\n", s, s) // s的类型:[3]bool,s的值:[false false false]
}
可以通过以下方式来实现数组的初始化:
2.1 初始化列表
使用初始化列表设置数组元素的值时,指定的值的个数不能大于数组的长度。如果指定的值的个数小于数组的长度,默认用元素数据类型对应的0值进行填充。
package main
import "fmt"
func main() {
var numArray1 = [3]int{1, 2, 3} // 使用指定的初始值完成初始化
var numArray2 = [3]int{1, 2} // 指定的值的个数与长度不匹配时,默认用该类型的0值填充
//var numArray3 = [3]int{1, 2, 3, 4} // 指定的值的个数不能大于数组的长度
var stringArray = [3]string{"cdc", "tt", "tr"}
fmt.Printf("numArray1:%v\n", numArray1) // numArray1:[1 2 3]
fmt.Printf("numArray2:%v\n", numArray2) // numArray2:[1 2 0]
fmt.Printf("stringArray:%v\n", stringArray) // stringArray:[cdc tt tr]
}
2.2 自行推断长度
使用初始化列表设置数组元素的值时,每次都要确保提供的初始值和数组长度一致,一般情况下我们可以让编译器根据初始值的个数自行推断数组的长度。
package main
import "fmt"
func main() {
var numArray = [...]int{1, 2}
var cityArray = [...]string{"北京", "上海", "深圳"}
fmt.Printf("numArray的类型:%T numArray的值:%v\n", numArray, numArray) // numArray的类型:[2]int numArray的值:[1 2]
fmt.Printf("cityArray的类型:%T cityArray的值:%v\n", cityArray, cityArray) // cityArray的类型:[3]string cityArray的值:[北京 上海 深圳]
}
2.3 使用索引
我们还可以使用指定索引值的方式来初始化数组。数组的长度为指定的最大的索引值+1,对于未指定值的索引,默认使用元素数据类型对应的0值填充。
package main
import "fmt"
func main() {
data := [...]bool{0: true, 5: true}
fmt.Printf("data的类型:%T data的值:%v\n", data, data) // data的类型:[6]bool data的值:[true false false false false true]
}
3. 访问数组
3.1 访问某个元素
可以通过下标(索引)获取数组的某个值或者修改某个值。
package main
import "fmt"
func main() {
num := [4]int{1, 2, 3, 4}
// 通过索引取值
fmt.Println(num[2]) // 3
// 通过索引修改值
num[0] = 100
fmt.Println(num) // [100 2 3 4]
}
3.2 数组切片
数组支持通过切片操作访问部分元素。切片操作需要传入两个索引值,一个是开始切片的位置,一个是停止切片的位置,开始位置索引要小于结束位置。需要注意的是,使用切片操作后,得到的不再是数组类型数据,而是一个切片类型的数据。
package main
import "fmt"
func main() {
var numArray = [10]int{1, 2, 3, 4, 5, 100, 200, 300, 400, 500}
ret := numArray[2:5]
fmt.Println(ret)
fmt.Printf("ret的类型:%T\n", ret) // ret的类型:[]int
//ret = numArray[5:3]
//fmt.Println(numArray) // .\main.go:156:16: invalid slice index: 5 > 3
}
3.3 遍历数组
- 通过长度遍历
package main
import "fmt"
func main() {
num := [4]int{1, 2, 3, 4}
for i := 0; i < len(num); i++ {
fmt.Printf("索引值:%v,对应的元素值:%v\n", i, num[i])
}
}
- 使用
for + range
遍历
package main
import "fmt"
func main() {
num := [4]int{100, 200, 300, 400}
// 使用两个变量接收时,即获取索引,又获取值
for index, value := range num {
fmt.Printf("索引值:%v,对应的元素值:%v\n", index, value)
}
// 只用一个变量接收时,只能获取到索引值
for index := range num {
fmt.Printf("索引值:%v\n", index)
}
// 只获取值
for _, value := range num {
fmt.Printf("元素值:%v\n", value)
}
}
4. 多维数组
数组中的元素也是数组,这种数组的嵌套类型称为多维数组。以二维数组为例:
package main
import "fmt"
func main() {
cityArray := [2][3]string{
{"南京", "上海", "北京"},
{"深圳", "长沙", "重庆"}, // 多维数组使用列表初始化时,最后一行也必须加上逗号
}
// cityArray的类型:[2][3]string cityArray的值:[[南京 上海 北京] [深圳 长沙 重庆]]
fmt.Printf("cityArray的类型:%T cityArray的值:%v\n", cityArray, cityArray)
// 通过索引取值
fmt.Println(cityArray[0][0]) // 南京
fmt.Println(cityArray[0][1]) // 上海
fmt.Println(cityArray[0][2]) // 北京
}
初始化多维数组时,也支持自动推断长度,但是只有第一层数组可以使用该方式:
package main
import "fmt"
func main() {
// 错误示例 编译报错:use of [...] array outside of array literal
//cityArray := [...][...]string{
// {"南京", "上海", "北京"},
// {"深圳", "长沙", "重庆"}, // 多维数组使用列表初始化时,最后一行也必须加上逗号
//}
//
//fmt.Println(cityArray)
// 正确示例
cityArray := [...][3]string{
{"南京", "上海", "北京"},
{"深圳", "长沙", "重庆"}, // 多维数组使用列表初始化时,最后一行也必须加上逗号
}
fmt.Println(cityArray) // [[南京 上海 北京] [深圳 长沙 重庆]]
}
遍历多维数组时,也要使用多层for循环嵌套遍历:
package main
import "fmt"
func main() {
cityArray := [...][3]string{
{"南京", "上海", "北京"},
{"深圳", "长沙", "重庆"}, // 多维数组使用列表初始化时,最后一行也必须加上逗号
}
// 根据长度进行遍历
for i := 0; i < len(cityArray); i++ {
inner := cityArray[i] // 针对最外层的数组进行遍历,得到的第二层的每一个数组
fmt.Printf("内层数组:%v\n", inner)
// 针对第二层的每个数组进行遍历,得到的是每个元素的值
for j := 0; j < len(inner); j++ {
fmt.Printf("城市信息:%v\n", inner[j])
}
}
// 使用 for+range 进行遍历
for _, inner := range cityArray {
fmt.Printf("内层数组:%v\n", inner)
for _, city := range inner {
fmt.Printf("城市信息:%v\n", city)
}
}
}
二、切片类型
1. 切片类型的引入
由于数组的长度是固定的,因此在使用数组时会带来一些限制。如果我们申请的长度太大会浪费内存,申请的太小又不够用,且数组无法动态的改变长度:
package main
import "fmt"
func main() {
// 数组最多能存储3个元素,但是循环填充了5个值,会报错
numArray := [3]int{}
for i := 0; i <= 5; i++ {
numArray[i] = i
}
// panic: runtime error: index out of range [3] with length 3
}
因此,Go 语言中会使用切片类型来解决上述问题。
2. 切片类型介绍
2.1 切片类型声明
切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。切片是一个引用类型,它的内部结构包含地址、长度和容量,可以通过使用内置的len()
函数求长度,使用内置的cap()
函数求切片的容量。切片一般用于快速地操作一块数据集合。
切片类型的基本声明语法如下:
package main
import "fmt"
func main() {
var slice1 []int
fmt.Printf("slice1的类型:%T 长度:%v 容量:%v\n", slice1, len(slice1), cap(slice1)) // slice1的类型:[]int 长度:0 容量:0
}
切片是引用类型,即声明过的切片如果不进行初始化,是不会被分配内存的:
package main
import "fmt"
func main() {
var slice1 []int
if slice1 == nil {
fmt.Println("切片未分配内存,是空的") // 会走到这个判断
}else {
fmt.Println("切片已分配内存")
}
}
所以要看一个切片是否有值,不能通过切片是否为空来判断,应该判断切片的长度是否为0
3. 切片的初始化
3.1 直接初始化
package main
import "fmt"
func main() {
var numSlice1 = []int{1, 2, 3, 4}
fmt.Printf("numSlice1的类型:%T 长度:%v 容量:%v 值:%v\n", numSlice1, len(numSlice1), cap(numSlice1), numSlice1) // numSlice1的类型:[]int 长度:4 容量:4 值:[1 2 3 4]
}
3.2 通过数组切片初始化
package main
import "fmt"
func main() {
var numArray = [10]int{1, 2, 3, 4, 5, 100, 200, 300, 400, 500}
var numSlice2 = numArray[:] // 获取全部
var numSlice3 = numArray[3:5] // 获取部分
fmt.Printf("numSlice2的类型:%T 长度:%v 容量:%v 值:%v\n", numSlice2, len(numSlice2), cap(numSlice2), numSlice2) // numSlice2的类型:[]int 长度:10 容量:10 值:[1 2 3 4 5 100 200 300 400 500]
fmt.Printf("numSlice3的类型:%T 长度:%v 容量:%v 值:%v\n", numSlice3, len(numSlice3), cap(numSlice3), numSlice3) // numSlice3的类型:[]int 长度:2 容量:7 值:[4 5]
}
3.3 通过 make 函数初始化
package main
import "fmt"
func main() {
numSlice4 := make([]int, 5, 10)
fmt.Printf("numSlice4的类型:%T 长度:%v 容量:%v 值:%v\n", numSlice4, len(numSlice4), cap(numSlice4), numSlice4) // numSlice4的类型:[]string 长度:5 容量:10 值:[0 0 0 0 0]
}
4. 切片的原理
4.1 切片本质
切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。可以通过一个例子来探究一下切片操作的底层实现原理。
示例1,有一个长度为10,元素类型为整型的数组 a := [int]{1,2,3,4,5,6,7,8,9,10}
,现在对其进行切片操作得到一个切片类型 b := a[2:6]
,其底层其实是先创建一个切片 b,切片的起始位置指针指向了数组 a 的索引为 2 的元素,切片的结束位置指针指向了数组 a 的索引为 5 的元素,整个切片的长度为 结束位置索引 - 开始位置索引
,即长度为4;切片的容量为 底层数组长度 - 切片开始位置索引
,即容量为8。
我们通过代码来验证一下:
package main
import "fmt"
func main() {
array1 := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice1 := array1[2:6]
fmt.Printf("slice1的类型:%T\n", slice1) // slice1的类型:[]int
fmt.Printf("slice1的值:%v\n", slice1) // slice1的值:[3 4 5 6]
fmt.Printf("slice1的长度:%d\n", len(slice1)) // slice1的长度:4
fmt.Printf("slice1的容量:%d\n", cap(slice1)) // slice1的容量:8
}
底层原理的图示如下:
4.2 切片表达式
切片表达式从字符串、数组、指向数组或切片的指针构造子字符串或切片。它有以下两种变体:
4.2.1 简单切片表达式
简单切片表达式指定了 low 和 high 两个索引界限值(左包含,右不包含),得到的切片长度为 high - low
,容量为 底层数组的长度 - low
。
package main
import "fmt"
func main() {
a := [5]int{1, 2, 3, 4, 5}
s := a[1:3] // s := a[low:high]
fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s)) // s:[2 3] len(s):2 cap(s):4
}
为了方便起见,可以省略切片表达式中的任何索引。省略了 low
则默认为0,省略了 high
则默认为切片操作数的长度:
a[2:] // 等同于 a[2:len(a)]
a[:3] // 等同于 a[0:3]
a[:] // 等同于 a[0:len(a)]
对于数组或字符串,使用简单切片表达式时,取值必须满足:0 <= low <= high <= len(a)
;对于切片而言,对切片再次进行切片时,索引上限并不是原切片的长度,而是原切片的容量。举个例子:
package main
import "fmt"
func main() {
array1 := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice1 := array1[2:6]
fmt.Printf("slice1的值:%v 长度:%v 容量:%v\n", slice1, len(slice1), cap(slice1)) // slice1的值:[3 4 5 6] 长度:4 容量:8
slice2 := slice1[2:6]
fmt.Printf("slice2的值:%v 长度:%v 容量:%v\n", slice2, len(slice2), cap(slice2)) // slice2的值:[5 6 7 8] 长度:4 容量:6
}
针对于切片 slice1
,值为 [3 4 5 6]
,再次对其切片操作取 [2:6]
时,按理已经超过 slice1
自身的长度,应该报越界错误,但是实际上 slice2
取值时并不是根据 slice1
的长度对应的底层数组元素([3 4 5 6])取值,而是根据 slice1
的容量对应的底层数组元素([3 4 5 6 7 8 9 10])取值。
4.2.2 完整切片表达式
相比简单切片表达式,完整切片表达式多了一个参数 max
,用于规定切片操作得到的切片的容量。对于数组,指向数组的指针或切片支持完整切片表达式,字符串类型不支持。设置了 max
以后,切片的容量为 max-low
package main
import "fmt"
func main() {
array1 := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice1 := array1[2:6:8]
fmt.Printf("slice1的值:%v 长度:%v 容量:%v\n", slice1, len(slice1), cap(slice1)) // slice1的值:[3 4 5 6] 长度:4 容量:6
slice2 := array1[2:6:10]
fmt.Printf("slice2的值:%v 长度:%v 容量:%v\n", slice2, len(slice2), cap(slice2)) // slice2的值:[3 4 5 6] 长度:4 容量:8
}
完整切片表达式需要满足的条件是 0 <= low <= high <= max <= cap(a)
,其他条件和简单切片表达式相同。
5. 切片常用方法
5.1 获取切片元素
- 通过索引获取单个元素
package main
import "fmt"
func main() {
s1 := []string{"AAA", "BBB", "CCC", "DDD", "EEE", "FFF"}
fmt.Println(s1[0]) // AAA
fmt.Println(s1[2]) // CCC
}
- 通过切片操作获取部分元素
package main
import "fmt"
func main() {
s1 := []string{"AAA", "BBB", "CCC", "DDD", "EEE", "FFF"}
fmt.Println(s1[2:5]) // [CCC DDD EEE]
}
- 遍历切片
package main
import "fmt"
func main() {
s1 := []string{"AAA", "BBB", "CCC", "DDD", "EEE", "FFF"}
// 根据切片长度遍历
for i := 0; i < len(s1); i++ {
fmt.Println(s1[i])
}
// for + range 遍历
// 使用一个变量接收时,只获取索引值
for index := range s1 {
fmt.Printf("索引值:%v\n", index)
}
// 使用两个变量接收时,获取索引值和元素值
for index, value := range s1 {
fmt.Printf("索引值:%v 元素值:%v\n", index, value)
}
// 使用匿名遍历实现只获取元素值
for _, value := range s1 {
fmt.Printf("元素值:%v\n", value)
}
}
5.2 切片新增元素
5.2.1 append 方法的使用
append
方法用于给切片的末尾添加新的元素,并返回一个新的切片。append
支持以下几种操作:
- 新增单个元素
package main
import "fmt"
func main() {
// 声明并初始化一个切片
s1 := []string{"AAA", "BBB"}
fmt.Printf("s1的地址:%p s1的值:%v\n", s1, s1) // s1的地址:0xc0000443c0 s1的值:[AAA BBB]
s2 := append(s1, "CCC")
fmt.Printf("s2的地址:%p s2的值:%v\n", s2, s2) // s2的地址:0xc000020080 s2的值:[AAA BBB CCC]
}
- 新增多个元素
package main
import "fmt"
func main() {
// 声明并初始化一个切片
s1 := []string{"AAA", "BBB"}
fmt.Printf("s1的地址:%p s1的值:%v\n", s1, s1) // s1的地址:0xc0000443c0 s1的值:[AAA BBB]
s3 := append(s1, "CCC", "DDD", "EEE")
fmt.Printf("s3的地址:%p s3的值:%v\n", s3, s3) // s3的地址:0xc000050050 s3的值:[AAA BBB CCC DDD EEE]
}
- 新增另一个切片,使用
...
将切片元素打散
package main
import "fmt"
func main() {
// 声明并初始化一个切片
s1 := []string{"AAA", "BBB"}
fmt.Printf("s1的地址:%p s1的值:%v\n", s1, s1) // s1的地址:0xc0000443c0 s1的值:[AAA BBB]
strSlice := []string{"CCC", "DDD", "EEE"}
s4 := append(s1, strSlice...)
fmt.Printf("s4的地址:%p s4的值:%v\n", s4, s4) // s4的地址:0xc0000500a0 s4的值:[AAA BBB CCC DDD EEE]
}
5.2.2 使用 append 省略切片初始化
由于切片是引用类型,只声明不进行初始化不会被分配内存,因此无法直接赋值:
var slice1 []int
slice1[0] = 100 // panic: runtime error: index out of range [0] with length 0
由于 append
操作会返回一个新的初始化后的切片,因此通过var声明的零值切片可以在 append()
函数直接使用,无需初始化。
package main
import "fmt"
func main() {
var slice1 []int
slice1 := append(slice1, 1, 2, 3, 4, 5)
fmt.Println(slice1) // [1 2 3 4 5]
}
5.2.3 切片扩容策略
每个切片会指向一个底层数组,这个数组的长度够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行扩容,此时该切片指向的底层数组就会更换。扩容操作往往发生在 append()
函数调用,所以我们通常都需要用原变量接收 append 函数的返回值。
package main
import "fmt"
func main() {
var s1 []int
for i := 1; i <= 10; i++ {
s1 = append(s1, i)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", s1, len(s1), cap(s1), s1)
}
}
运行结果:
[1] len:1 cap:1 ptr:0xc00000c0c8
[1 2] len:2 cap:2 ptr:0xc00000c120
[1 2 3] len:3 cap:4 ptr:0xc0000101a0
[1 2 3 4] len:4 cap:4 ptr:0xc0000101a0
[1 2 3 4 5] len:5 cap:8 ptr:0xc000016200
[1 2 3 4 5 6] len:6 cap:8 ptr:0xc000016200
[1 2 3 4 5 6 7] len:7 cap:8 ptr:0xc000016200
[1 2 3 4 5 6 7 8] len:8 cap:8 ptr:0xc000016200
[1 2 3 4 5 6 7 8 9] len:9 cap:16 ptr:0xc00007e000
[1 2 3 4 5 6 7 8 9 10] len:10 cap:16 ptr:0xc00007e000
切片的扩容策略如下:
- 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。
- 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),
- 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
- 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。
需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如int
和string
类型的处理方式就不一样。
5.3 切片拷贝
由于切片是引用类型,所以通过赋值操作得到的新切片实际上和老切片指向的是同一块内存地址,对新切片中的元素进行修改也会影响老切片:
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3, 4, 5}
fmt.Printf("s1的值:%v\n", s1) // s1的值:[1 2 3 4 5]
s2 := s1
s2[0] = 100
fmt.Printf("s2的值:%v\n", s2) // s2的值:[100 2 3 4 5]
fmt.Printf("s1的值:%v\n", s1) // s1的值:[100 2 3 4 5]
}
如果想修改新切片但是不影响原切片,可以使用 copy
方法拷贝得到新切片而不是使用赋值方式:
package main
import "fmt"
func sliceHandle4() {
s1 := []int{1, 2, 3, 4, 5}
fmt.Printf("s1的值:%v\n", s1) // s1的值:[1 2 3 4 5]
s3 := make([]int, 5, 5)
copy(s3, s1)
s3[0] = 100
fmt.Printf("s3的值:%v\n", s3) // s3的值:[100 2 3 4 5]
fmt.Printf("s1的值:%v\n", s1) // s1的值:[1 2 3 4 5]
}
5.4 切片删除元素
切片操作中没有现成的删除方法,可以通过 append + 切片
操作来实现:
package main
import "fmt"
func main(){
s1 := []int{1, 2, 3, 4, 5}
// 要删除索引为2的元素
s1 = append(s1[:2], s1[3:]...)
fmt.Println(s1) // [1 2 4 5]
}
三、map类型
1. map类型简介和定义
Go 语言中提供的映射关系容器为 map
,其内部使用散列表(hash)实现。map
是一种无序的基于 key-value
的数据结构,Go 语言中的 map
是引用类型,必须初始化才能使用。
声明一个 map
类型变量的基本语法如下:
// var 变量名 map[key的类型]value的类型
var m map[string]string
2. map类型初始化
- 通过值填充进行初始化,使用该方式时注意每个键值对后面都要加逗号
package main
import "fmt"
func main() {
m1 := map[string]string{
"name": "cdc",
"city": "南京",
"address": "江宁区",
}
fmt.Printf("%#v\n", m1) // map[string]string{"address":"江宁区", "city":"南京", "name":"cdc"}
}
- 通过 make 方法初始化,使用该方式初始化时可以指定 map 的长度(长度非必填)
package main
import "fmt"
func main() {
m2 := make(map[string]int, 8)
m2["cdc"] = 100
m2["ctt"] = 100
m2["cee"] = 100
m2["ccc"] = 100
fmt.Printf("%#v\n", m2) // map[string]int{"ccc":100, "cdc":100, "cee":100, "ctt":100}
}
3. map类型常用方法
3.1 通过键名操作值
可以通过键名获取或者修改对应的值
package main
import "fmt"
func main(){
userInfo := map[string]string{
"name": "cdc",
"city": "南京",
"address": "江宁区",
}
// 通过键名获取值
city := userInfo["city"]
fmt.Println(city) // 南京
// 通过键名修改值
userInfo["address"] = "雨花台"
fmt.Println(userInfo) // map[address:雨花台 city:南京 name:cdc]
}
3.2 判断键是否存在
package main
import "fmt"
func main() {
userInfo := map[string]string{
"name": "cdc",
"city": "南京",
"address": "江宁区",
}
// 如果键存在,ok为true,v为对应的值;不存在,ok为false,v为值类型的零值
v, ok := userInfo["name"]
fmt.Printf("v的值:%v ok的值:%v\n", v, ok) // v的值:cdc ok的值:true
v1, ok1 := userInfo["age"]
fmt.Printf("v1的值:%v ok1的值:%v\n", v1, ok1) // v1的值: ok1的值:false
}
3.3 遍历map类型
Go 语言中可以使用 for + range
对 map 进行遍历,主要分为以下几种情况:
- 只用一个变量接收时,只获取键名
package main
import "fmt"
func main() {
userInfo := map[string]string{
"name": "cdc",
"city": "南京",
"address": "江宁区",
}
for key := range userInfo {
fmt.Println(key)
}
}
/*
执行结果:
name
city
address
*/
- 使用两个变量接收时,能获取到键名和对应的值
package main
import "fmt"
func main() {
userInfo := map[string]string{
"name": "cdc",
"city": "南京",
"address": "江宁区",
}
for key, value := range userInfo {
fmt.Printf("key: %v value: %v\n", key, value)
}
}
/*
执行结果:
key: name value: cdc
key: city value: 南京
key: address value: 江宁区
*/
- 只想遍历值时,可以使用匿名遍历接收键
package main
import "fmt"
func main() {
userInfo := map[string]string{
"name": "cdc",
"city": "南京",
"address": "江宁区",
}
for _, value := range userInfo {
fmt.Println(value)
}
}
/*
执行结果:
cdc
南京
江宁区
*/
3.4 删除键值对
可以使用 delete()
内置函数从 map 中删除一组键值对
package main
import "fmt"
func main() {
userInfo := map[string]string{
"name": "cdc",
"city": "南京",
"address": "江宁区",
}
delete(userInfo, "name")
delete(userInfo, "age") // 如果删除的键不存在也不会引发错误
fmt.Printf("%#v\n", userInfo) // map[string]string{"address":"江宁区", "city":"南京"}
}
3.5 和其他数据类型组合使用
- 元素为map的切片
package main
import "fmt"
func main() {
// 这里只是对外层的切片进行了初始化
var mapSlice = make([]map[string]string, 3)
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
}
// 需要对切片中的map元素进行初始化
mapSlice[0] = make(map[string]string, 10)
mapSlice[0]["name"] = "cdc"
mapSlice[0]["city"] = "南京"
mapSlice[0]["address"] = "江宁区"
mapSlice[1] = make(map[string]string, 10)
mapSlice[1]["name"] = "tr"
mapSlice[1]["city"] = "南京"
mapSlice[1]["address"] = "雨花台"
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
}
}
- 值为切片类型的map
package main
import "fmt"
func main() {
// 这里只对外层的map进行了初始化
var sliceMap = make(map[string][]string, 3)
fmt.Println(sliceMap) // map[]
key := "name"
value, ok := sliceMap[key]
if !ok {
// 对键对应的值类型进行初始化,即对切片进行初始化
value = make([]string, 0, 2)
}
value = append(value, "cdc", "tr")
sliceMap[key] = value
fmt.Printf("%#v\n", sliceMap) // map[string][]string{"name":[]string{"cdc", "tr"}}
}
4. 使用示例
- 示例1:按学生序号输出其对应的分数
package main
import (
"fmt"
"math/rand"
"time"
"sort"
)
func main() {
rand.Seed(time.Now().UnixNano())
scoreMap := make(map[string]int, 100)
for i := 1; i < 100; i++ {
key := fmt.Sprintf("stu%03d", i) //生成stu开头的字符串
value := rand.Intn(100) // //生成0~99的随机整数
scoreMap[key] = value
}
fmt.Println(scoreMap)
// 将学号取出并存放到切片中
stuSlice := make([]string, 0, 100)
for key := range scoreMap {
stuSlice = append(stuSlice, key)
}
fmt.Println(stuSlice)
// 对切片进行排序
sort.Strings(stuSlice)
fmt.Println(stuSlice)
// 按照排序后的顺序进行输出
for _, stu := range stuSlice {
fmt.Printf("学号:%v 分数:%v\n", stu, scoreMap[stu])
}
}
- 示例2:统计一个字符串中每个字母出现的次数(除空格)
package main
import "fmt"
func main() {
str := "how do you do"
strMap := make(map[string]int)
for _, v := range str {
key := string(v)
if key != " " {
_, ok := strMap[key]
if !ok {
strMap[key] = 0
}
strMap[key] += 1
}
}
fmt.Printf("%#v\n", strMap)
}