[TOC]
复合数据类型之字典Map
一 Map类型介绍
Map类型又可以称之为字典类型,是一种无序的、基于key:value的数据结构
定义语法如下
var 变量名 map[Key的类型]Value的类型
需要注意的是:
1、map中所有的Key都必须是相同的类型,所有的value也必须都是相同的类型,但是key与value之间可以是不同的类型
2、关于key的类型:Key必须是可以与非nil值进行相等性判断的类型(所有的值类型都支持相等性判断,所有的引用类型只能与nil值判断),因此我们可以通过与key的相等性判断来判定key是否存在。虽然浮点类型也支持与非nil值进行相等性运算,但是不建议用浮点型作为Key,因为浮点数存在非数NaN,而NaN与任何浮点数都不相等,这便失去了意义。
3、关于value的类型:可以是任意类型。
二 声明与初始化
因为map类型也属于复合类型中的引用类型,所以
(1)直接声明一个map类型,零值为nil(引用类型的零值都为nil),nil代表空、没有分配内存。
var m map[string]int
fmt.Println(m) // map[]
fmt.Println(m==nil) // true
m["k1"]=111 // panic异常: 因为没有开辟内存空间
(2)初始化切片类型的方式有两种(与切片的初始化都一样)
方式一:先调用make()函开辟空间,然后再塞值
var m = make(map[string]int)
fmt.Println(m) // map[]
fmt.Println(m==nil) // false
m["k1"]=111
fmt.Println(m) // map[k1:111]
方式二:一步到位式,即赋一个完整的值,该方式等同于make完毕之后再塞值,如下所示
scoreMap:=map[string]int{
"egon":99,
"铁蛋":100,
"铜蛋":88,
}
// 上述代码等同于
scoreMap := make(map[string]int)
scoreMap["egon"] = 99
scoreMap["铁蛋"] = 100
scoreMap["铜蛋"] = 88
fmt.Println(scoreMap["egon"]) // 99
延伸:我们可以创建一个空的map:表达式是map[string]int{}
var scoreMap = map[string]int{}
fmt.Println(scoreMap == nil) // false
scoreMap["egon"] = 99
fmt.Println(scoreMap["egon"]) // 99
三 基本操作
3.1 增删改查
var m = make(map[string]string)
// 增加
m["name"]="egon"
m["age"]="18"
m["gender"]="male"
fmt.Println(m) // map[age:18 gender:male name:egon]
了解:当新增元素时,map也会随着元素数量的增长而重新分配更大的内存空间,即Go中map实现中元素的地址是变化的,
// 删除
delete(m,"gender")
fmt.Println(m) // map[age:18 name:egon]
delete(m,"kkk") // 即使key不在map中也没有关系
// 改
m["name"]="Egon"
fmt.Println(m)
// 查
fmt.Println(m["name"]) // Egon
fmt.Println(m["gender"]) // 若key不存在则返回value对应类型的零值
// ps:针对查操作,因为当key不存在时返回的是value对应类型的零值,所以我们可以这么做
var ages = make(map[string]int)
ages["egon"] += 1 // ages["egon"] = ages["egon"] + 1
fmt.Println(ages) // 1
或者
ages["egon"]++
fmt.Println(ages) // 2
3.2 判断key是否存在于map中
针对操作m[key],当key不存在时,返回的是value对应类型的零值,而极有可能出现key存在,但该key对应的值与其零值是相等的,如下所示
var ages = make(map[string]int)
ages["张三"]=0
fmt.Println(ages["张三"]) // 键"张三"存在,取到值为0
fmt.Println(ages["egon"]) // 键"egon"不存在,取到值对应类型的零值同样为0
所以我们无法通过m[key]取到的值作为判断依据来判定key是否存在,需要这么做
age1,ok:=ages["张三"]
fmt.Println(age1,ok) // 0 true,true代表key存在,0就是取到的值
age2,ok:=ages["egon"]
fmt.Println(age2,ok) // 0 false,false代表key不存在,0是值对应类型的零值
if ok {
fmt.Println(age2)
} else {
fmt.Println("查无此人")
}
3.3 二元运算
(1)map与slice一样,同属于复合类型中的引用类型,复合类型中的引用类型不支持直接进行相等性判断,只能与nil直接进行相等性判断,
若要判断两个map是否包含相同的key和value,我们需要自己循环取出map中的key和value逐一进行比较,如下所示
func equal(x, y map[string]int) bool {
if len(x) != len(y) {
return false // 两个map类型的长度不一样直接返回false,代表不相等
}
for k, xv := range x {
if yv, ok := y[k]; !ok || yv != xv {
return false // // 两个map类型但凡有一组对应key与value不相等就返回false,代表不相等
}
}
return true // 如果能够执行该行代码,返回true,则代表两个map是相等的,即所有key与value都一样
}
注意注意注意:我们不应该简单地用xv != y[k]判断
package main
import (
"fmt"
)
func equal(x, y map[string]int) bool {
if len(x) != len(y) {
return false
}
for k, xv := range x {
if xv != y[k] {
return false
}
}
return true
}
func main() {
res := equal(map[string]int{"A": 0}, map[string]int{"B": 0})
fmt.Println(res) // true
}
3.4 遍历map
(1)Go语言中使用for range
遍历map
var ages = make(map[string]int)
ages["egon"] = 18
ages["张三"] = 19
ages["李四"] = 20
// 1、循环读取k和v
for k,v := range ages {
fmt.Println(k,v)
}
// 2、循环只读取k,下述两种方式都可以
for k,_ := range ages {
fmt.Println(k,v)
}
for k := range ages {
fmt.Println(k)
}
// 3、循环只读取v
for _,v := range ages {
fmt.Println(v)
}
(2)遍历map时的元素顺序与添加键值对的顺序无关,若想按照指定顺序遍map,需要将map中的key存入切片,然后对切片进行排序后,依据切片中排序好的key依次取map中的value
var ages = make(map[string]int)
ages["egon"] = 18
ages["张三"] = 19
ages["李四"] = 20
ages["王五"] = 21
ages["牛佰"] = 22
// 1、无序遍历
for k, v := range ages {
fmt.Println(k, v)
}
// 2、有序遍历
var keys []string
for k := range ages {
keys = append(keys, k)
}
sort.Strings(keys) // Strings函数接收的参数是切片类型,这也是我们把age中的key放入切片类型中的原因
for _, k := range keys {
fmt.Printf("%s\t%d\n", k, ages[k])
}
// ps:
因为我们一开始就知道keys的最终大小,因此给切片keys分配一个合适的大小将会更有效,如下所示
keys := make([]string, 0, len(ages)) // 容量刚好可以放下map中全部的key:
3.5 go禁止对map元素取地址
对比同为复合类型中的切片来看,我们可以取切片元素的地址
numSlice:=[]int{111,222,333}
fmt.Println(&numSlice[0]) // 0xc00001e0c0
但是我们不能取map中value的地址
var m = make(map[string]int)
m["k1"]=111
fmt.Println(&m["k1"]) // 异常cannot take the address of m["k1"]
了解:map中的元素为何不可以被取地址
当我们根据key去取map类型中的元素值时。若key不存在则返回零值,而零值是常量,我们无法取常量的地址
四 在函数参数中传递
map在函数参数中的传递与切片一样都是属于“引用传递”。
内置的引用类型,如 slice,map,interface,channel,这些类型比较特殊,声明他们的时候,实际上是创建了一个 header, 对于他们也是直接定义值接收者类型的方法。这样,调用函数时,是直接 copy 了这些类型的 header,而 header 本身就是为复制设计的。
五 练习
统计一个字符串中单词出现的次数
var s="Love me, Love my dog"
// 1、定义一个map[string]int
var wordCount=make(map[string]int,10)
// 2、知道都有哪些单词
words:=strings.Split(s," ")
// 3、遍历单词做统计
for _,word:=range words{
wordCount[word]++
}
fmt.Println(wordCount) // map[Love:2 dog:1 me,:1 my:1]