Structs
Structs
Imagine que você está preenchendo um formulário de informações pessoais: nome, idade, número de telefone, endereço — essas informações estão relacionadas entre si e juntas descrevem uma pessoa. Em Go, uma struct é esse "formulário": ela combina múltiplos campos de diferentes tipos em um tipo composto personalizado. Comparado a maps, os campos de uma struct são determinados em tempo de compilação, tornando-os seguros em tipo e semanticamente claros. Structs são a forma central de organizar dados em Go.
1. Conceitos Principais
| Conceito | Descrição |
|---|---|
| Definição | type NomeStruct struct { ... }, o nome do campo vem antes do tipo |
| Inicialização | Literal S{c1: v1, c2: v2}, new(S) retorna um ponteiro, &S{} |
| Acesso a Campos | Notação ponto s.Campo, ponteiros também usam notação ponto p.Campo (não é necessário ->) |
| Tipo por Valor | Structs são tipos por valor; atribuição e argumentos de função copiam a struct inteira |
| Receptor por Ponteiro | Obtenha um ponteiro via &s; modificar a struct através do ponteiro afeta o valor original |
| Campos Anônimos | Escreva apenas o tipo sem nome de campo; o nome do tipo se torna o nome do campo, permitindo "composição" |
| Structs Aninhadas | Campos de struct podem ser structs, suportando aninhamento em múltiplos níveis |
| Tags de Struct | Metadados envoltos em crases, comumente usados para mapeamento JSON/banco de dados |
*S.
2. Sintaxe/Uso Básico
Definindo uma Struct
// Define uma struct Pessoa
type Pessoa struct {
Nome string
Idade int
Cidade string
}
Métodos de Inicialização
// Método 1: nome do campo: valor (recomendado, boa legibilidade, ordem não importa)
p1 := Pessoa{Nome: "Alice", Idade: 25, Cidade: "São Paulo"}
// Método 2: atribuir por ordem (não recomendado para muitos campos, propenso a erros)
p2 := Pessoa{"Bob", 30, "Rio de Janeiro"}
// Método 3: declarar primeiro, depois atribuir
var p3 Pessoa
p3.Nome = "Charlie"
p3.Idade = 28
p3.Cidade = "Belo Horizonte"
// Método 4: criar com new (retorna ponteiro)
p4 := new(Pessoa) // *Pessoa
p4.Nome = "Diana"
// Método 5: pegar endereço (retorna ponteiro)
p5 := &Pessoa{Nome: "Eve", Idade: 22, Cidade: "Curitiba"}
var p Pessoa inicializa todos os campos com o valor zero de seus respectivos tipos (string → "", int → 0, bool → false).
Acessando e Modificando Campos
p := Pessoa{Nome: "Alice", Idade: 25}
// Ler um campo
fmt.Println(p.Nome) // Alice
// Modificar um campo
p.Idade = 26
fmt.Println(p.Idade) // 26
p->Nome.
Ponteiros de Struct
p := &Pessoa{Nome: "Alice", Idade: 25}
// Go automaticamente desreferencia, use notação ponto diretamente
fmt.Println(p.Nome) // Alice
// Modificar o valor original através do ponteiro
p.Idade = 30
->. Se p é um valor ou um ponteiro, você sempre usa p.Campo para acessar campos.
3. Código de Exemplo
Exemplo 1: Uso Básico (Dificuldade ⭐)
Definir uma struct Livro, criar instâncias e imprimir informações.
package main
import "fmt"
// Livro define uma struct de livro
type Livro struct {
Titulo string
Autor string
Paginas int
Preco float64
}
func main() {
// Criar usando um literal
livro1 := Livro{
Titulo: "The Go Programming Language",
Autor: "Alan Donovan",
Paginas: 350,
Preco: 59.9,
}
// Imprimir a struct
fmt.Println(livro1)
// Acessar campos individuais
fmt.Printf("Titulo: %s\n", livro1.Titulo)
fmt.Printf("Autor: %s\n", livro1.Autor)
fmt.Printf("Preco: %.1f\n", livro1.Preco)
// Modificar um campo
livro1.Preco = 49.9
fmt.Printf("Novo preco: %.1f\n", livro1.Preco)
// Criar por ordem (não recomendado)
livro2 := Livro{"Python Crash Course", "Eric Matthes", 280, 45.0}
fmt.Println(livro2)
}
Saída:
{The Go Programming Language Alan Donovan 350 59.9}
Titulo: The Go Programming Language
Autor: Alan Donovan
Preco: 59.9
Novo preco: 49.9
{Python Crash Course Eric Matthes 280 45}
Exemplo 2: Uso Avançado (Dificuldade ⭐⭐)
Usando ponteiros, structs aninhadas e campos anônimos.
package main
import "fmt"
// Endereco struct
type Endereco struct {
Cidade string
Rua string
CEP string
}
// Funcionario struct (usa campo anônimo Endereco)
type Funcionario struct {
Nome string
Idade int
Endereco // Campo anônimo: nome do tipo é o nome do campo
Salario float64
}
func main() {
// ========== Inicialização de struct aninhada ==========
func1 := Funcionario{
Nome: "Alice",
Idade: 30,
Salario: 15000,
Endereco: Endereco{
Cidade: "São Paulo",
Rua: "Rua Augusta, 100",
CEP: "01310-100",
},
}
fmt.Printf("Funcionario: %s\n", func1.Nome)
fmt.Printf("Cidade: %s\n", func1.Endereco.Cidade) // Acesso explícito
// ========== Acesso "promovido" a campo anônimo ==========
// Campos anônimos podem ser acessados diretamente pelo nome do tipo
fmt.Printf("Rua: %s\n", func1.Rua) // Mesmo que func1.Endereco.Rua
fmt.Printf("CEP: %s\n", func1.CEP) // Mesmo que func1.Endereco.CEP
// ========== Operações com ponteiros ==========
funcPtr := &func1
// Modificar através do ponteiro (Go automaticamente desreferencia)
funcPtr.Idade = 31
funcPtr.Salario = 18000
funcPtr.Cidade = "Rio de Janeiro" // Modificar campo anônimo através do ponteiro
fmt.Printf("\nApós modificação: %+v\n", *funcPtr)
// ========== Tipo por valor vs Tipo por ponteiro ==========
fmt.Println("\n--- Tipo por valor vs Tipo por ponteiro ---")
// Tipo por valor: atribuição é cópia
func2 := func1
func2.Nome = "Bob"
fmt.Printf("func1.Nome: %s\n", func1.Nome) // Ainda Alice
fmt.Printf("func2.Nome: %s\n", func2.Nome) // Bob
// Tipo por ponteiro: atribuição é compartilhamento
func3 := funcPtr
func3.Nome = "Charlie"
fmt.Printf("funcPtr.Nome: %s\n", funcPtr.Nome) // Charlie (modificado)
}
Saída:
Funcionario: Alice
Cidade: São Paulo
Rua: Rua Augusta, 100
CEP: 01310-100
Após modificação: {Nome:Alice Idade:31 Endereco:{Cidade:Rio de Janeiro Rua:Rua Augusta, 100 CEP:01310-100} Salario:18000}
--- Tipo por valor vs Tipo por ponteiro ---
func1.Nome: Alice
func2.Nome: Bob
funcPtr.Nome: Charlie
Exemplo 3: Aplicação Completa (Dificuldade ⭐⭐⭐)
Usando tags de struct para serialização/deserialização JSON e construindo um sistema de gerenciamento de alunos.
package main
import (
"encoding/json"
"fmt"
"sort"
"strings"
)
// Aluno struct com tags JSON
type Aluno struct {
ID int `json:"id"`
Nome string `json:"nome"`
Idade int `json:"idade"`
Nota float64 `json:"nota"`
Tags []string `json:"tags,omitempty"` // omitempty: omitir quando vazio
secreto string // Campo não exportado, não acessível via JSON
}
// SalaDeAula (struct aninhada)
type SalaDeAula struct {
NomeDaSala string `json:"nome_da_sala"`
Alunos []Aluno `json:"alunos"`
}
// AdicionarAluno adiciona um aluno
func (s *SalaDeAula) AdicionarAluno(a Aluno) {
s.Alunos = append(s.Alunos, a)
}
// ObterTopN obtém os top N alunos por nota
func (s *SalaDeAula) ObterTopN(n int) []Aluno {
// Fazer uma cópia para evitar modificar os dados originais
ordenado := make([]Aluno, len(s.Alunos))
copy(ordenado, s.Alunos)
// Ordenar por nota em ordem decrescente
sort.Slice(ordenado, func(i, j int) bool {
return ordenado[i].Nota > ordenado[j].Nota
})
if n > len(ordenado) {
n = len(ordenado)
}
return ordenado[:n]
}
// Media calcula a nota média
func (s *SalaDeAula) Media() float64 {
if len(s.Alunos) == 0 {
return 0
}
total := 0.0
for _, a := range s.Alunos {
total += a.Nota
}
return total / float64(len(s.Alunos))
}
// BuscarPorNome busca por nome (correspondência parcial)
func (s *SalaDeAula) BuscarPorNome(palavraChave string) []Aluno {
var resultado []Aluno
for _, a := range s.Alunos {
if strings.Contains(a.Nome, palavraChave) {
resultado = append(resultado, a)
}
}
return resultado
}
func main() {
// Criar uma sala de aula
sala := &SalaDeAula{NomeDaSala: "Turma 1, Ano 2024"}
// Adicionar alunos
alunos := []Aluno{
{ID: 1, Nome: "João Silva", Idade: 18, Nota: 92.5, Tags: []string{"bom em matemática", "monitor"}},
{ID: 2, Nome: "Maria Santos", Idade: 19, Nota: 88.0, Tags: []string{"bom em esportes"}},
{ID: 3, Nome: "Pedro Oliveira", Idade: 18, Nota: 95.5},
{ID: 4, Nome: "Ana Costa", Idade: 20, Nota: 78.0, Tags: []string{"talento artístico"}},
{ID: 5, Nome: "Lucas Pereira", Idade: 19, Nota: 91.0, Tags: []string{"bom em matemática", "vencedor de competição"}},
}
for _, a := range alunos {
sala.AdicionarAluno(a)
}
// ========== Serialização JSON ==========
fmt.Println("=== Serialização JSON ===")
jsonData, err := json.MarshalIndent(sala, "", " ")
if err != nil {
fmt.Println("Falha na serialização:", err)
} else {
fmt.Println(string(jsonData))
}
// ========== Deserialização JSON ==========
fmt.Println("\n=== Deserialização JSON ===")
jsonStr := `{"id":6,"nome":"Carlos Souza","idade":17,"nota":97.0,"tags":["bom em inglês"]}`
var novoAluno Aluno
if err := json.Unmarshal([]byte(jsonStr), &novoAluno); err != nil {
fmt.Println("Falha na deserialização:", err)
} else {
fmt.Printf("Novo aluno: %+v\n", novoAluno)
}
// ========== Demonstração de Recursos ==========
fmt.Println("\n=== Informações da Turma ===")
fmt.Printf("Turma: %s, total de %d alunos\n", sala.NomeDaSala, len(sala.Alunos))
fmt.Printf("Nota média: %.1f\n", sala.Media())
fmt.Println("\n=== Top 3 Notas ===")
top3 := sala.ObterTopN(3)
for i, a := range top3 {
fmt.Printf(" #%d: %s (%.1f)\n", i+1, a.Nome, a.Nota)
}
fmt.Println("\n=== Buscar 'Silva' ===")
resultados := sala.BuscarPorNome("Silva")
for _, a := range resultados {
fmt.Printf(" %s (ID: %d, Nota: %.1f)\n", a.Nome, a.ID, a.Nota)
}
}
Saída:
=== Serialização JSON ===
{
"nome_da_sala": "Turma 1, Ano 2024",
"alunos": [
{
"id": 1,
"nome": "João Silva",
"idade": 18,
"nota": 92.5,
"tags": [
"bom em matemática",
"monitor"
]
},
{
"id": 2,
"nome": "Maria Santos",
"idade": 19,
"nota": 88
},
{
"id": 3,
"nome": "Pedro Oliveira",
"idade": 18,
"nota": 95.5
},
{
"id": 4,
"nome": "Ana Costa",
"idade": 20,
"nota": 78,
"tags": [
"talento artístico"
]
},
{
"id": 5,
"nome": "Lucas Pereira",
"idade": 19,
"nota": 91,
"tags": [
"bom em matemática",
"vencedor de competição"
]
}
]
}
=== Deserialização JSON ===
Novo aluno: {ID:6 Nome:Carlos Souza Idade:17 Nota:97 Tags:[bom em inglês]}
=== Informações da Turma ===
Turma: Turma 1, Ano 2024, total de 5 alunos
Nota média: 89.0
=== Top 3 Notas ===
#1: Pedro Oliveira (95.5)
#2: João Silva (92.5)
#3: Lucas Pereira (91.0)
=== Buscar 'Silva' ===
João Silva (ID: 1, Nota: 92.5)
3. Cenários de Aplicação Comuns
Cenário 1: Interação com Dados JSON
Em desenvolvimento web, structs são a forma padrão de tratar requisições/respostas JSON. Tags controlam os nomes dos campos JSON.
package main
import (
"encoding/json"
"fmt"
)
// RespostaAPI estrutura de resposta unificada da API
type RespostaAPI struct {
Codigo int `json:"codigo"`
Mensagem string `json:"mensagem"`
Dados interface{} `json:"dados,omitempty"`
}
// UsuarioDTO objeto de transferência de dados do usuário
type UsuarioDTO struct {
ID int `json:"id"`
Nome string `json:"nome"`
Email string `json:"email"`
Senha string `json:"-"` // "-" significa ignorar este campo no JSON
}
func main() {
// Construir uma resposta
usuario := UsuarioDTO{
ID: 1,
Nome: "alice",
Email: "alice@exemplo.com",
Senha: "senha123", // Não será serializado
}
resp := RespostaAPI{
Codigo: 200,
Mensagem: "sucesso",
Dados: usuario,
}
// Serializar
jsonBytes, _ := json.MarshalIndent(resp, "", " ")
fmt.Println(string(jsonBytes))
// Deserializar
jsonStr := `{"codigo":404,"mensagem":"Usuário não encontrado"}`
var errResp RespostaAPI
json.Unmarshal([]byte(jsonStr), &errResp)
fmt.Printf("\nCódigo de erro: %d, Mensagem: %s\n", errResp.Codigo, errResp.Mensagem)
}
Saída:
{
"codigo": 200,
"mensagem": "sucesso",
"dados": {
"id": 1,
"nome": "alice",
"email": "alice@exemplo.com"
}
}
Código de erro: 404, Mensagem: Usuário não encontrado
Cenário 2: Simulação Orientada a Objetos (Composição em Vez de Herança)
Go não tem classes ou herança; a reutilização de código é alcançada através de aninhamento de structs (composição).
package main
import "fmt"
// Logger capacidade de logging
type Logger struct {
Prefixo string
}
// Log exibe uma mensagem de log
func (l *Logger) Log(msg string) {
fmt.Printf("[%s] %s\n", l.Prefixo, msg)
}
// Cache capacidade de cache
type Cache struct {
dados map[string]string
}
// Obter recupera do cache
func (c *Cache) Obter(chave string) (string, bool) {
val, ok := c.dados[chave]
return val, ok
}
// Definir define uma entrada no cache
func (c *Cache) Definir(chave, valor string) {
if c.dados == nil {
c.dados = make(map[string]string)
}
c.dados[chave] = valor
}
// ServicoUsuario reutiliza as capacidades Logger e Cache através de composição
type ServicoUsuario struct {
Logger // Embute Logger
Cache // Embute Cache
Nome string
}
func main() {
svc := ServicoUsuario{
Logger: Logger{Prefixo: "ServicoUsuario"},
Nome: "Serviço de Usuário",
}
// Chamar métodos embutidos diretamente
svc.Log("Serviço iniciado") // De Logger
svc.Definir("usuario:1", "Alice") // De Cache
svc.Log("Cache inicializado")
if val, ok := svc.Obter("usuario:1"); ok {
svc.Log(fmt.Sprintf("Usuário encontrado: %s", val))
}
}
Saída:
[ServicoUsuario] Serviço iniciado
[ServicoUsuario] Cache inicializado
[ServicoUsuario] Usuário encontrado: Alice
❓ Perguntas Frequentes
P1: Uma struct é um tipo por valor ou tipo por referência?
Structs são tipos por valor. Atribuição e argumentos de função copiam a struct inteira. Para modificar o valor original, passe um ponteiro:
func atualizarIdade(p *Pessoa, idade int) {
p.Idade = idade // Modifica diretamente o valor original
}
p := Pessoa{Nome: "Alice", Idade: 25}
atualizarIdade(&p, 30)
fmt.Println(p.Idade) // 30
P2: Para que servem campos anônimos? Como são diferentes de structs aninhadas?
Campos anônimos implementam "composição". Os métodos e campos do tipo interno são "promovidos" para o tipo externo:
type Endereco struct {
Cidade string
}
type Pessoa struct {
Nome string
Endereco // Campo anônimo
}
p := Pessoa{Nome: "Alice", Endereco: Endereco{Cidade: "São Paulo"}}
fmt.Println(p.Cidade) // Acesso direto, mesmo que p.Endereco.Cidade
fmt.Println(p.Endereco.Cidade) // Também funciona com acesso explícito
Se structs aninhadas tiverem campos com o mesmo nome, você precisa usar o caminho completo para desambiguar.
P3: O que são tags de struct? Como usá-las?
Tags são metadados envoltos em crases, comumente usados para controlar o comportamento de JSON, ORM de banco de dados e outras bibliotecas:
type Usuario struct {
ID int `json:"id" db:"user_id"`
Nome string `json:"nome" db:"user_name"`
Senha string `json:"-" db:"password"` // Ignorado no JSON
Email string `json:"email,omitempty" db:"email"` // Omitido no JSON quando vazio
}
Tags comuns:
json:"nomeDoCampo"— Controla o nome do campo JSONjson:"-"— Ignora o campo durante a serializaçãojson:",omitempty"— Omite quando valor zerodb:"nome_coluna"— Mapeamento de campo de banco de dados (requer suporte de ORM)
P4: Structs podem ser comparadas?
Depende dos tipos dos campos. Se todos os campos forem tipos comparáveis (int, string, bool, arrays, etc.), structs podem ser comparadas com == e !=. Se contiverem campos incomparáveis como slices, maps ou funcs, não podem ser comparadas diretamente:
type Ponto struct { X, Y int }
p1 := Ponto{1, 2}
p2 := Ponto{1, 2}
fmt.Println(p1 == p2) // true
// Structs contendo slices não podem usar ==
type Dados struct { Itens []int }
// d1 == d2 // Erro de compilação!
📖 Resumo
- struct é o tipo composto personalizado do Go, combinando múltiplos campos em uma única unidade
- Structs são tipos por valor; atribuição e argumentos de função copiam a struct inteira; passe um ponteiro
*Spara modificar o original - O acesso a campos usa notação ponto
s.Campo; ponteiros também usamp.Campo, não é necessário-> - Campos anônimos (apenas tipo, sem nome de campo) implementam composição; campos internos são "promovidos" para o nível externo
- Structs aninhadas suportam organização de dados em múltiplos níveis
- Tags de struct são definidas com crases, comumente usadas para mapeamento de campos JSON/ORM
json:"-"ignora um campo;json:",omitempty"omite valores zero- Se uma struct é comparável depende de seus tipos de campo
📝 Exercícios
Exercício 1 (⭐)
Definir uma struct Retangulo com campos Largura e Altura. Implementar o seguinte:
- Criar uma instância de Retangulo e imprimir
- Escrever um método
Area()para calcular a área - Escrever um método
Perimetro()para calcular o perímetro - Escrever um método
EhQuadrado()para verificar se é um quadrado
Exercício 2 (⭐⭐)
Definir uma struct ContaBancaria com campos Titular (string) e Saldo (float64). Implementar o seguinte:
Depositar(valor)para depósitosSacar(valor)para saques (notificar quando saldo insuficiente)Transferir(outra *ContaBancaria, valor)para transferências- Usar tags JSON para serializar informações da conta (ocultar saldo usando
json:"-")
Exercício 3 (⭐⭐⭐)
Implementar um sistema de gerenciamento de inventário de produtos:
- Definir uma struct
Produto(ID, Nome, Preco, Estoque, Tags) - Definir uma struct
Inventario(NomeDaLoja, slice de Produtos) - Implementar métodos:
AdicionarProduto,RemoverProduto(id),BuscarPorTag(tag),ValorTotal()(valor total do inventário) - Implementar serialização JSON com requisitos: Preco com duas casas decimais, omitir Estoque quando 0
- Criar instâncias, adicionar produtos, buscar por tag, calcular valor total e saída como JSON



