00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Puntatori in Go

In Go, un puntatore è una variabile che contiene l’indirizzo di memoria di un’altra variabile. A differenza del C, Go non supporta l’aritmetica dei puntatori, rendendo il linguaggio più sicuro pur mantenendo la flessibilità offerta dai puntatori.

Dichiarare un Puntatore

Per dichiarare un puntatore si utilizza l’operatore * seguito dal tipo di dato a cui il puntatore fa riferimento:

var p *int       // puntatore a un intero
var s *string    // puntatore a una stringa
var f *float64   // puntatore a un float64

A questo punto, il valore di ciascun puntatore è nil, ovvero non punta a nessun indirizzo valido.

Operatore Address-Of (&)

Per ottenere l’indirizzo di memoria di una variabile si usa l’operatore &:

numero := 42
p := &numero // p contiene l'indirizzo di memoria di 'numero'

fmt.Println(p)  // stampa qualcosa come 0xc0000b6010

L’operatore & restituisce un puntatore al valore della variabile specificata.

Dereferenziazione

Per accedere al valore puntato da un puntatore si usa l’operatore *:

numero := 42
p := &numero

fmt.Println(*p) // stampa 42

*p = 100
fmt.Println(numero) // stampa 100, il valore originale è stato modificato

La dereferenziazione permette sia di leggere che di modificare il valore a cui il puntatore fa riferimento.

Valore Zero: nil

Il valore zero di un puntatore è nil. Un puntatore nil non punta a nessun indirizzo di memoria valido. Dereferenziare un puntatore nil causa un panic a runtime:

var p *int
fmt.Println(p) // stampa <nil>

// *p = 10 // PANIC: runtime error: invalid memory address

Prima di dereferenziare un puntatore, è buona pratica controllare che non sia nil:

if p != nil {
    fmt.Println(*p)
} else {
    fmt.Println("Il puntatore è nil")
}

Puntatori a Struct

Go semplifica notevolmente l’accesso ai campi di una struct tramite puntatore. Non è necessario usare la dereferenziazione esplicita:

type Persona struct {
    Nome string
    Eta  int
}

p := &Persona{Nome: "Marco", Eta: 30}

// Accesso diretto ai campi (Go dereferenzia automaticamente)
fmt.Println(p.Nome) // stampa "Marco"
fmt.Println(p.Eta)  // stampa 30

// Equivalente esplicito (meno comune)
fmt.Println((*p).Nome) // stampa "Marco"

Questa sintassi semplificata rende il codice più leggibile e pulito.

Nessuna Aritmetica dei Puntatori

A differenza del C e del C++, Go non supporta l’aritmetica dei puntatori. Non è possibile incrementare o decrementare un puntatore per spostarsi in memoria:

arr := [3]int{10, 20, 30}
p := &arr[0]

// p++ // ERRORE DI COMPILAZIONE: operazione non valida
// p = p + 1 // ERRORE DI COMPILAZIONE

Questa scelta progettuale aumenta la sicurezza del linguaggio, prevenendo errori comuni legati alla manipolazione diretta della memoria.

Puntatori come Parametri di Funzione

I puntatori sono molto utili per passare valori alle funzioni senza copiarli e per permettere alle funzioni di modificare il valore originale:

func raddoppia(n *int) {
    *n = *n * 2
}

func main() {
    valore := 5
    raddoppia(&valore)
    fmt.Println(valore) // stampa 10
}

Senza puntatori, Go passerebbe una copia del valore e la modifica non avrebbe effetto al di fuori della funzione.

La Funzione new()

Go fornisce la funzione built-in new() per allocare memoria e restituire un puntatore:

p := new(int)    // alloca un int e restituisce *int
fmt.Println(*p)  // stampa 0 (valore zero di int)

*p = 42
fmt.Println(*p)  // stampa 42

La funzione new(T) alloca memoria per un valore di tipo T, lo inizializza al suo valore zero e restituisce un puntatore *T.

Quando Usare i Puntatori

Ecco alcune linee guida su quando è opportuno usare i puntatori in Go:

  1. Struct di grandi dimensioni: per evitare copie costose quando si passano struct grandi a funzioni.
  2. Modificare il valore originale: quando una funzione deve modificare il valore passato come argomento.
  3. Valori opzionali: un puntatore può essere nil, utile per rappresentare l’assenza di un valore.
  4. Receiver dei metodi: usare un pointer receiver quando il metodo deve modificare lo stato della struct.
type Contatore struct {
    Valore int
}

// Pointer receiver: modifica lo stato della struct
func (c *Contatore) Incrementa() {
    c.Valore++
}

func main() {
    c := Contatore{Valore: 0}
    c.Incrementa()
    fmt.Println(c.Valore) // stampa 1
}

Conclusione

I puntatori in Go offrono un modo sicuro ed efficiente per lavorare con indirizzi di memoria. L’assenza dell’aritmetica dei puntatori e la dereferenziazione automatica per le struct rendono Go un linguaggio più sicuro rispetto al C, pur mantenendo la potenza e la flessibilità dei puntatori. Comprendere quando e come usare i puntatori è fondamentale per scrivere codice Go performante e idiomatico.