2014-04-13 10 views
6

Qui è dove mi confonde di più mentre imparo. Sappiamo tutti che i metodi su T riguardano solo la copia di T e i metodi su *T influiranno sui dati effettivi su T.Qual è la ragione per cui golang discrimina i set di metodi su T e * T?

Perché i metodi su T possono essere utilizzati anche da *T, ma il contrario non è consentito? Quindi, puoi darmi un esempio (o una ragione) del perché non consentono il metodo su ?

Quali sono i pro e i contro di questo design?

+0

Credo che questo link ti aiuterà a trovare la risposta: https://code.google.com/p/go-wiki/wiki/MethodSets – nvcnvn

risposta

1

Hai la chiave per la tua risposta nella tua domanda: i metodi su T riguardano solo la copia di T. Quindi, con queste informazioni, il seguente estratto da Go's FAQ dovrebbe contribuire a chiarire il resto della confusione:

Dal Go Spec:

"Il metodo set di qualsiasi altro tipo T di nome è composto da tutti i metodi con il tipo di ricevitore T. Il set di metodi del tipo di puntatore corrispondente * T è l'insieme di tutti i metodi con ricevitore * T o T (ovvero, anche contiene il set di metodi di T). "

Se un valore di interfaccia contiene un puntatore * T, una chiamata metodo può ottenere un valore dereferenziando puntatore , ma se un valore interfaccia contiene un valore T, non c'è modo utile per una chiamata metodo per ottenere un puntatore.

Anche nei casi in cui il compilatore può passare l'indirizzo di un valore a passare al metodo, se il metodo modifica il valore, le modifiche saranno perse nel chiamante. Come un esempio comune, questo codice:

var buf bytes.Buffer 
io.Copy(buf, os.Stdin) 

copierà standard input in una copia di BUF, non in BUF stessa. Questo non è quasi mai il comportamento desiderato .

3

Uno dei migliori articoli sull'interfaccia è "How to use interfaces in Go" di Jordan OREILLI.

Esso comprende l'esempio:

type Animal interface { 
    Speak() string 
} 

type Dog struct { 
} 

func (d Dog) Speak() string { 
    return "Woof!" 
} 

type Cat struct { 
} 

func (c *Cat) Speak() string { 
    return "Meow!" 
} 

E spiega:

un tipo puntatore può accedere ai metodi di questo tipo valore associato, ma non viceversa.
Cioè, un valore *Dog possono utilizzare il metodo Speak definita su Dog, ma come abbiamo visto in precedenza, un valore Cat non possono accedere al metodo Speak definita su *Cat.

(che riflette la tua domanda)

che può sembrare criptico, ma ha senso quando si ricorda quanto segue: tutto in Go è passato per valore.
Ogni volta che si chiama una funzione, i dati che ci si sta passando vengono copiati. Nel caso di un metodo con un ricevitore di valore, il valore viene copiato quando si chiama il metodo.

Questo è un po 'più evidente quando si capisce che un metodo per la seguente firma:

func (t T) MyMethod(s string) { 
    // ... 
} 

è una funzione del tipo func(T, string); i ricevitori del metodo vengono passati alla funzione in base al valore proprio come qualsiasi altro parametro.

Eventuali modifiche al ricevitore effettuate all'interno di un metodo definito su un tipo di valore (ad es., func (d Dog) Speak() { ... }) non verranno visualizzate dal chiamante in quanto il chiamante sta valutando un valore Dog completamente separato.

(Questa è la "copia per valore" parte)

Poiché tutto viene passato per valore, dovrebbe essere ovvio perché un metodo *Cat non è utilizzabile da un valore Cat; qualsiasi valore Cat può avere qualsiasi numero di puntatori *Cat che puntano ad esso. Se proviamo a chiamare un metodo *Cat utilizzando un valore Cat, non abbiamo mai avuto un puntatore *Cat per iniziare.

Al contrario, se abbiamo un metodo sul tipo Dog, e abbiamo un puntatore *Dog, sappiamo esattamente quale Dog valore da utilizzare quando si chiama questo metodo, perché i *Dog puntatore punta a esattamente un Dog valore ; il runtime Go trasferirà il puntatore al relativo valore Dog associato ogni volta che è necessario.
Vale a dire, dato un valore *Dog e un metodo Speak sul tipo Dog, possiamo solo dire d.Speak(); non abbiamo bisogno di dire qualcosa come d->Speak() come potremmo fare in altre lingue.

6

Ci sono molte risposte qui, ma nessuno di loro risponde perché questo è il caso.

Per prima cosa prendi il caso che tu abbia un * T e desideri chiamare un metodo che prenda T. Per fare questo, tutto ciò che devi fare è passare * tuoT (dove * è usato per dereferenziare il puntatore) a la funzione. Ciò è garantito perché è possibile copiare blob di memoria in una posizione nota.

Ora diciamo che hai una T e vuoi un * T. Potresti pensare che potresti semplicemente fare & e ricevere il suo indirizzo. Ma la vita non è sempre così semplice. Non c'è sempre un indirizzo statico da prendere.


Da the spec:

Per un operando x di tipo T, l'operazione indirizzo & x genera un puntatore di tipo T * a x. L'operando deve essere indirizzabile, ovvero una variabile, puntatore indiretto, o operazione di indicizzazione delle sezioni; o un selettore di campo di un operando struct indirizzabile; o un'operazione di indicizzazione dell'array di un array indirizzabile. Come eccezione al requisito di indirizzabilità, x può anche essere un letterale composito (eventualmente tra parentesi).

Forse ti starai chiedendo perché avrebbero posto queste restrizioni arbitrarie per ottenere un indirizzo di memoria. Ogni variabile deve avere qualche indirizzo di memoria, giusto? Mentre questo è vero, le ottimizzazioni possono rendere questi indirizzi piuttosto effimeri.

Ad esempio, consente di dire la variabile era all'interno di una mappa:

In questo caso, si sta effettivamente dicendo che vuoi un puntatore alla memoria che si terrà all'interno di una mappa. Ciò imporrebbe a ogni implementazione di Go di implementare la mappa in modo tale che gli indirizzi di memoria rimangano statici. Ciò limiterebbe severamente le strutture interne del runtime e offrirà agli implementatori molta meno libertà nella costruzione di una mappa efficiente.

Esistono altri esempi come i ritorni delle funzioni o le interfacce, ma è sufficiente un solo esempio per dimostrare che l'operazione non è garantita possibile.


La linea di fondo è che la memoria del computer non è semplice e, mentre si potrebbe voler dire "basta prendere l'indirizzo", non è sempre così semplice. Prendere un indirizzo che è garantito essere statico non è sempre possibile. Pertanto, non è possibile garantire che qualsiasi istanza di T possa essere trasformata in un puntatore e passata a un metodo di puntatore.

Problemi correlati