Go: Fluxo de controle em Go: as quatro principais…
O fluxo de controle faz com que os programas “pensem” — o Go abrange todos os cenários de fluxo de controle com apenas quatro palavras-chave simples, eliminando a redundância dos loops
whileedo-while.
A filosofia de design do fluxo de controle do Go: Menos é mais. As quatro palavras-chave if, switch, for e defer, combinadas com break, continue e label, são suficientes para lidar com 99% da lógica de negócios. Nesta lição, você vai dominar todos os conceitos fundamentais do controle de fluxo do Go.
1. Você aprenderá
- se for uma instrução condicional (incluindo instruções de inicialização)
- alternar de ramificação (interrupção automática; não é necessária nenhuma ação manual)
- As quatro formas do loop “for” (alternativas ao “while” e ao “do-while”)
- Adiar: Execução adiada e ordem LIFO
- Saltos precisos com “break”, “continue” e “label”
- Criação de um sistema de correção de provas on-line utilizando fluxo de controle
2. Uma história real sobre uma plataforma de educação online
(1) Problema: 500 linhas de código em C++ para corrigir provas, mas os bugs continuam surgindo
Charlie é engenheiro de back-end em uma plataforma de educação online. Ele acaba de assumir o código de um sistema de correção automática:
“O primeiro-ministro disse que agora leva 8 horas para processar 1.000 provas, e toda vez que surge um bug, é ou um erro de ‘fora do intervalo’ ou a falta de
{}em uma instruçãoifda linguagem C, o que faz com que as notas de toda a turma fiquem confusas...”
Ele abriu o código em C++ que seu antecessor havia deixado e deu uma olhada:
// C++ Style: Verbose + Prone to Errors
if (score >= 60) {
grade = "Passing Grade";
}
else // Note: C++ else on a different line is valid, but team style is inconsistent
grade = "Fail";
// Omission of a loop condition = an infinite loop
while (true) { // Wanted 3 iterations, but forgot the counter
// ... Didn't realize until 500 lines later
}
Depois de assumir o projeto, Charlie decidiu reescrevê-lo em Go.
(2) Solução em Go
O Go abrange todo o processo com quatro palavras-chave:
// score_evaluator.go
package main
import "fmt"
func evaluateScore(score int) string {
// (1) if contains an initialization statement
if grade := calculateGrade(score); grade != "" {
return fmt.Sprintf("Score: %d, Level: %s", score, grade)
}
return "Invalid Score"
}
func calculateGrade(score int) string {
switch {
case score >= 90:
return "A"
case score >= 80:
return "B"
case score >= 60:
return "C"
default:
return "D"
}
}
func main() {
// (2) for 4 forms
scores := []int{95, 82, 67, 45, 88}
for i, s := range scores { // range form
fmt.Printf("Question %d: %d points -> %s\n", i+1, s, evaluateScore(s))
}
}
Resultado:
Question 1: 95 points -> Score: 95, Level A
Question 2: 82 points -> Score: 82, Level B
Question 3: 67 points -> Score: 67, Level C
Question 4: 45 points -> Score: 45, Level D
Question 5: 88 points -> Score: 88, Level B
(3) Benefícios: Comparação da simplicidade do controle de processos
| Linguagem | palavra-chave “if” | palavra-chave de loop | quebra automática em “switch” | Volume de código para o sistema de pontuação |
|---|---|---|---|---|
| C/C++ | if |
for/while/do-while |
Requer manual break |
~500 linhas |
| Java | if |
for/while/do-while |
Requer break manual |
~400 linhas |
| Python | if |
for/while |
N/A (sem switch) |
~350 linhas |
| Ir | if |
for (4 formas) |
quebra automática | ~200 linhas |
for e remove break do comportamento padrão de switch — uma palavra-chave a menos, dez tipos de erros a menos.
3. Instrução if
(1) if / else básico
package main
import "fmt"
func main() {
score := 85
if score >= 90 {
fmt.Println("Excellent")
} else if score >= 60 {
fmt.Println("Passing Grade")
} else {
fmt.Println("Fail")
}
}
Saída:
Passing Grade
else deve estar na mesma linha que } (é obrigatório o uso de chaves). A sintaxe }else{ resulta em um erro de compilação.
(2) ▶ Exemplo: Determinação da nota
package main
import "fmt"
func judgeGrade(score int) string {
if score >= 90 {
return "A"
} else if score >= 80 {
return "B"
} else if score >= 70 {
return "C"
} else if score >= 60 {
return "D"
} else {
return "F"
}
}
func main() {
fmt.Println(judgeGrade(95)) // A
fmt.Println(judgeGrade(72)) // C
fmt.Println(judgeGrade(50)) // F
}
Saída:
A
C
F
(3) se houver instruções de inicialização (um recurso do Go)
if pode executar primeiro uma instrução de inicialização, seguida por uma verificação condicional — limitando o escopo das variáveis:
package main
import (
"fmt"
"strconv"
)
func main() {
// Syntax: if initialization statement; condition { ... }
// The variable `err` is scoped only within the `if-else` block.
if n, err := strconv.Atoi("123"); err == nil {
fmt.Printf("Conversion successful: %d\n", n)
} else {
fmt.Printf("Conversion failed: %v\n", err)
}
}
Saída:
Conversion successful: 123
(4) ▶ Exemplo: Lendo um arquivo + fechando com defer
package main
import (
"fmt"
"os"
)
func readFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close() // automatically close file before function returns
data := make([]byte, 1024)
n, _ := file.Read(data)
fmt.Printf("%d bytes read\n", n)
return nil
}
func main() {
readFile("config.txt")
}
(5) Práticas comuns para o tratamento de erros em instruções if (uma característica marcante do Go)
O tratamento de erros em Go é feito por meio de instruções if. Uso convencional:
if err := doSomething(); err != nil {
return err // early return
}
// Normal Logic
error e as melhores práticas para esse padrão.
4. Mudança de ramificação (interrupção automática)
(1) Noções básicas sobre interruptores: interrupção automática
Por padrão, a instrução switch do Go não se propaga — ela encerra automaticamente após encontrar uma correspondência:
package main
import "fmt"
func main() {
day := "Monday"
switch day {
case "Monday":
fmt.Println("Monday: Meeting Day")
case "Tuesday":
fmt.Println("Tuesday: Development Day")
case "Friday":
fmt.Println("Friday: Demo Day")
default:
fmt.Println("Weekend: Day off")
}
}
Saída:
Monday: Meeting Day
(2) em comparação com C/Java: Não há necessidade de um break
| Linguagem | Sintaxe | Quebra automática |
|---|---|---|
| C/C++ | case 1: ...; case 2: ... |
Requer um manual break; |
| Java | case 1: ...; case 2: ... |
Requer um manual break; |
| Ir | case "Monday": ... |
Quebra automática |
Se você quiser usar o fall-through, utilize a palavra-chave fallthrough:
switch n {
case 1:
fmt.Println("One")
fallthrough // force execution of the next case
case 2:
fmt.Println("Two")
}
Saída:
One
Two
(3) Alterar o formato da expressão (sem tag)
Não coloque uma variável logo após switch; escreva a condição para cada case separadamente:
package main
import "fmt"
func gradeLevel(score int) string {
switch {
case score >= 90:
return "A"
case score >= 80:
return "B"
case score >= 60:
return "C"
default:
return "D"
}
}
func main() {
fmt.Println(gradeLevel(95))
fmt.Println(gradeLevel(72))
}
Saída:
A
C
(4) ▶ Exemplo: Switch com correspondência de múltiplos valores + roteamento por código de status HTTP
package main
import "fmt"
func routeStatus(code int) string {
switch code {
case 200, 201, 204:
return "Success"
case 301, 302:
return "Redirect"
case 400, 401, 403, 404:
return "Client Error"
case 500, 502, 503:
return "Server Error"
default:
return "Unknown"
}
}
func main() {
fmt.Println(routeStatus(200)) // Success
fmt.Println(routeStatus(404)) // Client Error
fmt.Println(routeStatus(500)) // Server Error
}
Saída:
Success
Client Error
Server Error
5. As 4 formas do loop “for”
O Go unifica todos os loops sob a construção for, utilizando sintaxes diferentes para implementar while, do-while e loops infinitos.
(1) O clássico loop “for” de três partes
package main
import "fmt"
func main() {
for i := 1; i <= 3; i++ {
fmt.Printf("The %dth time\n", i)
}
}
Saída:
The 1th time
The 2th time
The 3th time
(2) ▶ Exemplo: estilo “while” (loop condicional)
package main
import "fmt"
func main() {
n := 1
for n < 5 { // equivalent to while (n < 5)
fmt.Printf("n=%d\n", n)
n++
}
}
Saída:
n=1
n=2
n=3
n=4
(3) Loop infinito
package main
import (
"fmt"
"time"
)
func main() {
// Classic Infinite Loop (In real-world scenarios, it would use `break` or `return`)
for {
fmt.Println("Print once per second...")
time.Sleep(1 * time.Second)
break // prevent actual infinite loop, for demo only
}
}
Saída:
Print once per second...
(4) ▶ Exemplo: Iteração sobre uma fatia, um mapa ou uma string usando range
package main
import "fmt"
func main() {
scores := []int{95, 82, 67}
// Iterate through the slice: index, value
for i, score := range scores {
fmt.Printf("Problem %d: %d points\n", i+1, score)
}
// If only value is present: use _ to ignore index
for _, score := range scores {
fmt.Printf("Score: %d\n", score)
}
// Iterate through a map (key-value)
ages := map[string]int{"Alice": 28, "Bob": 32}
for name, age := range ages {
fmt.Printf("%s is %d years old\n", name, age)
}
// Iterate over a string (by rune, not by byte)
for i, r := range "Go" {
fmt.Printf("Position %d: %c (code point %d)\n", i, r, r)
}
}
Saída (a ordem no mapa é aleatória):
Problem 1: 95 points
Problem 2: 82 points
Problem 3: 67 points
Score: 95
Score: 82
Score: 67
Alice is 28 years old
Bob is 32 years old
Position 0: G (code point 71)
Position 1: o (code point 111)
range map não é garantida — ela pode variar a cada execução. Se você precisar de uma ordem consistente, classifique as chaves antes de iniciar a iteração.
(5) Referência rápida às quatro formas de “for”
| Forma | Sintaxe | Alternativa |
|---|---|---|
| Padrão para | for i := 0; i < n; i++ {} |
C/Java for |
| Estilo “while” | for condition {} |
C/Java while |
| Infinite Loop | for {} |
while(true) |
| iteração de intervalo | for i, v := range slice {} |
Python for...in |
flowchart LR
A[for init; condition; post] --> B{condition true?}
B -->|yes| C[execute loop body]
C --> D[execute post statement]
D --> B
B -->|no| E[exit loop]
style A stroke-width:2px
style E stroke:#f44,stroke-width:2px
6. defer: Execução diferida (ordem LIFO)
defer adia a execução de uma instrução até o momento imediatamente anterior ao retorno da função atual. É comumente usado para a limpeza de recursos (fechar arquivos, liberar recursos e encerrar conexões).
(1) Noções básicas sobre o defer
package main
import "fmt"
func main() {
fmt.Println("1: Start of function")
defer fmt.Println("3: defer execution (before the function returns)")
fmt.Println("2: Function Body")
}
Saída:
1: Start of function
2: Function Body
3: defer execution (before the function returns)
(2) Adiar a ordem LIFO (último a entrar, primeiro a sair)
package main
import "fmt"
func main() {
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
fmt.Println("Main logic complete")
}
Saída:
Main logic complete
3
2
1
Por que LIFO? Porque defer simula o “gerenciamento de recursos baseado em pilha” — os recursos abertos por último são fechados primeiro.
(3) ▶ Exemplo: Uso prático de defer — Fechamento de uma conexão com o banco de dados
package main
import (
"database/sql"
"fmt"
"log"
)
func queryUser(db *sql.DB, userID int) {
// Get the connection
rows, err := db.Query("SELECT name FROM users WHERE id = ?", userID)
if err != nil {
log.Fatal(err)
}
// Key Point: `defer` releases resources (LIFO)
defer rows.Close() // Close result set when function returns
// Business logic...
for rows.Next() {
var name string
rows.Scan(&name)
fmt.Printf("User: %s\n", name)
}
}
func main() {
fmt.Println("Example of Resource Management with `defer`")
// Real-world scenario: db, _ := sql.Open("mysql", "...")
// queryUser(db, 1)
}
defer file.Close() será executado mesmo que return seja chamado no meio — esse é um caso de uso emblemático do gerenciamento de recursos do Go. Observação: use apenas defer para liberar os recursos que você abriu nesta função — não use defer db.Close() aqui, pois db foi passado como parâmetro e seu ciclo de vida é gerenciado pelo chamador.
7. break / continue / label: Saltos precisos
(1) break: Sair do loop
package main
import "fmt"
func main() {
for i := 1; i <= 10; i++ {
if i == 5 {
break // exit the entire loop
}
fmt.Printf("%d ", i)
}
fmt.Println("End of loop")
}
Saída:
1 2 3 4 End of loop
(2) ▶ Exemplo: continuar pulando + incrementar os números ímpares
package main
import "fmt"
func sumOdd(n int) int {
sum := 0
for i := 1; i <= n; i++ {
if i%2 == 0 {
continue // skip even numbers
}
sum += i
}
return sum
}
func main() {
fmt.Printf("1-10: Sum of odd numbers: %d\n", sumOdd(10))
}
Saída:
1-10: Sum of odd numbers: 25
(3) Use um label para sair de um loop aninhado
label nomeia o loop; break label sai do loop especificado:
package main
import "fmt"
func main() {
outer:
for i := 1; i <= 3; i++ {
for j := 1; j <= 3; j++ {
if i*j == 6 {
fmt.Printf("Hit: i=%d, j=%d\n", i, j)
break outer // directly exit the outer loop
}
fmt.Printf("(%d, %d) ", i, j)
}
}
fmt.Println("Done")
}
Saída:
(1, 1) (1, 2) (1, 3) (2, 1) (2, 2) (2, 3) (3, 1) (3, 2) Hit: i=3, j=2
Done
(4) Comparação dos três tipos de redirecionamentos
| Palavra-chave | Função | Fora do escopo |
|---|---|---|
break |
Encerra o loop ou a instrução switch atual | Nível único |
continue |
Pular o restante desta iteração | Nível único |
break label |
Sair do loop no rótulo especificado | Aninhado |
8. Exemplo completo: Sistema de correção de provas on-line
Interconecte todos os fluxos de controle desta lição para implementar um sistema de correção de provas multidimensional:
// exam_evaluator.go
package main
import "fmt"
// Structure of the Problem
type Question struct {
Type string // single/multi/judge
Correct string // correct answer
Student string // student answer
Score float64 // score value
}
// Scoring Function: Initialized with an `if` statement + `switch` expression + `for range`
func evaluateExam(name string, questions []Question) (total float64, details []string) {
defer func() {
// defer: Collect results (ensures that results are collected even if a return statement is encountered midway)
details = append(details, fmt.Sprintf("[%s] Total score: %.1f", name, total))
}()
for i, q := range questions {
var earned float64
// switch expression format: multi-condition branching
switch q.Type {
case "single":
if q.Student == q.Correct {
earned = q.Score
}
case "judge":
if q.Student == q.Correct {
earned = q.Score
} else {
earned = 0
}
case "multi":
// Multiple-choice question: Points are awarded only for exact matches
if q.Student == q.Correct {
earned = q.Score
} else {
earned = 0
}
default:
continue // skip unknown question type
}
total += earned
details = append(details,
fmt.Sprintf("Problem %d (%s): Scored %.1f points", i+1, q.Type, earned))
}
return total, details
}
func main() {
questions := []Question{
{Type: "single", Correct: "B", Student: "B", Score: 2},
{Type: "judge", Correct: "true", Student: "true", Score: 1},
{Type: "multi", Correct: "A,B,C", Student: "A,B", Score: 3},
{Type: "single", Correct: "D", Student: "D", Score: 2},
}
// Use a `for range` loop to iterate through the student list
students := []string{"Alice", "Bob", "Charlie"}
for _, name := range students {
total, details := evaluateExam(name, questions)
fmt.Println("========================================")
for _, d := range details {
fmt.Println(d)
}
}
}
Resultado esperado (notas detalhadas de cada aluno + resumo do adiamento):
========================================
Problem 1 (single): Scored 2.0 points
Problem 2 (judge): Scored 1.0 points
Problem 3 (multi): Scored 0.0 points
Problem 4 (single): Scored 2.0 points
[Alice] Total score: 5.0
========================================
Problem 1 (single): Scored 2.0 points
Problem 2 (judge): Scored 1.0 points
Problem 3 (multi): Scored 0.0 points
Problem 4 (single): Scored 2.0 points
[Bob] Total score: 5.0
========================================
Problem 1 (single): Scored 2.0 points
Problem 2 (judge): Scored 1.0 points
Problem 3 (multi): Scored 0.0 points
Problem 4 (single): Scored 2.0 points
[Charlie] Total score: 5.0
defer para que ele fique visível no escopo externo — no exemplo acima, details usa o valor de retorno nomeado (total float64, details []string), e é por isso que append dentro de defer entra em vigor.
❓ Perguntas Frequentes
P: Por que o Go não tem uma palavra-chave
while? R: O Go usaforpara todos os loops:for condition {}é equivalente awhile, efor {}é equivalente awhile(true). Com uma palavra-chave a menos, as equipes não precisam discutir se devem usarwhileoudo-while.
P: Não é um incômodo que
switchnão passe automaticamente por padrão? R: Pelo contrário, a equipe do Go acredita que 90% dos casos deswitchdevem ser independentes — o problema de “esquecer de escreverbreak, causando a passagem automática” em C e Java é um dos bugs clássicos. O Go usafallthroughpara indicar explicitamente a intenção de avançar, o que é mais seguro.
P: Por que a ordem do mapa é aleatória ao iterar com
for range? R: O Go foi projetado intencionalmente para não ter ordem — a fim de evitar que os programadores dependam da ordem do mapa. Se você precisar de uma ordem consistente, classifique as chaves antes de iterar:keys := make([]string, 0, len(m)); for k := range m { keys = append(keys, k) }; sort.Strings(keys).
P: Quando a instrução
deferé executada? Qual é a ordem de execução para múltiplas instruçõesdefer? R: A instruçãodeferé executada antes que a função atual retorne (ou seja, após a execução da instruçãoreturn, mas antes que a função realmente seja encerrada). Várias instruçõesdefersão executadas na ordem LIFO (último a entrar, primeiro a sair), simulando o gerenciamento de recursos baseado em pilha: os recursos empurrados para a pilha por último são liberados primeiro.
P: Qual é o escopo de uma instrução de inicialização
if? R: As variáveis declaradas em uma instrução de inicializaçãoifsão visíveis apenas dentro do blocoif-else; elas se tornam inválidas assim que se sai do blocoif-else. Isso evita o erro comum de “variáveis vazando para o escopo externo”.
P: Os
labelsão usados com frequência em projetos reais? R: Raramente.labelé usado principalmente para sair de loops aninhados; em 99% dos casos,breakcombinado comreturné suficiente. O uso excessivo delabelreduz a legibilidade do código — uma lição aprendida na eragoto.
P: A declaração
switchdo Go oferece verificação de tipos? R: Sim.switch x := y.(type)é usado para a verificação de tipos de interface; abordaremos isso com mais detalhes na Lição 7. No entanto, essa sintaxe se aplica apenas a variáveis do tipointerface{}.
P: É possível usar
continueebreakem uma instruçãoswitch? R: Sim, mas eles se comportam de maneira diferente.breakencerra a instruçãoswitch; em um loopfor, ele encerra o loopfor.continuesó pode ser usado em um loopfor; ele não pode ser usado em uma instruçãoswitch(já que umswitchnão é considerado um loop).
📖 Resumo
- O Go abrange todas as estruturas de controle de fluxo com apenas quatro palavras-chave:
if/switch/for/defer ifsuporta instruções de inicialização, que limitam o escopo das variáveis ao blocoif-elseswitchinterrompe automaticamente por padrão; usefallthroughexplicitamente quando precisar prosseguir com a instrução- Quatro formas de
forque podem substituirwhileedo-while:for init;cond;post{}/for cond{}/for{}/for range deferé executado antes que a função retorne; várias instruçõesdefersão executadas na ordem LIFO (último a entrar, primeiro a sair).breaksai do loop,continuepula a iteração atual ebreak labelsai do loop no rótulo especificado- A filosofia do Go para o projeto de controle de fluxo: menos palavras-chave +
breakautomático + gerenciamento de recursos LIFO
📝 Exercícios
-
Problema básico (Dificuldade ⭐): Escreva um programa FizzBuzz usando
foreswitch: Imprima os números de 1 a 100. Quando encontrar um múltiplo de 3, imprima “Fizz”; quando encontrar um múltiplo de 5, imprima “Buzz”; e quando encontrar um múltiplo de 15, imprima “FizzBuzz”. -
Problema Avançado (Dificuldade ⭐⭐): Implemente a função
countPrimes(n int) int, que retorna o número de números primos entre 1 e n. Requisitos: (1) Use um loopfor; (2) Use uma instruçãoifpara verificar se um número é primo; (3) Usedeferpara imprimir o tempo de execução (usandotime.Now()) antes que a função retorne. -
Problema de Desafio (Dificuldade ⭐⭐⭐): Implemente um analisador de notas dos alunos: Dado um conjunto de notas ([]float64), exiba (1) a nota média; (2) a taxa de aprovação (≥60 pontos); (3) as notas mais alta e mais baixa; (4) uma divisão por nota (A/B/C/D/F). Requisitos: (1) Use
for range; (2) Use uma instruçãoswitchpara determinar as notas; (3) Usedeferpara imprimir as informações resumidas antes que a função retorne.