Skip to content

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

go
// Exemplo correto
var a [5]int

// Exemplo errado
l := 1
var b [l]int

Primeiro vamos inicializar um array de inteiros com comprimento 5

go
var nums [5]int

Também pode inicializar com elementos

go
nums := [5]int{1, 2, 3}

Pode deixar o compilador inferir automaticamente o comprimento

go
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 array

Também pode obter um ponteiro através da função new

go
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

go
length := 5 // isso é uma variável
var nums [length]int

length é uma variável, portanto não pode ser usado para inicializar o comprimento do array, exemplo correto abaixo

go
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 comum

Uso

Com o nome do array e o índice, pode acessar o elemento correspondente no array.

go
fmt.Println(nums[0])

Também pode modificar elementos do array

go
nums[0] = 1

Também pode acessar a quantidade de elementos do array através da função incorporada len

go
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.

go
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

go
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]
go
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
[]int

Para 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

go
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

go
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

go
var nums []int // valor
nums := []int{1, 2, 3} // valor
nums := make([]int, 0, 0) // valor
nums := new([]int) // ponteiro

Pode-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.

go
func append(slice []Type, elems ...Type) []Type

Primeiro crie um slice vazio com comprimento 0 e capacidade 0, depois insira alguns elementos no final, por fim mostre o comprimento e a capacidade.

go
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

go
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

Inserir elementos no início

go
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

go
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

go
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

go
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

Remover n elementos do início

go
nums = nums[n:]
fmt.Println(nums) //n=3 [4 5 6 7 8 9 10]

Remover n elementos do final

go
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

go
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

go
nums = nums[:0]
fmt.Println(nums) // []

Cópia

Ao copiar slices, precisa-se garantir que o slice de destino tenha comprimento suficiente, por exemplo

go
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

go
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

go
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

go
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.

go
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

go
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

go
s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} // cap = 9
s2 := s1[3:4] // cap = 9 - 3 = 6

Isso 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

go
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

go
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

go
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

go
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.

Golang por www.golangdev.cn edit