【问题标题】:Empty Interfaces in GolangGolang 中的空接口
【发布时间】:2020-05-15 12:35:49
【问题描述】:

编辑:这不是在 Go 中使用接口的正确方式。这个问题的目的是让我了解 Go 中的空接口是如何工作的。

如果 Go 中的所有类型都实现了interface{}(空接口),为什么我不能访问CatDog 结构中的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
}

【问题讨论】:

  • 经过进一步研究,我发现这是通过 A Tour of Go 进行的。 tour.golang.org/methods/15.
  • 接口指定行为(读取方法),而不是数据。

标签: go go-interface


【解决方案1】:

interface{} 是方法集,而不是字段集。如果一个类型的方法包括该接口的方法,则该类型实现该接口。由于空接口没有任何方法,所有类型都实现它。

如果需要访问某个字段,则必须获取原始类型:

name, ok:=i.(Dog).name

如果iDog,这将恢复名称。

或者,为DogCat 实现一个getName() 函数,然后它们都将实现以下接口:

type NamedType interface {
   getName() string
}

然后你可以将你的函数重写为:

func sayHi(i NamedType) {
   fmt.Println(i.getName()) 
}

【讨论】:

    【解决方案2】:

    你不能这样做,因为接口值不这样做。

    接口值做什么——不管接口类型本身;接口类型是否为空并不重要——它们拥有两个东西:

    • 某个值的具体类型(或无类型);和
    • 具体类型的值(或无值)。

    因此,如果某个变量v 或表达式e 的类型为I,其中I 是一个接口类型,那么您可以使用一些语法检查这两个“字段”中的一个或两个。它们不是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) 表示让我看看类型字段

    获取实际的 比较困难,因为 Go 或多或少需要您先检查类型。在您的情况下,您知道i 包含DogCat,因此您可以这样写:

    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 指针,则 ivalue 部分可以是nil 但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 的正确方法

    大多数情况下——也有例外——我们甚至不尝试查看某个接口的类型。相反,我们定义了一个接口来提供方法——我们可以调用的函数——来做我们需要做的事情。请参阅Burak Serdar's answer,其中接口类型具有getName 方法。然后,与其试图找出某人给我们的一些有限的类型中的哪一种,我们只是说:

    name := i.getName()
    

    在底层具体值上调用getName 方法。如果i 持有Dog,则调用func (Dog) getName() string,您需要对其进行定义。如果i 持有Cat,它会调用func (Cat) getName() string。如果您决定将名为 Bird 的类型添加到您的集合中,您可以定义 func (Bird) getName() string,依此类推。

    (通常,这些方法也会导出GetName,而不是getName。)

    【讨论】:

      【解决方案3】:

      就像你说的,interface{} 是一个界面。你怎么能假设“空”的东西中有一个name 字段(fmt.Println(i.name))?你不能。事实上,go 不支持接口中的字段,只支持方法。

      你可以做的(当然有很多解决方案)是定义一个接口(我们称之为Pet),它有一个返回宠物名字的方法:

      type Pet interface {
          getName() string
      }
      

      然后你可以在sayHi函数中接收这个接口(它的对象),并用它来打印宠物的名字:

      func sayHi(i Pet) {
          fmt.Println(i.getName())
      }
      

      现在,为了能够将DogCat 传递给sayHi(),这两个结构都必须实现接口。因此,为它们定义getName() 方法:

      func (d Dog) getName() string {
          return d.name
      }
      
      func (c Cat) getName() string {
          return c.name
      }
      

      就是这样。

      【讨论】:

        【解决方案4】:

        也可以使用下面的开关类型来实现-

        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
        }
        

        游乐场链接 - https://go.dev/play/p/p4s3_jmmbGJ

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-06-09
          • 2019-04-26
          • 2018-07-29
          • 2019-03-20
          • 1970-01-01
          • 2017-04-26
          相关资源
          最近更新 更多