Slices
Em Go, arrays e slices parecem quase idênticos, mas têm funcionalidades bastante diferentes. Arrays são estruturas de dados de comprimento fixo, uma vez especificado o comprimento não pode ser alterado, enquanto slices têm comprimento variável e expandem automaticamente quando a capacidade é insuficiente.
Arrays
Se você souber de antemão o comprimento dos dados a serem armazenados e não houver necessidade de expansão durante o uso subsequente, pode considerar usar arrays. Arrays em Go são tipos de valor, não referências, não são ponteiros para o primeiro elemento.
TIP
Arrays como tipos de valor, quando passados como parâmetros para funções, como Go usa passagem por valor, o array inteiro é copiado.
Inicialização
Ao declarar um array, o comprimento deve ser uma constante, não uma variável, você não pode declarar uma variável e depois usar a variável como comprimento do array
// Exemplo correto
var a [5]int
// Exemplo errado
l := 1
var b [l]intPrimeiro vamos inicializar um array de inteiros com comprimento 5
var nums [5]intTambém pode inicializar com elementos
nums := [5]int{1, 2, 3}Pode deixar o compilador inferir automaticamente o comprimento
nums := [...]int{1, 2, 3, 4, 5} // equivalente a nums := [5]int{1, 2, 3, 4, 5}, as reticências devem existir, caso contrário gera um slice, não um arrayTambém pode obter um ponteiro através da função new
nums := new([5]int)Todas as formas acima vão alocar um espaço de memória de tamanho fixo para nums, a diferença é que a última forma obtém um ponteiro.
Ao inicializar arrays, note que o comprimento deve ser uma expressão constante, caso contrário não vai compilar, uma expressão constante é uma expressão cujo resultado final é uma constante, exemplo de erro
length := 5 // isso é uma variável
var nums [length]intlength é uma variável, portanto não pode ser usado para inicializar o comprimento do array, exemplo correto abaixo
const length = 5
var nums [length]int // constante
var nums2 [length + 1]int // expressão constante
var nums3 [(1 + 2 + 3) * 5]int // expressão constante
var nums4 [5]int // o mais comumUso
Com o nome do array e o índice, pode acessar o elemento correspondente no array.
fmt.Println(nums[0])Também pode modificar elementos do array
nums[0] = 1Também pode acessar a quantidade de elementos do array através da função incorporada len
len(nums)A função incorporada cap acessa a capacidade do array, a capacidade do array é igual ao seu comprimento, capacidade só tem significado para slices.
cap(nums)Fatiamento
O formato para fatiar um array é arr[startIndex:endIndex], o intervalo de fatiamento é fechado à esquerda e aberto à direita. E após fatiar, o array se torna um tipo slice. Exemplo abaixo
nums := [5]int{1, 2, 3, 4, 5}
nums[:] // intervalo do sub-slice [0,5) -> [1 2 3 4 5]
nums[1:] // intervalo do sub-slice [1,5) -> [2 3 4 5]
nums[:5] // intervalo do sub-slice [0,5) -> [1 2 3 4 5]
nums[2:3] // intervalo do sub-slice [2,3) -> [3]
nums[1:3] // intervalo do sub-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])
}Saída
[5]int
[]intPara converter um array em tipo slice, basta fatiar sem parâmetros, o slice convertido e o array original apontam para a mesma memória, modificar o slice causa mudanças no conteúdo do array original
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)
}Saída
array: [0 2 3 4 5]
slice: [0 2 3 4 5]Se quiser modificar o slice convertido sem afetar o original, recomenda-se usar a seguinte forma de conversão
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)
}Saída
array: [1 2 3 4 5]
slice: [0 2 3 4 5]Slices
Slices têm uma aplicação muito mais ampla em Go do que arrays, eles são usados para armazenar dados de comprimento desconhecido, e durante o uso subsequente podem frequentemente inserir e remover elementos.
Inicialização
As formas de inicializar slices são as seguintes
var nums []int // valor
nums := []int{1, 2, 3} // valor
nums := make([]int, 0, 0) // valor
nums := new([]int) // ponteiroPode-se ver que a diferença externa entre slice e array é apenas a ausência de um comprimento de inicialização. Normalmente, recomenda-se usar make para criar um slice vazio, só que para slices, a função make recebe três parâmetros: tipo, comprimento, capacidade. Vamos dar um exemplo para explicar a diferença entre comprimento e capacidade, suponha que há um balde de água, a água não está cheia, a altura do balde é a capacidade do balde, representando quantos litros de água pode armazenar no total, enquanto a altura da água no balde representa o comprimento, a altura da água deve ser menor ou igual à altura do balde, caso contrário a água transborda. Portanto, o comprimento do slice representa o número de elementos no slice, a capacidade do slice representa quantos elementos o slice pode armazenar no total, a maior diferença entre slice e array é que a capacidade do slice expande automaticamente, enquanto o array não, para mais detalhes vá para Manual de Referência - Comprimento e Capacidade.
TIP
A implementação subjacente do slice ainda é um array, é um tipo de referência, pode ser entendido simplesmente como um ponteiro para um array subjacente (essencialmente um slice em Go é uma struct, contendo um ponteiro para o array subjacente, valor de comprimento, valor de capacidade). Portanto, quando um slice é passado como parâmetro de função, o array subjacente não é copiado, modificações no slice passado dentro da função serão refletidas no slice original.
Um slice declarado através de var nums []int tem valor padrão nil, então não vai alocar memória para ele, ao usar make para inicializar, recomenda-se pré-alocar uma capacidade suficiente, isso pode reduzir efetivamente o consumo de memória em expansões subsequentes.
Uso
O uso básico de slices é completamente idêntico ao de arrays, a diferença é que slices podem ter comprimento dinâmico, vejamos alguns exemplos.
Slices podem realizar muitas operações através da função append, a assinatura da função é a seguinte, slice é o slice alvo onde elementos serão adicionados, elems são os elementos a serem adicionados, o valor de retorno é o slice após a adição.
func append(slice []Type, elems ...Type) []TypePrimeiro crie um slice vazio com comprimento 0 e capacidade 0, depois insira alguns elementos no final, por fim mostre o comprimento e a capacidade.
nums := make([]int, 0, 0)
nums = append(nums, 1, 2, 3, 4, 5, 6, 7)
fmt.Println(len(nums), cap(nums)) // 7 8 pode-se ver que o comprimento e a capacidade não são consistentes.O tamanho do buffer reservado para o novo slice tem uma certa regularidade. Antes da atualização da versão golang 1.18, a maioria dos artigos na internet descrevia a estratégia de expansão do slice assim Quando a capacidade do slice original é menor que 1024, a capacidade do novo slice se torna 2 vezes a original; quando a capacidade do slice original excede 1024, a capacidade do novo slice se torna 1,25 vezes a original. Após a atualização da versão 1.18, a estratégia de expansão do slice mudou para Quando a capacidade do slice original (oldcap) é menor que 256, a capacidade do novo slice (newcap) é 2 vezes a original; quando a capacidade do slice original excede 256, a capacidade do novo slice newcap = oldcap+(oldcap+3*256)/4
Inserindo Elementos
A inserção de elementos em slices também precisa ser usada em combinação com a função append, dado o slice abaixo
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}Inserir elementos no início
nums = append([]int{-1, 0}, nums...)
fmt.Println(nums) // [-1 0 1 2 3 4 5 6 7 8 9 10]Inserir elementos do índice intermediário 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]Inserir elementos no final é o uso mais original de append
nums = append(nums, 99, 100)
fmt.Println(nums) // [1 2 3 4 5 6 7 8 9 10 99 100]Removendo Elementos
A remoção de elementos de slices precisa ser usada em combinação com a função append, dado o slice abaixo
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}Remover n elementos do início
nums = nums[n:]
fmt.Println(nums) //n=3 [4 5 6 7 8 9 10]Remover n elementos do final
nums = nums[:len(nums)-n]
fmt.Println(nums) //n=3 [1 2 3 4 5 6 7]Remover n elementos a partir do índice i no meio
nums = append(nums[:i], nums[i+n:]...)
fmt.Println(nums)// i=2, n=3, [1 2 6 7 8 9 10]Remover todos os elementos
nums = nums[:0]
fmt.Println(nums) // []Cópia
Ao copiar slices, precisa-se garantir que o slice de destino tenha comprimento suficiente, por exemplo
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 o comprimento para 10, a saída é
[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]Percorrendo
Percorrer slices é completamente idêntico a percorrer arrays, usando loop for
func main() {
slice := []int{1, 2, 3, 4, 5, 7, 8, 9}
for i := 0; i < len(slice); i++ {
fmt.Println(slice[i])
}
}Loop for range
func main() {
slice := []int{1, 2, 3, 4, 5, 7, 8, 9}
for index, val := range slice {
fmt.Println(index, val)
}
}Slices Multidimensionais
Veja o exemplo abaixo, a documentação oficial também explica: Effective Go - Slices Bidimensionais
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)
}O resultado da saída é
[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]
[]
[]
[]
[]
[]Pode-se ver que, sendo ambos bidimensionais, arrays e slices têm estruturas internas diferentes. Arrays têm comprimento fixo em ambas as dimensões durante a inicialização, enquanto slices têm comprimento variável, cada slice dentro de um slice pode ter comprimento diferente, então precisa ser inicializado separadamente, a parte de inicialização de slices pode ser modificada como o código abaixo.
slices := make([][]int, 5)
for i := 0; i < len(slices); i++ {
slices[i] = make([]int, 5)
}O resultado final da saída é
[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]Expressões Estendidas
TIP
Apenas slices podem usar expressões estendidas
Tanto slices quanto arrays podem usar expressões simples para fatiamento, mas expressões estendidas só podem ser usadas por slices, essa característica foi adicionada na versão Go 1.2, principalmente para resolver o problema de leitura e escrita de slices compartilhando arrays subjacentes, o formato principal é o seguinte, precisa satisfazer a relação low<= high <= max <= cap, a capacidade do slice fatiado com expressão estendida é max-low
slice[low:high:max]low e high ainda têm os mesmos significados originais, enquanto o max adicional refere-se à capacidade máxima, por exemplo, no exemplo abaixo max é omitido, então a capacidade de s2 é cap(s1)-low
s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} // cap = 9
s2 := s1[3:4] // cap = 9 - 3 = 6Isso vai criar um problema óbvio, s1 e s2 compartilham o mesmo array subjacente, ao ler e escrever em s2, pode-se afetar os dados de s1, o código abaixo é um exemplo dessa situação
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) // adicionando novo elemento, como a capacidade é 6, não há expansão, modifica diretamente o array subjacente
fmt.Println(s2)
fmt.Println(s1)A saída final é
[4 1]
[1 2 3 4 1 6 7 8 9]Pode-se ver que ao adicionar elementos em s2, até s1 foi modificado junto, expressões estendidas foram criadas para resolver esse tipo de problema, basta uma pequena modificação para resolver
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) // capacidade insuficiente, aloca novo array subjacente
fmt.Println(s2)
fmt.Println(s1)
}Agora o resultado obtido está correto
[4 1]
[1 2 3 4 5 6 7 8 9]clear
No go 1.21 foi adicionada a função incorporada clear, clear vai definir todos os valores dentro do slice como valor zero
package main
import (
"fmt"
)
func main() {
s := []int{1, 2, 3, 4}
clear(s)
fmt.Println(s)
}Saída
[0 0 0 0]Se quiser esvaziar o slice, pode fazer
func main() {
s := []int{1, 2, 3, 4}
s = s[:0:0]
fmt.Println(s)
}Limitando a capacidade após o fatiamento, isso evita sobrescrever elementos subsequentes do slice original.
