Conteúdo

Tipos compostos

Tipos compostos em Go são nada mais do que a combinação de múltiplos tipos primitivos para representar diferentes tipos de dados. Com os tipos compostos temos uma liberdade muito grande de modelar as informações da forma que melhor resolvem o problema com o qual estamos lidando. Os principais tipos compostos que usamos no nosso dia a dia são: arrays, slices, maps e structs.

Arrays

Um array guarda um número específico de elementos do mesmo tipo, que podem ser de tipos básicos, como int e string, ou do tipo struct. Em geral, eles são menos utilizados que slices, mas é importante entendê-los pois são a estrutura de dados básica dos slices e maps.

Representação de um array

índice

[0]

[1]

[2]

[3]

valor

127

290

7

83

tipo

int64

int64

int64

int64

Todos os elementos têm o mesmo tipo (int64) e cada um tem um índice que pode ser usado para acessá-lo (primeira linha). Se o array representado se chamasse numeros, utilizaríamos a sintaxe numeros[2] para acessar o elemento de índice 2 e valor 7.

Exemplo de declaração:

var diasDaSemana [7]string
diasDaSemana = [7]string{"Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado"}

Também é possível fazer a declaração usando o array literal, que permite declarar o tipo do array e atribuir valor a elementos na mesma operação.

Exemplo de declaração:

diasDaSemana := [7]string{"Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado"}

O tipo do array sempre leva em consideração sua quantidade de elementos. Por exemplo, o tipo de um array [5]string é diferente do tipo de um array [7]string. Caso se tente atribuir o valor de um ao outro, haverá um erro de compilação.

Exemplo de código com erro de compilação:

package main

func main() {
	diasDaSemana := [7]string{"Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado"}
	diasUteis := [5]string{"Segunda", "Terça", "Quarta", "Quinta", "Sexta"}
	diasDaSemana = diasUteis
}

Ao rodar o código acima, o erro ./prog.go:6:15: cannot use diasUteis (type [5]string) as type [7]string in assignment acontece em tempo de compilação.

O array de n elementos pode ser acessado pelos índices 0 ao n-1. No caso do array diasDaSemana, seu tipo é [7]string, seu tamanho é de 7 elementos, e seus índices vão de 0 a 6.

Slices

Slices são parecidos com arrays, mas oferecem uma flexibilidade muito maior para quem desenvolve principalmente por terem tamanho variável. Além disso, slices têm a função embutida append(), que permite adicionar mais elementos com facilidade - a conheceremos em breve! Em materiais traduzidos para o português, é comum slice ser traduzido como “fatia”.

A sintaxe de um slice é []T, sendo T o tipo que cada um dos elementos do slice terá. Note que, diferentemente de array, não há número entre os colchetes. Por exemplo, um slice de strings tem o tipo []string.

Exemplo de declaração:

var identificador []string

Slice literal

Um slice sempre parte de um array, então vamos aproveitar a declaração de array do exemplo de cima e demonstrar a declaração e atribuição de valor usando um slice literal.

var diasDaSemana = []string{"Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado"}

E se você não souber quais elementos vai precisar colocar no slice no momento de sua declaração?

Se você declarar seu slice da forma var nome []string e tentar inserir elementos através de seus índices, como no exemplo abaixo, receberá o erro index out of range em tempo de execução, pois esse slice foi criado com tamanho 0 e estamos tentando acessar índices inexistentes.

package main

func main() {
	var diasDaSemana []string
	diasDaSemana[0] = "Domingo"
	diasDaSemana[1] = "Segunda"
}

Usando append()

A função embutida append() pode ser utilizada para evitar esse cenário. Ela facilita o processo de estender o slice ao abstrair o aumento de seu tamanho e capacidade.

Exemplo:

package main

import "fmt"

func main() {
	var diasDaSemana []string
	// Antes do append
	fmt.Println("tamanho do slice: ", len(diasDaSemana))
	
	diasDaSemana = append(diasDaSemana, "Domingo", "Segunda")
	
	// Depois do append
	fmt.Println("tamanho do slice: ", len(diasDaSemana))
}

Duas funções embutidas que também podem ser usadas com slices são len() e cap(). A primeira informa o tamanho do slice, e a segunda sua capacidade.

Note que, antes do append(), o tamanho do slice era 0. Após, mudou para 2, sem termos que explicitamente indicar isso.

Usando make()

Outra função embutida útil quando falamos em construir slices (mas não apenas eles!) é make(). Essa função cria uma instância do tipo que for passado como o primeiro argumento e define tamanho e capacidade de acordo com o segundo argumento. O terceiro argumento é opcional: se for passado, representará a capacidade - nesse caso, o segundo argumento será o tamanho, que pode ser diferente da capacidade mas nunca maior). Vamos ilustrar?

Sintaxe:

// Passando dois argumentos
<identificador> := make([]T, <tamanho e capacidade>)
exemplo := make([]string, 7)

// Passando três argumentos
<identificador> := make([]T, <tamanho>, <capacidade>)
exemplo := make([]string, 5, 7)

Dessa forma, podemos atribuir valores ao nosso slice diasDeSemana usando os índices sem receber o erro index out of range, da seguinte forma:

package main

import "fmt"

func main() {
	diasDaSemana := make([]string, 7)
	diasDaSemana[0] = "Domingo"
	diasDaSemana[1] = "Segunda"
	diasDaSemana[2] = "Terça"
	diasDaSemana[3] = "Quarta"
	diasDaSemana[4] = "Quinta"
	diasDaSemana[5] = "Sexta"
	diasDaSemana[6] = "Sábado"

	fmt.Println("tamanho do slice: ", len(diasDaSemana))
	fmt.Println("capacidade do slice: ", cap(diasDaSemana))
	fmt.Println("elementos do slice: ", diasDaSemana)
}

Instância

Você pode ter percebido essa palavra no parágrafo acima. Daqui para frente a usaremos bastante, bem como alguns derivados: instanciar, instanciado.

Esse é um termo técnico usado para dizer que criamos uma cópia de um objeto de um tipo específico. Por exemplo, quando declaramos var nome []string estamos instanciando um objeto do tipo []string que terá o identificador nome.

Outro termo técnico que pode ser usado como sinônimo é alocação em memória.

Acessando subconjuntos do slice

É possível fatiar um slice para obter subconjuntos dele. Isso é feito usando : dentro do operador [ ]. Se nenhum índice for indicado, todos os elementos do slice original formarão a fatia. Nessa sintaxe, o primeiro parâmetro para definir um subconjunto é inclusivo, já o segundo é exclusivo (em notação matemática seria algo do tipo [índice:índice[). Alguns exemplos para ilustrar:

// Todos os elementos
diasDaSemana[:] // corresponde a diasDaSemana

// Primeiro elemento
diasDaSemana[0:1]

// Último elemento do array
diasDaSemana[len(diasDaSemana)-1:]

// Elementos intermediários do array
diasDaSemana[1:len(diasDaSemana)-1]

Rode o código abaixo na sua máquina para visualizar os subconjuntos:

package main

import (
	"fmt"
)

func main() {
	diasDaSemana := []string{"Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado"}

	// Todos os elementos
	fmt.Println(diasDaSemana[:])

	// Primeiro elemento
	fmt.Println(diasDaSemana[0:1])

	// Último elemento do array
	fmt.Println(diasDaSemana[len(diasDaSemana)-1:])

	// Elementos intermediários do array
	fmt.Println(diasDaSemana[1 : len(diasDaSemana)-1])
}

Organização interna

<em breve>

Maps

Mapas são estruturas de dados que associam chaves e valores (também poderíamos dizer que representam tabelas hash). Eles nos permitem mapear qualquer tipo de dado para qualquer tipo de dado. Quando falamos de map existem dois conceitos centrais: chave (key) e valor (value) onde, para cada chave - que é única - só existirá um valor associado.

Um exemplo de um “map” da vida real é um dicionário: as palavras são como chaves únicas que identificam os “valores” de seus significados.

Sintaxe:

map[<tipo da chave>]<tipo do valor>

Exemplo:

map[string]int

Usando make()

Também podemos usar a função embutida make() para instanciar um map. Diferentemente dos slices, não indicamos tamanho e capacidade, apenas o tipo do mapa.

Exemplo:

// Note que nesse mapa a chave é o do tipo string e o valor é do tipo int
var mapa = make(map[string]int)

Atribuindo valores ao map

A atribuição de valores em maps é parecida com slices, a chave atua como se fosse um índice. Também similar ao slice, se tentarmos armazenar um par de chave e valor a um mapa que não foi inicializado (declarado apenas na forma var nome map[string]string), receberemos o erro assignment to entry in nil map em tempo de execução. Criar o mapa com make() evita essa situação.

Exemplo:

package main

import "fmt"

func main() {
	mapa := make(map[string]int)
	mapa["um"] = 1
	mapa["dois"] = 2
	mapa["quatro"] = 4
	mapa["cinco"] = 5

	fmt.Println(mapa)
}

Mapa literal

Também é possível usar um mapa literal para instanciar um mapa e atribuir valores no momento da sua declaração. A sintaxe é a seguinte:

<identificador> := map[string]string{“chave1”: “valor1”, “chave2”: “valor2”}

Exemplo:

package main

import "fmt"

func main() {
	cores := map[string]string{
		"cinza": "#cccccc",
		"roxo":  "#a378f8",
	}

	fmt.Println(cores)
}

Recuperando valores de um mapa

Para recuperar um valor do mapa, usamos sua chave.

Exemplo:

roxo := cores[“roxo”]

Checando se um par chave e valor existe

Quando formos resgatar um valor em um mapa, é possível verificar se a chave existe. A sintaxe é a seguinte:

<identificador>, <validador> := map[<chave>]

Exemplo:

package main

import "fmt"

func main() {
	cores := map[string]string{
		"cinza": "#cccccc",
		"roxo":  "#a378f8",
	}

	azul, existe := cores["azul"]
	if !existe {
		fmt.Println("azul não existe no mapa")
		return
	}
	fmt.Println(azul)
}

Deletando valores de um mapa

Para deletar valores de um mapa, podemos usar a função embutida delete(). Essa função recebe dois argumentos: o nome do mapa e a chave que identifica o par chave e valor que vai ser removido.

Exemplo:

delete(cores, “cinza”)

Structs

Structs são estruturas de dados que agrupam um conjunto de campos de tipos definidos por quem os descreve. Permitem assim representar na aplicação o mundo real, e por isso são extremamente importantes. São muito usados em vários contextos diferentes, para modelar entidades, agregados e requisitos necessários para a aplicação.

Structs nos permitem representar não apenas dados jogados, como variáveis chamadas nome do tipo string e idade do tipo int, mas sim uma Pessoa com seus atributos nome e idade agregados.

Sintaxe:

type <nome-da-struct> struct {
// Campos aqui
<nome-do-campo> <tipo-do-campo>
}

Explorando a ideia da struct para representar uma pessoa: vamos criar uma struct do tipo Pessoa contendo os campos nome e idade.

Exemplo:

type Pessoa struct {
		Nome string
		Idade int
}

Instanciando uma struct

Como instanciamos uma “nova pessoa” registrando seus atributos nos campos da struct? O exemplo abaixo ilustra:

pessoa := Pessoa{Nome: “Maria”, Idade: 20}

Acessando os campos

Os campos individuais são acessados usando ponto, como no exemplo abaixo.

package main

import (
	"fmt"
)

type Pessoa struct {
	Nome  string
	Idade int
}

func main() {
	pessoa := Pessoa{Nome: "Maria", Idade: 20}
	fmt.Printf("%+v\n", pessoa)

	//Acessando os elementos de pessoa
	fmt.Printf("%s tem %d anos\n", pessoa.Nome, pessoa.Idade)
}

Os campos podem receber novos valores, por exemplo: pessoa.Idade = 21

Estruturas podem também conter outras estruturas. No exemplo, Aula é uma struct que tem um campo do tipo string, um do tipo Pessoa e outro do tipo “slice de Pessoas”.

Exemplo:

package main

import "fmt"

type Pessoa struct {
	Nome  string
	Idade int
}

type Aula struct {
	Descricao string
	Docente   Pessoa
	Discentes []Pessoa
}

func main() {

	eliana := Pessoa{Nome: "Eliana", Idade: 47}
	joao := Pessoa{Nome: "João", Idade: 19}
	zeca := Pessoa{Nome: "Zeca", Idade: 34}
	aline := Pessoa{Nome: "Aline", Idade: 42}

	aula := Aula{
		Descricao: "Aula de Go",
		Docente:   eliana,
		Discentes: []Pessoa{
			joao,
			zeca,
			aline,
		},
	}

	fmt.Printf("%+v", aula)
}

Last updated