下面这段代码输出什么?为什么?

package main

import "fmt"

type People interface {
    Show()
}

type Student struct{}

func (stu *Student) Show() {

}

func main() {

    var s *Student
    if s == nil {
        fmt.Println("s is nil")
    } else {
        fmt.Println("s is not nil")
    }
    var p People = s
    if p == nil {
        fmt.Println("p is nil")
    } else {
        fmt.Println("p is not nil")
    }
}
s is nil
p is not nil

记住一点,当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil。

代码解析:

1. 变量 s 的判断

  • var s *Student 声明了一个 *Student 类型的指针变量 s,默认值为 nil
  • 直接判断 s == niltrue,输出 s is nil

2. 变量 p 的判断

  • var p People = ss(值为 nil 的指针)赋值给接口类型 People 的变量 p
  • Go 接口的底层结构:接口变量 p 包含两个字段:
    • 动态类型*Student):存储赋值给接口的具体类型
    • 动态值nil):存储具体类型的值
  • 接口判空规则:只有当接口的 动态类型动态值 同时为 nil 时,接口变量才等于 nil
    • 本例中,p动态类型*Student(非 nil),动态值nil,因此 p == nilfalse,输出 p is not nil

关键结论

  • 指针判空var s *T 的默认值是 nil
  • 接口判空
    • 如果直接将 nil 赋给接口(如 var p People = nil),则接口的 动态类型动态值 均为 nil,此时 p == niltrue
    • 如果通过具体类型的 nil 指针赋给接口(如 var p People = s),则接口的 动态类型 会被记录为具体类型(非 nil),即使值为 nil,接口变量也不等于 nil

总结:

我们分配给变量 p 的值明明是 nil,然而 p 却不是 nil。记住一点,当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil。上面的代码中,给变量 p 赋值之后,p 的动态值是 nil,但是动态类型却是 *Student,是一个 nil 指针,所以 p == nilfalse