不说废话,直接上代码,运行结果是什么?
package main
import (
"fmt"
)
var (
testStruct *TestStruct
testMap = map[string]func(str string){
"test": testStruct.PrintName,
}
)
type TestStruct struct {
Name string
Age int
}
func (t *TestStruct) PrintName(str string) {
fmt.Printf("%s.%s\n", t.Name, str)
}
func main() {
// 设置testStruct的值
testStruct = &TestStruct{Name: "Tom", Age: 10}
if printFunc, ok := testMap["test"]; ok {
printFunc("Jerry")
}
}
--------------------------------------------------分隔线-----------------------------------------------------
--------------------------------------------------分隔线-----------------------------------------------------
--------------------------------------------------分隔线-----------------------------------------------------
--------------------------------------------------分隔线-----------------------------------------------------
--------------------------------------------------分隔线-----------------------------------------------------
--------------------------------------------------分隔线-----------------------------------------------------
--------------------------------------------------分隔线-----------------------------------------------------
--------------------------------------------------分隔线-----------------------------------------------------
--------------------------------------------------分隔线-----------------------------------------------------
--------------------------------------------------分隔线-----------------------------------------------------
--------------------------------------------------分隔线-----------------------------------------------------
会出现空指针错误:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4441029]
goroutine 1 [running]:
main.(*TestStruct).PrintName(0x0, 0x45734ff, 0x5)
你知道在哪里会产生空指针错误吗?没错,就是这里:
fmt.Printf("%s.%s\n", t.Name, str)
其中,t
是空,为什么呢?代码中明明在main()
方法中先对testStruct
变量进行了赋值,再去调用的map中的方法。按照我们的正常理解不应该出现空指针错误才对。
其实,Golang中代码运行并不完全像我们想象的那样,按照某个顺序运行,这里主要想介绍的就是Golang的全局变量初始化的一个顺序。
熟悉Go语言的都知道,全局变量、常量、init()方法这些在程序启动时有一个先后的执行顺序,当一个包被引用时,这个包内的全局变量、常量、init()方法会被执行,执行顺序为:
常量 > 全局变量 > init()方法
好的,了解以上内容后,再看代码:
var (
testStruct *TestStruct
testMap = map[string]func(str string){
"test": testStruct.PrintName,
}
)
首先,声明了两个全局变量,其中testMap进行了初始化,并设置了key = "test",value = testStruct.PrintName
,value是一个函数类型,这里指定的是testStruct的方法,他们的函数签名(指函数的参数及返回值类型、数量都一样)一致。
注意,此时testStruct还没有进行赋值,只是声明了类型,因此这个变量的值还是nil。
然后看main方法:
func main() {
// 设置testStruct的值
testStruct = &TestStruct{Name: "Tom", Age: 10}
if printFunc, ok := testMap["test"]; ok {
printFunc("Jerry")
}
}
这里对首先对testStruct全局变量进行赋值,然后判断了一下map中是否存在key为"test",存在就调用对应的方法。
因为全局变量先于 main
方法执行,因此,testMap
的值早就存在了,这里可以获取到key="test"
的值,但是, testStruct
在map赋值的时候,是一个nil,即使在 main
方法中先进行了 testStruct
变量的赋值,map中的值也已经无法改变,这里还涉及到一个参数复制的问题,不展开讲,只需要知道map中存储的 testStruct
数据,其地址已经和声明的全局变量的 testStruct
地址完全不一样了,所以当调用 PrintName
方法时会出现空指针,那么如何避免上面的问题呢?很简单,也是利用Go语言执行顺序的机制来解决:
package main
import (
"fmt"
)
var (
testStruct *TestStruct
testMap = map[string]func(str string){}
)
func init() {
// 设置testStruct的值
testStruct = &TestStruct{Name: "Tom", Age: 10}
// 设置map
testMap = map[string]func(str string){
"test": testStruct.PrintName,
}
}
type TestStruct struct {
Name string
Age int
}
func (t *TestStruct) PrintName(str string) {
fmt.Printf("%s.%s\n", t.Name, str)
}
func main() {
if printFunc, ok := testMap["test"]; ok {
printFunc("Jerry")
}
}
新增了一个init()
方法,把testStruct
的赋值从 main
方法中移到 init()
方法中,同时把testMap
的赋值逻辑也放到了 init()
方法中,并且在testStrcut
赋值之后。
此时,再次执行程序,会发现,如期输出:Tom.Jerry
。
至此,应该对 Go 语言中变量的初始化以及 init()
方法等执行顺序有了一个大致的了解了吧!也能在以后的编码中多注意如何尽量避免出现空指针,毕竟 Go 语言处理这种错误异常还是挺麻烦的。。