聊过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)
}

看一下运行结果:

聊过interface聊过reflect再聊聊go语言的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。我们看一下结果:

聊过interface聊过reflect再聊聊go语言的nil

不知道和大家预想的是否一样呢?

这里我来解释一下,首先 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语言之美

相关推荐