我写了一段代码,实际上是这样的:
package main
import "fmt"
type SomeInterface interface {
Retrieve(identifier string)
}
type SomeStruct struct {}
func (r SomeStruct) Retrieve(identifier string) {
fmt.Println("identifier ", identifier)
}
type Handler struct {
Name string
SomeObject SomeInterface
}
func main() {
var someStruct *SomeStruct
var h = Handler{
Name: "helo",
SomeObject: someStruct,
}
fmt.Printf("before %+vrn", h.SomeObject)
if h.SomeObject == nil {
fmt.Printf("during %+vrn", h.SomeObject)
}
fmt.Printf("after %+vrn", h.SomeObject)
}
谁能给我解释一下为什么上面的输出是:
before <nil>
after <nil>
我一直在阅读关于nil
类型的接口,但在这种情况下,我已经将接口分配给了一个尚未分配的指针,所以我会认为接口== nil
和我会看到during <nil>
-唉,情况并非如此。
接口值是一个简单的数据结构,由两部分组成:类型和底层值。因此,接口值本身可以是nil
,或者接口值存在但底层值可以是nil
。例如:
var x interface{} = nil // x is nil
var y interface{} = (interface{})(nil) // y is a interface{}, which *contains* nil
这在概念上与下面的区别有些类似:
var x []*int = nil // x is nil
var y []*int = []*int{nil} // y is a []*int, which *contains* nil
fmt.Printf
模糊了接口的区别,因为它格式化了输出的方式;如果您愿意,可以使用反射更清楚地看到差异。
SomeObject不是nil,它只是指向一个为nil的SomeStruct。
我认为混乱是错误的。Printf在这种情况下打印<nil>
,因为它在指针后面,最终结果是nil。
在Go语言中,引用某个接口实现的变量可以有多种类型。它的类型可以是<nil>
(是的,nil
既可以描述类型也可以描述值),它可以是它的一个实现者的类型,也可以是指向它的一个实现者的指针的类型。缺省情况下,引用接口的变量类型为nil
。一旦你给它赋值了一些东西(除了nil
本身),它就会采用你给它赋值的东西的类型(同样,要么是它的一个实现者的类型,要么是指向这些类型之一的指针)。
可以用%T
打印接口变量的类型,用%v打印它的值:
func main() {
var i SomeInterface
fmt.Printf("%T, %vn", i, i) // Prints <nil>, <nil>
var someStruct SomeStruct
i = someStruct
fmt.Printf("%T, %vn", i, i) // Prints main.SomeStruct, {}
var someStructPtr *SomeStruct
i = someStructPtr
fmt.Printf("%T, %vn", i, i) // Prints *main.SomeStruct, <nil>
}
现在,无论何时比较h.SomeObject == nil
,只有当两个操作数的类型和值匹配时,比较才会被计算为true。在您的示例中,h.SomeObject
的值显然是<nil>
(毕竟,someStruct
的值肯定是<nil>
,并且您将其值存储在h.SomeObject
中)。根据我刚才的解释,h.SomeObject
的类型应该是*SomeStruct
。nil
的值显然是<nil>
.
但是,nil
的类型是什么?
嗯,nil
可以接受许多类型,编译器必须决定每种用法应该采用哪种类型。当涉及到比较和赋值时,它只是接受被比较或赋值对象的类型。例如,如果将一个整型指针与nil
进行比较,那么在这种情况下,nil
的类型将是*int
。
但是所有这些必须在编译时决定,并且引用接口的变量可以在运行时更改类型。因此,当您将引用接口的变量与nil
进行比较时,在这种情况下,编译器会给nil
操作数提供什么类型?它给了它唯一一个合理的类型,<nil>
。
最后一个例子,考虑下面的代码:
func main() {
var p *SomeStruct = nil // = nil is optional; pointers default to nil
var i SomeInterface = p
printf("%tn", p == nil) // Prints true
printf("%tn", p == i) // Prints true
printf("%tn", i == nil) // Prints false
}
p == nil
为true,因为p
的类型为*SomeStruct
,值为<nil>
,而nil
的类型也为*SomeStruct
,值为<nil>
。
p == i
是true,因为i
也是类型为*SomeStruct
的,值为<nil>
(它只是存储p
的类型和值)。
但是,i == nil
是假的,因为在本例中,nil
的类型是<nil>
而不是*SomeStruct
。
nil
本身,永远不在接口引用变量中存储值为<nil>
的东西。这样,只要接口引用变量的值是<nil>
,它的类型也将是<nil>
,并且与nil
的比较将按预期工作。由于这个原因,您经常会看到这样的代码:
if p == nil {
i = nil
} else {
i = p
}