Arrays: tamanho fixo e alocação na stack
1. Introdução aos Arrays em Go
Em Go, arrays são tipos de valor (value types) com tamanho fixo determinado em tempo de compilação. Diferentemente de slices, que são dinâmicos, arrays possuem uma quantidade imutável de elementos do mesmo tipo.
A sintaxe básica para declarar arrays:
var a [5]int // array de 5 inteiros, todos zero
b := [3]string{"a", "b", "c"} // array de 3 strings
c := [5]int{1, 2, 3} // [1 2 3 0 0] - elementos restantes são zero
A diferença fundamental entre arrays e slices é que arrays têm tamanho fixo e são value types, enquanto slices são descritores de uma fatia de um array subjacente, com tamanho dinâmico.
2. Tamanho Fixo: Parte do Tipo
Em Go, o tamanho é parte do tipo do array. [3]int e [4]int são tipos completamente diferentes. Isso tem implicações importantes para funções:
func somaTres(nums [3]int) int {
total := 0
for _, v := range nums {
total += v
}
return total
}
func main() {
a := [3]int{1, 2, 3}
b := [4]int{1, 2, 3, 4}
fmt.Println(somaTres(a)) // OK
// fmt.Println(somaTres(b)) // Erro de compilação: cannot use b (type [4]int) as type [3]int
}
Quando você passa um array para uma função, ele é copiado por completo. Isso pode ser caro para arrays grandes:
func modificaArray(arr [3]int) {
arr[0] = 999 // Modifica apenas a cópia local
}
func main() {
original := [3]int{1, 2, 3}
modificaArray(original)
fmt.Println(original) // [1 2 3] - inalterado
}
Para contornar essa limitação, use slices ou ponteiros:
func modificaSlice(arr []int) {
arr[0] = 999 // Modifica o array original
}
3. Alocação na Stack vs. Heap
Arrays pequenos (tipicamente < 64 KB) são alocados na stack, o que é extremamente rápido. O compilador Go usa escape analysis para decidir se um array pode ficar na stack ou precisa ir para o heap.
Exemplo: array alocado na stack
func criaArray() {
a := [100]int{} // Alocado na stack (não escapa)
for i := range a {
a[i] = i
}
}
Exemplo: array escapa para o heap
func criaArrayHeap() *[100]int {
a := [100]int{} // Escapa para o heap porque retornamos um ponteiro
for i := range a {
a[i] = i
}
return &a
}
A diferença de desempenho é significativa. Alocação na stack é uma operação O(1) que apenas ajusta o ponteiro de stack, enquanto alocação no heap envolve gerenciamento de memória mais complexo e coleta de lixo.
Para verificar onde suas variáveis são alocadas, use a flag -gcflags="-m":
go build -gcflags="-m" seu_programa.go
4. Inicialização e Valores Zero
Arrays em Go são sempre inicializados. Se você não especificar valores, todos os elementos recebem o valor zero do tipo:
var a [5]int // [0 0 0 0 0]
var b [3]string // ["" "" ""]
var c [2]bool // [false false]
Inicialização literal completa e parcial:
a := [5]int{1, 2, 3, 4, 5} // Completa
b := [5]int{1, 2} // Parcial: [1 2 0 0 0]
c := [5]int{4: 10} // Apenas índice 4: [0 0 0 0 10]
d := [5]int{1, 4: 10} // Misto: [1 0 0 0 10]
Uso de chave-valor para inicialização explícita:
tabela := [5]int{1: 100, 3: 300} // [0 100 0 300 0]
dias := [7]string{0: "Dom", 1: "Seg", 2: "Ter", 3: "Qua", 4: "Qui", 5: "Sex", 6: "Sáb"}
5. Operações com Arrays
Iteração:
arr := [3]string{"Go", "Python", "Rust"}
// Com range
for i, v := range arr {
fmt.Printf("Índice %d: %s\n", i, v)
}
// Tradicional
for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}
Comparação direta:
Arrays do mesmo tipo podem ser comparados diretamente com == e !=:
a := [3]int{1, 2, 3}
b := [3]int{1, 2, 3}
c := [3]int{4, 5, 6}
fmt.Println(a == b) // true
fmt.Println(a == c) // false
Cópia explícita:
a := [3]int{1, 2, 3}
b := a // Cópia completa, não referência
b[0] = 999
fmt.Println(a) // [1 2 3] - inalterado
fmt.Println(b) // [999 2 3]
Limitação importante: Não é possível usar append diretamente em arrays. Use slices para isso.
6. Arrays Multidimensionais
Arrays multidimensionais em Go são alocados de forma contígua na stack (para tamanhos pequenos):
var matriz [3][3]int
matriz[0] = [3]int{1, 2, 3}
matriz[1] = [3]int{4, 5, 6}
matriz[2] = [3]int{7, 8, 9}
Ou de forma mais concisa:
matriz := [3][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
Iteração aninhada:
for i := 0; i < len(matriz); i++ {
for j := 0; j < len(matriz[i]); j++ {
fmt.Printf("%d ", matriz[i][j])
}
fmt.Println()
}
Diferença crucial: Arrays multidimensionais são blocos contíguos de memória. Já slices multidimensionais ([][]int) são slices de slices, com alocação não contígua e overhead adicional.
7. Casos de Uso e Boas Práticas
Quando usar arrays:
- Buffers de tamanho fixo conhecido em tempo de compilação
- Tabelas hash pequenas e fixas
- Representação de matrizes em computação gráfica (ex:
[4][4]float64para matrizes 4x4) - Máxima performance em código crítico (evita alocações no heap)
Exemplo prático: buffer fixo para leitura
type Packet struct {
Header [24]byte
Payload [1024]byte
}
Quando preferir slices:
- Tamanho desconhecido ou variável
- Passagem para funções sem cópia excessiva
- Operações de append e redimensionamento
Armadilhas comuns:
- Confundir array com slice:
[3]intvs[]int - Cópia acidental em funções: Passar array grande por valor é caro
- Esquecer que tamanho é parte do tipo:
[3]intnão é compatível com[4]int
Dica: Use [...]int{1, 2, 3} para inferir o tamanho automaticamente:
arr := [...]int{1, 2, 3, 4, 5} // Equivalente a [5]int{1, 2, 3, 4, 5}
fmt.Printf("Tipo: %T, len: %d\n", arr, len(arr)) // Tipo: [5]int, len: 5
Referências
- A Tour of Go: Arrays — Documentação oficial interativa sobre arrays em Go
- Go by Example: Arrays — Exemplos práticos de uso de arrays
- Understanding Go's Escape Analysis — Artigo técnico sobre escape analysis e alocação stack vs heap
- Effective Go: Arrays — Seção do Effective Go sobre arrays e suas propriedades
- Go Memory Stack vs Heap: A Complete Guide — Guia completo sobre alocação de memória em Go, incluindo arrays
- Stack vs Heap Allocation in Go — Comparação prática de desempenho entre alocação na stack e no heap