Go: Go Map e Estruturas
Última atualização: 2026-08-26
Os maps e as structs são os dois pilares da modelagem de dados em Go — os maps lidam com consultas dinâmicas, enquanto as structs descrevem estruturas fixas — e, juntos, podem descrever 90% dos objetos de negócios.
Os mapas permitem consultas de chave-valor em O(1), enquanto as estruturas agregam dados por meio de campos. Nesta lição, você vai dominar todos os conceitos fundamentais da modelagem de dados em Go e será capaz de usá-los para construir um sistema completo de estoque para comércio eletrônico.
1. Você aprenderá
- CRUD (Criar/Ler/Atualizar/Excluir) para mapas
- comma-ok: Verifica se a chave existe
- Iteração e exclusão de elementos no intervalo de um mapa
- Definir, inicializar e acessar campos em uma estrutura
- tag da estrutura (chave para serialização JSON)
- Estruturas aninhadas e composição
- Escolhendo entre um mapa e uma fatia
- Criação de um sistema de inventário de SKUs para comércio eletrônico usando Maps e Structs
2. A história real de um engenheiro de comércio eletrônico
(1) Problema: as consultas de fatias são muito lentas
Bob é engenheiro de back-end em uma plataforma de comércio eletrônico. Com a campanha de pré-venda do Double 11 em andamento, a API de consulta de estoque tem estado incrivelmente lenta:
“Temos 100.000 SKUs em estoque, armazenadas usando uma fatia
[]Product, e cada consulta envolve uma varredura linear. À medida que o QPS aumenta, o tempo médio de resposta é de 800 ms, com 100% de uso da CPU. Meu chefe me deu três dias para otimizar isso para menos de 100 ms.”
Ele abriu o código que havia escrito:
// Version 1: Store all products using a slice
var products []Product
func findProduct(sku string) *Product {
for _, p := range products { // O(n) linear scan
if p.SKU == sku {
return &p
}
}
return nil
}
100.000 produtos × 1.000 QPS = 100 milhões de varreduras por segundo; a utilização de 100% da CPU é inevitável.
(2) Solução em Go: use um mapa para consultas em O(1)
// inventory.go
package main
import "fmt"
type Product struct {
SKU string // Product unique identifier
Name string // Product name
Price float64 // Price
Stock int // Stock count
Category string // Classification
}
// Using map to create a hash table
type Inventory struct {
products map[string]Product // SKU -> Product
}
func NewInventory() *Inventory {
return &Inventory{products: make(map[string]Product)}
}
// O(1) query
func (inv *Inventory) Find(sku string) (Product, bool) {
p, ok := inv.products[sku]
return p, ok
}
// O(1) update
func (inv *Inventory) UpdateStock(sku string, delta int) error {
p, ok := inv.products[sku]
if !ok {
return fmt.Errorf("SKU %s not found", sku)
}
p.Stock += delta
inv.products[sku] = p
return nil
}
func main() {
inv := NewInventory()
// Bulk import of 100,000 SKUs
for i := 0; i < 100000; i++ {
sku := fmt.Sprintf("SKU-%05d", i)
inv.products[sku] = Product{
SKU: sku,
Name: fmt.Sprintf("Product-%d", i),
Price: 99.99,
Stock: 100,
Category: "electronics",
}
}
// Query performance comparison
p, ok := inv.Find("SKU-50000")
if ok {
fmt.Printf("Found: %s, price=%.2f\n", p.Name, p.Price)
}
// Update inventory
inv.UpdateStock("SKU-50000", -5)
}
Resultado:
Found: Product-50000, price=99.99
(3) Desempenho: Desempenho das consultas com slice versus map
| Tamanho dos dados | Varredura linear no corte | Consulta por hash no mapa | Diferença de desempenho |
|---|---|---|---|
| 100 SKUs | 50 ns | 50 ns | equivalente |
| 1.000 SKUs | 500 ns | 50 ns | 10x |
| 10.000 SKUs | 5 µs | 50 ns | 100x |
| 100.000 SKUs | 50 µs | 50 ns | 1.000x |
3. Noções básicas sobre mapas
(1) Três maneiras de criar um mapa
package main
import "fmt"
func main() {
// Method 1: make (Recommended)
m1 := make(map[string]int) // empty map, writable
// Method 2: make + pre-allocated capacity
m2 := make(map[string]int, 100) // pre-allocate 100 capacity, reduce resizing
// Method 3: Literal Initialization
m3 := map[string]int{
"Alice": 28,
"Bob": 32,
}
// Method 4: nil map (read-only, cannot be written to)
var m4 map[string]int // == nil, cannot do m4["a"] = 1
_ = m4
}
(2) CRUD de mapa
package main
import "fmt"
func main() {
ages := make(map[string]int)
// Create
ages["Alice"] = 28
ages["Bob"] = 32
// Read
fmt.Println(ages["Alice"]) // 28
// Update
ages["Alice"] = 29
// Delete
Excluir(ages, "Bob")
// Length
fmt.Printf("len=%d\n", len(ages))
fmt.Println(ages) // map[Alice:29]
}
Saída:
28
len=1
map[Alice:29]
(3) ▶ Exemplo: iteração no mapa (a ordem é aleatória)
package main
import "fmt"
func main() {
ages := map[string]int{"Alice": 28, "Bob": 32, "Charlie": 45}
// for range: key + value
for name, age := range ages {
fmt.Printf("%s is %d years old\n", name, age)
}
// Only the key
for name := range ages {
fmt.Printf("name: %s\n", name)
}
// Only the value (use _ to ignore the key)
for _, age := range ages {
fmt.Printf("age: %d\n", age)
}
}
Saída (em ordem aleatória):
Alice is 28 years old
Charlie is 45 years old
Bob is 32 years old
name: Bob
...
4. Sintaxe “comma-ok” (Ponto-chave)
(1) Distinguir entre “valor zero” e “não existe”
package main
import "fmt"
func main() {
ages := map[string]int{"Alice": 28}
// Incorrect approach: Cannot distinguish between "key does not exist" and "value = 0"
age := ages["Bob"]
fmt.Printf("Bob's age: %d\n", age) // 0 (but is Bob really 0 years old?)
// Correct approach: comma-ok
age, ok := ages["Bob"]
if ok {
fmt.Printf("Bob's age: %d\n", age)
} else {
fmt.Println("Bob not found")
}
// Just check if it exists: discard the value
_, exists := ages["Bob"]
fmt.Printf("Bob exists: %v\n", exists)
}
Saída:
Bob's age: 0
Bob not found
Bob exists: false
(2) O uso da vírgula na prática: armazenamento em cache de consultas
package main
import "fmt"
// Cache: key → value
var cache = make(map[string]string)
func getCached(key string) (string, bool) {
val, ok := cache[key]
return val, ok
}
func main() {
// Set cache
cache["user:1"] = "Alice"
cache["user:2"] = "Bob"
// Query cache
if val, ok := getCached("user:1"); ok {
fmt.Printf("Hit: %s\n", val)
} else {
fmt.Println("Miss")
}
if _, ok := getCached("user:999"); !ok {
fmt.Println("user:999 not in cache, fetching from DB...")
}
}
Saída:
Hit: Alice
user:999 not in cache, fetching from DB...
(3) Padrão geral “comma-ok”
| Contexto | Sintaxe | Significado |
|---|---|---|
| Pesquisa no mapa | v, ok := m[key] |
A chave existe? |
| Asserção de tipo | v, ok := x.(T) |
Correspondência de tipo? |
| Recebendo do canal | v, ok := <-ch |
Canal fechado? |
5. Noções básicas sobre estruturas
(1) Definição e inicialização de estruturas
package main
import "fmt"
// define struct
type User struct {
Name string
Age int
City string
}
func main() {
// Method 1: By field Ordem (not recommended; poor readability)
u1 := User{"Alice", 28, "Shanghai"}
// Method 2: Initializing Field Names (Recommended)
u2 := User{
Name: "Bob",
Age: 32,
City: "Beijing",
}
// Method 3: Partial Initialization (with the remainder set to zero)
u3 := User{Name: "Charlie"} // Age=0, City=""
// Method 4: new() returns a pointer
u4 := new(User)
u4.Name = "Dave"
fmt.Println(u1, u2, u3, u4)
}
Saída:
{Alice 28 Shanghai} {Bob 32 Beijing} {Charlie } &{Dave 0 }
(2) Acesso e modificação de campos
package main
import "fmt"
type User struct {
Name string
Age int
}
func main() {
u := User{Name: "Alice", Age: 28}
// Read field
fmt.Println(u.Name) // Alice
// Write to a field
u.Age = 29
// Pointer access (automatic dereferencing)
p := &u
fmt.Println(p.Name) // Alice (equivalent to (*p).Name)
p.Age = 30 // automatic dereferencing
}
(3) ▶ Exemplo: Cópia de estruturas versus ponteiros
package main
import "fmt"
type Counter struct {
Value int
}
// Pass-by-value: Copy the entire struct
func incrementByValue(c Counter) {
c.Value++ // modifies the copy
fmt.Printf("Inside Função: %d\n", c.Value)
}
// Pointer passing: pass by reference
func incrementByPointer(c *Counter) {
c.Value++ // modifies the original
fmt.Printf("Inside Função: %d\n", c.Value)
}
func main() {
c := Counter{Value: 10}
incrementByValue(c)
fmt.Printf("After value passing: %d\n", c.Value) // 10 (unchanged)
incrementByPointer(&c)
fmt.Printf("After pointer passing: %d\n", c.Value) // 11 (modified)
}
Saída:
Inside function: 11
After value passing: 10
Inside function: 11
After pointer passing: 11
6. struct tag (Chave para a serialização em JSON)
(1) Sintaxe da estrutura tag
type User struct {
Name string `json:"name" db:"user_name"`
Age int `json:"age" validate:"min=0,max=150"`
Email string `json:"email,omitempty"`
Password string `json:"-"` // - means JSON ignores this field
}
(2) Prática de serialização em JSON
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
Password string `json:"-"` // password not serialized
}
func main() {
u := User{
Name: "Alice",
Age: 28,
Email: "alice@example.com",
Password: "secret123",
}
// Serialization: struct → JSON
data, _ := json.Marshal(u)
fmt.Printf("JSON: %s\n", data)
// Deserialization: JSON → struct
jsonStr := `{"name":"Bob","age":32,"email":"bob@example.com"}`
var u2 User
json.Unmarshal([]byte(jsonStr), &u2)
fmt.Printf("Deserialization: %+v\n", u2)
}
Saída:
JSON: {"name":"Alice","age":28,"email":"alice@example.com"}
Deserialization: {Name:Bob Age:32 Email:bob@example.com Password:}
(3) Bibliotecas de tags de estruturas comumente utilizadas
| Biblioteca | Nome da tag | Finalidade |
|---|---|---|
| encoding/json | json:"name" |
Campo JSON name |
| gorm | gorm:"primaryKey" |
Restrições de campos ORM |
| validador | validate:"required" |
Validação de campo |
| yaml | yaml:"name" |
Serialização YAML |
7. Estruturas aninhadas
(1) Estruturas aninhadas (composição em vez de herança)
O Go não possui herança; ele utiliza estruturas aninhadas para alcançar a composição:
package main
import "fmt"
type Address struct {
City string
Country string
}
type User struct {
Name string
Age int
Address Address // nested Address
}
func main() {
u := User{
Name: "Alice",
Age: 28,
Address: Address{
City: "Shanghai",
Country: "China",
},
}
// Accessing nested fields
fmt.Println(u.Address.City) // Shanghai
fmt.Println(u.Address.Country) // China
}
(2) Aninhamento anônimo (promoção de campo)
package main
import "fmt"
type Address struct {
City string
Country string
}
// Anonymous nesting: fields are "promoted"
type User struct {
Name string
Age int
Address // equivalent to Address Address, but without the field name
}
func main() {
u := User{
Name: "Alice",
Age: 28,
Address: Address{
City: "Shanghai",
Country: "China",
},
}
// Field promotion: direct access, without u.Address.City
fmt.Println(u.City) // Shanghai
fmt.Println(u.Country) // China
// You can also use the full path
fmt.Println(u.Address.City)
}
(3) ▶ Exemplo: Tags aninhadas + JSON na prática
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
City string `json:"city"`
Country string `json:"country"`
}
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
Address Address `json:"address"`
}
func main() {
u := User{
Name: "Alice",
Age: 28,
Email: "alice@example.com",
Address: Address{
City: "Shanghai",
Country: "China",
},
}
data, _ := json.MarshalIndent(u, "", " ")
fmt.Println(string(data))
}
Saída:
{
"name": "Alice",
"age": 28,
"email": "alice@example.com",
"address": {
"city": "Shanghai",
"country": "China"
}
}
8. Escolhendo entre map e slice
(1) Tabela de referência para seleção de modelos
| Dimensão | fatia []T |
mapa map[K]V |
|---|---|---|
| Método de consulta | Varredura linear O(n) | Hash O(1) |
| Está ordenado? | ✅ Ordenado | ❌ Não ordenado |
| Uso de memória | Compacto (24 + len*size) | Menos compacto (tabela hash + bucket) |
| Excluir um elemento | Requer movimentação manual | delete(m, k) O(1) |
| Cenários típicos | Listas/Filas/Pilhas/Classificação | Dicionários/Caches/Índices |
(2) Árvore de decisão para seleção de modelos
graph TB
A[Need to store multiple elements] --> B{Need to look up by key?}
B -->|Yes| C[Use map<br/>O(1) lookup]
B -->|No| D{Need to maintain order?}
D -->|Yes| E[Use slice]
D -->|No| F{Element count < 100?}
F -->|Yes| G[Either slice or map works]
F -->|No| C
(3) Exemplo prático: gestão de notas dos alunos
package main
import "fmt"
// Store student grades in a map (look up by student ID)
type GradeBook struct {
scores map[string]int // Student ID → Score
}
func (gb *GradeBook) Set(id string, score int) {
gb.scores[id] = score
}
func (gb *GradeBook) Get(id string) (int, bool) {
score, ok := gb.scores[id]
return score, ok
}
// Use a slice to store a list of scores (for sorting or calculating the average)
func average(scores []int) float64 {
if len(scores) == 0 {
return 0
}
sum := 0
for _, s := range scores {
sum += s
}
return float64(sum) / float64(len(scores))
}
func main() {
gb := &GradeBook{scores: make(map[string]int)}
gb.Set("S001", 95)
gb.Set("S002", 82)
gb.Set("S003", 67)
if s, ok := gb.Get("S001"); ok {
fmt.Printf("S001: %d\n", s)
}
// Collect all scores and calculate the average
allScores := []int{}
for _, s := range gb.scores {
allScores = append(allScores, s)
}
fmt.Printf("Average Score: %.2f\n", average(allScores))
}
Saída:
S001: 95
Average Score: 81.33
9. Exemplo completo: Sistema de estoque de SKUs para comércio eletrônico
Combine todos os recursos de mapas e estruturas para criar um sistema completo de consulta de estoque para comércio eletrônico:
// inventory_system.go
package main
import (
"encoding/json"
"fmt"
"sort"
)
// Product
type Product struct {
SKU string `json:"sku"`
Name string `json:"name"`
Price float64 `json:"price"`
Stock int `json:"stock"`
Category string `json:"category"`
Tags []string `json:"tags,omitempty"` // Tags: new/hot/discount
}
// Inventory System
type Inventory struct {
products map[string]Product // SKU → Product
}
func NewInventory() *Inventory {
return &Inventory{products: make(map[string]Product)}
}
// Add product
func (inv *Inventory) Add(p Product) {
inv.products[p.SKU] = p
}
// Query (comma-ok)
func (inv *Inventory) Find(sku string) (Product, bool) {
p, ok := inv.products[sku]
return p, ok
}
// Update stock
func (inv *Inventory) UpdateStock(sku string, delta int) error {
p, ok := inv.products[sku]
if !ok {
return fmt.Errorf("SKU %s not found", sku)
}
newStock := p.Stock + delta
if newStock < 0 {
return fmt.Errorf("insufficient stock for %s: have %d, need %d",
sku, p.Stock, -delta)
}
p.Stock = newStock
inv.products[sku] = p
return nil
}
// Sort by price (unordered map → convert to slice)
func (inv *Inventory) ListByPrice() []Product {
list := make([]Product, 0, len(inv.products))
for _, p := range inv.products {
list = append(list, p)
}
sort.Slice(list, func(i, j int) bool {
return list[i].Price < list[j].Price
})
return list
}
// Filter by category
func (inv *Inventory) FindByCategory(category string) []Product {
var result []Product
for _, p := range inv.products {
if p.Category == category {
result = append(result, p)
}
}
return result
}
func main() {
inv := NewInventory()
// Initialize 100 SKUs
categories := []string{"electronics", "clothing", "food"}
for i := 0; i < 100; i++ {
inv.Add(Product{
SKU: fmt.Sprintf("SKU-%05d", i),
Name: fmt.Sprintf("Product-%d", i),
Price: float64(i%50 + 10),
Stock: 100 - i%30,
Category: categories[i%3],
})
}
// 1. Look up a single SKU
if p, ok := inv.Find("SKU-0050"); ok {
fmt.Printf("Found: %s, price=%.2f, stock=%d\n", p.Name, p.Price, p.Stock)
}
// 2. Update inventory
if err := inv.UpdateStock("SKU-0050", -10); err == nil {
fmt.Println("Stock updated successfully")
}
// 3. Filter by category
electronics := inv.FindByCategory("electronics")
fmt.Printf("\nElectronics products: %d\n", len(electronics))
// 4. Sort by price (top 3)
sorted := inv.ListByPrice()
fmt.Println("\nTop 3 cheapest:")
for i, p := range sorted[:3] {
fmt.Printf(" %d. %s: %.2f\n", i+1, p.Name, p.Price)
}
// 5. JSON Serialization (API Response)
if p, ok := inv.Find("SKU-0050"); ok {
data, _ := json.MarshalIndent(p, "", " ")
fmt.Printf("\nJSON output:\n%s\n", data)
}
}
Resultado esperado:
Found: Product-50, price=10.00, stock=70
Stock updated successfully
Electronics products: 34
Top 3 cheapest:
1. Product-0: 10.00
2. Product-30: 10.00
3. Product-60: 10.00
JSON output:
{
"sku": "SKU-0050",
"name": "Product-50",
"price": 10,
"stock": 60,
"category": "food"
}
ListByPrice, sort.Slice usa um closure como função de comparação. O closure captura a variável list e a chama durante cada comparação.
10. Conjuntos de exemplos adicionais
(1) ▶ Exemplo: Comparação de desempenho entre a inicialização com um literal de mapa e o uso de make
package main
import (
"fmt"
"time"
)
func main() {
// Literal initialization
start1 := time.Now()
m1 := map[string]int{
"a": 1, "b": 2, "c": 3, "d": 4, "e": 5,
"f": 6, "g": 7, "h": 8, "i": 9, "j": 10,
}
fmt.Printf("Literals: %v, %v\n", len(m1), time.Since(start1))
// make pre-allocation
start2 := time.Now()
m2 := make(map[string]int, 10)
m2["a"] = 1
m2["b"] = 2
m2["c"] = 3
m2["d"] = 4
m2["e"] = 5
m2["f"] = 6
m2["g"] = 7
m2["h"] = 8
m2["i"] = 9
m2["j"] = 10
fmt.Printf("make: %v, %v\n", len(m2), time.Since(start2))
}
Saída:
Literals: 10, 1.2µs
make: 10, 850ns
(2) ▶ Exemplo: Métodos que aceitam valores de estrutura versus métodos que aceitam ponteiros
package main
import "fmt"
type Counter struct {
Value int
}
// Value receiver: operates on a copy; does not affect the original
func (c Counter) IncrementValue() Counter {
c.Value++
return c
}
// Pointer receiver: directly modifies the original
func (c *Counter) IncrementPointer() {
c.Value++
}
func main() {
c := Counter{Value: 10}
// Value receiver: must use the return value
c = c.IncrementValue()
fmt.Printf("After value receiver: %d\n", c.Value) // 11
// Pointer receiver: direct modification
c.IncrementPointer()
fmt.Printf("After pointer receiver: %d\n", c.Value) // 12
}
Saída:
After value receiver: 11
After pointer receiver: 12
(3) ▶ Exemplo: Demonstração das armadilhas da concorrência na função map
package main
import (
"fmt"
"time"
)
func main() {
m := make(map[string]int)
// Writing goroutine
go func() {
for i := 0; i < 1000; i++ {
m["key"] = i // write operation
}
}()
// Reading goroutine
go func() {
for i := 0; i < 1000; i++ {
_ = m["key"] // read operation
}
}()
time.Sleep(100 * time.Millisecond)
fmt.Println("Concurrent reads and writes may cause a panic (fatal error: concurrent map read and map write)")
}
Saída (pode causar um erro grave durante a execução):
Concurrent reads and writes may cause a panic (fatal error: concurrent map read and map write)
sync.Map ou sync.Mutex; isso é abordado em detalhes na Lição 16.
❓ Perguntas Frequentes
P: O que acontece se você tentar acessar uma chave inexistente em um mapa? R: É retornado o valor zero. No entanto, um valor zero pode ser válido (por exemplo, idade=0); portanto, você deve usar “comma-ok” para distinguir entre “não existe” e “o valor é zero”.
P: O
mapé um tipo de referência? R: Sim. Uma variávelmapé um ponteiro para uma tabela hash; tanto a atribuição quanto a passagem de parâmetros compartilham a mesma tabela hash subjacente. É semelhante a uma fatia, mas mais radical — enquanto uma fatia pode acionar o redimensionamento de forma isolada por meio doappend, ummapnão possui esse mecanismo de isolamento.
P: É possível comparar estruturas? R: Somente se todos os campos forem comparáveis. Estruturas que contenham fatias, mapas ou funções não podem ser comparadas (erro de compilação). Use
reflect.DeepEqual()para uma comparação profunda.
P: Quando se deve usar estruturas aninhadas e quando se deve usar ponteiros? R: (1) Se for necessário modificar o objeto original → use ponteiros aninhados; (2) Se a estrutura for grande → use ponteiros aninhados (para evitar cópias); (3) Se for necessário polimorfismo (para implementar uma interface) → use ponteiros; (4) Semântica de valor, objetos imutáveis → use aninhamento de valores.
P: Quais são as restrições para as chaves de mapas? R: As chaves devem ser de tipos comparáveis — bool, int, float ou string — ou estruturas que contenham esses tipos. Elas não podem ser slices, mapas ou func.
P: O que acontece se o formato da tag da estrutura estiver incorreto? R: O código será compilado, mas bibliotecas como a de serialização JSON não conseguirão reconhecê-lo. Erros comuns incluem: erros ortográficos no nome da tag (por exemplo, um espaço a mais em
json:"name") ou o uso do tipo errado de aspas (é preciso usar aspas duplas).
P: Por que a iteração sobre um mapa é aleatória? R: Isso faz parte do design do Go — para evitar que os programadores dependam de uma ordem específica. Se você precisar de uma ordem consistente, primeiro use uma fatia para coletar todas as chaves, depois classifique as chaves e, por fim, consulte o mapa usando as chaves classificadas.
P: Um mapa é seguro para múltiplos threads? R: Não! Se várias goroutines lerem e gravarem no mesmo mapa simultaneamente, ocorrerá um panic (“leitura e gravação simultâneas no mapa”). Em cenários simultâneos, use
sync.Map(Lição 16) ou adicione um mutex (Lição 16).
📖 Resumo
- Um mapa permite consultas de chave-valor em O(1) e é o núcleo dos tipos de coleção do Go
- A sintaxe “comma-ok” distingue entre “valores zero” e “inexistência”, tornando-a uma abordagem segura para consultas em mapas
- As estruturas agregam dados por meio de campos e permitem a composição aninhada (como alternativa à herança)
- A tag “struct” é fundamental para a serialização JSON/ORM;
json:"name"é o formato mais comum - seleção por mapa vs. fatia: consulta por chave → mapa; manter a ordem / ordenar → fatia; muitas vezes, ambas são combinadas
- Tanto o
mapquanto astructseguem a semântica de valor, mas omapé um tipo de referência (compartilha os dados subjacentes), enquanto astructé um tipo de valor (é copiada quando passada como argumento) - Estruturas grandes ou que precisam ser modificadas devem usar ponteiros (
func (s *Struct) Method())
📝 Exercícios
-
Problema básico (Dificuldade ⭐): Use
map[string]intpara contar o número de vezes que cada palavra aparece em um texto. Dada a entrada"the quick brown fox jumps over the lazy dog the", a saída deve sermap[the:3 quick:1 brown:1 ...]. -
Problema Avançado (Dificuldade ⭐⭐): Defina uma estrutura
Student(Name string, Scores []int) e implemente os seguintes métodos: (1)Average() float64para calcular a nota média; (2)Grade() stringpara retornar uma nota (A/B/C/D/F); (3) Teste com pelo menos 3 alunos. -
Problema de desafio (Dificuldade ⭐⭐⭐): Implemente um aplicativo de lista de contatos: armazene contatos usando
map[string]Contact(ondeContactinclui Nome, Telefone, E-mail e Grupo); implemente (1) adicionar, excluir e pesquisar; (2) filtragem por grupo (família/amigos/trabalho); (3) exportação para um arquivo JSON (usandoos.WriteFile). Requisitos: tratamento completo de erros + tags JSON + pelo menos 10 contatos de teste.