【问题标题】:What's the meaning of interface{}?接口{}是什么意思?
【发布时间】:2014-06-02 15:35:49
【问题描述】:

我是接口新手,正在尝试通过github 进行 SOAP 请求

我不明白

的意思
Msg interface{}

在这段代码中:

type Envelope struct {
    Body `xml:"soap:"`
}

type Body struct {
    Msg interface{}
}

我在

中观察到了相同的语法
fmt.Println

但不明白

interface{}

【问题讨论】:

  • interface{} 或多或少等同于 C 语言中的 void *。它可以指向任何东西,您需要一个强制转换/类型断言才能使用它。
  • interface{}是什么意思?见stackoverflow.com/a/62337836/12817546

标签: go


【解决方案1】:

注意:Go 1.18(2022 年第一季度)确实将 interface{} 重命名为 anyinterface{} 的别名)。
请参阅issue 49884CL 368254commit 2580d0e
请参阅此答案的最后一部分。


可以参考文章“How to use interfaces in Go”(基于“Russ Cox’s description of interfaces”):

什么是接口?

一个接口是两个东西:

  • 它是一组方法,
  • 但它也是一种类型

interface{}类型(或Go 1.18+的any),空接口是没有方法的接口。

由于没有 implements 关键字,所有类型都至少实现零个方法,并且满足一个接口是自动完成的,所有类型都满足空接口
这意味着,如果您编写一个将 interface{} 值作为参数的函数,您可以为该函数提供任何值

(这就是Msg 在您的问题中所代表的含义:任何值)

func DoSomething(v interface{}) {
   // ...
}


func DoSomething(v any) {
   // ...
}

这就是令人困惑的地方:

DoSomething 函数内部,v 的类型是什么?

新手地鼠被引导相信“v 是任何类型”,但这是错误的。
v 不是任何类型; 它是interface{} 类型

将值传递给DoSomething 函数时,Go 运行时将执行类型转换(如有必要),并将值转换为interface{}.
所有值在运行时都只有一种类型,v 的一种静态类型是 interface{}(或在 Go 1.18+ 中使用 any)。

一个接口值由两个数据字构成

  • 一个词用于指向值的基础类型的方法表,
  • 另一个词用于指向该值所保存的实际数据。

附录:这是 Russ 的关于接口结构的文章相当完整:

type Stringer interface {
    String() string
}

接口值表示为两个字对,给出一个指向接口中存储的类型信息的指针和一个指向相关数据的指针。
将 b 分配给 Stringer 类型的接口值会设置接口值的两个字。

接口值中的第一个词指向我所说的接口表或 itable(读作 i-table;在运行时源代码中,C 实现名称是 Itab)。
itable 以有关所涉及类型的一些元数据开始,然后成为函数指针列表。
请注意,itable 对应于接口类型,而不是动态类型
在我们的示例中,Stringer 持有类型 Binary 的 itable 列出了用于满足 Stringer 的方法,即 String:Binary 的其他方法(Get)在 itable 中没有出现。

接口值中的第二个字指向实际数据,在本例中是b的副本。
赋值var s Stringer = b 复制b 而不是指向b,原因与var c uint64 = b 复制的原因相同:如果b 稍后更改,sc 应该具有原始价值,而不是新价值。
存储在接口中的值可能是任意大的,但只有一个字专门用于保存接口结构中的值,因此赋值会在堆上分配一块内存并将指针记录在一个字槽中。


Issue 33232 似乎在 Go 1.18(2022 年第一季度)中指出 any 作为 interface{} 的别名

Russ Cox explains:

  1. 'any' 仅用于约束是每个泛型文章(书籍、博客文章等)中都会出现的细节。 如果我们认为我们最终可能会允许它,那么从一开始就允许它并避免使所有书面材料无效是有意义的。

  2. 'any' 仅用于约束是一个意外的删减,它降低了概念的通用性和正交性。 说“让我们拭目以待”很容易,但规定用途往往会产生比完全概括更多的参差不齐的特征。我们在类型别名中也看到了这一点(谢天谢地,我们几乎拒绝了所有提议的删减)。

  3. 如果在泛型中允许使用“any”,但在非泛型代码中不允许使用,那么它可能会鼓励人们过度使用泛型,仅仅是因为“any”比“interface{}”写得更好,当决定仿制药与否真的应该考虑其他因素。

  4. 如果我们也允许 'any' 用于普通的非泛型用法,那么在代码中看到 interface{} 可以作为一种信号,表明该代码早于泛型并且在后泛型世界中尚未被重新考虑. 一些使用interface{} 的代码应该使用泛型。其他代码应该继续使用接口。
    以一种或另一种方式重写它以删除文本“interface{}”会给人们一种清晰的方式来查看他们更新的内容和没有更新的内容。 (当然,出于向后兼容的原因,一些使用泛型可能更好的代码仍必须使用interface{},但仍可以对其进行更新以确认已考虑并做出决定。)

该线程还包括一个explanation about interface{}

这不是特殊设计,而是 Go 类型声明语法的逻辑结果。

您可以使用具有多个方法的匿名接口:

func f(a interface{Foo(); Bar()}) {
   a.Foo()
   a.Bar()
}

类似于如何在需要类型的任何地方使用匿名结构:

func f(a struct{Foo int; Bar string}) {
   fmt.Println(a.Foo)
   fmt.Println(a.Bar)
}

一个空接口恰好匹配所有类型,因为所有类型至少有零个方法。

如果您想保持一致/不想引入特殊情况,删除 interface{} 意味着从语言中删除所有界面功能。

【讨论】:

  • “两个数据字”是什么意思?具体来说,“词”是什么意思?
  • @Mingyu 我已经完成了说明这两个词(32位点)的答案。
  • @Mingyu:VonC 指的是计算机体系结构意义上的word——定义固定大小数据的位集合。字长由您使用的处理器架构决定。
  • 感谢@VonC 的回复……事实上,当我问问题时,我已经厌倦了低调……人们大多数时候告诉我应该阅读文档……我如果我愿意为它适当地写一篇文章,我会记得你的建议......但我真的想不出另一种方式来提问。所以还是谢谢你,原谅我的低意志。欢迎你看看这个:stackoverflow.com/questions/45577301/… 澄清我为什么不喜欢问。
  • @vic 没问题,对于您之前作为提问者的糟糕经历,我们深表歉意。只是 cmets 不适合问答。
【解决方案2】:

interface{} 表示您可以放置​​任何类型的值,包括您自己的自定义类型。 Go 中的所有类型都满足一个空接口(@98​​7654323@ 是一个空接口)。
在您的示例中, Msg 字段可以具有任何类型的值。

例子:

package main

import (
    "fmt"
)

type Body struct {
    Msg interface{}
}

func main() {
    b := Body{}
    b.Msg = "5"
    fmt.Printf("%#v %T \n", b.Msg, b.Msg) // Output: "5" string
    b.Msg = 5

    fmt.Printf("%#v %T", b.Msg, b.Msg) //Output:  5 int
}

Go Playground

【讨论】:

    【解决方案3】:

    它被称为空接口,由所有类型实现,这意味着您可以在Msg字段中放置任何内容。

    例子:

    body := Body{3}
    fmt.Printf("%#v\n", body) // -> main.Body{Msg:3}
    
    body = Body{"anything"}
    fmt.Printf("%#v\n", body) // -> main.Body{Msg:"anything"}
    
    body = Body{body}
    fmt.Printf("%#v\n", body) // -> main.Body{Msg:main.Body{Msg:"anything"}}
    

    这是一个类型在拥有接口的所有方法后立即实现接口这一事实的逻辑扩展。

    【讨论】:

    • 意味着它可能是用户定义结构的 int ??
    • "这是一个逻辑扩展,一个类型一旦拥有接口的所有方法就实现一个接口。"在 Go 中理解 interface 的设计对我来说是一个非常有趣的想法。
    【解决方案4】:

    这里已经有了很好的答案。让我也为其他想要直观理解它的人添加我自己的:


    界面

    这是一个使用一种方法的界面:

    type Runner interface {
        Run()
    }
    

    所以任何具有Run() 方法的类型都满足 Runner 接口:

    type Program struct {
        /* fields */
    }
    
    func (p Program) Run() {
        /* running */
    }
    
    func (p Program) Stop() {
        /* stopping */
    }
    
    • 虽然 Program 类型也有 Stop 方法,但它仍然满足 Runner 接口,因为只需要一个接口的所有方法来满足它。

    • 所以,它有一个Run方法,并且满足Runner接口。


    空接口

    这是一个没有任何方法的命名空接口:

    type Empty interface {
        /* it has no methods */
    }
    

    所以任何类型都满足这个接口。因为,不需要任何方法来满足这个接口。例如:

    // Because, Empty interface has no methods, following types satisfy the Empty interface
    var a Empty
    
    a = 5
    a = 6.5
    a = "hello"
    

    但是,上面的程序类型满足吗?是的:

    a = Program{} // ok
    

    interface{} 等于上面的 Empty 接口。

    var b interface{}
    
    // true: a == b
    
    b = a
    b = 9
    b = "bye"
    

    如您所见,它没有什么神秘之处,但很容易被滥用。尽可能远离它。


    https://play.golang.org/p/A-vwTddWJ7G

    【讨论】:

    • type Runner interface 在 Go 操场示例中未使用。
    【解决方案5】:

    来自Golang Specifications

    接口类型指定了一个称为其接口的方法集。一种 接口类型的变量可以用方法存储任何类型的值 set 是接口的任何超集。据说这种类型 实现接口。未初始化变量的值 接口类型为零。

    一个类型实现了包含其方法的任何子集的任何接口 因此可以实现几个不同的接口。例如, 所有类型都实现空接口:

    界面{}

    要掌握的概念是:

    1. 一切都有一个类型。你可以定义一个新类型,我们称它为 T。假设现在我们的类型T 有 3 个方法:ABC
    2. 为类型指定的方法集称为“接口类型”。让我们在我们的示例中调用它:T_interface。等于T_interface = (A, B, C)
    3. 您可以通过定义方法的签名来创建“接口类型”。 MyInterface = (A, )
    4. 当您指定类型变量“接口类型”时,您可以只为其分配接口是接口超集的类型。 这意味着MyInterface 中包含的所有方法都必须包含在T_interface

    可以推断出所有类型的所有“接口类型”都是空接口的超集。

    【讨论】:

      【解决方案6】:

      扩展了@VonC 的出色答案和@NickCraig-Wood 的评论的示例。 interface{} 可以指向任何东西,你需要一个 cast/type 断言来使用它。

      package main
      
      import (
          . "fmt"
          "strconv"
      )
      
      var c = cat("Fish")
      var d = dog("Bone")
      
      func main() {
          var i interface{} = c
          switch i.(type) {
          case cat:
              c.Eat() // Fish
          }
      
          i = d
          switch i.(type) {
          case dog:
              d.Eat() // Bone
          }
      
          i = "4.3"
          Printf("%T %v\n", i, i) // string 4.3
          s, _ := i.(string)      // type assertion
          f, _ := strconv.ParseFloat(s, 64)
          n := int(f)             // type conversion
          Printf("%T %v\n", n, n) // int 4
      }
      
      type cat string
      type dog string
      func (c cat) Eat() { Println(c) }
      func (d dog) Eat() { Println(d) }
      

      i是一个空接口变量,值为cat("Fish")。从接口类型的值创建方法值是合法的。见https://golang.org/ref/spec#Interface_types

      类型开关确认i接口类型为cat("Fish")。见https://golang.org/doc/effective_go.html#type_switch。然后将i 重新分配给dog("Bone")。类型开关确认i 接口的类型已更改为dog("Bone")

      您还可以通过尝试赋值:var _ I = T{} 来要求编译器检查 T 类型是否实现了接口 I。请参阅https://golang.org/doc/faq#guarantee_satisfies_interfacehttps://stackoverflow.com/a/60663003/12817546

      所有类型都实现空接口interface{}。请参阅 https://talks.golang.org/2012/goforc.slide#44https://golang.org/ref/spec#Interface_types 。在此示例中,i 被重新分配,这次分配给字符串“4.3”。i 然后分配给带有i.(string) 的新字符串变量i.(string),然后将s 转换为float64 类型f使用strconv。最后将f 转换为n 一个等于4 的int 类型。见What is the difference between type conversion and type assertion?

      Go 的内置映射和切片,以及使用空接口构造容器(通过显式拆箱)的能力,这意味着在许多情况下,即使不太顺利,也可以编写能够实现泛型功能的代码。见https://golang.org/doc/faq#generics

      【讨论】:

      【解决方案7】:

      接口是编译时未知的类型

      它是对象和结构类型之间的契约,以满足通用功能 要么 作用于不同类型结构对象的通用功能 例如在下面的代码中,PrintDetails 是一种常见的功能,作用于不同类型的结构,如工程师、经理、 高级主管 请找到示例代码 interface examplehttps://play.golang.org/p/QnAqEYGiiF7

      【讨论】:

      【解决方案8】:
      • 方法可以绑定到 GO 中的任何类型(int、字符串、指针等)

      • 接口是一种明确一个类型应该有什么方法的方法,只要一个类型实现了这些方法,就可以将其分配给这个接口。

      • 接口{}只是没有明确方法,所以它可以接受任何类型

      【讨论】:

        猜你喜欢
        • 2022-05-23
        • 1970-01-01
        • 1970-01-01
        • 2011-04-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-09-30
        相关资源
        最近更新 更多