|
版权声明:我已加入“维权骑士”(http://rightknights.com)的版权保护计划,知乎专栏“网路行者”下的所有文章均为我本人(知乎ID:弈心)原创,未经允许不得转载。
如果你喜欢我的文章,请关注我的知乎专栏“网路行者”https://zhuanlan.zhihu.com/c_126268929, 里面有更多像本文一样深度讲解计算机网络技术的优质文章。
<hr/>自定义类型
和Python不同,在Golang中我们可以使用关键字type来创建自定义的数据类型,之前讲到的结构体和接口就已经用到了关键字type来创建基于结构体(struct)和接口(interface)的自定义数据类型,本篇将进一步讲解自定义类型的应用。
基本语法
自定义类型的基本语法如下:
type typeName baseType
其中typeName为用户自定义类型的名称,baseType为对应的原始类型(比如int, string, map, byte, struct, interface等等)。
举例如下:
package main
import &#34;fmt&#34;
func main() {
type zifuchuan string
var r1 zifuchuan = &#34;192.168.10.1&#34;
fmt.Println(r1)
}

这里我们通过&#34;type zifuchuan string&#34;自定义了一个叫做zifuchuan的新类型,该类型对应的原始类型为string,因此在声明变量r1时,虽然指定的数据类型为zifuchuan,不过依然是其对应的原始类型,即string。如果将一个非字符串的值赋值给变量r1的话则会报错,如下图所示。

自定义类型无法和对应原始类型作比较
Go是强类型语言,通过关键字type创建的自定义类型无法和它对应的原始类型直接作比较,举例如下。
package main
import &#34;fmt&#34;
func main() {
type zhengshu int
var n1 int = 100
var n2 zhengshu = 200
fmt.Println(n1 < n2)
}

这里通过“type zhengshu int”创建了一个叫做zhengshu的自定义类型,其对应的原始类型为int,变量n1的类型为int,变量n2的类型为zhengshu,两者无法直接做比较,“fmt.Println(n1 < n2)”会返回“(mismatched types int and zhengshu)”的报错。
此时必须使用int()将变量n2转换成int类型后才能与变量n1作比较,如下图所示。

<hr/>自定义方法
我曾在《网络工程师的Python之路》中讲到过函数和方法的区别:
方法(Method)和函数 (Function)大体来说是可以互换的两个词,它们之间有一个细微的区别:函数是独立的功能,无须与对象关联。方法则与对象有关,不需要传递数据或参数就可以使用。 在Golang中两者也是类似的区别(虽然Go语言本身并不是面向对象的语言,后文中的“对象”两字我都将加上引号),之前讲“自定义函数”时已经介绍过如何在Golang中自定义函数,这里举例介绍自定义方法的应用。
自定义方法举例
既然方法与对象有关,那么自然我们需要通过结构体(前面讲到过,Golang的结构体大致对应Python的类)创建一个“对象”,然后再为该“对象”创建自定义方法,举例如下。
package main
import &#34;fmt&#34;
type Router struct {
Hostname string
IP_address string
Port int
CPU_utilization float64
Power_on bool
}
func (r Router) getIPadd() string {
return r.IP_address
}
func main() {
router1 := Router{&#34;R1&#34;, &#34;192.168.2.11&#34;, 8, 11.1, true}
router2 := Router{&#34;R2&#34;, &#34;192.168.2.12&#34;, 8, 11.1, true}
fmt.Println(router1.getIPadd())
fmt.Println(router2.getIPadd())
}

这里我们定义了一个叫做Router的结构体,该结构体包含Hostname、IP_address、Port、CPU_utilization、Power_on总共五个字段,每个字段又分别对应字符串、整数、64位浮点数、布尔值等数据类型,用来描述一个路由器。
type Router struct {
Hostname string
IP_address string
Port int
CPU_utilization float64
Power_on bool
}
然后我们针对该结构体创建了一个叫做getIPadd()的自定义方法,该方法的作用很简单,就是用来返回结构体“实例化”后的“对象”的IP_address这个字段。
func (r Router) getIPadd() string {
return r.IP_address
}
最后将结构体Router“实例化”给两个“对象”router1和router2,最后分别对它们调用getIPadd()这个自定义方法,并将该返回的内容打印出来。
func main() {
router1 := Router{&#34;R1&#34;, &#34;192.168.2.11&#34;, 8, 11.1, true}
router2 := Router{&#34;R2&#34;, &#34;192.168.2.12&#34;, 8, 11.1, true}
fmt.Println(router1.getIPadd())
fmt.Println(router2.getIPadd())
}
指针在自定义方法中的使用
上面的例子我们只是创建了一个最简单的用来获取某个“对象”属性的自定义方法,下面来看下如何创建一个修改“对象”属性的自定义方法,为了实现这个需求,这里需要用到指针。
前面在讲指针时讲到了两个十分重要的概念:
- 在Go中,任何作为参数传递进函数(方法)中的变量并不是该变量本身,而是该变量的副本,调用函数(方法)时只会影响该变量副本的值,不会对变量本身产生任何影响。
- 只有将变量的指针作为参数传递进函数,调用函数时才会兑变量本身的值产生影响,因为修改*指针变量对应的值会同时修改底层变量的值。
顺着这个思路,下面来举例如何创建一个修改“对象”属性的自定义方法。
package main
import &#34;fmt&#34;
type Router struct {
Hostname string
IP_address string
Port int
CPU_utilization float64
Power_on bool
}
func (r Router) getIPadd() string {
return r.IP_address
}
func (r *Router) updateIPadd() {
r.IP_address = r.IP_address[:8] + &#34;20&#34; + r.IP_address[9:] //把IP地址的第三段从2换成20
}
func main() {
router1 := Router{&#34;R1&#34;, &#34;192.168.2.11&#34;, 8, 11.1, true}
router2 := Router{&#34;R2&#34;, &#34;192.168.2.12&#34;, 8, 11.1, true}
router1.updateIPadd()
router2.updateIPadd()
fmt.Println(router1.getIPadd())
fmt.Println(router2.getIPadd())
}

这里我们在之前例子的基础上新添了一个叫做updateIPadd()的自定义方法,该方法的作用是将IP地址的第三段改为20,该方法没有指定返回值的类型,所以也就不用添加return这个关键词。
注意这里我们用*号将指针作为参数传入了方法,这样再调用该方法后才会对变量本身的值产生影响,
func (r *Router) updateIPadd() {
r.IP_address = r.IP_address[:8] + &#34;20&#34; + r.IP_address[9:] //把IP地址的第三段从2换成20
}
func main() {
router1 := Router{&#34;R1&#34;, &#34;192.168.2.11&#34;, 8, 11.1, true}
router2 := Router{&#34;R2&#34;, &#34;192.168.2.12&#34;, 8, 11.1, true}
router1.updateIPadd()
router2.updateIPadd()
fmt.Println(router1.getIPadd())
fmt.Println(router2.getIPadd())
}
读者可以自行将*号拿掉,把func (r *Router) updateIPadd()写为func (r Router) updateIPadd(),在不使用指针的情况下观察该自定义方法是否生效。
Defer
网络工程师的日常工作中经常需要通过Telnet/SSH协议远程登录网络设备进行操作,因为任何网络设备的操作系统都会限制Telnet/SSH的会话数量以避免资源溢出和浪费,所以在结束操作后主动关闭这些会话、释放资源是十分有必要的,对各种格式的文本文件的操作也是同样的道理。
在Netdevops时代,越来越多的网络工程师通过编写脚本、程序实现自动化管理和操作网络设备,有些时候脚本和程序的代码会很长,我们会忘记在代码的末尾调用类似Python中Paramiko的paramiko.client.SSHClient().close()来关闭会话或者open().close()来关闭已开启的文本文件。
Go语言中引入了一个很方便的关键词defer来解决这个问题,即让我们在脚本、程序最开始的一段代码里就把关闭远程连接会话和文本文件的语句写入,以免后面忘记,举例如下。
package main
import (
&#34;fmt&#34;
&#34;os&#34;
)
func main() {
f := createFile(&#34;defer.txt&#34;)
defer closeFile(f)
writeFile(f)
}
func createFile(p string) *os.File {
fmt.Println(&#34;creating&#34;)
f, err := os.Create(p)
if err != nil {
panic(err)
}
return f
}
func writeFile(f *os.File) {
fmt.Println(&#34;writing&#34;)
fmt.Fprintln(f, &#34;data&#34;)
}
func closeFile(f *os.File) {
fmt.Println(&#34;closing&#34;)
err := f.Close()
if err != nil {
fmt.Fprintf(os.Stderr, &#34;error: %v\n&#34;, err)
os.Exit(1)
}
}

这里我们通过Golang内置的os模块编写了三个函数createFile(), writeFile()和closeFile(),分别用来创建、写入和关闭一个叫做&#34;defer.txt&#34;的文本文件,其中涉及到的文本写入、错误类型、错误处理的知识点在后面会详细讲到,这里只需要注意在调用closeFile()时,我们在其前面加入了defer这个关键词:
defer closeFile(f)
正因如此,closeFile虽然被写在main()下面的中间部分,但是它却在程序最末尾才被调用,因此closing这个词是最后才被打印出来的。 |
|