19 复合数据类型之map

[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]
上一篇
下一篇
Copyright © 2022 Egon的技术星球 egonlin.com 版权所有 帮助IT小伙伴学到真正的技术