Go语言中的零值坑记
原文链接:http://www.zhoubotong.site/post/45.html
开箱即用
什么叫开箱即用呢?因为Go
语言的零值让程序变得更简单了,有些场景我们不需要显示初始化就可以直接用,举几个例子:
切片,他的零值是nil
,即使不用make
进行初始化也是可以直接使用的,例如:
package main import ( "fmt" "strings" ) func main() { var s []string s = append(s, "love") s = append(s, "游戏") fmt.Println(strings.Join(s, " ")) // love 游戏 }
但是零值也并不是万能的,零值切片不能直接进行赋值操作:
var s []string s[0] = "love 游戏"
这样的程序就报错了。
-
方法接收者的归纳:利用零值可用的特性,我们配合空结构体的方法接受者特性,可以将方法组合起来,在业务代码中便于后续扩展和维护:
package main import ( "fmt" ) type T struct{} func (t *T) Run() { fmt.Println("love 游戏") } func main() { var t T t.Run() }
我在一些开源项目中看到很多地方都这样使用了,这样的代码最结构化。
零值并不是万能
Go
语言零值的设计大大便利了开发者,但是零值并不是万能的,有些场景下零值是不可以直接使用的:
-
未显示初始化的切片、map,他们可以直接操作,但是不能写入数据,否则会引发程序panic:
func main() { var s []string s[0] = "周伯通" // panic:runtime error: index out of range [0] with length 0 var m map[string]bool m["love"] = true // panic:assignment to entry in nil map fmt.Println(s, m) }
这两种写法使用都是错误的。
零值的指针
-
零值的指针就是指向
nil
的指针,无法直接进行运算,因为是没有无内容的地址:func main() { var p *uint32 *p++ fmt.Println(p) //panic: runtime error: invalid memory address or nil pointer dereference }
改成这样才可以
package main import ( "fmt" ) func main() { var p *uint64 a := uint64(0) p = &a *p++ fmt.Println(*p) // 1 }
零值的error类型
error内置接口类型是表示错误条件的常规接口,nil值表示没有错误,所以调用Error
方法时类型error
不能是零值,否则会引发panic
:
package main import ( "fmt" ) func main() { res := response() fmt.Println(res.Error()) //panic: runtime error: invalid memory address or nil pointer dereference } func response() error { return nil }
闭包中的nil函数
在日常开发中我们会使用到闭包,但是这其中也隐藏了一个问题,如果我们函数忘记初始化了,那么就会引发panic
:
package main var fun func(a, b, c int) func main() { fun(1, 2, 3) // panic: runtime error: invalid memory address or nil pointer dereference }
怎么解决呢?可以使用带参数闭包或者不带参数的闭包,以下作为参考示例:
package main import "fmt" func main() { //先调用闭包外面的方法传给变量 add_func := addNumber(1, 2) //再调用里面的方法,因为有了i++ 同一个内存地址 在一次编译中i的值会迭代加1 fmt.Println(add_func(1, 1)) //1 3 2 fmt.Println(add_func(0, 0)) //2 3 0 fmt.Println(add_func(2, 2)) //3 3 4 } // 闭包使用方法,定义add的传参 和函数差不多,再定义fun 可理解匿名函数 func addNumber(x1, x2 int) func(x3 int, x4 int) (int, int, int) { i := 0 // 这里需要对匿名函数return 理解调用add回调下func,再传参 return func(x3 int, x4 int) (int, int, int) { i++ //最后return出 return i, x1 + x2, x3 + x4 } }
package main import "fmt" func main() { /* add 为一个函数,函数 i 为 0 */ nextNumber := addNumber() /* 调用 add 函数,i 变量自增 1 并返回 */ fmt.Println(nextNumber()) //1 fmt.Println(nextNumber()) //2 fmt.Println(nextNumber()) //3 } func addNumber() func() int { i := 0 return func() int { i++ return i } }
关于零值不可用的场景先介绍这些,掌握这些才能在日常开发中减少写bug
的频率。
总结
总结一下本文叙说的几个知识点:
-
Go
语言中所有变量或者值都有默认值,对程序的安全性和正确性起到了很重要的作用。 -
Go
语言中的一些标准库利用零值特性来实现,简化操作。 -
可以利用"零值可用"的特性可以提升代码的结构化、使代码更简单、更紧凑。
-
零值也不是万能的,有一些场景下零值是不可用的,开发时要注意。