Slice
In Go, array e slice sembrano quasi identici, ma le funzionalità hanno differenze significative. Gli array sono strutture dati a lunghezza fissa; una volta specificata la lunghezza, non può essere modificata. Le slice sono a lunghezza variabile e si espandono automaticamente quando la capacità non è sufficiente.
Array
Se si conosce in anticipo la lunghezza dei dati da memorizzare e non c'è bisogno di espansione nell'uso successivo, si può considerare l'uso di un array. Gli array in Go sono tipi di valore, non riferimenti, non sono puntatori all'elemento iniziale.
TIP
Quando un array viene passato come parametro a una funzione, poiché Go passa i parametri per valore, l'intero array verrà copiato.
Inizializzazione
La lunghezza di un array può essere solo un'espressione costante al momento della dichiarazione, non può essere una variabile. Non puoi dichiarare una variabile e poi usarla come valore di lunghezza dell'array.
// Esempio corretto
var a [5]int
// Esempio errato
l := 1
var b [l]intInizializza prima un array di interi di lunghezza 5
var nums [5]intPuoi anche inizializzare con elementi
nums := [5]int{1, 2, 3}Puoi far sì che il compilatore deduca automaticamente la lunghezza
nums := [...]int{1, 2, 3, 4, 5} // Equivale a nums := [5]int{1, 2, 3, 4, 5}, i puntini di sospensione devono essere presenti, altrimenti viene generata una slice, non un arrayPuoi anche ottenere un puntatore tramite la funzione new
nums := new([5]int)I metodi precedenti allocatoranno tutti una memoria di dimensione fissa per nums, la differenza è solo che l'ultimo metodo ottiene un puntatore come valore.
Quando si inizializza un array, va notato che la lunghezza deve essere un'espressione costante, altrimenti non supererà la compilazione. Un'espressione costante significa che il risultato finale dell'espressione è una costante. Ecco un esempio errato:
length := 5 // Questa è una variabile
var nums [length]intlength è una variabile, quindi non può essere utilizzata per inizializzare la lunghezza dell'array. Di seguito è riportato un esempio corretto:
const length = 5
var nums [length]int // Costante
var nums2 [length + 1]int // Espressione costante
var nums3 [(1 + 2 + 3) * 5]int // Espressione costante
var nums4 [5]int // Il più comunemente usatoUtilizzo
Basta il nome dell'array e l'indice per accedere all'elemento corrispondente nell'array.
fmt.Println(nums[0])Allo stesso modo, è possibile modificare gli elementi dell'array
nums[0] = 1È anche possibile accedere al numero di elementi nell'array tramite la funzione incorporata len
len(nums)La funzione incorporata cap accede alla capacità dell'array. La capacità dell'array è uguale alla lunghezza dell'array. La capacità ha significato solo per le slice.
cap(nums)Taglio
Il formato per tagliare un array è arr[startIndex:endIndex], l'intervallo di taglio è chiuso a sinistra e aperto a destra. Dopo il taglio, l'array diventa di tipo slice. Ecco un esempio:
nums := [5]int{1, 2, 3, 4, 5}
nums[:] // Intervallo della sotto-slice [0,5) -> [1 2 3 4 5]
nums[1:] // Intervallo della sotto-slice [1,5) -> [2 3 4 5]
nums[:5] // Intervallo della sotto-slice [0,5) -> [1 2 3 4 5]
nums[2:3] // Intervallo della sotto-slice [2,3) -> [3]
nums[1:3] // Intervallo della sotto-slice [1,3) -> [2 3]func main() {
arr := [5]int{1, 2, 3, 4, 5}
fmt.Printf("%T\n", arr)
fmt.Printf("%T\n", arr[1:2])
}Output
[5]int
[]intPer convertire un array in tipo slice, basta effettuare il taglio senza parametri. La slice convertita e l'array originale puntano alla stessa memoria. Modificare la slice causerà cambiamenti nel contenuto dell'array originale.
func main() {
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[:]
slice[0] = 0
fmt.Printf("array: %v\n", arr)
fmt.Printf("slice: %v\n", slice)
}Output
array: [0 2 3 4 5]
slice: [0 2 3 4 5]Se si desidera modificare la slice convertita, si consiglia di utilizzare il seguente metodo per la conversione
func main() {
arr := [5]int{1, 2, 3, 4, 5}
slice := slices.Clone(arr[:])
slice[0] = 0
fmt.Printf("array: %v\n", arr)
fmt.Printf("slice: %v\n", slice)
}Output
array: [1 2 3 4 5]
slice: [0 2 3 4 5]Slice
L'ambito di applicazione delle slice in Go è molto più ampio degli array. Vengono utilizzate per memorizzare dati di lunghezza sconosciuta e durante l'uso successivo potrebbero esserci frequenti inserimenti ed eliminazioni di elementi.
Inizializzazione
I metodi di inizializzazione di una slice sono i seguenti
var nums []int // Valore
nums := []int{1, 2, 3} // Valore
nums := make([]int, 0, 0) // Valore
nums := new([]int) // PuntatoreSi può vedere che la differenza tra slice e array è solo la mancanza di una lunghezza di inizializzazione. In genere, si consiglia di utilizzare make per creare una slice vuota. Per le slice, la funzione make accetta tre parametri: tipo, lunghezza, capacità. Facciamo un esempio per spiegare la differenza tra lunghezza e capacità. Supponiamo di avere un secchio d'acqua, l'acqua non è piena. L'altezza del secchio è la capacità del secchio, che rappresenta quanta acqua può contenere in totale. L'altezza dell'acqua nel secchio rappresenta la lunghezza. L'altezza dell'acqua deve essere minore o uguale all'altezza del secchio, altrimenti l'acqua traboccherà. Quindi, la lunghezza della slice rappresenta il numero di elementi nella slice, la capacità della slice rappresenta quanti elementi può contenere in totale. La differenza più grande tra slice e array è che la capacità della slice si espande automaticamente, mentre l'array no. Per maggiori dettagli visita Riferimento - Lunghezza e capacità.
TIP
L'implementazione sottostante delle slice è ancora un array, è un tipo di riferimento. Può essere semplicemente inteso come un puntatore all'array sottostante (essenzialmente una slice in Go è una struttura che contiene un puntatore all'array sottostante, un valore di lunghezza e un valore di capacità). Pertanto, quando una slice viene passata come parametro a una funzione, l'array sottostante non viene copiato e le modifiche apportate alla slice all'interno della funzione si rifletteranno nella slice originale.
Dichiarando una slice con var nums []int, il valore predefinito è nil, quindi non verrà allocata memoria. Quando si utilizza make per l'inizializzazione, si consiglia di preallocare una capacità sufficiente per ridurre efficacemente il consumo di memoria per le espansioni successive.
Utilizzo
L'uso di base di una slice è completamente coerente con quello di un array, la differenza è solo che la slice può variare dinamicamente in lunghezza. Di seguito alcuni esempi.
Le slice possono eseguire molte operazioni tramite la funzione append. La firma della funzione è la seguente: slice è la slice di destinazione a cui aggiungere elementi, elems sono gli elementi da aggiungere, il valore di ritorno è la slice dopo l'aggiunta.
func append(slice []Type, elems ...Type) []TypeCrea prima una slice vuota con lunghezza 0 e capacità 0, quindi inserisci alcuni elementi alla fine e infine stampa lunghezza e capacità.
nums := make([]int, 0, 0)
nums = append(nums, 1, 2, 3, 4, 5, 6, 7)
fmt.Println(len(nums), cap(nums)) // 7 8 Si può vedere che lunghezza e capacità non sono coerenti.La dimensione del buffer riservato per la nuova slice ha una certa regolarità. Prima dell'aggiornamento della versione golang 1.18, la maggior parte degli articoli online descriveva la strategia di espansione delle slice in questo modo: Quando la capacità della slice originale è inferiore a 1024, la nuova capacità della slice diventa il doppio dell'originale; quando la capacità della slice originale supera 1024, la nuova capacità della slice diventa 1,25 volte l'originale. Dopo l'aggiornamento della versione 1.18, la strategia di espansione delle slice è diventata: Quando la capacità della slice originale (oldcap) è inferiore a 256, la nuova capacità della slice (newcap) è il doppio dell'originale; quando la capacità della slice originale supera 256, la nuova capacità della slice newcap = oldcap+(oldcap+3*256)/4
Inserimento di elementi
L'inserimento di elementi in una slice deve essere combinato con la funzione append. Ecco una slice esistente:
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}Inserisci elementi dall'inizio
nums = append([]int{-1, 0}, nums...)
fmt.Println(nums) // [-1 0 1 2 3 4 5 6 7 8 9 10]Inserisci elementi dall'indice intermedio i
nums = append(nums[:i+1], append([]int{999, 999}, nums[i+1:]...)...)
fmt.Println(nums) // i=3, [1 2 3 4 999 999 5 6 7 8 9 10]Inserisci elementi dalla fine, è l'uso più originale di append
nums = append(nums, 99, 100)
fmt.Println(nums) // [1 2 3 4 5 6 7 8 9 10 99 100]Eliminazione di elementi
L'eliminazione di elementi da una slice deve essere combinata con la funzione append. Ecco una slice esistente
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}Elimina n elementi dall'inizio
nums = nums[n:]
fmt.Println(nums) //n=3 [4 5 6 7 8 9 10]Elimina n elementi dalla fine
nums = nums[:len(nums)-n]
fmt.Println(nums) //n=3 [1 2 3 4 5 6 7]Elimina n elementi a partire dall'indice intermedio i
nums = append(nums[:i], nums[i+n:]...)
fmt.Println(nums)// i=2, n=3, [1 2 6 7 8 9 10]Elimina tutti gli elementi
nums = nums[:0]
fmt.Println(nums) // []Copia
Durante la copia di una slice, è necessario assicurarsi che la slice di destinazione abbia una lunghezza sufficiente. Ad esempio
func main() {
dest := make([]int, 0)
src := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(src, dest)
fmt.Println(copy(dest, src))
fmt.Println(src, dest)
}[1 2 3 4 5 6 7 8 9] []
0
[1 2 3 4 5 6 7 8 9] []Modificando la lunghezza a 10, l'output è il seguente
[1 2 3 4 5 6 7 8 9] [0 0 0 0 0 0 0 0 0 0]
9
[1 2 3 4 5 6 7 8 9] [1 2 3 4 5 6 7 8 9 0]Iterazione
L'iterazione di una slice è completamente coerente con quella di un array. Ciclo for
func main() {
slice := []int{1, 2, 3, 4, 5, 7, 8, 9}
for i := 0; i < len(slice); i++ {
fmt.Println(slice[i])
}
}Ciclo for range
func main() {
slice := []int{1, 2, 3, 4, 5, 7, 8, 9}
for index, val := range slice {
fmt.Println(index, val)
}
}Slice multidimensionali
Prima guarda l'esempio seguente, c'è anche una spiegazione nella documentazione ufficiale: Effective Go - Slice bidimensionali
var nums [5][5]int
for _, num := range nums {
fmt.Println(num)
}
fmt.Println()
slices := make([][]int, 5)
for _, slice := range slices {
fmt.Println(slice)
}Il risultato dell'output è
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[]
[]
[]
[]
[]Si può vedere che, sebbene siano entrambi array e slice bidimensionali, la loro struttura interna è diversa. Quando un array viene inizializzato, le sue dimensioni unidimensionali e bidimensionali sono già fisse, mentre la lunghezza di una slice non è fissa. Ogni slice nella slice può avere lunghezze diverse, quindi deve essere inizializzata separatamente. Modificando la parte di inizializzazione della slice con il seguente codice, funziona.
slices := make([][]int, 5)
for i := 0; i < len(slices); i++ {
slices[i] = make([]int, 5)
}Il risultato finale dell'output è
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]Espressione estesa
TIP
Solo le slice possono utilizzare l'espressione estesa
Sia le slice che gli array possono utilizzare l'espressione semplice per il taglio, ma solo le slice possono utilizzare l'espressione estesa. Questa funzionalità è stata aggiunta nella versione Go 1.2, principalmente per risolvere il problema della condivisione dell'array sottostante in lettura/scrittura da parte delle slice. Il formato principale è il seguente, deve soddisfare la relazione low <= high <= max <= cap. La capacità della slice tagliata utilizzando l'espressione estesa è max-low
slice[low:high:max]low e high mantengono il loro significato originale, mentre max si riferisce alla capacità massima. Nell'esempio seguente, max viene omesso, quindi la capacità di s2 è cap(s1)-low
s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} // cap = 9
s2 := s1[3:4] // cap = 9 - 3 = 6Questo comporta un problema evidente: s1 e s2 condividono lo stesso array sottostante. Durante la lettura/scrittura di s2, è possibile che i dati di s1 vengano influenzati. Il seguente codice appartiene a questa situazione
s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} // cap = 9
s2 := s1[3:4] // cap = 9 - 3 = 6
s2 = append(s2, 1) // Aggiunge un nuovo elemento, poiché la capacità è 6, non c'è espansione, modifica direttamente l'array sottostante
fmt.Println(s2)
fmt.Println(s1)L'output finale è
[4 1]
[1 2 3 4 1 6 7 8 9]Si può vedere che, anche se si aggiungono elementi solo a s2, anche s1 viene modificato. L'espressione estesa è nata per risolvere questo tipo di problema. Basta modificare leggermente per risolvere il problema
func main() {
s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} // cap = 9
s2 := s1[3:4:4] // cap = 4 - 3 = 1
s2 = append(s2, 1) // Capacità insufficiente, alloca un nuovo array sottostante
fmt.Println(s2)
fmt.Println(s1)
}Ora il risultato ottenuto è normale
[4 1]
[1 2 3 4 5 6 7 8 9]clear
In go1.21 è stata aggiunta la funzione incorporata clear. clear imposterà tutti i valori nella slice a zero.
package main
import (
"fmt"
)
func main() {
s := []int{1, 2, 3, 4}
clear(s)
fmt.Println(s)
}Output
[0 0 0 0]Se si desidera svuotare una slice, è possibile
func main() {
s := []int{1, 2, 3, 4}
s = s[:0:0]
fmt.Println(s)
}Limitando la capacità dopo il taglio, è possibile evitare di sovrascrivere gli elementi successivi della slice originale.
