Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

I puntatori

Impariamo come usare in modo corretto i puntatori in linguaggio Go, comprendendone sia la sintassi che le peculiarità.
Impariamo come usare in modo corretto i puntatori in linguaggio Go, comprendendone sia la sintassi che le peculiarità.
Link copiato negli appunti

I puntatori sono variabili che non gestiscono valori bensì gli indirizzi delle locazioni di memoria che li custodiscono. Con essi avremo a disposizione una modalità estremamente
flessibile e dinamica per lavorare sui dati. E' un approccio che rende il programmatore protagonista della gestione di strutture dati sebbene, in Go, con un approccio piuttosto moderno.
In questa lezione, ne approfondiremo il concetto e li vedremo al lavoro sia con semplici variabili sia con nodi più articolati basati su struct.

Come funzionano i puntatori

La sintassi dei puntatori si basa principalmente su due simboli:

  • * che permette di accedere al valore contenuto in una locazione di memoria cui il puntatore fa riferimento;
  • & che restituisce il riferimento alla locazione di memoria di una variabile.

Se ad esempio definiamo una normale variabile intera x1:=10, avremo una locazione di memoria di indirizzo &x1 che contiene il
valore 10. Scriviamo un pò di codice partendo da due variabili e stampiamone i loro riferimenti in modo da poterli confrontare:

x1:=10
fmt.Println(x1)
fmt.Println(&x1)
x2:=20
fmt.Println(x2)
fmt.Println(&x2)

Otterremo il seguente output:

10
0xc00009c000
20
0xc00009c020

Come si vede, vengono stampati i valori delle variabili (10 e 20) mentre gli altri due sono indirizzi in memoria espressi in notazione esadecimale,
0xc00009c000 e 0xc00009c020. Notare, tra l'altro, come questi ultimi siano ravvicinati in quanto riferiti a due variabili dichiarate consecutivamente.

I puntatori possono anche essere utilizzati per creare ulteriori accessi alle variabili. Esaminiamo in merito il seguente codice:

// dichiariamo una variabile intera ed un puntatore
	x1:=10
	var indirizzo *int
	// il valore del puntatore diventa l'indirizzo di x1
	indirizzo=&x1
	fmt.Printf("Indirizzo di x1 = %x\n",&x1)
	fmt.Printf("Contenuto del puntatore = %x\n",indirizzo)
	// assegniamo un nuovo valore mediante il puntatore
	*indirizzo=1000
	// verifichiamo se il valore di x1 è cambiato
	fmt.Printf("Valore di x1 dopo modifica = %d",x1)

I passaggi della sperimentazione sono:

  • dichiariamo una variabile intera di valore 10;
  • dichiariamo un puntatore a intero non inizializzato;
  • inizializziamo il puntatore con l'indirizzo della variabile intera;
  • modifichiamo il valore di x1 non mediante il suo riferimento ma indirettamente tramite il puntatore.

Se il tutto avrà avuto successo, avremo x1 impostata a 1000 senza averne esplicitamente toccato il valore:

Indirizzo di x1 = c000018030
Contenuto del puntatore = c000018030
Valore di x1 dopo modifica = 1000

In sintesi, i puntatori svolgono questo importante ruolo: creare collegamenti a valori posizionati in memoria.

Esempio: lista dinamica

I puntatori possono essere utilizzati per indirizzare qualsiasi tipo di variabile comprese le struct. Questo impiego è molto utile in quanto
con tali costrutti si può manipolare aggregazioni di dati anche molto articolate. In questo esempio, creiamo una lista dinamica di struct che
rappresentano delle generiche persone valorizzate con due stringhe, nome e cognome. Si noti che ogni nodo della lista, secondo la definizione della struct,
contiene anche un puntatore di tipo *persona e ciò permetterà di collegare ogni elemento ad un altro componendo così la struttura dati.

Quando ad esempio dichiareremo l'oggetto p4 il campo successivo in esso contenuto punterà all'indirizzo di
p3. Organizzeremo poi un ciclo che userà un ulteriore puntatore, cursore, per puntare un singolo nodo della struttura.
Sarà poi l'istruzione cursore=cursore.successivo che aggiornerà il puntatore facendolo passare al nodo successivo. In questo modo, potremo
stampare tutti i dati inclusi all'interno di ogni singola struct. Si noti l'importanza dell'incremento del cursore: qualora dimenticassimo quella riga il
puntatore continuerebbe ad indicare lo stesso nodo rendendo il ciclo infinito e stampando sempre la medesima stringa ("Marta Arancioni" in questo specifico esempio):

package main
import (
	"fmt"
)
type persona struct {
    nome string
    cognome string
    successivo *persona
}
func main() {
  p1:=persona{nome: "Gigi",  cognome: "Bianchi"}
  p2:=persona{nome: "Alessio",  cognome: "Neri", successivo: &p1}
  p3:=persona{nome: "Sabrina",  cognome: "Gialli", successivo: &p2}
  p4:=persona{nome: "Marta",  cognome: "Arancioni", successivo: &p3}
  var cursore *persona=&p4
  _ = p1
  for cursore!=nil{
    fmt.Printf("%s %s\n", cursore.nome, cursore.cognome)
    cursore=cursore.successivo
  }
}

Ecco l'output ottenuto:

Marta Arancioni
Sabrina Gialli
Alessio Neri
Gigi Bianchi

Un approccio di questo genere mostra non solo un modo pratico per sperimentare puntatori e struct insieme ma anche una via per creare una
struttura dati in proprio, pratica ed ottimizzabile, i cui nodi potranno essere estesi secondo proprie necessità solo aggiornando la definizione della
struct alla loro base.

Ti consigliamo anche