Tipos Compostos em Go

Tipos Compostos em Go

Olá Leitor, seja bem vindo a mais uma lição da jornada GoLang 😄

Em lições anteriores, você aprendeu um pouco sobre a utilização dos tipos básicos em GoLang, ainda se lembra deles?

Não? Então deixa eu refrescar sua memória:

  • string
  • int
  • float
  • complex
  • bool

Se ainda não sabe como utilizá-los, recomendo que dê uns passos atrás, e volte na lição que fala sobre eles 🤭

Hoje, nós iremos aprender sobre os tipos compostos, ou também conhecidos como composite types, que representam um passo a mais sobre os tipos básicos.

O que são tipos compostos?

Em GoLang, nós temos os tipos array, slice e map, que são conhecidos como tipos compostos, eles são chamados dessa maneira pois por de baixo dos panos, eles são construídos a partir de outros tipos, como os próprios tipos básicos (int, string, float64, etc.).

Mas para que você possa entender o que de fato são os tipos compostos, primeiro você aprender o que são matrizes e vetores em uma linguagem de programação.

Basicamente, uma matriz (arrays multidimensionais) nada mais são do que estruturas que são capazes de armazenar e manipular coleções de valores, como listas de produtos, registros de clientes, listas de nomes, listas de números e etc.

Essas estruturas de dados são fundamentais, pois são a partir delas que conseguimos e armazenar informações na memória, de modo a facilitar o acesso e a manipulação de informações e dados.

Pense em uma matriz como se fosse uma caixa, onde dentro dela existe diversas subdivisões com seus respectivos nomes ou índices (números que vão de 0 a N).

Caso preferir, você também pode pensar numa matriz como uma planilha do excel, onde cada linha e coluna é usada para o armazenamento de dados.

Isso dá a possibilidade do desenvolvedor ter dentro de uma mesma "caixa" (ou planilha) diversos tipos de dados diferentes.

O que é uma matriz?

No mundo da programação e tecnologia, uma matriz é como uma tabela organizada em linhas (horizontais) e colunas (verticais)

Onde cada posição dentro dessa tabela é chamada de célula na qual podemos identificar cada uma usando dois números:

  • Primeiro número: indica a linha (de cima para baixo).
  • Segundo número: indica a coluna (da esquerda para a direita).

As posições na matriz são identificadas por dois números entre colchetes [linha][coluna]. Por exemplo:

  • A célula no canto superior esquerdo é [0][0].
  • A célula no canto inferior direito é [2][2].

Assim como também funciona nas planilhas do excel, onde a primeira célula é sempre a A1, a segunda é A2, e assim por diante...

Para que você possa entender melhor, vamos dar uma olhada nessa ilustração abaixo:

Imagine que essa matriz acima é como uma prateleira de frutas, onde cada caixinha é responsável por armazenar uma fruta por vez.

E para encontrar uma fruta específica, você precisa dizer o número da linha e a coluna, assim como você o faria caso estivesse usando um mapa 🗺️

Por exemplo, para você pegar a banana basta ir até a posição [1][5], ou seja, primeira linha na quinta coluna.

Além das matrizes multidimensionais, existem matrizes mais simples, chamadas de matrizes unidimensionais, que em GoLang são conhecidos como arrays (ou vetores).

Eles são mais simples porque têm apenas uma direção (ou seja, uma única linha de elementos).

Um array é como uma fila ou uma linha de caixas. Cada caixa guarda um valor, e você pode acessar qualquer um deles usando um número chamado índice, vejamos um exemplo:

🧮 Como funciona o índice?

  • O primeiro elemento está na posição 0.
  • O segundo está na posição 1.
  • E assim por diante.

Nesse caso, para acessar o valor 30 por exemplo, basta informar a posição [2] 🙂

Observação: a contagem de um array e um slice no GoLang sempre começa no índice 0, e não no índice 1.

Agora que você já sabe o que é uma matriz, podemos finalmente colocar a mão na massa e aprender um pouco mais sobre como criar arrays, slices e maps no GoLang!

Preparado?

Criando seu projeto de testes

Dentro da pasta JornadaGoLang, nós iremos criar uma nova pasta chamada de 10-tipos-compostos-em-go, onde dentro dela, vamos criar o nosso arquivo main.go:

package main

func main(){

}

Feito isso, vamos aprender um pouco mais sobre tipos compostos em GoLang 😁

Trabalhando com Arrays em Go

Começando pelo mais tradicional, dentro do GoLang (assim como na maioria das outras linguagens de programação) nós temos os arrays.

Os arrays são estruturas homogêneas, isto é, devem sempre armazenar o tipo declarado em toda a sua estrutura (se você declarar um array de strings, ele só poderá armazenar string, e assim também funciona com os outros tipos básicos), além disso, a sua própria estrutura precisa ser fixa.

Isso significa dizer que se você definir um array com 10 posições, ele deve permanecer com 10 posições e isso nunca poderá ser alterado.

Se você definir um array do tipo int, ele deverá apenas armazenar tipos inteiros, e isso nunca poderá ser alterado. (A mesma coisa vale para os outros tipos)

A sintaxe de um array em GoLang é a seguinte:

var nome_do_array [tamanho]tipo

Primeiro você declara a variável contendo o nome da sua lista (nome_do_array), seguido do tamanho informado dentro dos colchetes ([tamanho]), e seu tipo (tipo básico, podendo ser string, int, float e etc) no final.

Vejamos um exemplo prático:

package main

func main(){
    var meuArray [10]string
}

No exemplo acima, criamos uma variável chamada meuArray, que deverá armazenar até 10 strings.

Se mostrarmos os valores existentes dentro desse array que acabamos de criar usando o Println():

package main

import "fmt"

func main(){
    var meuArray [10]string
    fmt.Println(meuArray)
}

Teremos o seguinte resultado:

[         ]

Ué, mas não retornou nada? 😅

É obvio rs

Pois tudo o que fizemos acima foi apenas declarar nosso array, nós não definimos nenhum valor que será amazenado, somente seu tamanho e o seu tipo.

Caso tivéssemos criado um array de inteiros:

package main

import "fmt"

func main() {
	var meuArrayDeInteiros [7]int
	fmt.Println(meuArrayDeInteiros)
}

Veríamos esse resultado no terminal:

[0 0 0 0 0 0 0]

Ué? Porque aqui ele retornou diversos ZEROS, e anteriormente ele não retornou nada?

No caso do meuArray, foi retornado diversos espaços em branco, representando nossas strings vazias. Já no caso do meuArrayDeInteiros, ele sempre vai armazenar o menor valor do tipo int, e qual é o menor valor do tipo int

Isso mesmo, o número 0 🥳

Inserindo valores dentro de arrays em Go

Para inicializarmos o nosso array de strings, podemos fazer isso de algumas formas diferentes:

🟢 Atribuição manual: nela pode atribuir valores a posições específicas usando os índices:

package main

import "fmt"

func main() {
    var meuArray [10]string

    // Atribuindo valores por índice
    meuArray[0] = "Maçã"
    meuArray[1] = "Banana"
    meuArray[9] = "Uva"

    fmt.Println(meuArray) // Saída: [Maçã Banana       Uva]
}

Note que como os índices 2, 3, 4, 5, 6, 7 e 8 não foram inicializados, eles permanecem em branco.

🔵 Atribuição durante a declaração: Se já souber os valores, você pode preencher o array imediatamente:

package main

import "fmt"

func main() {
    var meuArray = [10]string{"Maçã", "Banana", "Uva", "Pera"}
    fmt.Println(meuArray)//[Maçã Banana Uva Pera      ]
}

Note que durante a declaração, usamos as chaves para definir os elementos, além disso a string "Maçã" sempre começa no índice 0, e assim por diante.

🟡 Tipo de inferência: Com o comando :=, você pode deixar o Go deduzir o tipo do array:

package main

import "fmt"

func main() {
    meuArray := [10]string{"Maçã", "Banana", "Uva"}
    fmt.Println(meuArray)//[Maçã Banana Uva       ]
}

🔴 Inicializar com tamanho implícito: Também é possível inicializar um array fazendo com que o Go conte os elementos de maneira automática, para isso basta usar o [...]:

package main

import "fmt"

func main() {
    meuArray := [...]string{"Maçã", "Banana", "Uva"}
    fmt.Println(meuArray) // Saída: [Maçã Banana Uva]
}

🟠 Inicializando informando índices específicos: Você pode definir valores em índices específicos:

package main

import "fmt"

func main() {
    meuArray := [10]string{0: "Maçã", 3: "Banana", 9: "Uva"}
    fmt.Println(meuArray) // Saída: [Maçã    Banana      Uva]
}

Note que durante a declaração, eu informei os índices de onde cada string deverá se posicionar.

🟣 Preencher com valores repetidos: Se quiser repetir valores ou seguir um padrão, um for ajuda:

package main

import "fmt"

func main() {
    var meuArray [10]string

    for i := 0; i < len(meuArray); i++ {
        meuArray[i] = "Fruta"
    }

    fmt.Println(meuArray) // Saída: [Fruta Fruta Fruta ...]
}

Note que usamos a função len(), que por sua vez, tem a capacidade de retornar o valor numérico contendo a quantidade de elementos existentes em um array.

package main

import "fmt"

func main() {
    meuArray := [5]int{10, 20, 30, 40, 50}
    
    fmt.Println("Tamanho do array:", len(meuArray)) // Saída: 5
}

Observação: A função len() em GoLang é usada para retornar o comprimento (tamanho) de estruturas como arrays, slices, strings e maps.

Varrendo arrays usando o for

Agora que você já sabe como criar arrays, que tal aprender a percorrer toda a sua estrutura de forma automática?

Em lições anteriores, você aprendeu a utilizar a estrutura de controle chamada for:

package main

import "fmt"

func main() {
    for i := 0; i < 9; i++ {
        fmt.Println("Contador:", i)
    }
    //O comando acima fará a contagem de 0 até 9.
}

E será por meio dela que você aprenderá a percorrer um array 😋

Supondo que temos um array com 15 números inteiros:

package main

import "fmt"

func main() {
    // Declarando um array com 15 números inteiros
    numeros := [15]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
}

E que desejamos percorrer este array mostrando cada um dos valores ali contidos dentro de um terminal, podemos usar o for da seguinte forma:

package main

import "fmt"

func main() {
    // Declarando um array com 15 números inteiros
    numeros := [15]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}

    // Percorrendo o array com um for tradicional
    for i := 0; i < len(numeros); i++ {
        fmt.Printf("Índice: %d, Valor: %d\n", i, numeros[i])
    }
}

Uma outra forma mais concisa de se percorrer um array usando o for, é em conjunto com o range:

package main

import "fmt"

func main() {
    numeros := [15]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}

    // Percorrendo o array com for range
    for indice, valor := range numeros {
        fmt.Printf("Índice: %d, Valor: %d\n", indice, valor)
    }
}

É importante ressaltar que o comando range em GoLang é usado para iterar (percorrer) diferentes tipos de estruturas de dados, tais como:

  • Arrays (matrizes unidimensionais)
  • Slices (listas dinâmicas)
  • Maps (dicionários ou tabelas de chave-valor)
  • Strings (sequências de caracteres)
  • Canais (channels) (para comunicação concorrente)

Vejamos outro exemplo:

package main

import "fmt"

func main() {
    frutas := []string{"maçã", "banana", "uva"}

    for i, fruta := range frutas {
        fmt.Printf("Índice: %d, Fruta: %s\n", i, fruta)
    }
}

Observação: o exemplo acima não declarou um array sem limites, mas sim uma slice, tipo composto na qual você irá aprender no próximo tópico 😄

É possível remover índices de um array?

Não é possível remover um índice diretamente de um array, pois o tamanho do array não pode ser alterado após a sua declaração.

Para "remover" um elemento de um array, você teria que criar um novo array com um tamanho menor, sem o elemento que você deseja remover, por exemplo:

package main

import "fmt"

func main() {
    arr := [5]int{1, 2, 3, 4, 5}
    indexToRemove := 2 // Queremos remover o índice 2 (o valor 3)

    // Criando um novo array com 4 elementos
    var newArr [4]int
    copy(newArr[:], append(arr[:indexToRemove], arr[indexToRemove+1:]...))

    fmt.Println("Array original:", arr)
    fmt.Println("Novo array:", newArr)
}

O append() é utilizado para juntar essas duas slices em uma nova slice, efetivamente removendo o elemento no índice desejado.

Trabalhando com Slice

Uma slice é uma sequência de elementos de um tipo específico, mas ao contrário de um array, seu tamanho pode variar ao longo do tempo. Ela é, na verdade, uma abstração sobre arrays, oferecendo mais flexibilidade.

Ao contrário de arrays, que têm um tamanho fixo, slices são dinâmicas e podem ser redimensionadas durante a execução do programa.

A sintaxe de uma slice é a seguinte:

var nome_do_slice []tipo

Note que definimos um colchete vazio, indicando que seu tamanho é ilimitado.

Vejamos como declarar uma slice:

package main

import "fmt"

func main() {
	frutasUm := []string{"maçã", "banana", "uva"}

	var frutasDois []string
}

No comando acima, declaramos dois slices, o primeiro com valores pré-definidos, e o seguindo com valores vazios. No caso dos dois, é possível adicionar mais elementos dentro de cada um deles.

Caso desejar, você pode inicializar uma slice vazia com capacidade de 10 elementos usando a função make:

frutas := make([]string, 0, 10)

A função make([]type, length, capacity) cria uma slice com o tipo especificado, com o comprimento e capacidade informados.

Operações mais comuns com slices

Assim como acontece nos arrays, nós podemos acessar elementos de uma slice utilizando um índice da seguinte forma:

fmt.Println(frutasUm[0]) // Imprime o primeiro elemento, "maçã"

Caso queira adicionar elementos dentro de uma slice, use a função append():

frutas = append(frutas, "laranja") // Adiciona "laranja" à slice

Nota: A função append() pode criar uma nova slice se a capacidade da slice original for insuficiente para armazenar o novo elemento.

Além disso, você pode criar uma nova slice a partir de uma slice existente, especificando um intervalo de índices:

frutasSubset := frutas[1:3] // Cria uma slice com os elementos de índice 1 e 2

Vejamos agora um exemplo de uma slice sendo percorrida por um for, onde ignoramos seu índice por meio da anotação _:

package main

import "fmt"

func main() {
    frutas := []string{"maçã", "banana", "uva"}

    for _, fruta := range frutas {
        fmt.Printf("Fruta: %s\n", i, fruta)
    }
}

Vejamos um outro exemplo, onde a função append() é usada para redimensionar automaticamente a slice se a capacidade for excedida:

nums := []int{1, 2, 3}
nums = append(nums, 4)  // nums agora é [1, 2, 3, 4]

Medindo o comprimento e capacidades de um array e slice

Como dito anteriormente, nós temos dois grandes aliados que podem nos ajudar a medir a capacidade de um array e uma slice, são eles:

len(): Retorna o número de elementos de uma sequência (array, slice ou string)

cap(): Retorna a capacidade total (número máximo de elementos) que a sequência pode armazenar antes de uma nova alocação de memória ser necessária.

Vejamos um exemplo de seus usos:

package main

import "fmt"

func main() {
    s := []int{1, 2, 3}
    fmt.Println(len(s), cap(s)) // Inicialmente len = 3, cap = 3

    s = append(s, 4) // Adiciona um elemento, a capacidade pode ser duplicada
    fmt.Println(len(s), cap(s)) // Agora len = 4, cap = 6 (ou algo maior, dependendo da implementação)
}

Criando uma slice de uma slice

Em alguns momentos, você vai se deparar com uma sintaxe similar a esta [:a3], mas não se desespere, ela é mais fácil de se entender do que você imagina.

Em GoLang, você pode criar um fatiamento (slicing) de uma slice. O fatiamento permite extrair uma parte (ou subsequência) de uma slice, usando um intervalo de índices.

slice[inicio:fim]

inicio: Índice onde o fatiamento começa (inclusivo). Se omitido, assume-se 0 (começa do início).

fim: Índice onde o fatiamento termina (exclusivo). Se omitido, a slice vai até o final.

Vejamos o exemplo a seguir:

package main

import "fmt"

func main() {
    frutas := []string{"maçã", "banana", "uva", "laranja", "morango"}

    // Fatiamento até o índice 3 (não inclui o elemento no índice 3)
    parte := frutas[:3]

    fmt.Println(parte) // Imprime: [maçã banana uva]
}

Neste exemplo, [:3] significa que estamos pegando os elementos nos índices 0, 1 e 2 da slice frutas, ou seja, as três primeiras frutas.

package main

import "fmt"

func main() {
    frutas := []string{"maçã", "banana", "uva", "laranja", "morango"}

    // Fatiamento da posição 2 até o final
    parte := frutas[2:]

    fmt.Println(parte) // Imprime: [uva laranja morango]
}

No exemplo acima, [2:] significa "começar do índice 2 (inclusive) até o final da slice".

package main

import "fmt"

func main() {
    frutas := []string{"maçã", "banana", "uva", "laranja", "morango"}

    // Fatiamento do índice 1 até o índice 4 (não inclui o 4)
    parte := frutas[1:4]

    fmt.Println(parte) // Imprime: [banana uva laranja]
}

Aqui, [1:4] pega os elementos nos índices 1, 2 e 3 da slice frutas, ou seja, "banana", "uva" e "laranja".

Você também pode criar fatiamentos para criar subsequências específicas:

package main

import "fmt"

func main() {
    frutas := []string{"maçã", "banana", "uva", "laranja", "morango"}

    // Fatiamento das duas primeiras frutas
    parte1 := frutas[:2]
    fmt.Println(parte1) // Imprime: [maçã banana]

    // Fatiamento das duas últimas frutas
    parte2 := frutas[3:]
    fmt.Println(parte2) // Imprime: [laranja morango]

    // Combinando as duas partes
    resultado := append(parte1, parte2...)
    fmt.Println(resultado) // Imprime: [maçã banana laranja morango]
}
  • [:a3]: Cria uma nova slice desde o início até o índice a3-1.
  • [a1:a2]: Cria uma nova slice desde o índice a1 até o índice a2-1.
  • [a1:]: Cria uma nova slice desde o índice a1 até o final.
  • [:a2]: Cria uma nova slice do início até o índice a2-1.

Criando slices com make

No GoLang, você consegue criar um slice usando a função make da seguinte forma:

make([]tipo, comprimento, capacidade)

A função make() é usada para criar slices, maps e channels, mas aqui vamos falar especificamente sobre como ela funciona para uma slice.

tipo: O tipo dos elementos da slice (como int, string, float64, etc.).

comprimento: O número de elementos iniciais na slice. Este valor também será o valor retornado por len() da slice.

capacidade (opcional): A capacidade máxima da slice antes de uma realocação de memória. Se não for fornecido, a capacidade será igual ao comprimento.

Vejamos um exemplo:

package main

import "fmt"

func main() {
    // Criando uma slice de inteiros com comprimento 3 e capacidade 5
    s := make([]int, 3, 5)

    fmt.Println(s)         // Imprime: [0 0 0]
    fmt.Println(len(s))    // Imprime: 3 (comprimento)
    fmt.Println(cap(s))    // Imprime: 5 (capacidade)
}

No exemplo acima, criamos uma slice que começa com 3 elementos, e todos os elementos são inicializados com o valor zero para o tipo (0 para int).

A capacidade total da slice é 5, o que significa que ela pode crescer até 5 elementos antes de precisar de uma realocação de memória.

Vejamos um outro exemplo, na qual estamos criando uma slice modificando seus elementos:

package main

import "fmt"

func main() {
    // Criando uma slice de strings com comprimento 2 e capacidade 3
    s := make([]string, 2, 3)

    // Modificando os elementos da slice
    s[0] = "maçã"
    s[1] = "banana"

    fmt.Println(s)         // Imprime: [maçã banana]
    fmt.Println(len(s))    // Imprime: 2
    fmt.Println(cap(s))    // Imprime: 3
}

Caso desejar, você pode criar uma slice com apenas o comprimento sem capacidade especificada:

package main

import "fmt"

func main() {
    // Criando uma slice de inteiros com comprimento e capacidade 3
    s := make([]int, 3)

    fmt.Println(s)         // Imprime: [0 0 0]
    fmt.Println(len(s))    // Imprime: 3
    fmt.Println(cap(s))    // Imprime: 3
}

Após a criação de uma slice com make(), você pode expandi-la além da capacidade inicial usando a função append().

Quando isso acontece, o Go automaticamente realoca a memória conforme necessário:

package main

import "fmt"

func main() {
    // Criando uma slice com comprimento 2 e capacidade 3
    s := make([]int, 2, 3)

    // Adicionando elementos à slice
    s = append(s, 4, 5) // Agora a slice tem 4 elementos

    fmt.Println(s)       // Imprime: [0 0 4 5]
    fmt.Println(len(s))  // Imprime: 4 (comprimento atualizado)
    fmt.Println(cap(s))  // Imprime: 6 (capacidade pode ter sido dobrada)
}

Usando a função copy

A função copy() em Go é usada para copiar elementos de uma slice para outra. Ela é útil quando você deseja duplicar uma slice ou copiar parte de uma slice para outra.

Sua sintaxe é a seguinte:

copy(destino, origem)

destino: A slice que irá receber os elementos (a slice de destino).

origem: A slice da qual os elementos serão copiados (a slice de origem).

Retorno: Um int indicando o número de elementos copiados.

Vejamos um exemplo do seu uso:

package main

import "fmt"

func main() {
    origem := []int{1, 2, 3, 4, 5}
    destino := make([]int, len(origem))

    n := copy(destino, origem)

    fmt.Println("Origem:", origem)     // [1 2 3 4 5]
    fmt.Println("Destino:", destino)   // [1 2 3 4 5]
    fmt.Println("Elementos copiados:", n) // 5
}

Incrível, não acha? 😄

É possível ter dois tipos básicos em um array ou slice no Go?

Esse é um tipo de pergunta que sempre vem à tona quando estamos trabalhando com array ou slices na linguagem GoLang.

E apesar dos arrays e slices serem estruturas homogêneas, ou seja, todos os elementos devem ser do mesmo tipo. Você pode utilizar interfaces para armazenar diferentes tipos em um mesmo array ou slice.

Observação: nós ainda não entramos no conceito de interfaces, portanto, não se sinta mal caso não souber o que é isso ainda 😆

No GoLang, você pode ter mais de um tipo básico por meio do uso de interface, e isso pode ser feito da seguinte forma:

package main

import "fmt"

func main() {
    // Slice que armazena diferentes tipos de dados
    mixedSlice := []interface{}{42, "texto", 3.14, true}

    for _, v := range mixedSlice {
        fmt.Printf("Valor: %v, Tipo: %T\n", v, v)
    }
}

Exemplo de saída:

Valor: 42, Tipo: int
Valor: texto, Tipo: string
Valor: 3.14, Tipo: float64
Valor: true, Tipo: bool

Já com a chegada da versão 1.18+, o GoLang ganhou um novo tipo específico chamado de any, que aceita qualquer tipo básico:

package main

import "fmt"

func main() {
    var mixed []any = []any{123, "olá", 4.56}

    for _, item := range mixed {
        fmt.Printf("Valor: %v, Tipo: %T\n", item, item)
    }
}

Trabalhando com Map em Go

Não é só de índices numéricos que vivem as listas em GoLang, sabia?

Mas como também podemos criar listas de chave e valor, onde neste caso, conseguimos fazer de um índice númerico uma string ou qualquer outro tipo básico.

No Go, maps (ou dicionários em outras linguagens) são estruturas que armazenam pares de chave-valor. No entanto:

✅ As chaves de um map não se limitam a números: podem ser de qualquer tipo comparável (ex.: string, int, float, complex, bool etc.).

❌ Não podemos usar tipos não comparáveis: como chave, por exemplo, um slice, map ou uma função.

A sintaxe de um map é a seguinte:

map[TipoDaChave]TipoDoValor

Vamos ver um exemplo básico de sua utilização:

package main

import "fmt"

func main() {
    // Criando um map com chave string e valor int
    idades := map[string]int{
        "Alice": 30,
        "Bob":   25,
        "Eve":   22,
    }

    fmt.Println(idades) // Saída: map[Alice:30 Bob:25 Eve:22]
}

No exemplo acima, inicializamos um map, cujo as chaves (índices) serão strings, e seus respectivos valores serão inteiros (int).

Para criar um map vazio afim de preenchê-lo depois, a função make() pode te ajudar com isso, observe:

contagem := make(map[string]int) // Cria um map vazio
contagem["Maçã"] = 5
contagem["Banana"] = 3

fmt.Println(contagem) // Saída: map[Maçã:5 Banana:3]

Caso, desejar, você pode adicionar e atualizar valores em um map da seguinte forma:

pontos := make(map[string]int)

pontos["João"] = 50 // Adiciona um novo par
pontos["Ana"] = 75  // Adiciona outro par
pontos["João"] = 100 // Atualiza um valor existente

fmt.Println(pontos) // Saída: map[Ana:75 João:100]

Além disso, é possível acessar os valores de um map da seguinte forma, bastando apenas informar seu índice:

notas := map[string]float64{
    "Matemática": 9.5,
    "História":   8.0,
}

fmt.Println(notas["Matemática"]) // Saída: 9.5

Para remover itens de um map, você pode utilizar a função delete() da seguinte forma:

nomes := map[string]string{
    "123": "Carlos",
    "456": "Ana",
}

delete(nomes, "123") // Remove o item com a chave "123"

fmt.Println(nomes) // Saída: map[456:Ana]

Para verificar se uma determinada chave (índice) em um map existe, faça isso da seguinte forma:

idades := map[string]int{"João": 30}

idade, existe := idades["Ana"]

if existe {
    fmt.Println("Idade encontrada:", idade)
} else {
    fmt.Println("Chave não encontrada")
}

Observação: Se a chave não existir, o valor retornado será o zero value do tipo básico escolhido (ex.: 0 para int, "" para string).

Para percorrer um map, você também pode utilizar a mesma estratégia de for em conjunto com range, como estamos acostumados, observe:

frutas := map[string]int{
    "Maçã":    5,
    "Banana":  3,
    "Laranja": 2,
}

for chave, valor := range frutas {
    fmt.Printf("%s: %d\n", chave, valor)
}

Para limpar um map, basta usar o make() da seguinte forma:

meuMapa = make(map[string]int) // Novo map vazio

É importante ressaltar que maps são passados por referência, ou seja, modificações em uma cópia afetam o original, vamos ver um exemplo:

original := map[string]int{"A": 1}
copia := original

copia["A"] = 42
fmt.Println(original) // Saída: map[A:42]

Para realizar uma cópia totalmente independente, faça isso da seguinte forma:

novo := make(map[string]int)
for k, v := range original {
    novo[k] = v
}

Aninhando Maps

Você sabia que é possível criar uma estrutura de chave e valor dentro de outra, e assim sucessivamente?

Não, pois saiba que com o map isso é totalmente possível, observe como isso pode ser feito:

notas := map[string]map[string]float64{
    "João": {
        "Matemática": 9.5,
        "História":   8.0,
    },
    "Ana": {
        "Matemática": 10.0,
        "História":   9.0,
    },
}

fmt.Println(notas["João"]["Matemática"]) // Saída: 9.5

No Go, não há um limite explícito de profundidade para mapas aninhados. Em teoria, você pode aninhar quantos map quiser, desde que a memória do seu sistema suporte.

package main

import "fmt"

func main() {
    estrutura := map[string]map[string]map[string]int{
        "primeiro": {
            "segundo": {
                "terceiro": 42,
            },
        },
    }

    fmt.Println(estrutura["primeiro"]["segundo"]["terceiro"]) // Saída: 42
}

Vejamos outro exemplo:

estrutura := make(map[string]map[string]map[string]int)
estrutura["nivel1"] = make(map[string]map[string]int)
estrutura["nivel1"]["nivel2"] = make(map[string]int)
estrutura["nivel1"]["nivel2"]["nivel3"] = 99

fmt.Println(estrutura["nivel1"]["nivel2"]["nivel3"]) // Saída: 99

Tipos permitidos em chaves de map

É importante que você conheça os tipos permitidos e que podem ser usados como chaves (índices) em um map 😁

int, string, float64, bool, struct (se comparável), podem ser utilizados como chave.

❌ Entretanto, não é possível usar slice, map ou função como chave.

Repositório da lição

Todos os arquivos relacionados com esta lição, podem ser encontrados nos seguintes repositórios abaixo:

Conclusão

Nesta lição, você aprendeu a usar os tipos compostos slice, map e array.

Na próxima lição, vamos entrar em conceitos um pouco mais avançados como structs, heranças e interfaces.

Até logo 🤩

Criadores de Conteúdo

Foto do William Lima
William Lima
Fundador da Micilini

Inventor nato, escreve conteudos de programação para o portal da micilini.

Torne-se um MIC 🤖

Mais de 100 mic's já estão conectados na plataforma.