学习Go语言也有一段时间了,对基本的语法和控制流程也能熟练使用了,但是还是对一些高级特性只了解于表面,没有亲自使用过或者是在IDE下提示使用,但不知道为什么这么使用
异常处理
panic
主动Panic
对于业务系统来说后续不可恢复的错误,需要将异常向上处理或者,停止当前程序运行,像是初始化配置错误等等,致命性错误
func CallPanic() {
panic("An exception occurred in the system")
}
当前panic函数可以传入nil,后续介绍recover时会提到,(这里有坑
Runtime Panic
由Go语言执行引发的panc 比如除零异常,nil地址解引用异常,等等
func DivErr() {
var i int
i += 1 / i
}
上述代码会发生panic: runtime error: integer divide by zero 如果不做处理后续代码不会执行,当前goroutine就停止运行了
recover
在发生panic 发生后尝试捕获并将值进行判断。
func DivErr() {
defer func() {
if err := recover(); err != nil {
log.Println("Catch Err")
}
}()
var i int
i += 1 / i
}
执行结果
捕获异常后续可能对其做判断,前提是不为nil 一般调用panic后会传入一个不为nil的值用于告知发生错误的原因或者trace error,如果在panic中传入一个nil,如果没有recover程序会发生终止并打印堆栈,如果存在recover就无法判断是否是捕获到了异常还是未发生异常
func PanicNilErr() {
defer func() {
if err := recover(); err != nil {
log.Println("Catch Err")
}
}()
panic(nil)
}
如果像PanicNilErr函数一样panic(nil) 对应的recover函数无法判断是否真的发生了异常,对应的处理跳转也无法执行,这是需要注意的地方
defer
上述都提到了panic和recover,其中recover的调用又是在defer中执行的,这又不得不提到defer关键字了
defer是go中一种延迟调用机制,defer后面的函数只有在当前函数执行完毕后才能执行,将延迟的语句按defer的逆序进行执行,也就是说先被defer的语句最后被执行,最后被defer的语句,最先被执行,通常用于释放资源
在当前函数执行的最后返回值之前执行对应的defer 函数栈,多个defer存在时,会按照先进后出的顺序执行 defer 的右侧的表达式如果是函数调用那么参数是即时计算的
func DeferVar() {
var f = func(p int) {
log.Println(p)
}
var i int
defer f(i)
i++
}
执行的结果是0
GO 闭包
在go 语言里函数是一等公民,函数可以作为返回值,闭包就像一个书包将变量的作用域打包带走可以实现一些非常方便的特性,而不需要对应的对象保存对应的状态
func TimeCalc() func(func(time.Duration)) {
now := time.Now()
return func(f func(time.Duration)) {
f(time.Since(now))
}
}
可以很简单的实现一个程序计时器 传入对应的Hook取得值,通过保存now变量的作用域,go语言编译器也会去对其进行逃逸分析. (tips: 逃逸分析 Wiki
可以配合defer使用
func main() {
calc := TimeCalc()
defer calc(func(duration time.Duration) {
log.Println(duration)
})
time.Sleep(time.Second)
}
使用也很方便,搭配可变参数还可以实现可选计时器 hooks
Cgo
Go程序可能会遇到要访问C语言的某些硬件驱动函数的场景,或者是从一个C++语言实现的嵌入式数据库查询记录的场景 通过cgo调用C代码
反射
go的反射主要对变量分为类型和值 Type - Value
reflect.Value:操作值,用于运行时动态修改值reflect.Type:操作类型信息,用于读取类型信息 篇幅受限,下次深入学习
tips: 为何需要反射?
总结
Go语本身的高级特性还有很多,对于底层原理的学习也不能停下,反射这一阶段还有很多可以学习的地方,反射的结构体字段tag还没有提及但是在很多框架中都使用到了,json序列化等等,(^▽^)