La varietà di variabili in Go
Roberto Giacomelli
Articolo sul blog http://okpanico.wordpress.com
e-mail: giaconet dot mailbox at gmail dot com
16 aprile 2013
Sommario
Faremo un excursus sul significato delle variabili chiarendone il loro funzionamento nei moderni
linguaggi di programmazione, in particolare con riferimento al Go. Ci soffermeremo sul concetto di
variabile valore e su quello di variabile riferimento. Per la comprensione e l’esecuzione degli esperimenti
proposti, è necessario solo un po’ di concentrazione e la lettura dei post sul linguaggio Go apparsi su
questo stesso blog.
Indice
1
1 Variabili
1
2 Dal C, al C++, a Java
2
3 Tipi di variabili
2
4 Variabili valore
4.1 ...e le strutture? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
3
5 Variabili riferimento
3
6 Il terzo tipo: i puntatori
3
7 Risposta uno
4
8 Risposta due
5
9 Risposta tre
5
10 Una domanda per il lettore curioso
5
11 Conclusione
6
12 Licenza ed informazioni varie
12.1 Distribuzione/Citazioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12.2 Colophon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
6
6
Variabili
Tutti o quasi tutti i linguaggi di programmazione comprendono tra i concetti di base quello di variabile.
Il nome stesso ci dice che è un qualcosa che può cambiare durante l’esecuzione del programma e quel
qualcosa è il dato che la variabile rappresenta.
Per fare un esempio di codice in un linguaggio generico, possiamo in modo molto naturale assegnare il
valore numerico 10 alla variabile v e sottrarre poi 5:
1
2
3
-- in un linguaggio generico...
v = 10
print( v ) --> stampa 10
1
4
5
6
v = v - 5
print( v )
--> stampa 5
Se provo a creare una seconda variabile q con il valore della prima mi aspetto che riceva il valore che
v conteneva al momento dell’assegnazione. Ma se poi la prima variabile cambia che ne è della seconda? Il
codice seguente chiarisce il dubbio sul comportamento delle variabili:
1
2
3
4
5
6
7
8
9
10
-- sempre scrivendo il codice
-- in un linguaggio generico
v = 10
q = v
print(q) --> stampa 10
-- poi v cambia...
v = v - 5
print(q) --> stampa 10 o 5?
Ci si può attendere due soli risultati:
1. la seconda variabile non cambia mantenendo il valore 10;
2. la seconda variabile cambia cambiando a sua volta in 5 come la prima.
2
Dal C, al C++, a Java
Siamo partiti da un concetto piuttosto semplice: dare un nome al contenitore di un dato che può
cambiare durante l’esecuzione del programma — ovvero a run-time — ma ben presto ci siamo resi conto
che è necessario scegliere come effettivamente rappresentare le informazioni, e queste scelte influenzeranno
nel bene e nel male le applicazioni.
Ogni linguaggio dunque affronta problemi di progettazione complessi, con esigenze spesso opposte,
come ben testimonia la loro evoluzione storica, per esempio dal C, al C++, a Java. Ad ogni generazione
l’ingegneria del software mette a frutto anni di lavoro e l’esperienza di milioni di righe di codice in un
nuovo linguaggio.
3
Tipi di variabili
Eravamo rimasti al dubbio se nel nostro ipotetico linguaggio sia opportuno o meno agganciare il valore
della seconda variabile a quello della variabile da cui era stata costruita. Verifichiamo subito qual è stata
la scelta dei progettisti del Go :-)
Per una variabile di tipo intero la verifica è:
1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import "fmt"
func main() {
v := 10
q := v
// modo compatto di scrivere v = v - 5
v -= 5
// stampa 10 o stampa 5?
fmt.Println(q)
}
invece per una variabile di tipo []int, ovvero uno slice la verifica è:
1
2
3
4
5
6
7
8
package main
import "fmt"
func main() {
// uno slice:
v := []int{10}
q := v
2
Figura 1: Schema di funzionamento delle variabili valore
v
v = 10
q = v
v = v - 5
9
10
11
12
10
v
q
10
10
v
q
5
10
v[0] -= 5
// stampa [10] o stampa [5]?
fmt.Println(q)
}
Otteniamo tutti e due i comportamenti! Verificate per esercizio a quale dei due esempi corrispondono
i casi (non vi farà male e vi ruberà solo un minuto se utilizzate Go Playground).
4
Variabili valore
Se la variabile è intesa come il contenitore in cui si trova il dato, creandone una per mezzo di
un’assegnazione di un’altra variabile verrà creato semplicemente un nuovo contenitore con il dato in quel
momento contenuto in quest’ultima. Stiamo parlando delle variabili valore che forniscono l’accesso
diretto al dato.
Quello che abbiamo definito contenitore è in sostanza il segmento di memoria che contiene la rappresentazione binaria del dato. Nella figura di seguito è rappresenta la dinamica della creazione e della
modifica delle variabili valore v e q del codice di esempio.
4.1
...e le strutture?
Questo ve lo posso dire: le strutture in Go sono memorizzate in variabili valore, lascio a voi scrivere
per utile esercizio il breve codice che lo verifica...
5
Variabili riferimento
Se la variabile è intesa come il nome del contenitore in cui si trova il dato, allora creandone una per
mezzo di un’assegnazione di un’altra variabile, verrà copiato il nome del contenitore nella nuova. Si tratta
delle variabili riferimento che forniscono un accesso indiretto al dato tramite informazioni riguardanti
il contenitore.
Anche le variabili riferimento sono di tipo valore nel senso che dopotutto contengono informazioni che
non rappresentano direttamente il dato ma solo quello che al compilatore serve per raggiungerlo.
Ecco una rappresentazione schematica dello stesso codice precedente che coinvolge le variabili v e q
ma stavolta di classe reference.
6
Il terzo tipo: i puntatori
Non dovrebbe esistere un terzo tipo di variabili perché all’inizio del post abbiamo riscontrato che ci
possono essere solo due situazioni che poi abbiamo associato alle variabili valore ed alle variabili riferimento.
In Go sono disponibili, come in C ed in C++, i puntatori ed in effetti non costituiscono un terzo tipo
di variabile perché (almeno in Go) sono contenuti in normali variabili valore. Per convincerci eseguiamo il
solito programma di prova:
1
2
3
4
package main
import "fmt"
3
Figura 2: Schema di funzionamento delle variabili valore
v
v = 10
q = v
10
v
q
10
v
q
v = v - 5
5
6
7
8
9
10
11
12
13
14
5
func main() {
n, m := 10, 5 // tipi int
p1 := &n // tipo *int
p2 := p1
p1 = &m
fmt.Println(*p1) // stampa 5
fmt.Println(*p2) // stampa 10
}
I puntatori quindi sono variabili valore che contengono l’indirizzo grezzo di memoria dove si trova un
dato. Dal punto di vista del linguaggio possiamo considerare i puntatori come delle variabili riferimento
primitive. Con i puntatori infatti, gestiamo esplicitamente indirizzi di memoria ottenendoli con l’operatore
& come abbiamo appena visto nel listato precedente, ed indichiamo esplicitamente il valore puntato
deferenziando il puntatore con l’operatore *.
Se affermiamo questo, possiamo anche dire in modo speculare che le variabili riferimento sono un tipo
evoluto di puntatore perché nel codice le scriviamo come normali variabili. È il compilatore che svolge il
lavoro primitivo per noi elaborando nel modo opportuno le informazioni effettive celate nella variabile
riferimento.
Possiamo a questo punto rispondere a queste domande:
1. due variabili riferimento di uno stesso oggetto avranno lo stesso indirizzo di memoria?
2. sapendo che in Go alle funzioni vengono passate le copie degli argomenti, cosa accade se modifichiamo
una variabile riferimento all’interno di una funzione?
3. in Go, conviene passare ad una funzione un puntatore a slice o lo slice stesso?
7
Risposta uno
Quale indirizzo di memoria avranno due variabili riferimento di uno stesso oggetto?
Se una variabile riferimento è veramente una variabile valore che contiene un riferimento ad un dato in
memoria, ne segue che avrà un proprio indirizzo di memoria diverso da quello di qualsiasi altra variabile
dello stesso tipo anche se si riferisce allo stesso oggetto.
Verifichiamo per prima cosa se possiamo conoscere facilmente l’indirizzo di memoria di una variabile
valore e di una variabile riferimento con questo minicodice:
1
2
3
4
5
6
7
8
9
10
package main
import "fmt"
func main(){
val := 10
// stampa --> ’val’ address: 0x10d50038
fmt.Println("’val’ address:", &val)
ref := []int{1, 2, 3}
4
11
12
13
// stampa --> ’ref’ address: &[1 2 3]
fmt.Println("’ref’ address:", &ref)
}
L’operatore & restituisce il puntatore con l’indirizzo di memoria di una variabile qualsiasi, ma la funzione
fmt.Println() esegue giustamente una stampa ad alto livello del puntatore alla variabile riferimento
rispettandone la natura. Anziché far decidere alla funzione tuttofare fmt.Println() il formato di stampa
possiamo farlo esplicitamente usando il segnaposto %p (consulta la documentazione del pacchetto fmt):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import "fmt"
func main() {
// una slice, due variabili reference
s1 := []int{1, 2, 3, 4 ,5}
s2 := s1
fmt.Printf("address di s1: %p\n", &s1)
fmt.Printf("address di s2: %p\n", &s2)
// stampano -->
// address di s1: 0x10d6f100
// address di s2: 0x10d6f0f0
// verifichiamo che le due variabili
// si riferiscono allo stesso slice
s1[0]++
s1[1]++
s1[2]-s2[2]-s2[3]++
s2[4]++
fmt.Println(s1,s2)
// stampa --> [2 3 1 5 6] [2 3 1 5 6]
}
La risposta iniziale è corretta: due variabili riferimento non sono puntatori non contenendo un
riferimento diretto alla memoria ma contengono dati nascosti al programmatore ciascuna in un’area di
memoria propria.
8
Risposta due
Sapendo che in Go alle funzioni vengono passate le copie degli argomenti, cosa accade se
modifichiamo una variabile riferimento all’interno di una funzione?
Dunque, se l’argomento viene copiato all’interno della funzione la variabile riferimento sarà si una copia
dell’originale, conterrà quindi le stesse informazioni nascosto per l’accesso ai dati e quindi la modifica
all’interno della funzione modificherà l’oggetto, l’unico oggetto, a cui argomento passato e argomento di
funzione si riferiscono.
Utilizzando il tipo map[string]int lascio al lettore la scrittura del minicodice di verifica. . .
9
Risposta tre
In Go, conviene passare ad una funzione un puntatore a slice o lo slice stesso
La variabile che contiene uno slice è di tipo reference, quindi passare un puntatore a slice è solo più
complicato e non ci si guadagna nemmeno con la dimensione in memoria degli argomenti.
10
Una domanda per il lettore curioso
In Go le funzioni sono tipi di prima classe (come in Lua, copioni!). Questo significa che possiamo
passare una funzione come argomento di un’altra e creare funzioni anonime. La domanda è questa:
Le variabili che contengono una funzione in Go sono di tipo valore o di tipo riferimento?
5
11
Conclusione
Abbiamo studiato insieme le due classi di variabili in linguaggio Go, variabili valore e variabili
riferimento, lasciando al lettore un po’ di utile lavoro da fare su alcuni esercizi.
Come regola generale i moderni linguaggi di programmazione fanno si che ai tipi di dato semplici come
i numeri ed i booleani sia associato il tipo di variabile valore, mentre ai dati complessi come gli oggetti sia
associato il tipo di variabile riferimento.
Un saluto. R.
12
Licenza ed informazioni varie
Questo articolo come tutto il materiale didattico/divulgativo del blog http://okpanico.wordpress.com è
rilasciato sotto licenza Creative Commons “Attribuzione-Non commerciale-Non opere derivate” 2.5 Italia,
il cui testo integrale con valore legale è consultabile a questo indirizzo. Ciò significa che:
1. Bisogna sempre attribuire la paternità del materiale a http://okpanico.wordpress.com;
2. Non si può usare il materiale per fini commerciali;
3. Non si può alterare o trasformare i contenuti, ne’ usarne stralci per creare altre opere.
Se esplicitamente indicato nei commenti iniziali, il codice relativo a programmi software è rilasciato
nella specifica licenza.
12.1
Distribuzione/Citazioni
Ogni volta che usi o distribuisci parti ridistribuibili quest’opera devi farlo secondo i termini con cui esse
sono state rilasciate e avendo cura di comunicare tali termini con chiarezza. Ricorda di inserire sempre un
hyperlink alla risorsa che ridistribuisci o citi.
Il modo migliore per dimostrarmi il vostro apprezzamento è semplicemente quello di linkare direttamente
le pagine del blog, senza copiare gli articoli in altri siti, oltre naturalmente a lasciare un commento. Ci
sono però casi in cui vorreste poter estrapolare alcune parole dai miei articoli per incuriosire i vostri lettori.
In quel caso la prassi convenuta e che, grazie a tutti i blogger seri, viene coscienziosamente rispettata, è
questa:
• Creare un “blockquote”, ossia un campo in cui è inserita una citazione;
• Inserire nel blockquote solo il primo periodo (le prime poche frasi) di un articolo, diciamo fino ad
arrivare al link “Leggi il resto. . . ”;
• Linkare la rimanente parte dell’articolo all’originale su http://okpanico.wordpress.com.
Grazie per la collaborazione.
12.2
Colophon
Questo documento è stato composto con LATEX attraverso uno script in Lua chiamato wp2pdf che
elabora il file originale html del post pubblicato sul blog in WordPress. Per saperne di più contattatemi
via posta elettronica all’indirizzo nel titolo del documento, o lasciate un commento sul blog.
6
Scarica

La varietà di variabili in Go - Ok, panico