开发者学堂课程【Go语言核心编程 - 基础语法、数组、切片、Map:map 使用细节和陷阱】与课程紧密联系,让用户快速学习知识
课程地址:https://developer.aliyun.com/learning/course/625/detail/9662
map 使用细节和陷阱
内容介绍:
一、总结map使用细节
二、map是引用类型
三、map的容量自动扩容
四、map的value使用结构体
一、总结map使用细节
接下来介绍 map 的使用细节,这里给大家总结了3点,都非常重要。
1.map 是引用类型,遵守引用类型传递的机制,在一个函数接收 map,修改后,会直接修改原来的 map【案例演示】。
2.map 的容量达到后,再想 map 增加元素,会自动扩容,并不会发生 panic,也就是说map 能动态的增长键值对(key-value)【索例演示】。
3.map 的 value 也经常使用 struct 类型,更适合管理复杂的数据(比前面value是一个map更好),比如 value 为 Student 结构体【案例演示,因为还没有学结构体,体验一下即可】。
二、map是引用类型
来看一下 map 使用细节有哪些地方。第一个,首先要清楚的知道 map 是一个引用类型的数据,因此它就会遵守信用类型传递的机制,也就是说如果用一个函数接收map,修改过后,会直接修改原来的 map。也就是说 map 是引用类型的,这点要注意,跟切片一样,切片是引用类型,map也是引用类型。
举个例子说明特点:新建一个文件夹叫 mapdetails,然后新建一个文件名叫main.go。在之前的文件里复制一个通用的代码过来,代码如下:
package main
import (
“fmt”
)
func main() {
}
先讲map细节的第一点,测试这个特点是否正确。写一个函数 modify,在这里接收一个 map,类型是 int 类型,代码如下:
func modify(map1 map[int]int) {
map1[10] = 900
//下标为10,改成了900
//但是要确保有一个key为10
}
在主函数里声明一个 map,代码如下:
map1 := make(map[int]int)
//
make 一个 map[int]int 类型的 map
map1[1] = 90
//第一个放的key为1,map等于90
map1[2] = 88
//key为2,map等于88
map1[10] = 1
//key为10,map等于1
map1[20] = 2
//key为20,map等于2
modify(map1)
//调用modify的时候把map传给它
fmt.Println(map1) //看看结果,map[10] = 900,说明map是引用类型
输出观察 key 为10的这个元素对应的值是1还是900。如果输出的是900就说明它的确是引用传递类型;如果仍然保存是1,那就说明它是默认只拷贝、只传递了。
运行这段阶段代码,在 cmd.exe里输入以下命令回车:
cd..
cd mapdetails
go run main.go
输出结果是:
map[1:90 2:88 10:900 20:2] //输出的结果和分析的结果是一样的
可以看到key为10的变成了900,也就是说当把 map 传递给 modify 函数过后,modify 里的修改会直接影响到 map 里面的 map1,从而说明 map 是引用类型。
三、map的容量自动扩容
再来看 map 使用的第二个细节。map 容量达到后,再想 map 增加元素,会自动扩容,并不会发生panic。但是切片不可以,切片得用 append 才可以。也就是说map 能动态的增长键值对(key-value)。
案例演示:
map1 := make(map[int]int, 2)
//make一个map[int]int类型的map,默认容量是2
map1[1] = 90
map1[2] = 88
map1[10] = 1
map1[20] = 2
//key为20,map等于2
modify(map1)
//调用 modify 的时候把 map 传给它
fmt.Println(map1)
可以看到在在map1[10]的时候已经超过了2了,但是没有报错,此时是可以运行的,运行结果是:
map[10:900 20:2 1:90 2:88]
这说明map确实可以自动扩容。
四、map的value使用结构体
再看第三个细节,map的value也经常使用struct类型(struct就是结构体),更适合管理复杂的数据(比前面是一个map更好),比如value为Student结构体【因为还没有学结构体,体验一下即可】。(其实 map 这个值用结构体用的更多,所以这里只是写一段代码体验一下)
案例演示:
对前面的案例进行修改,map 的 key 还是用的学号,然后它的值用结构体来完成。
这里将要求粘贴过来:
// map 的 value 也经常使 struct 类型,
//更适合管理复杂的数据(比前面是一个map更好),
//比如 value 为 Student 结构体【因为还没有学结构体,体验一下即可】
//(1)map 的 key 为学生的学号,是唯一的
//(2)map 的 value 为结构体,包含学生的名字,年龄,地址
第一步要定义一个结构体:
//定义一个学生结构体
type Stu struct {
Name string //定义名字
Age int //定义年龄
Address string //定义地址
}
//这就是一个学生结构体
声明一个map:
students := make(map[string]Stu, 10) //放10个学生进去,make 一个这样的 map,map 的 key 是 string,map 的值是 Stu 结构体。
//创建2个学生
stu1 := Stu{”tom”, 18,”北京”} //这里做了简单化处理,第一个学生名字是tom,年龄是18,地址是北京
stu2 := Stu{“mary”, 28,”上海”} 第一个学生名字是 mary,年龄是28,地址是上海
//现在要将两个学生交到 string 这个值中
students[“no1”] = stu1 //学号它的 key 是 number1,是 stu1这个学生
students[“no2”] = stu2 //学号它的 key 是 number2,是 stu2这个学生
fmt.Println(students)
//输出这个 students,观察此时此刻是否已经用这个 map 把这个学生(string)保留起来,也就是说这个值Stu已经是个结构体了,这样肯定是更好保存一个学生的信息。
在 cmd.exe 里输入命令 go run main.go 回车得到:
map[1:90 2:88 10=900 20:2]
map [no1:<tom 18北京> no2:<mary 28 上海>]
可以很清晰的看出 map 里面有一个 no1,key 为no1,它是一个结构体:tom,18,北京;第二个学生是 no2,它的结构体是 mary,28,上海。这样就会更好一点,因为将来结构体的数据可以很轻松的增加它的储存部分,比如再增加一个属性就很轻松了。
再讲如何遍历一个学生,想把每一个学生具体的信息做出来:
//遍历各个学生信息
for k, v := range students {
//for 第一个把key遍历出来,把值遍历出来,k就是学生的编号,v就是一个结构体fmt.Printf(“学生的编号是%v \n”, k)
//每输出一个就换行
fmt.Printf(“学生的名字是%v \n”, v.Name)
fmt.Printf(“学生的年龄是%v \n”, v.Age)
fmt.Printf(“学生的地址是%v \n”, v.Address)
fmt.Println() //最后再换一行
}
执行看遍历是否成功,在 cmd.exe 里输入命令 go run main.go 回车得到:
map[2:88 10:900 20:2 1:90]
map[no1:<tom 18 北京> no2:<mary 28 上海>]
学生的编号是no1
学生的名字是tom
学生的年龄是18
学生的地址是北京
学生的编号是no2
学生的名字是mary
学生的年龄是28
学生的地址是上海
可以看出信息是非常清晰的,第一个学生编号是 no1,名字、年龄、地址等都有;第二个学生编号是 no2,名字、年龄等如上。
这里就讲了 map 使用的第三个细节,在实际开发中 value 更多的情况下用的是结构体类型,而且这种方式来保存或者管理数据也更加的方便,更加的灵活。
关于 map 使用细节就讲完了,尤其要注意第三点,还有就是 map 是引用类型,map 的容量会自动增加。



