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

GO
// 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

GO
// 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
💡 Dica: Slices 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

GO
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
💡 Dica: 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.

💡 Dica: Ao usar 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 ⭐)

GO
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))
}
▶ Experimente

Saída:

TEXT
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 ⭐⭐)

GO
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)
}
▶ Experimente

Saída:

TEXT
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 ⭐⭐⭐)

GO
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()
}
▶ Experimente

Saída:

TEXT
=== 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)

GO
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

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

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

GO
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


📝 Exercícios

Exercício 1 (⭐)

Escreva um programa que crie um slice de 10 inteiros (valores 1~10), depois:

  1. Imprima o comprimento e capacidade do slice
  2. Fatie do índice 2 ao 7, imprima seu conteúdo, comprimento e capacidade
  3. 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:


Próxima Lição

Próxima Lição: Map →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%