聊过interface聊过reflect再聊聊go语言的nil
之前我们聊过go语言的interface以及反射,今天说一下go语言中的nil,nil和interface有什么关系。
我们先看一下go语言中数据类型的默认值都是什么:
bool -> false numbers -> 0 string -> "" ------------------------- pointers -> nil slices -> nil maps -> nil channels -> nil functions -> nil interfaces -> nil
这里我们可以看到,某些类型的默认值和其他语言是不一样的,我们在使用过程中需要注意一下。还有就是struct,其默认值就是其字段对应类型的默认值。这里我们主要看nil,nil只能赋值给指针、channel、func、interface、map或slice类型的变量,否则会引发panic。
那么我们看一下slice和map与nil的关系,首先还是看代码:
package main import ( "fmt" "reflect" ) func main() { mapa := map[string]string{} fmt.Println("mapa 是否等于 nil:", mapa == nil) fmt.Println(reflect.ValueOf(mapa).IsNil()) mapa["a"] = "s" var mapb map[string]string fmt.Println("mapb 是否等于 nil:", mapb == nil) fmt.Println(reflect.ValueOf(mapb).IsNil()) var s1 []string fmt.Println("s1 是否等于 nil:", s1 == nil) s2 := []string{} fmt.Println("s2 是否等于 nil:", s2 == nil) }
看一下运行结果:
这里我们可以看到,对于map和slice的两种不同声明方式,其结果也是不同的,对于使用没有使用 “:=” 声明的变量,其结果就是一个nil,因为这样相当于只有一个指针,这个指针指向一个空的map所以就是一个nil,对于另外一种方式(mapa和s2),其指针已经指向了一个map或者slice,只不过其内容是空的,所以就不是nil。就是说空的map或者slice当然不是nil(结构体也是如此)。
我们再回顾一下interface,其数据结构总的来说就是两部分,一部分是其具体类型的指针,另一部分是其具体数据的指针。我们在通过反射时便可以找到其运行时的具体内容。好,我们看一下下面的代码:
package main import ( "fmt" "reflect" ) type Namer interface { GetName() string } type Student struct { Name string Age int } func (this *Student) GetName() string { // 如果没有这个判断,this是nil会panic if this == nil { return "" } return this.Name } func main() { var s *Student fmt.Println("s 是否等于 nil:", s == nil) fmt.Println("s 的 value:", reflect.ValueOf(s)) fmt.Println("s 的 type:", reflect.TypeOf(s)) fmt.Println(s.GetName()) // 对于nil也是可以调用方法 var n Namer = s fmt.Println("n 是否等于 nil:", n == nil) fmt.Println("n 的 value:", reflect.ValueOf(n)) fmt.Println("n 的 type:", reflect.TypeOf(n)) }
大家可以先猜一下都是什么结果,3,2,1。我们看一下结果:
不知道和大家预想的是否一样呢?
这里我来解释一下,首先 var s *Student 这里,s 就是一个空的指针,它指向的内容就是空,这个上面也解释过。主要是 var n Namer = s 这部分,大家可能会想,s指向的是空,那么给了n,所以n应该也是指向空啊。事实上,n时一个接口类型,接口类型数据结构有两部分,类型和值,我们将 s 给了 n 之后,n的内存数据变化了,即n的type变成了s的type(*main.Student),但是其值依然是nil。这也就是说,对于接口类型,只有当其类型和值都为nil时,才是nil,所以,对于s就不是nil。这时我们再回想一下接口的数据结构,其指向具体类型的指针,和其指向具体数据的指针是不是更加容易理解了。只有这两个指针都是nil,其接口类型对象才是nil。
这里再给大家一个小的建议,如果项目中遇到了类似的问题,我们有两个角度来解决,第一,就是如果一个函数返回的是一个接口类型,我们就要注意一下外面是不是有对其是否等于nil的判断,如果有就要注意了,这是一个坑。我个人不推荐通过这个角度处理,go语言有一个特点是支持多个返回值的,最多的就是一个返回值加上一个error,我们外面先判断error然后再处理具体的返回内容,这样不近逻辑清晰,而且不容易出问题,可以很容易的找到问题所在,我们要利用好go语言的特点,无论什么语言,或者工具、框架,我们都要知道其主要功能特点,才能得心应手。
(头条的代码支持依然让人头疼)更多内容可以关注公众号:Go语言之美。