Slice in Go
Le slice sono la struttura dati piu utilizzata in Go. A differenza degli array, le slice hanno dimensione dinamica e rappresentano una vista flessibile su un array sottostante. Comprendere le slice, la loro struttura interna e i pattern di utilizzo e essenziale per ogni programmatore Go. In questa guida esploreremo tutto cio che serve sapere.
Creare Slice con Letterali
Il modo piu semplice per creare una slice e usare un letterale, simile a un array ma senza specificare la dimensione.
package main
import "fmt"
func main() {
// Slice letterale (nota: nessuna dimensione tra le parentesi)
frutti := []string{"mela", "banana", "arancia"}
fmt.Println(frutti) // [mela banana arancia]
fmt.Println(len(frutti)) // 3
}
Creare Slice con make
La funzione make permette di creare una slice specificando il tipo, la lunghezza e opzionalmente la capacita.
// Slice di 5 interi (tutti inizializzati a 0)
numeri := make([]int, 5)
fmt.Println(numeri) // [0 0 0 0 0]
// Slice con lunghezza 3 e capacita 10
dati := make([]int, 3, 10)
fmt.Println(len(dati)) // 3
fmt.Println(cap(dati)) // 10
Creare una slice con capacita maggiore della lunghezza e utile quando si prevede di aggiungere elementi, evitando riallocazioni.
Creare Slice da Array
Una slice puo essere creata come sottoinsieme di un array esistente.
array := [5]int{10, 20, 30, 40, 50}
// Slice dall'indice 1 (incluso) al 4 (escluso)
slice := array[1:4]
fmt.Println(slice) // [20 30 40]
// Dall'inizio all'indice 3
inizio := array[:3]
fmt.Println(inizio) // [10 20 30]
// Dall'indice 2 alla fine
fine := array[2:]
fmt.Println(fine) // [30 40 50]
// Copia completa dell'array come slice
tutto := array[:]
fmt.Println(tutto) // [10 20 30 40 50]
Lunghezza e Capacita
Ogni slice ha una lunghezza (len) e una capacita (cap). La lunghezza e il numero di elementi nella slice. La capacita e il numero di elementi nell’array sottostante a partire dal primo elemento della slice.
s := make([]int, 3, 7)
fmt.Printf("Lunghezza: %d, Capacita: %d\n", len(s), cap(s))
// Lunghezza: 3, Capacita: 7
Aggiungere Elementi con append
La funzione append aggiunge elementi a una slice. Se la capacita non e sufficiente, viene allocato un nuovo array sottostante.
package main
import "fmt"
func main() {
var numeri []int // Slice nil
fmt.Println(numeri, len(numeri), cap(numeri)) // [] 0 0
numeri = append(numeri, 1)
numeri = append(numeri, 2, 3)
numeri = append(numeri, 4, 5, 6)
fmt.Println(numeri) // [1 2 3 4 5 6]
// Aggiungere un'altra slice usando ...
altri := []int{7, 8, 9}
numeri = append(numeri, altri...)
fmt.Println(numeri) // [1 2 3 4 5 6 7 8 9]
}
Nota fondamentale: append restituisce una nuova slice. Il risultato deve sempre essere assegnato.
Slicing di Slice
Si puo creare una sub-slice da una slice esistente. Entrambe condividono lo stesso array sottostante.
originale := []int{10, 20, 30, 40, 50}
sub := originale[1:3]
fmt.Println(sub) // [20 30]
// Modifica attraverso la sub-slice
sub[0] = 999
fmt.Println(originale) // [10 999 30 40 50]
Attenzione: la modifica della sub-slice influisce sull’originale perche condividono la stessa memoria.
Copiare Slice con copy
Per creare una copia indipendente, si usa la funzione copy.
originale := []int{1, 2, 3, 4, 5}
copia := make([]int, len(originale))
copiati := copy(copia, originale)
fmt.Println("Elementi copiati:", copiati) // 5
fmt.Println("Copia:", copia) // [1 2 3 4 5]
// La modifica della copia non influisce sull'originale
copia[0] = 999
fmt.Println("Originale:", originale) // [1 2 3 4 5]
fmt.Println("Copia:", copia) // [999 2 3 4 5]
Slice Nil vs Slice Vuota
Una slice nil e una slice vuota sono diverse ma si comportano in modo simile.
var nilSlice []int // Slice nil
vuota := []int{} // Slice vuota
vuotaMake := make([]int, 0) // Slice vuota
fmt.Println(nilSlice == nil) // true
fmt.Println(vuota == nil) // false
fmt.Println(vuotaMake == nil) // false
// Entrambe hanno lunghezza e capacita 0
fmt.Println(len(nilSlice), len(vuota)) // 0 0
// append funziona su entrambe
nilSlice = append(nilSlice, 1)
vuota = append(vuota, 1)
In pratica, una slice nil e la forma preferita per dichiarare una slice vuota.
Struttura Interna della Slice
Internamente, una slice e composta da tre campi (slice header):
- Puntatore: indirizzo del primo elemento nell’array sottostante
- Lunghezza: numero di elementi accessibili
- Capacita: numero totale di elementi disponibili nell’array
// Rappresentazione concettuale:
// type slice struct {
// array unsafe.Pointer
// len int
// cap int
// }
s := make([]int, 3, 5)
// puntatore -> [0, 0, 0, _, _]
// ^-----------^
// len=3 cap=5
Rimuovere Elementi da una Slice
Go non ha una funzione built-in per rimuovere elementi. Si usano pattern con slicing e append.
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5}
// Rimuovere l'elemento all'indice 2 (mantenendo l'ordine)
indice := 2
s = append(s[:indice], s[indice+1:]...)
fmt.Println(s) // [1 2 4 5]
}
Pattern: Filtrare una Slice
Un pattern comune e filtrare una slice in-place senza allocare memoria aggiuntiva.
func filtraPari(numeri []int) []int {
risultato := numeri[:0] // Slice con lunghezza 0, stessa capacita
for _, n := range numeri {
if n%2 == 0 {
risultato = append(risultato, n)
}
}
return risultato
}
func main() {
numeri := []int{1, 2, 3, 4, 5, 6, 7, 8}
pari := filtraPari(numeri)
fmt.Println(pari) // [2 4 6 8]
}
Slice Multidimensionali
Le slice possono contenere altre slice, creando strutture multidimensionali.
matrice := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
for _, riga := range matrice {
fmt.Println(riga)
}
Conclusione
Le slice sono il cuore della gestione dei dati sequenziali in Go. Comprendere la differenza tra lunghezza e capacita, il funzionamento dello slice header e la condivisione dell’array sottostante e fondamentale per evitare bug sottili. Usate make per pre-allocare la capacita quando conoscete la dimensione approssimativa, copy per creare copie indipendenti e append per aggiungere elementi in modo sicuro.