Go语言中的零值坑记
原文链接:http://www.zhoubotong.site/post/45.html
开箱即用
什么叫开箱即用呢?因为Go
语言的零值让程序变得更简单了,有些场景我们不需要显示初始化就可以直接用,举几个例子:
切片,他的零值是nil
,即使不用make
进行初始化也是可以直接使用的,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package main import ( "fmt" "strings" ) func main() { var s []string s = append(s, "love" ) s = append(s, "游戏" ) fmt.Println(strings.Join(s, " " )) // love 游戏 } |
但是零值也并不是万能的,零值切片不能直接进行赋值操作:
1 2 | var s []string s[0] = "love 游戏" |
这样的程序就报错了。
-
方法接收者的归纳:利用零值可用的特性,我们配合空结构体的方法接受者特性,可以将方法组合起来,在业务代码中便于后续扩展和维护:
12345678910111213141516package
main
import
(
"fmt"
)
type
T
struct
{}
func
(t *T) Run() {
fmt.Println(
"love 游戏"
)
}
func
main() {
var
t T
t.Run()
}
我在一些开源项目中看到很多地方都这样使用了,这样的代码最结构化。
零值并不是万能
Go
语言零值的设计大大便利了开发者,但是零值并不是万能的,有些场景下零值是不可以直接使用的:
-
未显示初始化的切片、map,他们可以直接操作,但是不能写入数据,否则会引发程序panic:
1234567func
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
的指针,无法直接进行运算,因为是没有无内容的地址:12345func
main() {
var
p *uint32
*p++
fmt.Println(p)
//panic: runtime error: invalid memory address or nil pointer dereference
}
改成这样才可以
12345678910111213package
main
import
(
"fmt"
)
func
main() {
var
p *uint64
a := uint64(0)
p = &a
*p++
fmt.Println(*p)
// 1
}
零值的error类型
error内置接口类型是表示错误条件的常规接口,nil值表示没有错误,所以调用Error
方法时类型error
不能是零值,否则会引发panic
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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
:
1 2 3 4 5 6 7 | 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 } |
怎么解决呢?可以使用带参数闭包或者不带参数的闭包,以下作为参考示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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 } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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
语言中的一些标准库利用零值特性来实现,简化操作。 -
可以利用"零值可用"的特性可以提升代码的结构化、使代码更简单、更紧凑。
-
零值也不是万能的,有一些场景下零值是不可用的,开发时要注意。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!