第三章 内建容器 数组, 切片, map

时间:2022-07-25
本文章向大家介绍第三章 内建容器 数组, 切片, map,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

本章讲解了3方面的内容

1. 数组

2. 切片

3. map

一、数组

1. 数组的定义方式

var arr1 [5]int
arr2 := [3]int{1, 3, 5}
arr3 := [...]int{2, 4, 6, 8, 10}

var grid [4][5]int

fmt.Println(arr1, arr2, arr3)
fmt.Println(grid)

输出结果:

[0 0 0 0 0] [1 3 5] [2 4 6 8 10]
[[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]

2. 数组遍历的方式:

for i := 0; i < len(arr2); i++  {
    fmt.Println(arr2[i])
}
for i, v := range arr3 {
  fmt.Println(i, v)
}        

rang方式遍历的三种

// 第一种: 只获取数组的下标
for i := range arr3 {
    fmt.Println(arr3[i])
}

// 第二种: 获取数组的下标和值
for i, v := range arr3 {
    fmt.Println(i, v)
}

// 第三种: 只获取值
for _, v := range arr3 {
    fmt.Println(v)
}

3. arr[5] 和 arr[3]是不同的类型

func printArray(arr [5]int) {
    arr[0] = 100
    for _, v := range arr {
        fmt.Println(v)
    }
}

func main() {
    var arr1 [5]int
    arr2 := [3]int{1, 3, 5}
    arr3 := [...]int{2, 4, 6, 8, 10}

    var grid [4][5]int

    fmt.Println(arr1, arr2, arr3)
    fmt.Println(grid)

    printArray(arr1)
    printArray(arr3)
    // 下面这个打印会报错, 因为arr2是3个容量的数组
    // printArray(arr2)
}    

这里传递arr1 和arr3过去的时候, 可以正常打印数组,但是传递arr2过去的时候, 会报异常.

cannot use arr2 (type [3]int) as type [5]int in argument to printArray

原因是: [3]int 和[5]int是不同的类型

4. 数组的传递是值拷贝类型.

func printArray(arr [5]int) {
    arr[0] = 100
    for _, v := range arr {
        fmt.Println(v)
    }
}

func main() {
    //定义数组的三种方法
    var arr1 [5]int
    arr2 := [3]int{1, 3, 5}
    arr3 := [...]int{2, 4, 6, 8, 10}
    // 证明数组是值拷贝类型
    fmt.Println("证明数组是值拷贝类型")
    printArray(arr1)
    printArray(arr3)

    fmt.Println(arr1, arr3)
}

我们在函数printArray中修改数组第一个元素的值是100. 然后打印. 在打印原数组. 结果如下:

证明数组是值拷贝类型
100
0
0
0
0
100
4
6
8
10
[0 0 0 0 0] [2 4 6 8 10]

5. 如何实现数组的地址传递呢? 使用指针

func printArray1(arr *[5]int) {
    arr[0] = 100
    for _, v := range arr {
        fmt.Println(v)
    }
}

func main() {
    //定义数组的三种方法
    var arr1 [5]int
    arr2 := [3]int{1, 3, 5}
    arr3 := [...]int{2, 4, 6, 8, 10}
    // 证明数组是值拷贝类型
    fmt.Println("如何让数组实现值拷贝呢")
    printArray(&arr1)
    fmt.Println(arr1)
}

结果

如何让数组实现地址拷贝呢?
100
0
0
0
0
[100 0 0 0 0]

注意:

  a. 在方法printArray中参数接收是一个地址类型. 并且传递参数的时候也传递一个地址类型

  b. 在arr[0] = 100赋值的时候, 无需获取地址的值. 直接给指针类型的数组赋值即可

二、切片slice

1. 什么是slice

func main() {
    // 定义一个数组
    arr1 := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
    fmt.Println("arr1[2:6] = ", arr1[2:6])
    fmt.Println("arr1[2:] = ", arr1[2:])
    fmt.Println("arr1[:6] = ", arr1[:6])
    fmt.Println("arr1[:] = ", arr1[:] )
}

结果

arr1[2:6] =  [2 3 4 5]
arr1[2:] =  [2 3 4 5 6 7]
arr1[:6] =  [0 1 2 3 4 5]
arr1[:] =  [0 1 2 3 4 5 6 7]

  a. 通过arr[a:b]方式获取的值就是slice

  b. slice是数组的一个视图. slice不是值传递的. slice内存储的是数组的地址

2. 验证slice不是值传递

func updateSlice(s []int) {
    s[0] =100
}

func main() {
    // 定义一个数组
    arr1 := [...]int{0, 1, 2, 3, 4, 5, 6, 7}

    s1 := arr1[2:]
    s2 := arr1[:]
    
    fmt.Println("after updateSlice(s1)")
    updateSlice(s1)
    fmt.Println(s1)
    updateSlice(s2)
    fmt.Println(s2)
    fmt.Println(arr1)
}

结果:

after updateSlice(s1)
s1 =  [100 3 4 5 6 7]
s2 =  [100 1 100 3 4 5 6 7]
arr1 =  [100 1 100 3 4 5 6 7]

可以看到很有趣的现象. s1 的第一个元素被改100. 同时影响了s2的第3个元素. s2的第一个元素修改后,s1, s2同时都影响了数组arr1.

由此可见: s1, s2 都是指向的数组的地址

将s传递给函数printSlice, 在打印原来的s. 发现s的值变化了. 说明, 切片传递是地址传递, 而不是值传递.

可是上一章讲指针的时候,不是说go中只有值拷贝一种类型么? 那么为什么slice不是值拷贝呢? 因为slice是数组的一个视图. (可以理解为, 他取的是数组中指定元素的地址. 所以, slice不是值拷贝, 他的元素是地址)

思考: 在上面将数组的第5个问题, 数组如何作为一个地址拷贝的方式作为参数传递到方法里面呢? 我们的做法是: 将数组作为一个指针传过去. 学习了slice,我们可以换一种方法.

func printArray1(arr []int) {
    arr[0] = 100
    for _, v := range arr {
        fmt.Println(v)
    }
}

func main() {

    //定义数组的三种方法
    var arr1 [5]int
    arr2 := [3]int{1, 3, 5}
    arr3 := [...]int{2, 4, 6, 8, 10}


    fmt.Println(arr1, arr2, arr3)

    // 如何让数组实现地址拷贝呢?
    fmt.Println("如何让数组实现地址拷贝呢?方法2")
    printArray1(arr3[:])
    fmt.Println(arr3)
}

结果:

[0 0 0 0 0] [1 3 5] [2 4 6 8 10]
如何让数组实现地址拷贝呢?方法2
100
4
6
8
10
[100 4 6 8 10]
1. printArray1(arr []int)接收参数的时候, 使用arr []int就是切片. 如果是arr [5]int就是数组.
2.如何将一个数组传给接收切片的方法呢? 如下:printArray1(arr3[:])

是不是很巧妙. 这种方法就不用使用& *了

3. reslice

我们对数组可以进行slice操作, 对slice还可以继续进行slice操作, 就是reslice

package main

import "fmt"

func updateSlice(s []int) {
    s[0] =100
}

func main() {
    // 定义一个数组
    arr1 := [...]int{0, 1, 2, 3, 4, 5, 6, 7}

    s2 := arr1[:]

    fmt.Println("reslice操作")
    fmt.Println("slice 后: ", s2)
    s2 = s2[:5]
    fmt.Println("reslice 后: ", s2)
    s2 = s2[2:]
    fmt.Println("再次reslice 后: ", s2)
    updateSlice(s2)
    fmt.Println("再次reslice 然后修改第一个元素的值 后: ", s2)
    fmt.Println("原数组:", arr1)

}

输出结果:

reslice操作
slice 后:  [0 1 2 3 4 5 6 7]
reslice 后:  [0 1 2 3 4]
再次reslice 后:  [2 3 4]
再次reslice 然后修改第一个元素的值 后:  [100 3 4]
原数组: [0 1 100 3 4 5 6 7]

我们看到对slice再次进行slice, 就和对数组slice是类似的. 最终指向的都是数组中的地址

updateSlice(s2): 这个操作就比较有意思了. 我们看到, 对二次reslice后的数组,修改他的第一个元素的值. 然后在打印原数组,发现, 原数组的值也修改了. 再次证明, slice是数组的一个视图,最终存储的是数组的地址. 一旦被修改,会影响原数组.

4. slice扩展

看下面这个例子:

func main() {
    // 定义一个数组
    arr1 := [...]int{0, 1, 2, 3, 4, 5, 6, 7}

    s1 := arr1[2:6]
    s2 := s1[3:5]

    fmt.Println("s1 = ", s1, ", s2 = ", s2)

}

结果:

s1 =  [2 3 4 5] , s2 =  [5 6]

很奇怪: s1取的数组的4个元素. s2想要取s1的3-5个元素,可s1只有4个元素呀, s2还成功取出来了. 这是怎么回事呢?

为什么会这样呢?我们来分析一下

最开始, 数组中的值是0, 1, 2, 3, 4, 5, 6, 7

当我们取s1切片的时候, s1是底层数组的一个视图, 他取值是从2-6, 虽然,他只取了4个元素,但是因为他是对底层的一个视图,所以他是可以看到底层后面的元素的.

然后取s2切片. 我们发现他去的是s1下标的3-5个元素. 虽然s1只有4个元素, 因为他是可以看到底层其他后面的元素的, 所以, s2能够把数组中第5个元素取出来.

那么取出来对应的元素值时多少呢?对应到底层数组, 他的值就是5和6

那么s1只有4个元素, 为什么能够看到第五和第六个元素呢?

原来slice的底层结构是这么定义的

slice定义了3个对象.

ptr是一个指针, 记录的是切片的第一个地址(下面的方格代表数组),

len是切片中元素的个数, 如果获取超过元素个数的值, 会报下标越界

cap: 是capacity, 容量. 他存储的是从ptr指向的数组的元素开始, 一直到数组结束的元素.

所以,s1[3:5]获取的是cap中的数据. 正常返回.

总结: 1. slice 可以向后扩展, 但不能向前扩展. slice中cap的起始元素是从ptr开始

   2. s[i]不可以超越len(s), 向后扩展可以超越len(s),但不可以超过底层数组cap(t)

5. 向slice添加元素

package main

import "fmt"

func updateSlice(s []int) {
    s[0] =100
}

func main() {
    // 定义一个数组--为什么这样定义就能够被识别为一个数组呢
    arr1 := [...]int{0, 1, 2, 3, 4, 5, 6, 7}

    s1 := arr1[2:6]  // 2, 3, 4, 5
    s2 := s1[3:5]     // 4, 5
    fmt.Println("s2 = ", s2)
    fmt.Println("arr1 = ", arr1)
    s3 := append(s2, 10)    // 4, 5, 10
    fmt.Println("s3 = ", s3)
    fmt.Println("arr1 = ", arr1)
    s4 := append(s3, 11)    // 4, 5, 10 ,11
    fmt.Println("s4 = ", s4)
    fmt.Println("arr1 = ", arr1)
    s5 := append(s4, 12)    // 4, 5, 10 ,11, 12
    fmt.Println("s5 = ", s5)
    fmt.Println("arr1 = ", arr1)
    s6 := s5[2:5]
    fmt.Println("s6 = ", s6)
    updateSlice(s6)
    fmt.Println("arr1 = ", arr1)
}

结果

s2 =  [5 6] ,              arr1 =  [0 1 2 3 4 5 6 7]
s3 =  [5 6 10] ,           arr1 =  [0 1 2 3 4 5 6 10]
s4 =  [5 6 10 11] ,        arr1 =  [0 1 2 3 4 5 6 10]
s5 =  [5 6 10 11 12] ,     arr1 =  [0 1 2 3 4 5 6 10]
s5 =  [100 6 10 11 12] ,   arr1 =  [0 1 2 3 4 5 6 10]

添加元素是append. 那么我们来分析下这段代码:

s2的值是5 ,6 原数组:[0 1 2 3 4 5 6 7]

s3在s2基础上增加了元素10 , 我们发现原数组变为:[0 1 2 3 4 5 6 10]. 这说明, append也是对slice的cap进行处理的

s4在s3基础上增加了元素11, 我们发现原数组较上一个没变化:[0 1 2 3 4 5 6 10]. 原因是什么呢?s4 增加的数组已经超过了cap范围, 这时会重新开辟一块新的空间. 因为这块空间已经保存不下这个数据了.

s5的变化同s4, 虽增加了一个元素,但是, 原数组无变化

s5第二次打印可以证明, 数组已经重新开辟了一块新的空间. 因为我对s5的第一个元素值进行了修改, 但修改后数组的值没有变化, 表明此时s5的切片指向的地址已经不是原来数组的地址了.

下面来看看这几个元素的len 和cap是如何变化的

package main

import "fmt"

func updateSlice(s []int) {
    s[0] =100
}

func main() {
    // 定义一个数组--为什么这样定义就能够被识别为一个数组呢
    arr1 := [...]int{0, 1, 2, 3, 4, 5, 6, 7}

    s1 := arr1[2:6]  // 2, 3, 4, 5
    s2 := s1[3:5]     // 4, 5
    fmt.Println("s2 = ", s2, ",             arr1 = ", arr1, ", len(s2)", len(s2), ", cap(s2)", cap(s2))

    s3 := append(s2, 10)    // 4, 5, 10
    fmt.Println("s3 = ", s3, ",        arr1 = ", arr1, ", len(s3)", len(s3), ", cap(s3)", cap(s3))
    s4 := append(s3, 11)    // 4, 5, 10 ,11
    fmt.Println("s4 = ", s4, ",        arr1 = ", arr1, ", len(s4)", len(s4), ", cap(s4)", cap(s4))
    s5 := append(s4, 12)    // 4, 5, 10 ,11, 12
    fmt.Println("s5 = ", s5, ",    arr1 = ", arr1, ", len(s5)", len(s5), ", cap(s5)", cap(s5))
    updateSlice(s5)
    fmt.Println("s5 = ", s5, ",arr1 = ", arr1, ", len(s5)", len(s5), ", cap(s5)", cap(s5))
}

打印结果:

s2 =  [5 6] ,              arr1 =  [0 1 2 3 4 5 6 7]  ,len(s2) 2 ,cap(s2) 3
s3 =  [5 6 10] ,           arr1 =  [0 1 2 3 4 5 6 10] ,len(s3) 3 ,cap(s3) 3
s4 =  [5 6 10 11] ,        arr1 =  [0 1 2 3 4 5 6 10] ,len(s4) 4 ,cap(s4) 6
s5 =  [5 6 10 11 12] ,     arr1 =  [0 1 2 3 4 5 6 10] ,len(s5) 5 ,cap(s5) 6
s5 =  [100 6 10 11 12] ,   arr1 =  [0 1 2 3 4 5 6 10] ,len(s5) 5 ,cap(s5) 6    

我们打印出了每一次变化后len的值和cap的值. 从s4开始, 增加了一个元素, 原来的地址已经容纳不了这么多数据了, 于是新开辟了一块空间. 元素的len是4, cap是当前数组的2倍. 已经不是原来的数组了

总结:

1. 添加元素时,如果超越capacity, 那么会重新开辟一块更大的底层数组, 把slice切片值copy过去. 原来的数组, 如果有人用就依然存在, 如果没人用就会被垃圾回收掉.

2. 由于是值传递, 所以append必须要有一个返回值接收. 原因是: 当append的容量超过原来数组的时候, 会新开辟一块空间, 新开辟的空间需要有新的参数来接收.

7.slice的copy

package main

import "fmt"

func updateSlice(s []int) {
    s[0] =100
}

func printSlice(s []int) {
    fmt.Printf("len=%d, cap=%d n", len(s), cap(s))
}

func main() {

    var sli []int // 这样定义就是一个zero value, 他的值时nil

    // 给sli赋值. 因为是zero value, 所以,可以直接复制
    for i := 0; i < 200 ;i ++  {
        printSlice(sli)
        sli = append(sli, 2*i+1)
    }
}

结果

len=0, cap=0 
len=1, cap=1 
len=2, cap=2 
len=3, cap=4 
len=4, cap=4 
len=5, cap=8 
len=6, cap=8 
len=7, cap=8 
len=8, cap=8 
len=9, cap=16 
len=10, cap=16 
len=11, cap=16 
len=12, cap=16 
len=13, cap=16 
len=14, cap=16 
len=15, cap=16 
len=16, cap=16 
len=17, cap=32 
len=18, cap=32 
len=19, cap=32 
len=20, cap=32 
len=21, cap=32 
len=22, cap=32 
len=23, cap=32 
len=24, cap=32 
len=25, cap=32 
len=26, cap=32 
len=27, cap=32 
len=28, cap=32 
len=29, cap=32 
len=30, cap=32 
len=31, cap=32 
len=32, cap=32 
len=33, cap=64 
len=34, cap=64 
len=35, cap=64 
len=36, cap=64 
len=37, cap=64 
len=38, cap=64 
len=39, cap=64 
len=40, cap=64 
len=41, cap=64 
len=42, cap=64 
len=43, cap=64 
len=44, cap=64 
len=45, cap=64 
len=46, cap=64 
len=47, cap=64 
len=48, cap=64 
len=49, cap=64 
len=50, cap=64 
len=51, cap=64 
len=52, cap=64 
len=53, cap=64 
len=54, cap=64 
len=55, cap=64 
len=56, cap=64 
len=57, cap=64 
len=58, cap=64 
len=59, cap=64 
len=60, cap=64 
len=61, cap=64 
len=62, cap=64 
len=63, cap=64 
len=64, cap=64 
len=65, cap=128 
len=66, cap=128 
len=67, cap=128 
len=68, cap=128 
len=69, cap=128 
len=70, cap=128 
len=71, cap=128 
len=72, cap=128 
len=73, cap=128 
len=74, cap=128 
len=75, cap=128 
len=76, cap=128 
len=77, cap=128 
len=78, cap=128 
len=79, cap=128 
len=80, cap=128 
len=81, cap=128 
len=82, cap=128 
len=83, cap=128 
len=84, cap=128 
len=85, cap=128 
len=86, cap=128 
len=87, cap=128 
len=88, cap=128 
len=89, cap=128 
len=90, cap=128 
len=91, cap=128 
len=92, cap=128 
len=93, cap=128 
len=94, cap=128 
len=95, cap=128 
len=96, cap=128 
len=97, cap=128 
len=98, cap=128 
len=99, cap=128 
len=100, cap=128 
len=101, cap=128 
len=102, cap=128 
len=103, cap=128 
len=104, cap=128 
len=105, cap=128 
len=106, cap=128 
len=107, cap=128 
len=108, cap=128 
len=109, cap=128 
len=110, cap=128 
len=111, cap=128 
len=112, cap=128 
len=113, cap=128 
len=114, cap=128 
len=115, cap=128 
len=116, cap=128 
len=117, cap=128 
len=118, cap=128 
len=119, cap=128 
len=120, cap=128 
len=121, cap=128 
len=122, cap=128 
len=123, cap=128 
len=124, cap=128 
len=125, cap=128 
len=126, cap=128 
len=127, cap=128 
len=128, cap=128 
len=129, cap=256 
len=130, cap=256 
len=131, cap=256 
len=132, cap=256 
len=133, cap=256 
len=134, cap=256 
len=135, cap=256 
len=136, cap=256 
len=137, cap=256 
len=138, cap=256 
len=139, cap=256 
len=140, cap=256 
len=141, cap=256 
len=142, cap=256 
len=143, cap=256 
len=144, cap=256 
len=145, cap=256 
len=146, cap=256 
len=147, cap=256 
len=148, cap=256 
len=149, cap=256 
len=150, cap=256 
len=151, cap=256 
len=152, cap=256 
len=153, cap=256 
len=154, cap=256 
len=155, cap=256 
len=156, cap=256 
len=157, cap=256 
len=158, cap=256 
len=159, cap=256 
len=160, cap=256 
len=161, cap=256 
len=162, cap=256 
len=163, cap=256 
len=164, cap=256 
len=165, cap=256 
len=166, cap=256 
len=167, cap=256 
len=168, cap=256 
len=169, cap=256 
len=170, cap=256 
len=171, cap=256 
len=172, cap=256 
len=173, cap=256 
len=174, cap=256 
len=175, cap=256 
len=176, cap=256 
len=177, cap=256 
len=178, cap=256 
len=179, cap=256 
len=180, cap=256 
len=181, cap=256 
len=182, cap=256 
len=183, cap=256 
len=184, cap=256 
len=185, cap=256 
len=186, cap=256 
len=187, cap=256 
len=188, cap=256 
len=189, cap=256 
len=190, cap=256 
len=191, cap=256 
len=192, cap=256 
len=193, cap=256 
len=194, cap=256 
len=195, cap=256 
len=196, cap=256 
len=197, cap=256 
len=198, cap=256 
len=199, cap=256 

Process finished with exit code 0

从打印结果可以看出. 1) 没有个切片赋初始值, 他的默认长度是0 , 因此给切片append不会报错. 2) 每次cap容量不够的时候, 增长是按照当前元素值的2倍增长的.

package main

import "fmt"

func updateSlice(s []int) {
    s[0] =100
}

func printSlice(s []int) {
    fmt.Printf("%v, len=%d, cap=%d n", s, len(s), cap(s))
}

func main() {

    s1 := []int{1, 2, 3, 4}
    s2 := make([]int, 16)
    s3 := make([]int, 2)

    printSlice(s1)
    printSlice(s2)
    printSlice(s3)

    copy(s2, s1)
    printSlice(s2)
  

结果:

[1 2 3 4], len=4, cap=4 
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap=16 
[0 0], len=2, cap=2 
[1 2 3 4 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap=16 
[1 2], len=2, cap=2 

1) 定义切片可以使用的方法

  • var s1 []int. 初始值是nil, len是0 cap是0 ,可直接append
  • var s2 = []int{1, 2, 3, 4},有4个元素, len是4, cap是4
  • var s3 = make([]int, 16) , 有16个元素,初始值都是0 , len是16, cap是16.

2) copy是将s1 copy给s2.

3) 如果, 目标容量小于原容量, 则只copy目标容量个数的值

8. slice的delete

slice不能直接delete, 也就是没有提供delete方法,那么如果想要删除中间的某一个元素, 怎么办呢?

package main

import "fmt"

func updateSlice(s []int) {
    s[0] =100
}

func printSlice(s []int) {
    fmt.Printf("%v, len=%d, cap=%d n", s, len(s), cap(s))
}

func main() {
    s1 := []int{1, 2, 3, 4}
    s2 := make([]int, 16)
    s3 := make([]int, 2)

    printSlice(s1)
    printSlice(s2)
    printSlice(s3)

    copy(s2, s1)
    printSlice(s2)

    fmt.Println("删除元素值4")
    s4 := append(s2[:3], s2[4:]...)
    printSlice(s4)

}

结果

[1 2 3 4], len=4, cap=4 
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap=16 
[0 0], len=2, cap=2 
[1 2 3 4 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap=16 
删除s2的元素值4
[1 2 3 0 0 0 0 0 0 0 0 0 0 0 0], len=15, cap=16 

s2的值是[1 2 3 4 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap=16

删除中间的元素4, 怎么操作呢? 使用s4 := append(s2[:3], s2[4:]...)

注意:的一点是append的第二个参数是一个数组,而不是切片, 那么如何将切片转换为数组呢? 在切片后面加三个点...

三、map

map定义的三种方式

    // 第一种
    m := map[string]string {
        "aaa":"111",
        "bbb":"222",
        "ccc":"333",
        "ddd":"444",
    }
    
    // 第二种
    m1 := make(map[string]int)  // 使用make,创建了一个empty map

    // 第三种
    var m2 map[string]int         // 使用var, 创建了一个nil. nil和java中的null不同的是:nil可以参与运算.对null对象进行运算报异常
    fmt.Println(m, m1, m2)

重点看一下第二种和第三种方式有何不同.

方式二: 创建了一个empty map

方式三: 创建了一个nil 的map

虽然如此,二者都可以参与运算. go中的nil和java中的null不同的是: nil可以参与运算. 对null对象进行运算报异常

二.循环遍历使用range

//循环遍历
    for k, v := range m {
        fmt.Println(k, v) // map是无需的,每次输出可能不一样
    }

三.判断key是否存在

    // 判断key是否存在
    if v, ok := m["aaa"]; ok {
        fmt.Println(v, ok)
    } else {
        fmt.Println(ok)
    }
v, ok := m["aaa"]用来判断key是否存在

四. 删除一个元素

delete(m, "aaa")