版权声明:我已加入“维权骑士”( )的版权保护计划,知乎专栏“网路行者”下的所有文章均为我本人(知乎ID:弈心)原创,未经允许不得转载。
如果你喜欢我的文章,请关注我的知乎专栏“网路行者” , 里面有更多像本文一样深度讲解计算机网络技术的优质文章。
对Netdevops读者来说, Go 中的map大体上可以对应Python中的字典,而 结构体 ( struct )则类似于Python中的类(虽然Go并不是面向对象的语言),首先来看map的应用。
Map重要概念
- 和Python的字典一样,Go的map里的元素由键值对(key-value pair)构成。不同的是Go中map里的键值对是无序的,而Python从3.6版开始其字典由无序变为了有序的。
- 同Go的切片和Python的列表的区别一样,在Go中创建一个map时必须指定键和值的数据类型,这个和键、值的数据类型可以任何混用的Python字典是有本质区别的。
- Map中任何数据类型都可以拿来做键,但是通常不建议使用 布尔值 来做键的数据类型,道理也很简单,因为布尔值只有true和false两个值,除非你能确保你的map里只有两组键值对,不然完全不够用。
- 也不建议使用 浮点数 来作为键的数据类型,因为Go程序内部的浮点精度问题时常会导致bug的发生。
使用Map
下面分别举例讲解如何创建map以及对已有map的键值对增、改、删、查的操作方法。
创建map
在Go中声明map的语法如下:
map[key_type]value_type{
key1: value1
key2: value2
}
下面举例创建一个键和值均为 字符串 类型的map:
package main
import "fmt"
func main() {
switch1 := map[string]string{
"SN": "12345abcde",
"CPU": "25.1",
"version": "11.1",
"port": "48",
}
fmt.Println(switch1)
fmt.Println(len(switch1))
}
这里我们用map[string]string{}创建了一个键和值均为字符串的map,总共有4组键值对,分别用来保存一台 交换机 的序列号、CPU用量、系统版本以及物理端口数量,然后将其赋值给变量switch1。注意我们同样可以对map使用len()函数来获得map里键值对的数量。
添加键值对
对map添加键值对的方法和Python字典一样,这里我们用switch1[“model”] = “C9300″向switch1里添加一组”model”: “C9300″的键值对:
package main
import "fmt"
func main() {
switch1 := map[string]string{
"SN": "12345abcde",
"CPU": "25.1",
"version": "11.1",
"port": "48",
}
fmt.Println(switch1)
fmt.Println(len(switch1))
switch1["model"] = "C9300"
fmt.Println(switch1)
}
这里注意在将新的map打印出来后新添的键值对model:C9300并没有排在map的最后,说明了map是无序的。
修改键值对
对map修改已有键值对的方法和Python字典一样,这里我们使用switch1[“CPU”] = “19.0”将键值对”CPU”:”25.1″改为”CPU”:”19.0″:
package main
import "fmt"
func main() {
switch1 := map[string]string{
"SN": "12345abcde",
"CPU": "25.1",
"version": "11.1",
"port": "48",
}
fmt.Println(switch1)
fmt.Println(len(switch1))
switch1["model"] = "C9300"
switch1["CPU"] = "19.0"
fmt.Println(switch1)
}
删除键值对
在Go中我们使用delete()函数来删除map里的键值对,delete()函数里要用到两个参数,第一个是代表map的变量名,第二个是要被删除的键,这里我们使用delete(switch1, “SN”)来删除变量switch1里的”SN”:”12345abcde”这个键值对:
package main
import "fmt"
func main() {
switch1 := map[string]string{
"SN": "12345abcde",
"CPU": "25.1",
"version": "11.1",
"port": "48",
}
fmt.Println(switch1)
fmt.Println(len(switch1))
delete(switch1, "SN")
fmt.Println(switch1)
fmt.Println(len(switch1))
}
Nil Map
Map也有零值,其零值为 nil ,零值map也叫nil map。注意nil map和一个没有任何键值对的空map不是一个概念,我们不能对nil map添加任何键值对,否则程序会崩溃,但是我们可以对空map添加键值对,举例如下:
package main
import "fmt"
func main() {
test_map := map[string]int{} //声明一个空map
test_map["test"] = 1 //为空map添加键值对
fmt.Println("test_map:", test_map) //到这一步没有任何问题
test_map = nil //将map转换为nil map
test_map["test"] = 1 //尝试为nil map添加键值对,这里会导致程序崩溃
}
对空map添加键值对没有任何问题,但是一旦尝试对nil map添加键值对时程序随即崩溃。
用make()创建map
除了用来创建切片外,make()也可以用来创建空map(和通过make()只能创建空切片一样,用make()也只能创建空map),举例如下:
package main
import "fmt"
func main() {
switch1 := make(map[string]string) //用make创建一个空map
fmt.Println(switch1)
switch1["port"] = "48"
switch1["version"] = "11.1"
fmt.Println(switch1)
}
这里我们用make()函数创建了一个键和值均为字符串的空map,并将它赋值给变量switch1,随后我们手动为该map添加了”port”:”48″和”version”:”11.1″两组键值对。
验证指定的键值对是否存在于map中
在map中以variable[“key_value”]的形式获取键值时, 实际上会返回两个值:一个是键对应的值,一个是布尔值,其中布尔值的作用是用来判断键是否存在,如果存在则返回true,不存在则返回false(键不存在的时候,键对应的值为零值)。 我们可以通过双赋值(two-value assignment)的形式来获取这两个值,从而判断我们感兴趣的键是否存在于我们感兴趣的map中,举例如下:
package main
import "fmt"
func main() {
switch1 := make(map[string]int) //注意这里键的类型依然为字符串,但是值的类型不再是字符串,而是整数
fmt.Println(switch1)
v, ok := switch1["port"] //双赋值,变量v代表键对应的值,变量ok用来判断键"port"是否存在于switch1这个map中
fmt.Println(v, ok) // 因为swtich1为空map,因此v的值为0(整数的零值),ok为false,表示"port"这个键不存在
switch1["port"] = 48 //向switch1里添加"port":48这个键值对
v, ok = switch1["port"] //再次双赋值
fmt.Println(v, ok) //此时v为48,ok为true,表示"port"这个键存在
}
如果你说我只想知道键是否存在于map中,我不关心键对应的值是什么的话, 可以用下划线”_”来做变量名, 替代变量v,即将v, ok := switch[“port”]写成_, ok := switch[“port”],举例如下:
package main
import "fmt"
func main() {
switch1 := make(map[string]int)
fmt.Println(switch1)
_, ok := switch1["port"] //双赋值时,使用"_"来作为第一个变量,表示略过varaibale["key-value"]返回的第一个值
fmt.Println(ok) //当变量名为"_"时,不再需要使用它
switch1["port"] = 48
_, ok = switch1["port"] //同理
fmt.Println(ok) //同理
}
在Go语言中,很多函数会返回两个值(类似于Python中字典的items()函数),使用”_”做变量名来屏蔽掉自己不感兴趣的值的方法在双赋值的情况下很常用,后面的章节会多次用到。
结构体重要概念
相信很多学习Go语言到这里的Netdevops读者不禁有一个疑问:Go中的数组、切片、map统统都只能支持统一的数据类型,一个数组或切片里的元素要嘛全部是字符串,要嘛全部是整数、浮点数、布尔值等等,一个map里的键和值也是同样的道理,这点和Python里的列表和字典相比是不是显得太不灵活了?不用担心,Go中的结构体(struct)就能打破这样的限制。
前面在《数组、切片》一章里讲到切片的sliceHeader时我们已经提到了结构体的概念。简单点来说,结构体可以让我们将一组数据集合起来,以便我们能够更加便捷地操作这些数据。每个数据又由字段(field)和其对应的数据类型组成,最重要的是 每个字段对应的数据类型相互之间可以完全不一样。 除此以外, 我们还可以通过结构体根据我们自己的需要来自定义一个全新的数据类型(一个结构体就是一个新的数据类型)。 正因如此,在Go中我们使用 type 这个关键词来定义结构体,举例如下:
type Router struct{
Hostname string
IP_address string
Port int
CPU_utilization float64
Power_on bool
}
这里我们定义了一个叫做 router 的结构体(也就是创建了一个叫做router的数据类型),该结构体包含Hostname、 IP _address、Port、CPU_utilization、Power_on总共五个字段,每个字段又分别对应字符串、整数、64位浮点数、布尔值等数据类型,用来描述一个路由器。
使用结构体
创建了结构体后,接下来我们将它赋值给两个变量router1和router2,看看怎样使用结构体:
package main
import "fmt"
type router struct {
Hostname string
IP_address string
Port int
CPU_utilization float64
Power_on bool
}
func main() {
router1 := router{"R1", "192.168.2.11", 8, 11.1, true} //将接结构体router"实例化"给变量router1
router2 := router{"R2", "192.168.2.12", 8, 22.2, false} //将接结构体router"实例化"给变量router2
fmt.Println(router1)
fmt.Println(router2)
fmt.Println("Router1的主机名:", router1.Hostname)
fmt.Println("Router1的IP地址:", router1.IP_address)
fmt.Println("Router2的端口数量:", router2.Port)
fmt.Println("Router2的CPU用量:", router2.CPU_utilization)
fmt.Println("Router1的电源是否开启:", router2.Power_on)
}
这里我们将结构体router“实例化”给了router1和router2。可以看出Go中的结构体和Python的类相似, 虽然Go语言本身并不是面向对象的编程语言 ,但是也能做到类似于Python那样将一个类“实例化”的效果。这里的结构体router就像Python的类一样是一个抽象的模板,而router1和router2就类似于结构体router的实例(instance)。
注意:通常建议将结构体写在main()或者其他自定义函数的外面,这样把结构体放在全局(global)的位置的好处是将来方便它们在同一个Golang包下面被其他的程序调用。不过如果你确认整个包下面只有这一个程序,结构体不会“对外使用”,那将结构体写在main()或者其他自定义函数下面也是可以的。
用new()将结构体赋值给变量
用new()将结构体赋值给变量和用make()创建切片、map一样,我们都不能将结构体字段的初始参数分配给变量,举例如下:
package main
import "fmt"
type router struct {
Hostname string
IP_address string
Port int
CPU_utilization float64
Power_on bool
}
func main() {
router1 := new(router) //使用new()将结构体router"实例化"给router1
fmt.Println(router1)
}
这里注意两点:
- 用new()将结构体赋值给变量后,因为不能设定每个结构体字段的初始值,所以所有字段的值为零值,即这里的&{ 0 0 false}。
- 和make()函数创建的切片、map即为该切片、map本身不一样,new()函数返回的值并不是结构体本身,而是指向该结构体的指针 ,所以这里我们可以看到fmt.Println(router1)的时候可以看到{ 0 0 false}的前面多了一个&,这个&代表的就是指针,关于指针的知识我们会在后文中讲到。
接下来我们将router1的各项字段做增添:
package main
import "fmt"
type router struct {
Hostname string
IP_address string
Port int
CPU_utilization float64
Power_on bool
}
func main() {
router1 := new(router) //使用new()将结构体router"实例化"给router1
fmt.Println(router1)
router1.Hostname = "R1"
router1.IP_address = "192.168.2.11"
router1.Port = 8
router1.CPU_utilization = 11.1
router1.Power_on = true
fmt.Println(router1)
}