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

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):

  1. Puntatore: indirizzo del primo elemento nell’array sottostante
  2. Lunghezza: numero di elementi accessibili
  3. 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.