Golang 中的空接口
Empty Interfaces in Golang
编辑:这不是在 Go 中使用接口的正确方法。这个问题的目的是让我了解空接口在 Go 中是如何工作的。
如果 Go 中的所有类型都实现 interface{}
(空接口),为什么我不能访问 Cat
和 Dog
结构中的 name
字段?如何通过函数 sayHi() 访问每个结构体的名称字段?
package main
import (
"fmt"
)
func sayHi(i interface{}) {
fmt.Println(i, "says hello")
// Not understanding this error message
fmt.Println(i.name) // i.name undefined (type interface {} is interface with no methods)
}
type Dog struct{
name string
}
type Cat struct{
name string
}
func main() {
d := Dog{"Sparky"}
c := Cat{"Garfield"}
sayHi(d) // {Sparky} says hello
sayHi(c) // {Garfield} says hello
}
interface{}
是方法集,不是字段集。如果一个类型的方法包含该接口的方法,则它实现了一个接口。由于空接口没有任何方法,所有类型都实现它。
如果你需要访问一个字段,你必须得到原始类型:
name, ok:=i.(Dog).name
如果 i
是 Dog
,这将恢复名称。
或者,为 Dog
和 Cat
实现一个 getName()
函数,然后它们都将实现以下接口:
type NamedType interface {
getName() string
}
然后您可以将函数重写为:
func sayHi(i NamedType) {
fmt.Println(i.getName())
}
你不能那样做,因为接口值不那样做。
接口值做什么——不管接口类型本身;接口类型是否为空并不重要——它们包含两个东西:
- 某个值的具体类型(或无类型);和
- 该具体类型的值(或无值)。
因此,如果某些变量 v
或表达式 e
具有类型 I
,其中 I
是接口类型,那么您可以使用某些语法检查或这两个 "fields"。它们不是 struct
字段,因此您不能只使用 v.type
,但您 可以 这样做:
switch v.(type) {
case int: // the value in v has type int
case *float64: // the value in v has type float64
// etc
}
switch
中的.(type)
表示让我看一下类型字段。
获取实际的 value 更难,因为 Go 或多或少要求您先检查类型。在你的例子中,你知道 i
持有 Dog
或 Cat
,所以你可以写:
var name string
switch i.(type) {
case Dog: name = i.(Dog).name
case Cat: name = i.(Cat).name
default: panic("whatever 'i' is, it is not a Dog or Cat")
}
fmt.Println(name)
这很笨拙,有很多方法可以让它不那么笨拙,但这始终是第一步:弄清楚类型是什么。
好吧,有时在第一步之前有一个步骤:找出变量中是否有任何东西。你这样做:
if i == nil {
...
}
但是请注意,如果 i
中有一些 typed 值,并且该类型可以包含 nil 指针,则 value[= i
的 88=] 部分可以为零,但 i == nil
将为假。那是因为 i
里面有一个 type.
var i interface{}
var p *int
if i == nil {
fmt.Println("i is initially nil")
}
if p == nil {
fmt.Println("p is nil")
}
i = p
if i != nil {
fmt.Printf("i is now not nil, even though i.(*int) is %v\n", i.(*int))
}
(在 Go playground 上试试这个)。
这通常不是正确的使用方式 interface
大多数时候——也有例外——我们甚至不尝试查看某些接口的类型。相反,我们定义了一个接口,它提供 方法 ——我们可以调用的函数——来完成我们需要完成的事情。请参阅 ,其中接口类型具有 getName
方法。然后,我们不会试图弄清楚某人给了我们哪些有限的类型,我们只是说:
name := i.getName()
在基础具体值上调用 getName
方法 。如果 i
持有一个 Dog
,它调用 func (Dog) getName() string
,您需要对其进行定义。如果 i
持有 Cat
,它调用 func (Cat) getName() string
。如果您决定向您的集合中添加一个名为 Bird
的类型,您可以定义 func (Bird) getName() string
,依此类推。
(通常,这些方法也会 导出 :GetName
,而不是 getName
。)
如您所说,interface{}
是一个空 接口。你怎么能假设 "empty" 中有一个 name
字段 (fmt.Println(i.name)
)?你不能。事实上,go 不支持接口中的字段,只支持方法。
您可以做的(当然还有很多解决方案)是定义一个接口(我们称之为 Pet
),它有一个返回宠物名称的方法:
type Pet interface {
getName() string
}
然后你可以在sayHi
函数中接收这个接口(它的对象)并用它来打印宠物的名字:
func sayHi(i Pet) {
fmt.Println(i.getName())
}
现在,为了能够将 Dog
或 Cat
传递给 sayHi()
,这两个结构都必须实现接口。因此,为它们定义 getName()
方法:
func (d Dog) getName() string {
return d.name
}
func (c Cat) getName() string {
return c.name
}
就是这样。
也可以使用如下开关类型实现-
package main
import (
"fmt"
)
func sayHi(i interface{}) {
fmt.Println(i, "says hello")
switch x := i.(type) {
case Dog:
fmt.Println(x.name)
case Cat:
fmt.Println(x.name)
}
}
type Dog struct {
name string
}
type Cat struct {
name string
}
func main() {
d := Dog{"Sparky"}
c := Cat{"Garfield"}
sayHi(d) // {Sparky} says hello
sayHi(c) // {Garfield} says hello
}
游乐场link - https://go.dev/play/p/p4s3_jmmbGJ
编辑:这不是在 Go 中使用接口的正确方法。这个问题的目的是让我了解空接口在 Go 中是如何工作的。
如果 Go 中的所有类型都实现 interface{}
(空接口),为什么我不能访问 Cat
和 Dog
结构中的 name
字段?如何通过函数 sayHi() 访问每个结构体的名称字段?
package main
import (
"fmt"
)
func sayHi(i interface{}) {
fmt.Println(i, "says hello")
// Not understanding this error message
fmt.Println(i.name) // i.name undefined (type interface {} is interface with no methods)
}
type Dog struct{
name string
}
type Cat struct{
name string
}
func main() {
d := Dog{"Sparky"}
c := Cat{"Garfield"}
sayHi(d) // {Sparky} says hello
sayHi(c) // {Garfield} says hello
}
interface{}
是方法集,不是字段集。如果一个类型的方法包含该接口的方法,则它实现了一个接口。由于空接口没有任何方法,所有类型都实现它。
如果你需要访问一个字段,你必须得到原始类型:
name, ok:=i.(Dog).name
如果 i
是 Dog
,这将恢复名称。
或者,为 Dog
和 Cat
实现一个 getName()
函数,然后它们都将实现以下接口:
type NamedType interface {
getName() string
}
然后您可以将函数重写为:
func sayHi(i NamedType) {
fmt.Println(i.getName())
}
你不能那样做,因为接口值不那样做。
接口值做什么——不管接口类型本身;接口类型是否为空并不重要——它们包含两个东西:
- 某个值的具体类型(或无类型);和
- 该具体类型的值(或无值)。
因此,如果某些变量 v
或表达式 e
具有类型 I
,其中 I
是接口类型,那么您可以使用某些语法检查或这两个 "fields"。它们不是 struct
字段,因此您不能只使用 v.type
,但您 可以 这样做:
switch v.(type) {
case int: // the value in v has type int
case *float64: // the value in v has type float64
// etc
}
switch
中的.(type)
表示让我看一下类型字段。
获取实际的 value 更难,因为 Go 或多或少要求您先检查类型。在你的例子中,你知道 i
持有 Dog
或 Cat
,所以你可以写:
var name string
switch i.(type) {
case Dog: name = i.(Dog).name
case Cat: name = i.(Cat).name
default: panic("whatever 'i' is, it is not a Dog or Cat")
}
fmt.Println(name)
这很笨拙,有很多方法可以让它不那么笨拙,但这始终是第一步:弄清楚类型是什么。
好吧,有时在第一步之前有一个步骤:找出变量中是否有任何东西。你这样做:
if i == nil {
...
}
但是请注意,如果 i
中有一些 typed 值,并且该类型可以包含 nil 指针,则 value[= i
的 88=] 部分可以为零,但 i == nil
将为假。那是因为 i
里面有一个 type.
var i interface{}
var p *int
if i == nil {
fmt.Println("i is initially nil")
}
if p == nil {
fmt.Println("p is nil")
}
i = p
if i != nil {
fmt.Printf("i is now not nil, even though i.(*int) is %v\n", i.(*int))
}
(在 Go playground 上试试这个)。
这通常不是正确的使用方式 interface
大多数时候——也有例外——我们甚至不尝试查看某些接口的类型。相反,我们定义了一个接口,它提供 方法 ——我们可以调用的函数——来完成我们需要完成的事情。请参阅 getName
方法。然后,我们不会试图弄清楚某人给了我们哪些有限的类型,我们只是说:
name := i.getName()
在基础具体值上调用 getName
方法 。如果 i
持有一个 Dog
,它调用 func (Dog) getName() string
,您需要对其进行定义。如果 i
持有 Cat
,它调用 func (Cat) getName() string
。如果您决定向您的集合中添加一个名为 Bird
的类型,您可以定义 func (Bird) getName() string
,依此类推。
(通常,这些方法也会 导出 :GetName
,而不是 getName
。)
如您所说,interface{}
是一个空 接口。你怎么能假设 "empty" 中有一个 name
字段 (fmt.Println(i.name)
)?你不能。事实上,go 不支持接口中的字段,只支持方法。
您可以做的(当然还有很多解决方案)是定义一个接口(我们称之为 Pet
),它有一个返回宠物名称的方法:
type Pet interface {
getName() string
}
然后你可以在sayHi
函数中接收这个接口(它的对象)并用它来打印宠物的名字:
func sayHi(i Pet) {
fmt.Println(i.getName())
}
现在,为了能够将 Dog
或 Cat
传递给 sayHi()
,这两个结构都必须实现接口。因此,为它们定义 getName()
方法:
func (d Dog) getName() string {
return d.name
}
func (c Cat) getName() string {
return c.name
}
就是这样。
也可以使用如下开关类型实现-
package main
import (
"fmt"
)
func sayHi(i interface{}) {
fmt.Println(i, "says hello")
switch x := i.(type) {
case Dog:
fmt.Println(x.name)
case Cat:
fmt.Println(x.name)
}
}
type Dog struct {
name string
}
type Cat struct {
name string
}
func main() {
d := Dog{"Sparky"}
c := Cat{"Garfield"}
sayHi(d) // {Sparky} says hello
sayHi(c) // {Garfield} says hello
}
游乐场link - https://go.dev/play/p/p4s3_jmmbGJ