Gli array sono strutture molto efficienti da utilizzare ma profondamente statiche nella loro gestione: hanno una capacità fissa che risulta ideale per casi in cui siamo sicuri di quanti saranno gli elementi
da gestire ma rivelano limiti quando lo scenario si fa più dinamico. Per questi ultimi casi si ricorre agli slice, una sorta di versione potenziata degli array, senza i loro limiti e con più funzioni per poterne gestire gli elementi.
Dichiarare uno slice
Per prima cosa dobbiamo imparare a dichiarare slice e possiamo farlo con la notazione delle parentesi quadre o con la funzione make
. Nel primo caso possiamo dire che desideriamo avere a disposizione uno slice di interi:
var valori []int
Una volta dichiarato, uno slice viene caratterizzato dal suo contenuto (in questo caso sarà vuoto), una capacità ovvero il numero totale di elementi che potrà contenere ed una lunghezza che indica il numero di elementi che attualmente sono
contenuti al suo interno. Con la dichiarazione appena eseguita, abbiamo contemporaneamente sia lunghezza sia capacità nulli.
Con la funzione make
possiamo impostare inizialmente anche capacità e lunghezza dello slice:
valori = make([]int,3,8)
Con tale dichiarazione avremo uno slice di capacità 8 e lunghezza 3.
L'importanza della capacità in questo tipo di dichiarazioni consiste nello spazio che si ha a disposizione per poter inserire elementi. Ad esempio, nella prima dichiarazione avremo capacità zero pertanto non potremo inserire ancora elementi nella
struttura. L'assegnazione di valori infatti avviene negli slice come negli array, mediante parentesi quadre, ma ciò può essere fatto solo se la capacità rivela posizioni sufficienti. La seguente assegnazione porterà infatti ad un errore:
var valori []int
valori[0]=10
in quanto lo slice valori viene dichiarato come entità in memoria ma non ha ancora spazio allocato per sè. In questo caso, le funzioni len
e cap
restituirebbero entrambe 0 indicando così la lunghezza della struttura - la prima - e la capacità, la seconda.
Quando uno slice non ha spazio a disposizione lo si può espandere con la funzione append
:
// dichiarazione dello slice
var valori []int
// aggiunta dell'elemento 10
valori=append(valori,10)
// aggiunta dell'elemento 20
valori=append(valori,20)
// stampa di contenuto e dimensioni
fmt.Println(valori)
fmt.Println(len(valori))
fmt.Println(cap(valori))
Otteniamo in questo modo il seguente output:
[10 20]
2
2
dove la prima riga indica che lo slice in questo caso contiene due elementi - gli interi 10 e 20 - mentre la seconda e la terza indicano, rispettivamente, la lunghezza e la capacità restituite dalle funzioni len
e cap
.
Qualora avessimo dichiarato lo slice con la funzione make
avremmo già potuto avere degli spazi a disposizione allocati, disponibili per assegnazioni con parentesi quadre. Ecco un esempio:
// dichiarazione dello slice
valori := make([]int,4,8)
// assegnazione dei valori 10, 20, 30
valori[0]=10
valori[1]=20
valori[2]=30
// stampa delle dimensioni
fmt.Println(valori)
fmt.Println(len(valori))
fmt.Println(cap(valori))
Questa volta l'output indica i seguenti valori:
[10 20 30 0]
4
8
Al momento della definizione dello slice abbiamo indicato di volerne uno di capacità 8 ma lunghezza 4 e queste sono le due dimensioni indicate alle ultime due righe dell'output. Abbiamo però inserito al suo interno tre elementi, gli
interi 10, 20 e 30. Se osserviamo la prima riga dell'output questa è costituita da essi più un quarto valore impostato a 0: ciò deriva proprio dalla lunghezza pari a quattro che abbiamo richiesto in fase di inizializzazione.
Estrarre porzioni di slice
Una caratteristica importante degli slice consiste nella possibilità di estrarne una porzione utilizzando la notazione delle parentesi quadre indicando la posizione iniziale e finale del sub-slice che vogliamo estrarre. Ad esempio,
creiamo uno slice inizializzandolo dinamicamente in un colpo solo:
valori := []int{10, 20, 30, 40, 50, 60, 70, 80, 90}
Lo slice sarà così composto da nove elementi interi ed avrà contemporaneamente capacità e lunghezza pari a nove. Possiamo estrapolarne una porzione compresa, ad esempio, tra l'elemento in posizione 2 e quello in posizione 5 (escluso) con:
porzione := valori[2:5]
L'elemento porzione ottenuto avrà una lunghezza pari a tre ed il suo contenuto equivarrà a [30 40 50]. Si faccia attenzione però che la sequenza non è del tutto disgiunta ma con porzione abbiamo solo
ottenuto un modo per indirizzare velocemente alcuni elementi. Infatti, con il seguente codice
// dichiarazione iniziale
valori := []int{10, 20, 30, 40, 50, 60, 70, 80, 90}
// dichiarazione di porzione
porzione := valori[2:5]
// assegnazione del valore 500 alla posizione 0 di porzione
porzione[0]=500
// stampa di entrambi gli slice
fmt.Println(valori)
fmt.Println(porzione)
vediamo che il numero 500 è presente in entrambe le strutture dati, in un caso all'inizio, nell'altro in terza posizione e ciò dimostra il legame esistente tra porzione e valori:
[10 20 500 40 50 60 70 80 90]
[500 40 50]