Arrays e Slices
Arrays e Slices
Imagine que você compra uma caixa inteira de leite no supermercado — um array é como aquela caixa de capacidade fixa que não pode receber mais quando cheia; um slice é como uma sacola de compras que pode continuar expandindo — quando você tem mais itens, basta trocar por uma sacola maior. Em Go, arrays têm comprimento fixo e são tipos de valor; slices têm comprimento variável e são tipos de referência. Na prática, slices são usados muito mais frequentemente do que arrays, mas entender arrays é a base para entender slices.
1. Conceitos Fundamentais
| Conceito | Descrição |
|---|---|
Array [N]T |
Comprimento fixo; o tamanho deve ser especificado na declaração; tipo de valor — atribuição e passagem de parâmetros copiam o array inteiro |
Slice []T |
Comprimento variável; referencia um array subjacente; tipo de referência — atribuição e passagem de parâmetros compartilham o array subjacente |
make([]T, len, cap) |
Maneira recomendada de criar slices; pode especificar comprimento e capacidade |
append(slice, elems...) |
Adiciona elementos a um slice, retorna um novo slice; pode acionar expansão do array subjacente |
copy(dst, src) |
Copia conteúdo do slice src para dst, retorna o número de elementos copiados |
Comprimento len() |
O número real de elementos no slice |
Capacidade cap() |
O número de elementos da posição inicial do slice até o final do array subjacente |
2. Sintaxe/Uso Básico
Declaração e Inicialização de Array
// Declarar um array int de comprimento 5, inicializado com valor zero
var arr1 [5]int
// Declarar e inicializar
var arr2 = [5]int{1, 2, 3, 4, 5}
// Deixar o compilador inferir o comprimento
arr3 := [...]int{10, 20, 30}
// Inicializar com índices específicos
arr4 := [5]int{1: 100, 3: 300} // [0, 100, 0, 300, 0]
Declaração e Inicialização de Slice
// Declarar um slice nil (valor zero é nil, mas pode fazer append diretamente)
var s1 []int
// Inicialização literal
s2 := []int{1, 2, 3}
// Criar slice a partir de array
arr := [5]int{10, 20, 30, 40, 50}
s3 := arr[1:4] // [20, 30, 40]
// Criar slice com make
s4 := make([]int, 5) // Comprimento 5, capacidade 5
s5 := make([]int, 3, 10) // Comprimento 3, capacidade 10
nil e slices vazios ([]int{} ou make([]int, 0)) são funcionalmente equivalentes — len e cap são ambos 0, e append funciona normalmente. A diferença é que slices nil são serializados como null em JSON, enquanto slices vazios são serializados como [].
Operações de Slice
s := []int{10, 20, 30, 40, 50}
// Fatiar um sub-slice [fechado à esquerda, aberto à direita)
s1 := s[1:3] // [20, 30]
s2 := s[:3] // [10, 20, 30], do início
s3 := s[2:] // [30, 40, 50], até o final
s4 := s[:] // [10, 20, 30, 40, 50], slice completo
// Comprimento e capacidade
fmt.Println(len(s)) // 5
fmt.Println(cap(s)) // 5
// Adicionar elementos com append
s = append(s, 60) // Adicionar elemento único
s = append(s, 70, 80, 90) // Adicionar múltiplos elementos
s = append(s, []int{100}...) // Adicionar outro slice
// Copiar slices
src := []int{1, 2, 3}
dst := make([]int, len(src))
n := copy(dst, src) // dst = [1, 2, 3], n = 3
append retorna um novo slice; você deve capturar o valor de retorno. Quando len(s) == cap(s), append aloca um novo array subjacente, e os slices antigo e novo não compartilham mais dados.
s[baixo:alto] para fatiar, o novo slice compartilha o array subjacente com o original. Modificar elementos no sub-slice afeta o original. Use copy se precisar de uma cópia independente.
3. Código de Exemplo
Exemplo 1: Uso Básico (Dificuldade ⭐)
package main
import "fmt"
func main() {
// ========== Arrays ==========
// Declarar e inicializar um array de 5 notas
notas := [5]int{90, 85, 78, 92, 88}
fmt.Println("Array de notas:", notas)
// Acessar e modificar elementos por índice
fmt.Println("Primeira nota:", notas[0])
notas[2] = 80 // Alterar a terceira nota para 80
fmt.Println("Após modificação:", notas)
// Travessia de array: loop for tradicional
fmt.Println("\n--- Travessia for tradicional ---")
for i := 0; i < len(notas); i++ {
fmt.Printf("Nota %d: %d\n", i+1, notas[i])
}
// Travessia de array: range
fmt.Println("\n--- Travessia range ---")
for indice, valor := range notas {
fmt.Printf("Índice %d: %d\n", indice, valor)
}
// ========== Slices ==========
// Derivar slice de array
top3 := notas[0:3] // Primeiras 3 notas
fmt.Println("\nPrimeiras 3 notas:", top3)
// Criar slice com literal
frutas := []string{"Maçã", "Banana", "Laranja"}
fmt.Println("Slice de frutas:", frutas)
// Adicionar elemento com append
frutas = append(frutas, "Uva")
fmt.Println("Após adicionar Uva:", frutas)
// Comprimento e capacidade
fmt.Printf("Comprimento: %d, Capacidade: %d\n", len(frutas), cap(frutas))
}
Saída:
Array de notas: [90 85 78 92 88]
Primeira nota: 90
Após modificação: [90 85 80 92 88]
--- Travessia for tradicional ---
Nota 1: 90
Nota 2: 85
Nota 3: 80
Nota 4: 92
Nota 5: 88
--- Travessia range ---
Índice 0: 90
Índice 1: 85
Índice 2: 80
Índice 3: 92
Índice 4: 88
Primeiras 3 notas: [90 85 80]
Slice de frutas: [Maçã Banana Laranja]
Após adicionar Uva: [Maçã Banana Laranja Uva]
Comprimento: 4, Capacidade: 4
Exemplo 2: Uso Intermediário (Dificuldade ⭐⭐)
package main
import "fmt"
func main() {
// ========== append e expansão ==========
// Criar um slice com capacidade 3
s := make([]int, 0, 3)
fmt.Printf("Inicial: len=%d, cap=%d, %v\n", len(s), cap(s), s)
// Adicionar um por um, observar mudanças de capacidade
for i := 1; i <= 5; i++ {
s = append(s, i)
fmt.Printf("Após adicionar %d: len=%d, cap=%d, %v\n", i, len(s), cap(s), s)
}
// ========== Slices compartilhando array subjacente ==========
original := []int{10, 20, 30, 40, 50}
sub := original[1:3] // [20, 30]
fmt.Println("\n--- Demo de array subjacente compartilhado ---")
fmt.Println("Slice original:", original)
fmt.Println("Sub-slice: ", sub)
// Modificar o sub-slice afeta o slice original
sub[0] = 999
fmt.Println("\nApós modificar sub-slice:")
fmt.Println("Slice original:", original) // original[1] também mudou
fmt.Println("Sub-slice: ", sub)
// ========== Usando copy para criar cópia independente ==========
fmt.Println("\n--- Usando copy para cópia independente ---")
original = []int{10, 20, 30, 40, 50}
// Fatiar primeiro, depois copiar independentemente
subSlice := original[1:4] // [20, 30, 40]
independente := make([]int, len(subSlice))
copy(independente, subSlice)
independente[0] = 888
fmt.Println("Slice original:", original) // Não afetado
fmt.Println("Cópia independente:", independente) // Apenas a cópia mudou
// ========== Mesclando dois slices ==========
fmt.Println("\n--- Mesclando slices ---")
a := []int{1, 2, 3}
b := []int{4, 5, 6}
mesclado := append(a, b...)
fmt.Println("Resultado da mesclagem:", mesclado)
}
Saída:
Inicial: len=0, cap=3, []
Após adicionar 1: len=1, cap=3, [1]
Após adicionar 2: len=2, cap=3, [1 2]
Após adicionar 3: len=3, cap=3, [1 2 3]
Após adicionar 4: len=4, cap=6, [1 2 3 4]
Após adicionar 5: len=5, cap=6, [1 2 3 4 5]
--- Demo de array subjacente compartilhado ---
Slice original: [10 20 30 40 50]
Sub-slice: [20 30]
Após modificar sub-slice:
Slice original: [10 999 30 40 50]
Sub-slice: [999 30]
--- Usando copy para cópia independente ---
Slice original: [10 20 30 40 50]
Cópia independente: [888 30 40]
--- Mesclando slices ---
Resultado da mesclagem: [1 2 3 4 5 6]
Exemplo 3: Aplicação Abrangente (Dificuldade ⭐⭐⭐)
package main
import "fmt"
// removeElement remove o elemento no índice especificado de um slice (mantém a ordem)
func removeElement(s []int, index int) []int {
if index < 0 || index >= len(s) {
return s // Índice fora dos limites, retorna slice original
}
// Usar append para concatenar partes antes e depois do índice
return append(s[:index], s[index+1:]...)
}
// insertElement insere um elemento no índice especificado
func insertElement(s []int, index int, value int) []int {
if index < 0 || index > len(s) {
return s
}
// Primeiro expandir o slice, depois deslocar elementos, depois atribuir
s = append(s, 0) // Adicionar um elemento placeholder
copy(s[index+1:], s[index:]) // Deslocar elementos do índice em diante em um
s[index] = value // Atribuir valor na posição alvo
return s
}
// filterEven filtra números pares, retorna um novo slice
func filterEven(s []int) []int {
resultado := make([]int, 0, len(s)/2) // Estimar capacidade como metade
for _, v := range s {
if v%2 == 0 {
resultado = append(resultado, v)
}
}
return resultado
}
// Testar compartilhamento de memória de slice e mecanismo de expansão
func sliceInternals() {
fmt.Println("=== Demo de Internals de Slice ===")
// Criar um array subjacente
data := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// Fatiar um sub-slice (compartilha array subjacente)
s1 := data[2:5] // [2, 3, 4], len=3, cap=8
s2 := s1[1:3] // [3, 4], len=2, cap=7
fmt.Printf("data: %v, len=%d, cap=%d\n", data, len(data), cap(data))
fmt.Printf("s1: %v, len=%d, cap=%d\n", s1, len(s1), cap(s1))
fmt.Printf("s2: %v, len=%d, cap=%d\n", s2, len(s2), cap(s2))
// s2 modificando elementos afeta s1 e data
s2[0] = 999
fmt.Printf("\nApós modificar s2[0]=999:\n")
fmt.Printf("data: %v\n", data) // data[4] mudou para 999
fmt.Printf("s1: %v\n", s1) // s1[2] mudou para 999
fmt.Printf("s2: %v\n", s2) // s2[0] mudou para 999
// append pode causar desconexão
fmt.Println("\n--- append causa expansão ---")
s3 := data[0:2] // [0, 1], len=2, cap=10
fmt.Printf("Antes do append s3: %v, len=%d, cap=%d\n", s3, len(s3), cap(s3))
// append dentro da capacidade, ainda compartilhado
s3 = append(s3, 99)
fmt.Printf("Após append(99) s3: %v, len=%d, cap=%d\n", s3, len(s3), cap(s3))
fmt.Printf("data[2] = %d (modificado!)\n", data[2])
// Após exceder a capacidade, novo array é alocado
s3 = append(s3, 100, 200, 300, 400, 500, 600, 700, 800)
fmt.Printf("Após grande append s3: %v, len=%d, cap=%d\n", s3, len(s3), cap(s3))
fmt.Printf("data não afetado: %v\n", data)
}
func main() {
// ========== Operações de Slice na Prática ==========
fmt.Println("=== Operações de Slice na Prática ===")
// Dados iniciais
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println("Dados originais:", nums)
// Remover elemento no índice 4 (valor 5)
nums = removeElement(nums, 4)
fmt.Println("Após remover índice 4:", nums)
// Inserir 100 no índice 2
nums = insertElement(nums, 2, 100)
fmt.Println("Após inserir 100 no índice 2:", nums)
// Filtrar números pares
pares := filterEven(nums)
fmt.Println("Slice de pares:", pares)
// ========== Slice como Pilha ==========
fmt.Println("\n=== Slice Simulando Pilha ===")
var pilha []int
// Empilhar
for i := 1; i <= 5; i++ {
pilha = append(pilha, i)
fmt.Printf("Empilhar %d -> Pilha: %v\n", i, pilha)
}
// Desempilhar (do final)
for len(pilha) > 0 {
// Obter último elemento
topo := pilha[len(pilha)-1]
pilha = pilha[:len(pilha)-1]
fmt.Printf("Desempilhar %d -> Pilha: %v\n", topo, pilha)
}
// ========== Demo de Internals ==========
fmt.Println()
sliceInternals()
}
Saída:
=== Operações de Slice na Prática ===
Dados originais: [1 2 3 4 5 6 7 8 9 10]
Após remover índice 4: [1 2 3 4 6 7 8 9 10]
Após inserir 100 no índice 2: [1 2 100 3 4 6 7 8 9 10]
Slice de pares: [2 100 4 6 8 10]
=== Slice Simulando Pilha ===
Empilhar 1 -> Pilha: [1]
Empilhar 2 -> Pilha: [1 2]
Empilhar 3 -> Pilha: [1 2 3]
Empilhar 4 -> Pilha: [1 2 3 4]
Empilhar 5 -> Pilha: [1 2 3 4 5]
Desempilhar 5 -> Pilha: [1 2 3 4]
Desempilhar 4 -> Pilha: [1 2 3]
Desempilhar 3 -> Pilha: [1 2]
Desempilhar 2 -> Pilha: [1]
Desempilhar 1 -> Pilha: []
=== Demo de Internals de Slice ===
data: [0 1 2 3 4 5 6 7 8 9], len=10, cap=10
s1: [2 3 4], len=3, cap=8
s2: [3 4], len=2, cap=7
Após modificar s2[0]=999:
data: [0 1 2 3 999 5 6 7 8 9]
s1: [2 3 999]
s2: [3 999]
--- append causa expansão ---
Antes do append s3: [0 1], len=2, cap=10
Após append(99) s3: [0 1 99], len=3, cap=10
data[2] = 99 (modificado!)
Após grande append s3: [0 1 99 100 200 300 400 500 600 700 800], len=12, cap=20
data não afetado: [0 1 99 3 999 5 6 7 8 9]
3. Casos de Uso Comuns
Caso 1: Processamento de Dados em Lote (Filtrar e Transformar)
package main
import "fmt"
// cleanData filtra strings vazias de um slice de strings e converte para maiúsculas
func cleanData(input []string) []string {
resultado := make([]string, 0, len(input))
for _, s := range input {
if s != "" {
// Converter para maiúsculas (exemplo simplificado, use strings.ToUpper na prática)
maiuscula := ""
for _, c := range s {
if c >= 'a' && c <= 'z' {
maiuscula += string(c - 32)
} else {
maiuscula += string(c)
}
}
resultado = append(resultado, maiuscula)
}
}
return resultado
}
func main() {
raw := []string{"olá", "", "mundo", "", "go", "lang"}
cleaned := cleanData(raw)
fmt.Println("Limpo:", cleaned) // [OLÁ MUNDO GO LANG]
}
Caso 2: Implementando uma Fila Dinâmica
package main
import "fmt"
// Queue implementa uma fila FIFO simples usando um slice
type Queue struct {
items []string
}
// Enqueue adiciona um item à fila
func (q *Queue) Enqueue(item string) {
q.items = append(q.items, item)
}
// Dequeue remove e retorna o primeiro item
func (q *Queue) Dequeue() (string, bool) {
if len(q.items) == 0 {
return "", false
}
item := q.items[0]
q.items = q.items[1:] // Remove primeiro elemento
return item, true
}
// Size retorna o tamanho da fila
func (q *Queue) Size() int {
return len(q.items)
}
func main() {
q := &Queue{}
q.Enqueue("Tarefa A")
q.Enqueue("Tarefa B")
q.Enqueue("Tarefa C")
fmt.Printf("Tamanho da fila: %d\n", q.Size())
for q.Size() > 0 {
item, _ := q.Dequeue()
fmt.Println("Processando:", item)
}
}
❓ Perguntas Frequentes
P1: O slice declarado com var s []int é um slice nil? Pode ser usado diretamente?
Sim. Um slice nil tem len e cap iguais a 0, e append funciona perfeitamente. A recomendação oficial do Go: se um slice pode ser nil, nenhuma verificação nil extra é necessária antes de usar — basta fazer append diretamente.
var s []int // slice nil
s = append(s, 1, 2) // Completamente válido
fmt.Println(s) // [1 2]
P2: Fatiar com s[2:5] afeta o slice original?
Sim. O sub-slice produzido pelo fatiamento compartilha o array subjacente com o original. Modificar elementos no sub-slice será refletido no original. Use copy para uma cópia independente:
original := []int{1, 2, 3, 4, 5}
sub := make([]int, 3)
copy(sub, original[2:5]) // Cópia independente, sem efeito mútuo
P3: Por que o valor mudou após append?
Quando len(s) < cap(s), append escreve diretamente no array subjacente sem criar um novo. Se outros slices referenciam posições posteriores no mesmo array subjacente, eles verão mudanças "inesperadas". Solução: use copy para criar uma cópia independente antes do append.
P4: Como a capacidade do slice cresce?
O runtime Go determina a nova capacidade com base na capacidade atual do slice. A estratégia típica é: dobrar a capacidade quando menor que 1024, crescer cerca de 1.25x quando maior que 1024. A estratégia específica pode mudar entre versões do Go, então não dependa de regras exatas de crescimento.
📖 Resumo
- Array
[N]Ttem comprimento fixo, é tipo de valor; atribuição/passagem de parâmetros copia o array inteiro - Slice
[]Ttem comprimento variável, é tipo de referência; referencia um array subjacente make([]T, len, cap)é a maneira recomendada de criar slices; pré-alocar capacidade evita expansão frequenteappendretorna um novo slice; você deve capturar o valor de retorno; aloca novo array subjacente quando a capacidade é insuficiente- Fatiamento de slice
s[baixo:alto]compartilha o array subjacente com o original; mudanças afetam um ao outro copy(dst, src)cria uma cópia independente de um slicelen()retorna o número de elementos;cap()retorna a capacidade do array subjacente- O valor zero de um slice é
nil; você pode fazerappenddiretamente
📝 Exercícios
Exercício 1 (⭐)
Escreva um programa que crie um slice de 10 inteiros (valores 1~10), depois:
- Imprima o comprimento e capacidade do slice
- Fatie do índice 2 ao 7, imprima seu conteúdo, comprimento e capacidade
- Use range para iterar sobre o sub-slice, imprimindo cada elemento e seu índice
Exercício 2 (⭐⭐)
Escreva uma função unique que aceite um slice []int e retorne um novo slice com duplicatas removidas (mantendo a ordem da primeira ocorrência). Por exemplo, a entrada [1, 3, 2, 3, 1, 4, 2] deve retornar [1, 3, 2, 4].
Dica: Use um slice auxiliar para registrar elementos que já apareceram.
Exercício 3 (⭐⭐⭐)
Escreva uma função mergeSorted que aceite dois slices []int ordenados e os mescle em um único slice ordenado. Requisitos:
- A complexidade de tempo deve ser O(n); não mescle e depois ordene
- Por exemplo, a entrada
[1, 3, 5, 7]e[2, 4, 6, 8]deve retornar[1, 2, 3, 4, 5, 6, 7, 8] - Pense: Qual papel esta operação desempenha no merge sort?



