Controle de fluxo em Go
LidoNesta aula, exploramos as estruturas de controle de fluxo em Go: condicionais if com init statement, o loop for em suas variações, o switch com suas peculiaridades e o uso de labels para controle de fluxo avançado. Você verá exemplos práticos e exercícios para consolidar o conhecimento.
O controle de fluxo é a espinha dorsal de qualquer programa. Em Go, a sintaxe é limpa e direta, mas cheia de nuances que a tornam poderosa e expressiva. Nesta aula, vamos mergulhar nas estruturas condicionais e de repetição que o Go oferece, entendendo não apenas a sintaxe, mas também as boas práticas e casos de uso que farão seu código mais idiomático.
Vamos explorar o if com inicialização, o for como única estrutura de loop (flexível o suficiente para cobrir todos os padrões), o switch com sua quebra automática de casos e os labels que permitem controle de fluxo refinado em loops aninhados. Ao final, você estará apto a escrever lógica de controle robusta e elegante em Go.
if (com init statement)
Em Go, a instrução if é usada para executar um bloco de código se uma condição for verdadeira. Diferente de muitas linguagens, a condição não precisa estar entre parênteses, o que torna o código mais limpo. Mas o recurso mais distintivo é a possibilidade de incluir uma instrução de inicialização antes da condição, separada por ponto e vírgula. Essa instrução é executada antes da avaliação da condição e tem escopo limitado ao bloco do if (incluindo o else).
Essa construção é muito útil para executar uma função que retorna valores e, em seguida, testar um deles, sem poluir o escopo externo. Por exemplo, ao tentar abrir um arquivo, podemos verificar o erro diretamente. Vejamos um exemplo:
package main
import (
"fmt"
"os"
)
func main() {
// A função os.ReadFile retorna ([]byte, error)
// O init statement atribui os valores e a condição testa o erro
if data, err := os.ReadFile("exemplo.txt"); err != nil {
fmt.Println("Erro ao ler arquivo:", err)
} else {
fmt.Println("Conteúdo:", string(data))
}
// Aqui, data e err não estão mais acessíveis (escopo do if)
}Esse padrão é extremamente comum em Go, especialmente ao lidar com operações que retornam múltiplos valores, como leituras, escritas e chamadas de rede. Ele evita declarações temporárias e mantém o código conciso e seguro, pois as variáveis não vazam para fora do bloco.
for (a única forma de loop)
Go tem apenas uma instrução de repetição: o for. No entanto, ela é flexível e pode ser usada de várias maneiras: como um loop clássico com inicialização, condição e pós-incremento; como um loop estilo while; e até como um loop infinito. Além disso, a combinação com range permite iterar sobre slices, maps, strings e canais.
Vejamos as variações principais:
package main
import "fmt"
func main() {
// 1. Loop clássico
for i := 0; i < 5; i++ {
fmt.Println("i =", i)
}
// 2. Loop estilo while
j := 0
for j < 5 {
fmt.Println("j =", j)
j++
}
// 3. Loop infinito (com break)
k := 0
for {
fmt.Println("k =", k)
k++
if k == 3 {
break
}
}
// 4. Iterando sobre uma slice com range
frutas := []string{"maçã", "banana", "laranja"}
for indice, fruta := range frutas {
fmt.Printf("%d: %s\n", indice, fruta)
}
// 5. Iterando sobre um map
idades := map[string]int{"Alice": 30, "Bob": 25}
for nome, idade := range idades {
fmt.Printf("%s tem %d anos\n", nome, idade)
}
}No loop clássico, a inicialização e o pós-incremento são opcionais. Quando omitimos a condição, temos um loop infinito, que pode ser interrompido com break. O range é fundamental para trabalhar com coleções e retorna índices/valores (ou chaves/valores para maps). Se não precisar do índice, use _ para descartá-lo. Isso evita variáveis não utilizadas, que são um erro de compilação em Go.
switch
O switch em Go é mais poderoso do que na maioria das linguagens. Ele não exige break explícito ao final de cada caso, pois o Go automaticamente sai do switch após executar o bloco do caso correspondente. Além disso, a expressão avaliada pode ser de qualquer tipo comparável, e os casos podem conter múltiplas expressões separadas por vírgula. Também é possível usar uma forma sem expressão, que funciona como uma cadeia de if-else.
Outra característica interessante é a palavra-chave fallthrough, que força a execução do próximo caso, mesmo sem que a condição seja verdadeira. Isso deve ser usado com parcimônia, pois pode tornar o fluxo confuso. Vejamos exemplos:
package main
import "fmt"
func main() {
// Switch simples com valor
dia := "sábado"
switch dia {
case "segunda", "terça", "quarta", "quinta", "sexta":
fmt.Println("Dia útil")
case "sábado", "domingo":
fmt.Println("Fim de semana")
default:
fmt.Println("Dia inválido")
}
// Switch sem expressão (equivale a if-else)
idade := 18
switch {
case idade < 18:
fmt.Println("Menor de idade")
case idade <= 60:
fmt.Println("Adulto")
default:
fmt.Println("Idoso")
}
// Uso de fallthrough
num := 2
switch num {
case 1:
fmt.Println("Um")
fallthrough
case 2:
fmt.Println("Dois")
fallthrough
case 3:
fmt.Println("Três")
default:
fmt.Println("Outro")
}
}No exemplo com fallthrough, quando num é 2, ele imprime "Dois" e depois "Três", pois o fallthrough força a execução do próximo caso. Note que o default não é executado a menos que seja alcançado por fallthrough ou que nenhum caso corresponda.
Labels
Labels em Go são identificadores que podem ser usados para rotular instruções, principalmente para controlar loops a partir de um bloco interno. Eles são úteis quando precisamos interromper ou continuar um loop externo a partir de um loop aninhado. Sem labels, teríamos que usar variáveis de controle ou flags, o que polui o código. Com labels, podemos fazer break ou continue diretamente para o loop desejado.
Um label deve ser seguido por uma instrução (geralmente um loop) e é declarado antes dela. A sintaxe é: nomeDoLabel:. Vejamos um exemplo prático de busca em uma matriz:
package main
import "fmt"
func main() {
matriz := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
alvo := 5
encontrado := false
// Label para o loop externo
externo:
for i, linha := range matriz {
for j, valor := range linha {
if valor == alvo {
fmt.Printf("Encontrado %d na posição [%d][%d]\n", alvo, i, j)
encontrado = true
break externo // Interrompe o loop externo
}
}
}
if !encontrado {
fmt.Println("Não encontrado")
}
// Exemplo de continue com label: imprimir apenas números ímpares
fmt.Println("Números ímpares:")
externo2:
for i := 1; i <= 5; i++ {
for j := 1; j <= 5; j++ {
if (i*j)%2 == 0 {
continue externo2 // Pula para a próxima iteração do loop externo
}
fmt.Println(i, "*", j, "=", i*j)
}
}
}No primeiro exemplo, o break externo interrompe o loop externo assim que o valor é encontrado. No segundo, o continue externo2 faz com que o loop externo avance para a próxima iteração, ignorando as iterações internas restantes. Isso é muito útil para otimizar buscas e processamentos em estruturas aninhadas.
Boas Práticas e Observações Finais
Ao usar if com init, lembre-se de que as variáveis declaradas ali só existem dentro do bloco. Prefira essa forma quando a variável for usada somente na condição e no corpo, evitando poluir o escopo externo. No for, evite criar loops infinitos sem uma condição de saída clara; prefira usar uma condição explícita sempre que possível. No switch, não se esqueça de que o Go não exige break; isso evita erros comuns de outras linguagens. Use fallthrough somente quando a intenção for clara e documentada. Labels devem ser usados com moderação; se você precisar de muitos, considere refatorar o código para funções menores, pois loops aninhados complexos podem se tornar difíceis de ler.
Exercícios
Escreva um programa que use um
forpara imprimir os números de 1 a 10, mas que pare (break) se encontrar um número divisível por 7.✓ Resposta:package main import "fmt" func main() { for i := 1; i <= 10; i++ { if i%7 == 0 { break } fmt.Println(i) } }Utilizando um
ifcom init statement, escreva uma função que receba um inteirone retorne o quadrado densenfor par, ou o cubo se for ímpar. Faça a chamada no main e imprima o resultado.✓ Resposta:package main import "fmt" func calcular(n int) int { if quad := n * n; n%2 == 0 { return quad } else { return n * n * n } } func main() { fmt.Println(calcular(4)) // 16 fmt.Println(calcular(3)) // 27 }Crie um loop infinito que imprima números crescentes a partir de 0, mas que pare quando o número for maior que 100 e divisível por 10. Use
break.✓ Resposta:package main import "fmt" func main() { i := 0 for { if i > 100 && i%10 == 0 { break } fmt.Println(i) i++ } }Escreva um programa que use
switchpara classificar uma nota (0-10) em conceitos: A (9-10), B (7-8), C (5-6), D (3-4), F (0-2). Use a forma sem expressão.✓ Resposta:package main import "fmt" func conceito(nota int) string { switch { case nota >= 9: return "A" case nota >= 7: return "B" case nota >= 5: return "C" case nota >= 3: return "D" default: return "F" } } func main() { fmt.Println(conceito(8)) // B }Dada a matriz abaixo, use um label para encontrar a primeira ocorrência do número 0 e imprimir a posição. Se não houver, imprima "não encontrado".
matriz := [][]int{ {1, 2, 3}, {4, 5, 6}, {7, 8, 9}, }✓ Resposta:package main import "fmt" func main() { matriz := [][]int{ {1, 2, 3}, {4, 5, 6}, {7, 8, 9}, } alvo := 0 encontrado := false externo: for i, linha := range matriz { for j, valor := range linha { if valor == alvo { fmt.Printf("Encontrado %d na posição [%d][%d]\n", alvo, i, j) encontrado = true break externo } } } if !encontrado { fmt.Println("não encontrado") } }