Что означает интерфейс {}?


133

Я новичок в интерфейсах и пытаюсь выполнить запрос SOAP через github

Я не понимаю смысла

Msg interface{}

в этом коде:

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

type Body struct {
    Msg interface{}
}

Я наблюдал такой же синтаксис в

fmt.Println

но не понимаю, что достигается

interface{}

20
interface{}более или менее эквивалентен void *в C. Он может указывать на что угодно, и для его использования вам потребуется утверждение приведения / типа.
Ник Крейг-Вуд

Что означает интерфейс {}? См. Stackoverflow.com/a/62337836/12817546 .
Tom J

Ответы:


189

Вы можете обратиться к статье « Как использовать интерфейсы в Go » (на основе « описания интерфейсов Рассом Коксом »):

Что такое интерфейс?

Интерфейс - это две вещи:

  • это набор методов,
  • но это тоже тип

interface{}Типа, пустой интерфейс является интерфейсом , который не имеет методов.

Поскольку нет ключевого слова Implements, все типы реализуют по крайней мере нулевые методы, а удовлетворение интерфейса выполняется автоматически, все типы удовлетворяют пустому интерфейсу .
Это означает, что если вы напишете функцию, которая принимает interface{}значение в качестве параметра, вы можете предоставить этой функции любое значение .

(Это то, что Msgпредставляет в вашем вопросе: любое значение)

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

Вот где это сбивает с толку:

что это за тип внутри DoSomethingфункции ?v

Начинающих сусликов убеждают в том, что они «относятся к vлюбому типу», но это неверно.
vне любого типа; это interface{}типа .

При передаче значения в DoSomethingфункцию среда выполнения Go выполнит преобразование типа (при необходимости) и преобразует значение в interface{}значение .
Все значения имеют ровно один тип во время выполнения и vодин статический тип interface{}.

Значение интерфейса состоит из двух слов данных :

  • одно слово используется для указания таблицы методов для базового типа значения,
  • а другое слово используется для указания фактических данных, содержащихся в этом значении.

Приложение: Это статья Расса, довольно полная о структуре интерфейса:

type Stringer interface {
    String() string
}

Значения интерфейса представлены в виде пары из двух слов, дающей указатель на информацию о типе, хранящемся в интерфейсе, и указатель на связанные данные.
Назначение b значению интерфейса типа Stringer устанавливает оба слова значения интерфейса.

http://research.swtch.com/gointer2.png

Первое слово в значении интерфейса указывает на то, что я называю таблицей интерфейса или itable (произносится как i-table; в источниках времени выполнения имя реализации C - Itab).
Таблица начинается с некоторых метаданных о задействованных типах, а затем становится списком указателей на функции.
Обратите внимание, что itable соответствует типу интерфейса, а не динамическому типу .
В терминах нашего примера таблица для Stringerхранения типа Binary перечисляет методы, используемые для удовлетворения Stringer, что справедливо String: другие методы Binary ( Get) не появляются в itable.

Второе слово в значении интерфейса указывает на фактические данные , в данном случае на копию b.
Присваивание var s Stringer = bсоздает копию, bа не указывает на нее bпо той же причине, что var c uint64 = bи копия: если bпозже изменяется sи cпредполагается, что оно будет иметь исходное значение, а не новое.
Значения, хранящиеся в интерфейсах, могут быть произвольно большими, но только одно слово предназначено для хранения значения в структуре интерфейса, поэтому назначение выделяет кусок памяти в куче и записывает указатель в слот для одного слова.


4
Что вы подразумеваете под «двумя словами данных»? Что конкретно означает «слово»?
Mingyu

3
@Mingyu Я завершил ответ, чтобы проиллюстрировать эти два слова (32-битные точки).
VonC

2
@Mingyu: VonC имеет в виду слово в смысле компьютерной архитектуры - набор битов, которые определяют фрагмент данных фиксированного размера. Размер слова зависит от используемой архитектуры процессора.
Дэн Эспарса

1
спасибо @VonC за ваш ответ ... правда в том, что я устал получать downpost, когда я спрашиваю ... люди большую часть времени говорят мне, что я должен прочитать документы ... я вспомню ваше предложение, если я чувствую себя с буду правильно писать об этом ... но я действительно не могу придумать другой способ спросить. Так что в любом случае спасибо и извините за мою низкую волю. Приглашаем вас взглянуть на это: stackoverflow.com/questions/45577301/…, чтобы прояснить, почему я не люблю спрашивать.
Виктор

1
@vic без проблем, извините за ваш предыдущий неудачный опыт в качестве спрашивающего. Просто комментарии плохо подходят для вопросов и ответов.
VonC

34

interface{}означает, что вы можете поместить значение любого типа, включая ваш собственный тип. Все типы в Go удовлетворяют пустому интерфейсу ( interface{}это пустой интерфейс).
В вашем примере поле 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


12

Он называется пустым интерфейсом и реализуется всеми типами, что означает, что в 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 к определяемой пользователем структуре ??
пользователь

11

Здесь уже есть хорошие ответы. Позвольте мне добавить и свое собственное для тех, кто хочет понять это интуитивно:


Интерфейс

Вот интерфейс с одним методом:

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 {} совпадает с пустым интерфейсом выше.

var b interface{}

// true: a == b

b = a
b = 9
b = "bye"

Как видите, в этом нет ничего загадочного, но злоупотребить им очень легко. Держитесь подальше от этого, насколько можете.


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


Не type Runner interfaceиспользуется в примере с игровой площадкой Go.
Tom J

9

Из спецификаций Golang :

Тип интерфейса определяет набор методов, называемый его интерфейсом. Переменная типа интерфейса может хранить значение любого типа с набором методов, который является любым надмножеством интерфейса. Говорят, что такой тип реализует интерфейс. Значение неинициализированной переменной интерфейсного типа равно нулю.

Тип реализует любой интерфейс, содержащий любое подмножество его методов, и поэтому может реализовывать несколько отдельных интерфейсов. Например, все типы реализуют пустой интерфейс:

интерфейс{}

Понятия, которые нужно понять:

  1. У всего есть Тип . Вы можете определить новый тип, давайте назовем его Т. Допустим, теперь наш тип Tимеет 3 метода: A, B, C.
  2. Набор методов, указанных для типа, называется « типом интерфейса ». Назовем его в нашем примере: T_interface. РавноT_interface = (A, B, C)
  3. Вы можете создать «тип интерфейса», определив сигнатуру методов.MyInterface = (A, )
  4. При указании переменной от типа , «тип интерфейса», вы можете назначить на него только те типы , которые имеют интерфейс , который является надстройкой вашего интерфейса. Это означает, что все методы, содержащиеся в MyInterface, должны содержаться внутриT_interface

Вы можете сделать вывод, что все «типы интерфейса» всех типов являются надмножеством пустого интерфейса.


1

Пример, расширяющий отличный ответ @VonC и комментарий @ NickCraig-Wood. interface{}может указывать на что угодно, и для его использования вам потребуется утверждение приведения / типа.

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").

Вы также можете попросить компилятор проверить , что тип Tреализует интерфейс I, попытавшись задание: var _ I = T{}. См. Https://golang.org/doc/faq#guarantee_satisfies_interface и https://stackoverflow.com/a/60663003/12817546 .

Все типы реализуют пустой интерфейс interface{}. См. Https://talks.golang.org/2012/goforc.slide#44 и https://golang.org/ref/spec#Interface_types . В этом примере iпереназначается, на этот раз строке «4.3». iзатем присваивается новой строковой переменной, sа i.(string)before sпреобразуется в тип float64 fс использованием strconv. Наконец f, преобразуется в nтип int, равный 4. См. В чем разница между преобразованием типа и утверждением типа?

Встроенные карты и срезы Go, а также возможность использовать пустой интерфейс для создания контейнеров (с явной распаковкой) означают, что во многих случаях можно написать код, который делает то, что позволяют дженерики, хотя и менее плавно. См. Https://golang.org/doc/faq#generics .


Разделите код с интерфейсом. См. Stackoverflow.com/a/62297796/12817546 . Вызов метода «динамически». См. Stackoverflow.com/a/62336440/12817546 . Получите доступ к пакету Go. См. Stackoverflow.com/a/62278078/12817546 . Присвойте любое значение переменной. См. Stackoverflow.com/a/62337836/12817546 .
Tom J
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.