Skip to content

Metodi Go

La differenza tra metodi e funzioni in Go è che i metodi hanno un ricevitore, mentre le funzioni no, e solo i tipi personalizzati possono avere metodi. Prima guarda un esempio.

go
type IntSlice []int

func (i IntSlice) Get(index int) int {
  return i[index]
}
func (i IntSlice) Set(index, val int) {
  i[index] = val
}

func (i IntSlice) Len() int {
  return len(i)
}

Viene prima dichiarato un tipo IntSlice, il cui tipo sottostante è []int, e poi vengono dichiarati tre metodi Get, Set e Len. L'aspetto di un metodo non è molto diverso da quello di una funzione, ha solo un piccolo pezzo in più (i IntSlice). i è il ricevitore, IntSlice è il tipo del ricevitore. Il ricevitore è simile a this o self in altri linguaggi, ma in Go deve essere indicato esplicitamente.

go
func main() {
   var intSlice IntSlice
   intSlice = []int{1, 2, 3, 4, 5}
   fmt.Println(intSlice.Get(0))
   intSlice.Set(0, 2)
   fmt.Println(intSlice)
   fmt.Println(intSlice.Len())
}

L'uso di un metodo è simile alla chiamata di un metodo di membro di una classe: prima si dichiara, poi si inizializza e infine si chiama.

Ricevitore per valore

I ricevitori si dividono in due tipi: ricevitore per valore e ricevitore per puntatore. Prima guarda un esempio

go
type MyInt int

func (i MyInt) Set(val int) {
   i = MyInt(val) // Modificato, ma non ha alcun effetto
}

func main() {
   myInt := MyInt(1)
   myInt.Set(2)
   fmt.Println(myInt)
}

Dopo aver eseguito il codice precedente, si scopre che il valore di myInt è ancora 1, non è stato modificato in 2. Quando un metodo viene chiamato, il valore del ricevitore viene passato al metodo. Il ricevitore nell'esempio precedente è un ricevitore per valore, che può essere semplicemente visto come un parametro formale. Modificare il valore di un parametro formale non ha alcun effetto sul valore esterno al metodo. Allora cosa succede se si chiama tramite puntatore?

go
func main() {
  myInt := MyInt(1)
  (&myInt).Set(2)
  fmt.Println(myInt)
}

Purtroppo, questo codice non può ancora modificare il valore interno. Per poter corrispondere al tipo del ricevitore, Go lo dereferenzierà, interpretandolo come (*(&myInt)).Set(2).

Ricevitore per puntatore

Modificando leggermente, è possibile modificare normalmente il valore di myInt.

go
type MyInt int

func (i *MyInt) Set(val int) {
   *i = MyInt(val)
}

func main() {
   myInt := MyInt(1)
   myInt.Set(2)
   fmt.Println(myInt)
}

Ora il ricevitore è un ricevitore per puntatore. Sebbene myInt sia un tipo di valore, quando si chiama il metodo del ricevitore per puntatore tramite un tipo di valore, Go lo interpreterà come (&myint).Set(2). Quindi, quando il ricevitore di un metodo è un puntatore, indipendentemente dal fatto che il chiamante sia un puntatore o meno, è possibile modificare il valore interno.

Nel processo di passaggio dei parametri di una funzione, viene effettuata una copia per valore. Se viene passato un intero, viene copiato quell'intero. Se viene passata una slice, viene copiata quella slice. Ma se viene passato un puntatore, è necessario copiare solo quel puntatore. Ovviamente, passare un puntatore consuma meno risorse rispetto al passaggio di una slice. Lo stesso vale per i ricevitori: ricevitore per valore e ricevitore per puntatore seguono lo stesso principio. Nella maggior parte dei casi, si consiglia di utilizzare un ricevitore per puntatore, ma i due non dovrebbero essere utilizzati in modo misto. O si utilizzano entrambi, o non si utilizza nessuno dei due. Guarda l'esempio seguente.

TIP

È necessario comprendere prima Interfaccia

go
type Animal interface {
   Run()
}

type Dog struct {
}

func (d *Dog) Run() {
   fmt.Println("Run")
}

func main() {
   var an Animal
   an = Dog{}
   // an = &Dog{} Modo corretto
   an.Run()
}

Questo codice non supererà la compilazione. Il compilatore outputterà il seguente errore

cannot use Dog{} (value of type Dog) as type Animal in assignment:
  Dog does not implement Animal (Run method has pointer receiver)

Tradotto, significa che non è possibile utilizzare Dog{} per inizializzare una variabile di tipo Animal, perché Dog non implementa Animal. Ci sono due soluzioni: una è cambiare il ricevitore per puntatore in ricevitore per valore, l'altra è cambiare Dog{} in &Dog{}. Successivamente, verranno spiegati uno per uno.

go
type Dog struct {
}

func (d Dog) Run() { // Cambiato in ricevitore per valore
   fmt.Println("Run")
}

func main() { // Può essere eseguito normalmente
   var an Animal
   an = Dog{}
   // an = &Dog{} Anche questo funziona
   an.Run()
}

Nel codice originale, il ricevitore del metodo Run è *Dog, quindi naturalmente è il puntatore Dog a implementare l'interfaccia Animal, non la struttura Dog. Questi sono due tipi diversi, quindi il compilatore riterrà che Dog{} non sia un'implementazione di Animal, quindi non può essere assegnato alla variabile an. La seconda soluzione è assegnare il puntatore Dog alla variabile an. Tuttavia, quando si utilizza un ricevitore per valore, il puntatore Dog può ancora essere assegnato normalmente a animal. Questo perché Go dereferenzierà il puntatore quando appropriato. Poiché è possibile trovare la struttura Dog tramite il puntatore, ma il contrario non è possibile: non è possibile trovare il puntatore Dog tramite la struttura Dog. Se si utilizzano in modo misto ricevitore per valore e ricevitore per puntatore nella struttura, non ci sono problemi, ma dopo averli utilizzati con un'interfaccia, si verificheranno errori. Meglio utilizzare sempre o tutti ricevitori per valore, o tutti ricevitori per puntatore, formando una buona norma, che può anche ridurre il carico di manutenzione successiva.

C'è anche un'altra situazione: quando il ricevitore per valore è indirizzabile, Go inserirà automaticamente l'operatore puntatore per la chiamata. Ad esempio, le slice sono indirizzabili ed è ancora possibile modificare i loro valori interni tramite un ricevitore per valore. Ad esempio, il seguente codice

go
type Slice []int

func (s Slice) Set(i int, v int) {
  s[i] = v
}

func main() {
  s := make(Slice, 1)
  s.Set(0, 1)
  fmt.Println(s)
}

Output

[1]

Ma questo causerà un altro problema: se si aggiungono elementi, la situazione è diversa. Guarda l'esempio seguente

type Slice []int

func (s Slice) Set(i int, v int) {
  s[i] = v
}

func (s Slice) Append(a int) {
  s = append(s, a)
}

func main() {
  s := make(Slice, 1, 2)
  s.Set(0, 1)
  s.Append(2)
  fmt.Println(s)
}
[1]

Il suo output è ancora lo stesso di prima. La funzione append ha un valore di ritorno. Dopo aver aggiunto elementi alla slice, è necessario sovrascrivere la slice originale, specialmente dopo l'espansione della capacità. Modificare il ricevitore per valore nel metodo non ha alcun effetto, il che porta al risultato nell'esempio. Cambiando in ricevitore per puntatore, funziona normalmente.

go
type Slice []int

func (s *Slice) Set(i int, v int) {
  (*s)[i] = v
}

func (s *Slice) Append(a int) {
  *s = append(*s, a)
}

func main() {
  s := make(Slice, 1, 2)
  s.Set(0, 1)
  s.Append(2)
  fmt.Println(s)
}

Output

[1 2]

Golang by www.golangdev.cn edit