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语言中的一些标准库利用零值特性来实现,简化操作。 -
可以利用"零值可用"的特性可以提升代码的结构化、使代码更简单、更紧凑。
-
零值也不是万能的,有一些场景下零值是不可用的,开发时要注意。

浙公网安备 33010602011771号