Jakie jest znaczenie interfejsu {}?

140

Jestem nowy w interfejsach i próbuję wykonać żądanie SOAP przez github

Nie rozumiem znaczenia

Msg interface{}

w tym kodzie:

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

type Body struct {
    Msg interface{}
}

Zauważyłem tę samą składnię w

fmt.Println

ale nie rozumiem, przez co się osiąga

interface{}
użytkownik
źródło
21
interface{}jest mniej więcej odpowiednikiem void *w C. Może wskazywać na wszystko i potrzebujesz asercji rzutowania / typu, aby go użyć.
Nick Craig-Wood
Jakie jest znaczenie interfejsu {}? Zobacz stackoverflow.com/a/62337836/12817546 .
Tom L

Odpowiedzi:

195

Możesz zapoznać się z artykułem „ Jak korzystać z interfejsów w Go ” (na podstawie „ opisu interfejsów Russa Coxa ”):

Co to jest interfejs?

Interfejs to dwie rzeczy:

  • to zbiór metod,
  • ale to też jest typ

interface{}Litery tym pusta interfejs jest interfejsem, który nie ma żadnych metod.

Ponieważ nie ma słowa kluczowego implements, wszystkie typy implementują co najmniej zero metod, a spełnienie interfejsu jest wykonywane automatycznie, wszystkie typy spełniają pusty interfejs .
Oznacza to, że jeśli napiszesz funkcję, która przyjmuje interface{}wartość jako parametr, możesz nadać tej funkcji dowolną wartość .

(Oto, co Msgreprezentuje twoje pytanie: dowolna wartość)

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

Oto, gdzie robi się mylące:

wewnątrz DoSomethingfunkcji, jaki jest vjej typ?

Początkującym susłom prowadzi się przekonanie, że „ vsą dowolnego typu”, ale to jest złe.
vnie jest żadnego rodzaju; to jest interface{}typowe .

Podczas przekazywania wartości do DoSomethingfunkcji środowisko wykonawcze Go wykona konwersję typu (w razie potrzeby) i przekonwertuje wartość na interface{}wartość .
Wszystkie wartości mają dokładnie jeden typ w czasie wykonywania, a vjeden typ statyczny to interface{}.

Wartość interfejsu składa się z dwóch słów danych :

  • jedno słowo jest używane do wskazania tabeli metod dla typu bazowego wartości,
  • a drugie słowo jest używane do wskazania rzeczywistych danych przechowywanych przez tę wartość.

Dodatek: Oto artykuł Russa dotyczący struktury interfejsu:

type Stringer interface {
    String() string
}

Wartości interfejsu są reprezentowane jako para dwóch słów, dająca wskaźnik do informacji o typie przechowywanym w interfejsie oraz wskaźnik do powiązanych danych.
Przypisanie b do wartości interfejsu typu Stringer ustawia oba słowa wartości interfejsu.

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

Pierwsze słowo w wartości interfejsu wskazuje na to, co nazywam tabelą interfejsów lub itable (wymawiane i-table; w źródłach środowiska uruchomieniowego nazwa implementacji C to Itab).
Część itable zaczyna się od pewnych metadanych dotyczących typów, a następnie staje się listą wskaźników funkcji.
Zauważ, że itable odpowiada typowi interfejsu, a nie typowi dynamicznemu .
Jeśli chodzi o nasz przykład, opcja Stringerprzechowywania typu Binary zawiera listę metod używanych do zaspokojenia Stringer, czyli po prostu String: Inne metody Binary ( Get) nie pojawiają się w itable.

Drugie słowo w wartości interfejsu wskazuje na rzeczywiste dane , w tym przypadku kopię b.
Przypisanie var s Stringer = btworzy kopię bzamiast wskazywać na bz tego samego powodu, który var c uint64 = btworzy kopię: jeśli bpóźniej ulegnie zmianie si cprzypuszczalnie ma mieć oryginalną wartość, a nie nową.
Wartości przechowywane w interfejsach mogą być dowolnie duże, ale tylko jedno słowo jest przeznaczone do przechowywania wartości w strukturze interfejsu, więc przypisanie przydziela porcję pamięci na stercie i zapisuje wskaźnik w gnieździe jednowyrazowym.

VonC
źródło
4
Co masz na myśli mówiąc „dwa słowa danych”? A konkretnie, co oznacza „słowo”?
Mingyu
3
@Mingyu Ukończyłem odpowiedź, aby zilustrować te dwa słowa (punkty 32-bitowe).
VonC
2
@Mingyu: VonC odnosi się do słowa w sensie architektury komputera - zbioru bitów, które definiują fragment danych o stałym rozmiarze. Rozmiar słowa zależy od używanej architektury procesora.
Dan Esparza
1
dzięki @VonC za twoją odpowiedź ... prawda jest taka, że ​​mam dość stania w dół, kiedy pytam o rzeczy .. ludzie przez większość czasu mówią mi, że powinienem przeczytać dokumenty ... przypomnę sobie twoją sugestię, jeśli będę czuć się z chcę poprawnie napisać post ... ale naprawdę nie mogę wymyślić innego sposobu, aby zapytać. W każdym razie dziękuję i przepraszam za moją niską wolę. Zapraszamy do obejrzenia tego: stackoverflow.com/questions/45577301/ ... aby wyjaśnić, dlaczego nie lubię pytać.
Victor
1
@vic nie ma problemu i przepraszam za poprzednie złe doświadczenia jako pytającego. Po prostu komentarze nie pasują do pytań i odpowiedzi.
VonC,
34

interface{}oznacza, że ​​możesz podać wartość dowolnego typu, w tym własny typ niestandardowy. Wszystkie typy w Go spełniają wymagania pustego interfejsu ( interface{}jest to pusty interfejs).
W Twoim przykładzie pole Msg może mieć wartość dowolnego typu.

Przykład:

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
}

Idź na plac zabaw

Minty
źródło
12

Nazywa się to pustym interfejsem i jest implementowane przez wszystkie typy, co oznacza, że ​​możesz umieścić wszystko wMsg polu.

Przykład:

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

Jest to logiczne rozszerzenie faktu, że typ implementuje interfejs, gdy tylko ma wszystkie metody interfejsu.

Denys Séguret
źródło
oznacza, że ​​może to być struktura zdefiniowana przez użytkownika?
użytkownik
11

Tutaj są już dobre odpowiedzi. Dodam też własne dla innych, którzy chcą to zrozumieć intuicyjnie:


Berło

Oto interfejs z jedną metodą:

type Runner interface {
    Run()
}

Zatem każdy typ, który ma Run()metodę, spełnia interfejs Runnera:

type Program struct {
    /* fields */
}

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

func (p Program) Stop() {
    /* stopping */
}
  • Chociaż typ Program ma również metodę Stop, nadal spełnia on interfejs Runnera, ponieważ wszystko, czego potrzeba, to mieć wszystkie metody interfejsu, aby go spełnić.

  • Tak więc ma metodę Run i spełnia interfejs Runner.


Pusty interfejs

Oto nazwany pusty interfejs bez żadnych metod:

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

Więc każdy typ spełnia ten interfejs. Ponieważ nie jest potrzebna żadna metoda, aby spełnić wymagania tego interfejsu. Na przykład:

// Because, Empty interface has no methods, following types satisfy the Empty interface
var a Empty

a = 5
a = 6.5
a = "hello"

Ale czy powyższy typ programu to spełnia? Tak:

a = Program{} // ok

interfejs {} jest równy powyższemu pustemu interfejsowi.

var b interface{}

// true: a == b

b = a
b = 9
b = "bye"

Jak widzisz, nie ma w tym nic tajemniczego, ale bardzo łatwo jest go nadużyć. Trzymaj się od tego z daleka, jak tylko możesz.


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

Inanc Gumus
źródło
Nie type Runner interfacejest używany w przykładzie placu zabaw Go.
Tom L
9

Ze specyfikacji Golang :

Typ interfejsu określa zestaw metod zwany jego interfejsem. Zmienna typu interfejsu może przechowywać wartość dowolnego typu z zestawem metod będącym dowolnym nadzbiorem interfejsu. Mówi się, że taki typ implementuje interfejs. Wartość niezainicjowanej zmiennej typu interfejsu wynosi nil.

Typ implementuje dowolny interfejs zawierający dowolny podzbiór jego metod i dlatego może implementować kilka różnych interfejsów. Na przykład wszystkie typy implementują pusty interfejs:

berło{}

Koncepcje grapsów to:

  1. Wszystko ma swój typ . Można zdefiniować nowy typ, nazwijmy to powiedzmy T. Przejdźmy teraz nasz typ Tposiada 3 metody: A, B, C.
  2. Zestaw metod określonych dla typu jest nazywany „ typem interfejsu ”. Nazwijmy to w naszym przykładzie: T_interface. Jest równeT_interface = (A, B, C)
  3. Możesz utworzyć „typ interfejsu”, definiując sygnaturę metod.MyInterface = (A, )
  4. Po określeniu zmiennej z typu „typ interfejsu”, można przypisać do niego tylko typy, które posiadają interfejs, który jest rozszerzeniem interfejsu. Oznacza to, że wszystkie zawarte w nim metody MyInterfacemuszą być zawarte w środkuT_interface

Możesz wywnioskować, że wszystkie „typy interfejsów” wszystkich typów stanowią nadzbiór pustego interfejsu.

fabrizioM
źródło
1

Przykład, który rozszerza doskonałą odpowiedź @VonC i komentarz @ NickCraig-Wood. interface{}może wskazywać na cokolwiek i potrzebujesz asercji rzutowania / typu, aby go użyć.

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

ijest zmienną pustego interfejsu z wartością cat("Fish"). Tworzenie wartości metody z wartości typu interfejsu jest dozwolone. Zobacz https://golang.org/ref/spec#Interface_types .

Przełącznik typu potwierdza i, że typ interfejsu to cat("Fish"). Zobacz https://golang.org/doc/effective_go.html#type_switch . ijest następnie przypisywany do dog("Bone"). Przełącznik typu potwierdza, że ityp interfejsu zmienił się na dog("Bone").

Można również poprosić kompilator, by sprawdzić, czy typ Timplementuje interfejs Ipróbując zadanie: var _ I = T{}. Zobacz https://golang.org/doc/faq#guarantee_satisfies_interface i https://stackoverflow.com/a/60663003/12817546 .

Wszystkie typy implementują pusty interfejs interface{}. Zobacz https://talks.golang.org/2012/goforc.slide#44 i https://golang.org/ref/spec#Interface_types . W tym przykładzie izostanie ponownie przypisany, tym razem do łańcucha „4.3”. ijest następnie przypisywany do nowej zmiennej łańcuchowej, sz którą i.(string)przed sjest konwertowana na typ float64 fprzy użyciu strconv. Ostatecznie fjest konwertowany na ntyp int równy 4. Zobacz Jaka jest różnica między konwersją typu a potwierdzeniem typu?

Wbudowane mapy i wycinki w Go, a także możliwość używania pustego interfejsu do konstruowania kontenerów (z jawnym rozpakowywaniem) oznaczają, że w wielu przypadkach można napisać kod, który robi to, co generyczne umożliwiłyby, jeśli jest mniej płynne. Zobacz https://golang.org/doc/faq#generics .

Tom L.
źródło
Oddziel kod za pomocą interfejsu. Zobacz stackoverflow.com/a/62297796/12817546 . Wywołaj metodę „dynamicznie”. Zobacz stackoverflow.com/a/62336440/12817546 . Uzyskaj dostęp do pakietu Go. Zobacz stackoverflow.com/a/62278078/12817546 . Przypisz dowolną wartość do zmiennej. Zobacz stackoverflow.com/a/62337836/12817546 .
Tom L