У чому сенс інтерфейсу {}?


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 .
Том J

Відповіді:


189

Ви можете посилатися на статтю " Як використовувати інтерфейси в Go " (на основі " опису інтерфейсів Russ Cox "):

Що таке інтерфейс?

Інтерфейс - це дві речі:

  • це набір методів,
  • але це також тип

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

Оскільки не існує ключового слова реалізації, всі типи реалізують принаймні нульові методи, а задоволення інтерфейсу здійснюється автоматично, усі типи задовольняють порожній інтерфейс .
Це означає, що якщо ви пишете функцію, яка приймає 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 починається з деяких метаданих про задіяні типи, а потім стає списком функціональних покажчиків.
Зауважте, що itable відповідає типу інтерфейсу, а не динамічному типу .
З точки зору нашого прикладу, 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 за вашу відповідь ... правда, що я втомився підніматися вниз, коли я прошу щось .. люди більшу частину часу кажуть мені, що я повинен прочитати документи ... я згадаю вашу пропозицію, якщо я відчуваю, що з буде належним чином написати повідомлення про це ... але я дійсно не можу подумати про інший спосіб запитати. Тож все одно дякую і вибачте за мою низьку волю. Ви можете поглянути на це: 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
}

Ідіть на майданчик


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"}}

Це логічне розширення того факту, що тип реалізує інтерфейс, як тільки він має всі методи інтерфейсу.


означає, що це могла бути структурою, визначеною користувачем ??
користувач

11

Тут вже є хороші відповіді. Дозвольте додати своє також для інших, хто хоче це зрозуміти інтуїтивно:


Інтерфейс

Ось інтерфейс з одним методом:

type Runner interface {
    Run()
}

Отже, будь-який тип із Run()методом задовольняє інтерфейс Runner:

type Program struct {
    /* fields */
}

func (p Program) Run() {
    /* running */
}

func (p Program) Stop() {
    /* stopping */
}
  • Хоча тип програми також має метод 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

інтерфейс {} дорівнює Порожньому інтерфейсу вище.

var b interface{}

// true: a == b

b = a
b = 9
b = "bye"

Як бачите, в цьому немає нічого загадкового, але зловживати дуже легко. Тримайтеся подалі від нього стільки, скільки можете.


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


Не type Runner interfaceвикористовується у прикладі ігрової площадки Go.
Том Дж

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)перш ніж 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 .
Том J
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.