Skip to main content


Estruturas de dados


As Estruturas de dados são formas de organizar vários valores dentro de um programa. Até agora, usamos variáveis guardando um valor por vez:

nome := "Ana"
idade := 20
ativo := true

Isso funciona para dados simples, mas fica ruim quando precisamos guardar vários valores relacionados, como uma lista de notas, nomes, produtos ou usuários. Em Go, as estruturas mais importantes no começo são:

  • array
  • slice
  • map

Vamos começar por arrays.



Arrays


Um array é uma sequência de valores do mesmo tipo, mas com tamanho fixo. Isso significa que, ao criar um array, precisamos definir quantos elementos ele terá, e esse tamanho não muda depois.

var numeros [3]int

Esse código cria um array chamado numeros, com espaço para 3 valores do tipo int.



Criando um array


Podemos criar um array informando o tamanho, o tipo e os valores.

package main

import "fmt"

func main() {
// Cria um array com 3 números inteiros.
notas := [3]int{8, 7, 10}

fmt.Println(notas)
}

Resultado:

[8 7 10]

Aqui temos um array com 3 posições:

posição 0 -> 8
posição 1 -> 7
posição 2 -> 10

Em Go, a primeira posição começa em 0, não em 1.



Acessando valores do array


Para acessar um valor, usamos o índice. Esse índice é a posição do elemento dentro do array.

package main

import "fmt"

func main() {
notas := [3]int{8, 7, 10}

// Acessa a primeira posição.
fmt.Println(notas[0])

// Acessa a segunda posição.
fmt.Println(notas[1])

// Acessa a terceira posição.
fmt.Println(notas[2])
}

Resultado:

8
7
10

A posição 0 é o primeiro valor, a posição 1 é o segundo valor e a posição 2 é o terceiro valor.



Alterando valores do array


Podemos alterar uma posição específica do array usando =.

package main

import "fmt"

func main() {
notas := [3]int{8, 7, 10}

// Altera o valor da posição 1.
notas[1] = 9

fmt.Println(notas)
}


Valor zero em arrays


Quando criamos um array sem preencher todos os valores, as posições restantes recebem o valor zero daquele tipo.

package main

import "fmt"

func main() {
// Array com 3 posições.
// Apenas o primeiro valor foi informado.
numeros := [3]int{10}

fmt.Println(numeros)
}

Resultado:

[10 0 0]

Como o tipo é int, o valor zero é 0.



Descobrindo o tamanho com len


A função len retorna o tamanho do array.

package main

import "fmt"

func main() {
notas := [3]int{8, 7, 10}

// len retorna a quantidade de elementos do array.
fmt.Println(len(notas))
}

Resultado:

3

O len é muito usada com arrays, slices, maps e strings.



Deixando o Go contar o tamanho


Podemos usar ... para o Go contar o tamanho do array com base nos valores informados.

package main

import "fmt"

func main() {
// O Go conta os valores e cria um array de tamanho 3.
numeros := [...]int{10, 20, 30}

fmt.Println(numeros)
fmt.Println(len(numeros))
}

Resultado:

[10 20 30]
3

Apesar de não escrevermos [3]int, esse array ainda tem tamanho fixo, só que foi o Go quem contou o tamanho na criação, isso é útil quando nao sabemos quantos valores serão salvos.



O tamanho faz parte do tipo


Essa parte é importante, em Go, o tamanho do array faz parte do tipo. Isso significa que:

[3]int

// e:
[4]int

Os exemplos cima são tipos diferentes, mesmo ambos sendo array, mas são diferentes porque o tamanho de cada um mudou o tipo.



Quando usar array


Use array quando a quantidade de elementos é fixa e faz parte da regra do dado. Na maioria dos casos, você não vai e não deve usar array e sim slice.


Apenas entenda sobre array.



Slices


Os Slices são a forma mais comum de trabalhar com listas, ele é parecido com Array, mas diferente de um array onde o tamanho não pode ser alterado, no slice o tamanho pode crescer ou diminuir.

nomes := []string{"Ana", "Carlos", "Bia"}

O resultado ao exibir o slice nomes é:

[Ana Carlos Bia]

A diferença visual para array é o tamanho informado. O exemplo abaixo deixa isso mais evidente:

// Array:
notas := [3]int{8, 7, 10}

// Slice:
notas := []int{8, 7, 10}

Para trocar o valor de uma posição do slice:

nomes := []string{"Ana", "Carlos", "Bia"}

// Alterando a posição 0 (trocando o valor de Ana):
nomes[0] = "Bob"


Por que slices existem


Basicamente, o slice existe para resolver a limitação que os arrays possuem de ter tamanho fixo. Quando criamos um array, o tamanho dele faz parte do tipo, então não dá para simplesmente adicionar novos elementos depois. Isso pode tornar o trabalho com arrays complicado quando a quantidade de valores pode mudar.


Se você já conhece Python, pode ter percebido que o Slice lembra uma lista em Python. Ele é usado para guardar vários valores do mesmo tipo e permite adicionar novos elementos com mais facilidade.


Apesar de o slice usar um array internamente, no dia a dia é muito mais comum usar slice do que array, justamente porque ele é mais flexível. Arrays ainda existem e são importantes para entender a base, mas slices são a escolha mais comum quando precisamos trabalhar com listas.



Acessando valores do slice


Assim como arrays, slices usam índice, sempre começando em 0.

package main

import "fmt"

func main() {
nomes := []string{"Ana", "Carlos", "Bia"}

fmt.Println(nomes[0])
fmt.Println(nomes[1])
fmt.Println(nomes[2])
}

Resultado:

Ana
Carlos
Bia

As posições são:

  • índice 0 -> Ana
  • índice 1 -> Carlos
  • índice 2 -> Bia


Tamanho com len


A função len retorna a quantidade de elementos do slice.

package main

import "fmt"

func main() {
nomes := []string{"Ana", "Carlos", "Bia"}

// len retorna o tamanho atual do slice.
fmt.Println(len(nomes))
}

Resultado:

3

Se adicionarmos mais um valor, o tamanho muda:

package main

import "fmt"

func main() {
nomes := []string{"Ana", "Carlos", "Bia"}

nomes = append(nomes, "Marina")

fmt.Println(nomes)
fmt.Println(len(nomes))
}

Resultado:

[Ana Carlos Bia Marina]
4


Adicionando valores com append


Para adicionar valores em um slice, usamos append.

package main

import "fmt"

func main() {
numeros := []int{10, 20, 30}

// append retorna um novo slice com o valor adicionado.
numeros = append(numeros, 40)

fmt.Println(numeros)
}

Resultado:

[10 20 30 40]

Um detalhe importante é que append retorna o slice atualizado. Por isso, normalmente escrevemos:

numeros = append(numeros, 40)

E não apenas:

append(numeros, 40)

Esse segundo exemplo não funciona, porque o retorno de append não foi usado.



Adicionando vários valores


Também dá para adicionar mais de um valor de uma vez no append.

package main

import "fmt"

func main() {
numeros := []int{10, 20}

// Adiciona vários valores ao slice.
numeros = append(numeros, 30, 40, 50)

fmt.Println(numeros)
}

Resultado:

[10 20 30 40 50]


Criando slice vazio


Podemos criar um slice vazio assim:

package main

import "fmt"

func main() {
// Slice vazio de string.
nomes := []string{}

fmt.Println(nomes)
fmt.Println(len(nomes))
}

Depois podemos adicionar valores com append.



Slice nil


Também existe o slice nil.

package main

import "fmt"

func main() {
// Declara um slice, mas não cria valores.
var nomes []string

fmt.Println(nomes)
fmt.Println(len(nomes))
fmt.Println(nomes == nil)
}

Resultado:

[]
0
true

Um slice nil não tem elementos e ainda não aponta para uma estrutura interna de dados. Mesmo assim, podemos usar append nele normalmente.



Criando slice com make


A função make cria slices informando tamanho e, se quiser, capacidade, exemplo:

package main

import "fmt"

func main() {
// Cria um slice de int com tamanho 3.
numeros := make([]int, 3)

fmt.Println(numeros)
fmt.Println(len(numeros))
}

Resultado:

[0 0 0]
3

Como o tipo é int, as posições começam com valor zero.


Também dá para criar com tamanho e capacidade:

package main

import "fmt"

func main() {
// Cria um slice com tamanho 0 e capacidade 5.
numeros := make([]int, 0, 5)

fmt.Println(numeros)
fmt.Println(len(numeros))
fmt.Println(cap(numeros))
}

Resultado:

[]
0
5

O cap mostra a capacidade do slice, ou seja, quantos elementos ele consegue armazenar usando o array interno atual. Quando o slice cresce com append e passa dessa capacidade, o Go precisa alocar um novo array interno maior e copiar os valores antigos para esse novo espaço.



Entendendo len e cap

Um slice tem tamanho e capacidade. O len mostra quantos elementos o slice tem agora e o cap mostra quantos elementos cabem na estrutura interna atual, a partir do início do slice.

package main

import "fmt"

func main() {
numeros := make([]int, 0, 3)

fmt.Println("slice:", numeros, "len:", len(numeros), "cap:", cap(numeros))

numeros = append(numeros, 10)
fmt.Println("slice:", numeros, "len:", len(numeros), "cap:", cap(numeros))

numeros = append(numeros, 20)
fmt.Println("slice:", numeros, "len:", len(numeros), "cap:", cap(numeros))

numeros = append(numeros, 30)
fmt.Println("slice:", numeros, "len:", len(numeros), "cap:", cap(numeros))

numeros = append(numeros, 40)
fmt.Println("slice:", numeros, "len:", len(numeros), "cap:", cap(numeros))
}

Resultado possível:

slice: [] len: 0 cap: 3
slice: [10] len: 1 cap: 3
slice: [10 20] len: 2 cap: 3
slice: [10 20 30] len: 3 cap: 3
slice: [10 20 30 40] len: 4 cap: 6

Quando a capacidade acaba, o Go cria uma estrutura interna maior e copia os valores para ela. O valor exato da nova capacidade pode variar, então não é bom depender desse número para regra de negócio.



Fatiando um slice


Podemos criar um novo slice a partir de parte de outro slice.

package main

import "fmt"

func main() {
numeros := []int{10, 20, 30, 40, 50}

// Pega da posição 1 até antes da posição 4.
parte := numeros[1:4]

fmt.Println(parte)
}

Resultado:

[20 30 40]

A regra é:

slice[inicio:fim]

O inicio sempre será exibido, já o fim não será, exemplo:

numeros[1:3]

Vai exibir os índices 1 e 2.

índice 1 -> 20
índice 2 -> 30

O índice 3 fica de fora.



Omitindo início ou fim

Se omitimos o início, Go começa do índice 0.

package main

import "fmt"

func main() {
numeros := []int{10, 20, 30, 40, 50}

fmt.Println(numeros[:3])
}

Resultado:

[10 20 30]

Se omitimos o fim, Go vai até o final.

package main

import "fmt"

func main() {
numeros := []int{10, 20, 30, 40, 50}

fmt.Println(numeros[2:])
}

Resultado:

[30 40 50]

O slice inteiro poderia ser escrito assim:

numeros[:]


Slice aponta para uma estrutura interna


Quando criamos um slice a partir de outro slice, o Go não copia os valores automaticamente. Ele cria uma nova "visão" sobre parte dos mesmos dados internos.

package main

import "fmt"

func main() {
numeros := []int{10, 20, 30, 40, 50}

// Cria um novo slice usando parte de numeros.
parte := numeros[1:4]

// Altera a primeira posição de parte.
parte[0] = 99

fmt.Println("numeros:", numeros)
fmt.Println("parte:", parte)
}

Resultado:

numeros: [10 99 30 40 50]
parte: [99 30 40]

Isso acontece porque parte não é uma cópia independente dos valores. Ele aponta para a mesma estrutura interna usada por numeros, começando a partir do índice 1.


Neste trecho:

parte := numeros[1:4]

A parte representa esta parte de numeros:

numeros: [10 20 30 40 50]
↑ ↑ ↑
parte: [20 30 40]

Então, quando fazemos:

parte[0] = 99

Estamos alterando o valor que também aparece dentro de numeros.

numeros: [10 99 30 40 50]
parte: [99 30 40]

Em Go, o slice criado a partir de outro slice compartilha o mesmo array interno. Por isso, se você altera um índice em qualquer um dos slices, o array interno será alterado e ambos passam a ter o novo índice com o novo valor, se a intenção for ter uma cópia independente, é necessário copiar os valores:

package main

import "fmt"

func main() {
numeros := []int{10, 20, 30, 40, 50}

parte := make([]int, 3)

// Copia os valores de numeros[1:4] para parte.
copy(parte, numeros[1:4])

parte[0] = 99

fmt.Println("numeros:", numeros)
fmt.Println("parte:", parte)
}

Resultado:

numeros: [10 20 30 40 50]
parte: [99 30 40]


Copiando slice com copy


Quando queremos copiar os valores para outro slice, usamos copy.

package main

import "fmt"

func main() {
original := []int{10, 20, 30}

copia := make([]int, len(original))

// Copia os valores de original para copia.
copy(copia, original)

copia[0] = 99

fmt.Println("original:", original)
fmt.Println("copia:", copia)
}

Resultado:

original: [10 20 30]
copia: [99 20 30]

Agora alterar copia não altera original.



Removendo item de um slice


O Go não tem uma função pronta chamada remove para slice (infelizmente), mas uma forma comum é juntar a parte antes do item com a parte depois do item:

package main

import "fmt"

func main() {
nomes := []string{"Ana", "Carlos", "Bia", "Marina"}

// Remove o item do índice 1, que é "Carlos".
indice := 1

nomes = append(nomes[:indice], nomes[indice+1:]...)

fmt.Println(nomes)
}

Resultado:

[Ana Bia Marina]

A parte:

nomes[:indice]

Pega tudo antes do índice removido. Já a parte:

nomes[indice+1:]

Vai pegar tudo depois do índice removido, mas esse "tudo" não são valores soltos, e sim uma fatia do slice.


Por exemplo, se temos:

[Ana Carlos Bia Marina]

E queremos remover Carlos, a parte depois dele é:

[Bia Marina]

Essa parte ainda é um slice, como se o Go tivesse cortado o slice original e separado uma fatia dele. Quando usamos ..., estamos dizendo ao append para não enviar essa fatia inteira como um único valor. Estamos dizendo para pegar os itens que estão dentro dela e enviar um por um.


Então, em vez de tentar adicionar isto:

[Bia Marina]

Como um único item, o ... faz o Go usar os valores internos da fatia:

Bia
Marina

Na prática, a ideia é esta:

primeira parte: [Ana]
segunda fatia: [Bia Marina]

Com ..., a segunda fatia é enviada como:

Bia, Marina

Sem ..., estaríamos tentando colocar um slice dentro de outro slice, como se [Bia Marina] fosse um único elemento.


Se você prestou atenção e entendeu, deve estar se perguntando: Por que nomes[:indice] pode ser enviado como slice no começo, mas nomes[indice+1:] precisa usar ... no final?


A resposta está na forma como append funciona. A assinatura conceitual do append é:

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

Isso significa que o primeiro argumento precisa ser um slice:

slice []Type

E os próximos argumentos precisam ser os elementos que serão adicionados ao final desse slice:

elems ...Type

No nosso caso:

nomes[:indice]

É o slice base, ou seja, a primeira parte da lista, exemplo:

[Ana]

Já a parte abaixo:

nomes[indice+1:]

Também é um slice, só que ele está sendo usado na posição onde o append espera receber elementos, por isso precisamos usar .... Então:

[Bia Marina]

Vira:

Bia, Marina


Slice com any


Os Slice podem ter dados variados, para isso, usamos any.

package main

import "fmt"

func main() {
dados := []any{
"Ana",
20,
1.68,
true,
}

fmt.Println(dados)
}

Resultado:

[Ana 20 1.68 true]

O cuidado é o mesmo do map[string]any. Quando usamos any, o Go aceita valores de tipos diferentes, mas depois precisamos verificar ou converter o tipo antes de usar em operações.

package main

import "fmt"

func main() {
dados := []any{"Ana", 20, 1.68}

idade, ok := dados[1].(int)

if !ok {
fmt.Println("idade não é int")
return
}

fmt.Println(idade + 1)
}

Resultado:

21

Também dá para usar switch de tipo:

package main

import "fmt"

func main() {
dados := []any{"Ana", 20, 1.68, true}

for _, valor := range dados {
switch v := valor.(type) {
case string:
fmt.Println("string:", v)
case int:
fmt.Println("int:", v)
case float64:
fmt.Println("float64:", v)
case bool:
fmt.Println("bool:", v)
default:
fmt.Println("tipo desconhecido")
}
}
}


Slice multidimensional


Um slice multidimensional é um slice que guarda outros slices dentro dele, a forma mais comum é:

[][]int

Exemplo:

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

Nesse exemplo, matriz é um slice onde cada item interno também é um slice. Visualmente, podemos pensar assim:

[
[1 2 3]
[4 5 6]
[7 8 9]
]

Isso é parecido com uma tabela, onde temos linhas e colunas.



Acessando valores


Para acessar um valor, usamos dois índices.

package main

import "fmt"

func main() {
matriz := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}

// Primeiro índice acessa a linha.
// Segundo índice acessa a coluna.
fmt.Println(matriz[0][0]) // 1
fmt.Println(matriz[1][2]) // 6
fmt.Println(matriz[2][1]) // 8
}

Na prática:

linha 0 -> [1 2 3]
linha 1 -> [4 5 6]
linha 2 -> [7 8 9]

Então:

matriz[1][2] -> 6


Percorrendo slice multidimensional


Para percorrer todos os valores, usamos um range dentro de outro.

package main

import "fmt"

func main() {
matriz := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}

// Primeiro range percorre as linhas.
for indiceLinha, linha := range matriz {
// Segundo range percorre os valores dentro da linha.
for indiceColuna, valor := range linha {
fmt.Println("linha:", indiceLinha, "coluna:", indiceColuna, "valor:", valor)
}
}
}

Resultado:

linha: 0 coluna: 0 valor: 1
linha: 0 coluna: 1 valor: 2
linha: 0 coluna: 2 valor: 3
linha: 1 coluna: 0 valor: 4
linha: 1 coluna: 1 valor: 5
linha: 1 coluna: 2 valor: 6
linha: 2 coluna: 0 valor: 7
linha: 2 coluna: 1 valor: 8
linha: 2 coluna: 2 valor: 9


Adicionando uma nova linha


Como é um slice, podemos usar append para adicionar uma nova linha.

package main

import "fmt"

func main() {
matriz := [][]int{
{1, 2, 3},
{4, 5, 6},
}

// Adiciona uma nova linha.
matriz = append(matriz, []int{7, 8, 9})

fmt.Println(matriz)
}

Resultado:

[[1 2 3] [4 5 6] [7 8 9]]


Adicionando valor em uma linha específica


Também podemos adicionar um valor dentro de uma das linhas.

package main

import "fmt"

func main() {
matriz := [][]int{
{1, 2},
{3, 4},
}

// Adiciona o valor 99 na linha de índice 0.
matriz[0] = append(matriz[0], 99)

fmt.Println(matriz)
}

Resultado:

[[1 2 99] [3 4]]

Aqui, matriz[0] é um slice, então podemos usar append nele.