Qual è il significato dell'interfaccia {}?


133

Sono nuovo alle interfacce e sto provando a fare la richiesta SOAP di github

Non capisco il significato di

Msg interface{}

in questo codice:

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

type Body struct {
    Msg interface{}
}

Ho osservato la stessa sintassi in

fmt.Println

ma non capisco cosa si sta realizzando

interface{}

20
interface{}è più o meno l'equivalente di void *in C. Può indicare qualsiasi cosa ed è necessaria un'asserzione cast / tipo per usarla.
Nick Craig-Wood,

Qual è il significato dell'interfaccia {}? Vedi stackoverflow.com/a/62337836/12817546 .
Tom J

Risposte:


189

Puoi fare riferimento all'articolo " Come usare le interfacce in Go " (basato sulla " descrizione delle interfacce di Russ Cox "):

Che cosa è un'interfaccia?

Un'interfaccia è due cose:

  • è un insieme di metodi,
  • ma è anche un tipo

Il interface{}tipo, l' interfaccia vuota è l'interfaccia che non ha metodi.

Poiché non esiste una parola chiave implementa, tutti i tipi implementano almeno zero metodi e la soddisfazione di un'interfaccia viene eseguita automaticamente, tutti i tipi soddisfano l'interfaccia vuota .
Ciò significa che se si scrive una funzione che accetta un interface{}valore come parametro, è possibile fornire tale funzione con qualsiasi valore .

(Questo è ciò che Msgrappresenta nella tua domanda: qualsiasi valore)

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

Ecco dove diventa confuso:

all'interno della DoSomethingfunzione, qual è vil tipo?

I gopher principianti sono portati a credere che " vè di qualsiasi tipo", ma è sbagliato.
vnon è di alcun tipo; è di interface{}tipo .

Quando si passa un valore nella DoSomethingfunzione, il runtime Go eseguirà una conversione del tipo (se necessario) e convertirà il valore in un interface{}valore .
Tutti i valori hanno esattamente un tipo in fase di esecuzione e vun solo tipo statico è interface{}.

Un valore di interfaccia è costituito da due parole di dati :

  • una parola viene utilizzata per indicare una tabella dei metodi per il tipo sottostante del valore,
  • e l'altra parola viene utilizzata per indicare i dati effettivi mantenuti da quel valore.

Addendum: questo è dove l'articolo di Russ è abbastanza completo per quanto riguarda una struttura di interfaccia:

type Stringer interface {
    String() string
}

I valori dell'interfaccia sono rappresentati come una coppia di due parole che fornisce un puntatore alle informazioni sul tipo memorizzato nell'interfaccia e un puntatore ai dati associati.
L'assegnazione di b a un valore di interfaccia di tipo Stringer imposta entrambe le parole del valore di interfaccia.

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

La prima parola nel valore dell'interfaccia punta a ciò che chiamo una tabella di interfaccia o itable (i-table pronunciata; nelle fonti di runtime, il nome dell'implementazione C è Itab).
L'Itable inizia con alcuni metadati sui tipi coinvolti e quindi diventa un elenco di puntatori a funzioni.
Si noti che l'Itable corrisponde al tipo di interfaccia, non al tipo dinamico .
In termini del nostro esempio, l'itable per Stringercontenere il tipo Binary elenca i metodi usati per soddisfare Stringer, che è solo String: gli altri metodi di Binary ( Get) non appaiono in itable.

La seconda parola nel valore dell'interfaccia indica i dati effettivi , in questo caso una copia di b.
L'assegnazione var s Stringer = bcrea una copia banziché il punto bper lo stesso motivo che ne var c uint64 = bfa una copia: se bsuccessivamente cambia, se csi suppone che abbia il valore originale, non quello nuovo.
I valori memorizzati nelle interfacce potrebbero essere arbitrariamente grandi, ma solo una parola è dedicata a contenere il valore nella struttura dell'interfaccia, quindi l'assegnazione alloca un pezzo di memoria sull'heap e registra il puntatore nello slot di una parola.


4
Cosa intendi con "due parole di dati"? In particolare, cosa significa la "parola"?
Mingyu,

3
@Mingyu Ho completato la risposta per illustrare quelle due parole (punti a 32 bit).
VonC,

2
@Mingyu: VonC si riferisce a una parola nel senso dell'architettura del computer - una raccolta di bit che definiscono un dato di dimensioni fisse. Le dimensioni delle parole sono regolate dall'architettura del processore che stai utilizzando.
Dan Esparza,

1
grazie @VonC per la tua risposta ... il vero è che sono stanco di farmi retrocedere quando chiedo cose .. la maggior parte delle volte mi dice che dovrei leggere i documenti ... ricorderò il tuo suggerimento se mi sento con voglia di scrivere correttamente un post per questo ... ma non riesco davvero a pensare ad un altro modo di chiedere. Quindi grazie comunque e scusa la mia scarsa volontà. Ti invitiamo a dare un'occhiata a questo: stackoverflow.com/questions/45577301/… per chiarire perché non mi piace chiedere.
Victor,

1
@vic nessun problema, e mi dispiace per la tua precedente brutta esperienza come richiedente. È solo che i commenti non vanno bene per domande e risposte.
VonC,

34

interface{}significa che puoi inserire valore di qualsiasi tipo, incluso il tuo tipo personalizzato. Tutti i tipi in Go soddisfano un'interfaccia vuota ( interface{}è un'interfaccia vuota).
Nel tuo esempio, il campo Messaggio può avere valore di qualsiasi tipo.

Esempio:

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
}

Vai al parco giochi


12

Si chiama interfaccia vuota ed è implementata da tutti i tipi, il che significa che puoi mettere qualsiasi cosa sul Msgcampo.

Esempio :

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

Questa è l'estensione logica del fatto che un tipo implementa un'interfaccia non appena ha tutti i metodi dell'interfaccia.


significa che potrebbe essere un int per struttura definita dall'utente ??
utente

11

Ci sono già buone risposte qui. Vorrei aggiungere anche il mio per gli altri che vogliono capirlo intuitivamente:


Interfaccia

Ecco un'interfaccia con un metodo:

type Runner interface {
    Run()
}

Quindi qualsiasi tipo che ha un Run()metodo soddisfa l'interfaccia Runner:

type Program struct {
    /* fields */
}

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

func (p Program) Stop() {
    /* stopping */
}
  • Sebbene il tipo di programma abbia anche un metodo Stop, soddisfa comunque l'interfaccia Runner perché tutto ciò che serve è avere tutti i metodi di un'interfaccia per soddisfarla.

  • Quindi, ha un metodo Run e soddisfa l'interfaccia Runner.


Interfaccia vuota

Ecco un'interfaccia vuota denominata senza alcun metodo:

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

Quindi qualsiasi tipo soddisfa questa interfaccia. Perché, non è necessario alcun metodo per soddisfare questa interfaccia. Per esempio:

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

a = 5
a = 6.5
a = "hello"

Ma il tipo di programma sopra lo soddisfa? Sì:

a = Program{} // ok

l'interfaccia {} è uguale all'interfaccia vuota sopra.

var b interface{}

// true: a == b

b = a
b = 9
b = "bye"

Come vedi, non c'è nulla di misterioso, ma è molto facile abusarne. Stanne lontano il più possibile.


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


Non type Runner interfaceè utilizzato nell'esempio del parco giochi Go.
Tom J,

9

Dalle specifiche Golang :

Un tipo di interfaccia specifica un set di metodi chiamato interfaccia. Una variabile di tipo di interfaccia può memorizzare un valore di qualsiasi tipo con un set di metodi che rappresenta qualsiasi superset dell'interfaccia. Si dice che un tale tipo implementa l'interfaccia. Il valore di una variabile non inizializzata del tipo di interfaccia è zero.

Un tipo implementa qualsiasi interfaccia comprendente qualsiasi sottoinsieme dei suoi metodi e può quindi implementare diverse interfacce distinte. Ad esempio, tutti i tipi implementano l'interfaccia vuota:

interfaccia{}

I concetti da catturare sono:

  1. Tutto ha un tipo . È possibile definire un nuovo tipo, chiamiamola così dire di T. Let ora il nostro tipo Tha 3 metodi: A, B, C.
  2. L'insieme di metodi specificato per un tipo è chiamato " tipo di interfaccia ". Chiamiamolo nel nostro esempio: T_interface. È uguale aT_interface = (A, B, C)
  3. È possibile creare un "tipo di interfaccia" definendo la firma dei metodi.MyInterface = (A, )
  4. Quando si specifica una variabile di tipo , "tipo di interfaccia", è possibile assegnare ad essa solo tipi che hanno un'interfaccia che è un superset dell'interfaccia. Ciò significa che tutti i metodi contenuti MyInterfacedevono essere contenuti all'internoT_interface

Puoi dedurre che tutti i "tipi di interfaccia" di tutti i tipi sono un superset dell'interfaccia vuota.


1

Un esempio che estende l'eccellente risposta di @VonC e il commento di @ NickCraig-Wood. interface{}può indicare qualsiasi cosa e per utilizzarla è necessaria un'asserzione cast / tipo.

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è una variabile di un'interfaccia vuota con un valore cat("Fish"). È legale creare un valore di metodo da un valore di tipo di interfaccia. Vedi https://golang.org/ref/spec#Interface_types .

Un interruttore di tipo conferma iche il tipo di interfaccia è cat("Fish"). Vedi https://golang.org/doc/effective_go.html#type_switch . iviene quindi riassegnato a dog("Bone"). Un interruttore di tipo conferma che iil tipo di interfaccia è cambiato in dog("Bone").

Si può anche chiedere al compilatore di verificare che il tipo Timplementa l'interfaccia Itentando un incarico: var _ I = T{}. Vedi https://golang.org/doc/faq#guarantee_satisfies_interface e https://stackoverflow.com/a/60663003/12817546 .

Tutti i tipi implementano l'interfaccia vuota interface{}. Vedi https://talks.golang.org/2012/goforc.slide#44 e https://golang.org/ref/spec#Interface_types . In questo esempio, iviene riassegnato, questa volta a una stringa "4.3". iviene quindi assegnato a una nuova variabile stringa scon i.(string)prima sviene convertito in un tipo float64 futilizzando strconv. Alla fine fviene convertito in nun tipo int uguale a 4. Vedi Qual è la differenza tra la conversione del tipo e l'asserzione del tipo?

Le mappe e le sezioni integrate di Go, oltre alla possibilità di utilizzare l'interfaccia vuota per costruire contenitori (con unboxing esplicito), in molti casi è possibile scrivere codice che fa ciò che i generici consentirebbero, se meno agevolmente. Vedi https://golang.org/doc/faq#generics .


Codice di disaccoppiamento con un'interfaccia. Vedi stackoverflow.com/a/62297796/12817546 . Chiama un metodo "dinamicamente". Vedi stackoverflow.com/a/62336440/12817546 . Accedi a un pacchetto Go. Vedi stackoverflow.com/a/62278078/12817546 . Assegna qualsiasi valore a una variabile. Vedi stackoverflow.com/a/62337836/12817546 .
Tom J
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.