什么是 map
map 是 Go 中的一个内置类型,用于存储键值对。让我们以一个有几个员工的创业公司为例。为了简单起见,我们假设所有这些员工的名字都是唯一的。我们正在寻找一个数据结构来存储每个员工的工资。一个 map 将是这个用例的完美数据结构。员工的名字可以是键,工资可以是值。Maps 类似于 Python 等其他语言中的 map (dict)。
如何创建 map
可以通过将键和值的类型传递给 make
函数来创建 map。make(map[type of key]type of value)
是创建 map的语法。
personSalary := make(map[string]int)
上面这行代码创建了一个名为 personSalary
的 map,它具有 string
类型的键和 int
类型值。
package main
import (
"fmt"
)
func main() {
employeeSalary := make(map[string]int)
fmt.Println(employeeSalary)
}
上面的程序创建了一个名为 employeeSalary 的 map,map 中有 string
键和 int 值。上面的程序将打印。
map[]
由于我们没有给 map 添加任何元素,所以它是空的。
向 map 添加键值对
向 map 添加键值对的语法与数组的语法相同。下面的程序将添加键值对到personSalary
map中。
package main
import (
"fmt"
)
func main() {
personSalary := make(map[string]int)
personSalary["steve"] = 12000
personSalary["jamie"] = 15000
personSalary["mike"] = 9000
fmt.Println("personSalary map contents:", personSalary)
}
我们增加了三名员工 steve、jamie 和 mike 以及他们相应的工资。
上面程序输出
employeeSalary map contents: map[steve:12000 jamie:15000 mike:9000]
也可以声明的时候初始化
package main
import (
"fmt"
)
func main() {
employeeSalary := map[string]int {
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println("personSalary map contents:", personSalary)
}
上面的程序声明了 employeeSalary,并在声明期间向它添加两个元素。然后添加了一个键值为 mike
的元素。程序输出
personSalary map contents: map[steve:12000 jamie:15000 mike:9000]
map 的键类型不只是字符串类型。 所有可比较的类型,如布尔值,整数,浮点数,复数,字符串…也可以是键。甚至用户定义的类型,如结构体,也可以是键。 如果你想了解有关类型的更多信息,请访问 http://golang.org/ref/spec#Comparison_operators
Map 的零值
map 的零值是 nil。如果你试图向一个 nil map 添加元素,会发生运行时的 panic。因此,在添加元素之前,必须对 map 进行初始化。
package main
func main() {
var employeeSalary map[string]int
employeeSalary["steve"] = 12000
}
在上面的程序中,employeeSalary 为 nil,我们试图在 map 中添加一个新的键。程序会出现以下错误
panic: assignment to entry in nil map
访问 map 的键值对
现在我们已经向 map 添加了一些元素,让我们学习如何访问它们。map[key]
是访问map 元素的语法。
package main
import (
"fmt"
)
func main() {
employeeSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
"mike": 9000,
}
employee := "jamie"
salary := employeeSalary[employee]
fmt.Println("Salary of", employee, "is", salary)
}
上面的程序非常简单。检索并输出员工的工资 jamie
。程序输出
Salary of jamie is 15000
如果没有元素会发生什么?map 将返回该元素类型的零值。在 personSalary
map中,如果我们试图访问一个不存在的元素,那么将返回 int
类型的零值 0。
package main
import (
"fmt"
)
func main() {
employeeSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
fmt.Println("Salary of joe is", employeeSalary["joe"])
}
上述程序的输出为
Salary of joe is 0
上面的程序返回 joe 的工资为 0。我们运行时没有得到任何错误,说明键值 joe
不存在于personSalary
map中。
检查键是否存在
在上一节中我们了解到,当一个键不存在时,将返回该类型的零值。当我们想知道键是否真的存在于 map 中时,这并没有帮助。
例如,我们想知道一个键是否存在于 employeeSalary map 中。
value, ok := map[key]
上面的语法用于确定 map 中是否存在特定的键。如果 ok
为真,那么键就是存在的,它的值在变量 value
中存在,否则键就是不存在的。
package main
import (
"fmt"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
newEmp := "joe"
value, ok := personSalary[newEmp]
if ok == true {
fmt.Println("Salary of", newEmp, "is", value)
} else {
fmt.Println(newEmp,"not found")
}
}
在上面的程序中第 15 行,因为 map 里木有 joe
键值,ok
为 false。因此程序将输出
joe not found
遍历 map 中的所有元素
for
循环的 range
形式用于遍历 map 的所有元素。
package main
import (
"fmt"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println("All items of a map")
for key, value := range personSalary {
fmt.Printf("personSalary[%s] = %d\n", key, value)
}
}
程序输出
All items of a map
personSalary[mike] = 9000
personSalary[steve] = 12000
personSalary[jamie] = 15000
一个要注意的的点是,在使用 for range
时,不能保证每次执行程序时, map 检索值的顺序都是相同的。
删除键值对
delete(map, key) 是从 map
中删除 key
的语法,delete 函数不返回任何值。
package main
import (
"fmt"
)
func main() {
employeeSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
"mike": 9000,
}
fmt.Println("map before deletion", employeeSalary)
delete(employeeSalary, "steve")
fmt.Println("map after deletion", employeeSalary)
}
上面的程序删除键 steve 并输出
map before deletion map[steve:12000 jamie:15000 mike:9000]
map after deletion map[mike:9000 jamie:15000]
如果我们试图删除一个 map 中不存在的键,不会出现运行时错误。
带有结构体的 map
到目前为止,我们只在 map 中存储了员工的工资。如果我们也能在 map 中存储每个员工的国家,那不是更好吗?这可以通过带有结构体的 map 来实现。雇员可以用一个包含字段 salary 和 country 的结构体来表示,它们将用一个字符串键和结构体值存储在 map 中。让我们写一个程序来了解如何做到这一点。
package main
import (
"fmt"
)
type employee struct {
salary int
country string
}
func main() {
emp1 := employee{
salary: 12000,
country: "USA",
}
emp2 := employee{
salary: 14000,
country: "Canada",
}
emp3 := employee{
salary: 13000,
country: "India",
}
employeeInfo := map[string]employee{
"Steve": emp1,
"Jamie": emp2,
"Mike": emp3,
}
for name, info := range employeeInfo {
fmt.Printf("Employee: %s Salary:$%d Country: %s\n", name, info.salary, info.country)
}
}
在上面的程序中,employee 结构中包含了 salary 和 country 两个字段,我们创建了三个员工 emp1、emp2 和 emp3。
在第 25 行,我们初始化了一个键类型为 string,值类型为 employee 的 map,其中包含我们创建的三个雇员。
第 31 行中对 map 进行迭代,并在下一行中打印员工的详细信息。这个程序将打印,
Employee: Mike Salary:$13000 Country: India
Employee: Steve Salary:$12000 Country: USA
Employee: Jamie Salary:$14000 Country: Canada
Map 的长度
map 的长度可以使用 len 函数来确定。
package main
import (
"fmt"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println("length is", len(personSalary))
}
上面程序中的 len(personSalary) 确定 map 的长度。程序输出
length is 2
Map 是引用类型
与切片类似, map 也是引用类型。当将 map 分配给一个新变量时,它们都指向相同的内部数据结构。因此,一个 map 改变了值也会影响另外一个 map。
package main
import (
"fmt"
)
func main() {
employeeSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
"mike": 9000,
}
fmt.Println("Original employee salary", employeeSalary)
modified := employeeSalary
modified["mike"] = 18000
fmt.Println("Employee salary changed", employeeSalary)
}
在上面程序的第 14 行,employeeSalary 被赋值给 modified。在下一行中,mike 的 salary 在 modified map 中被改为 18000。现在 mike 的 salary 在 employeeSalary 中也将是 18000。程序输出,
Original person salary map[steve:12000 jamie:15000 mike:9000]
Person salary changed map[steve:12000 jamie:15000 mike:18000]
类似的情况还有将 map 作为参数传递给函数的情况。当函数内部的 map 发生任何更改时,调用者都会看到。
Map 的比较
不能使用 ==
运算符来比较 map。==
只能用于检查 map 是否为 nil
。
package main
func main() {
map1 := map[string]int{
"one": 1,
"two": 2,
}
map2 := map1
if map1 == map2 {
}
}
程序将抛出编译错误 invalid operation: map1 == map2 (map can only be compared to nil)。
检查两个 map 是否相等的一种方法是逐个比较每个 map 的元素。我建议你可以写一个程序来验证它们:)。
我已将我们讨论过的所有的知识点都编译成一个程序。你可以从github下载。