Estruturas de controle: if, for e switch

Artigo 05 — Estruturas de controle: if, for e switch

Curso: Dominando Go em 1 Ano Prof. Ricardo Matos Módulo 1 — Fundamentos da Linguagem


Controle de fluxo: o esqueleto lógico de qualquer programa

Todo programa útil precisa tomar decisões e repetir operações. As estruturas de controle são os mecanismos que permitem isso. Go possui um conjunto deliberadamente pequeno: if, for e switch. Não existe while, não existe do while, não existe foreach como palavra-chave separada. Tudo é feito com variações do for. Essa simplicidade é intencional e, na prática, suficiente para qualquer situação.


O if em Go

A estrutura básica do if dispensa parênteses em torno da condição — diferente de C, Java e JavaScript — mas exige chaves {} mesmo quando o bloco tem apenas uma linha:

package main

import "fmt"

func main() {
    temperatura := 38.5

    if temperatura > 37.5 {
        fmt.Println("Febre detectada.")
    } else if temperatura >= 36.0 {
        fmt.Println("Temperatura normal.")
    } else {
        fmt.Println("Hipotermia possível.")
    }
}

Essa obrigatoriedade das chaves elimina uma categoria inteira de bugs famosos em outras linguagens, onde a indentação enganosa sugeria que uma linha fazia parte de um if quando na verdade não fazia.


Inicialização no if

Uma das características mais úteis e idiomáticas do Go é a possibilidade de executar uma instrução de inicialização dentro do próprio if, separada da condição por ponto e vírgula. A variável declarada nessa inicialização existe apenas dentro do escopo do bloco if/else:

package main

import (
    "fmt"
    "strconv"
)

func main() {
    entrada := "42"

    if valor, err := strconv.Atoi(entrada); err == nil {
        fmt.Println("Conversão bem-sucedida:", valor*2)
    } else {
        fmt.Println("Erro na conversão:", err)
    }

    // "valor" e "err" não existem aqui fora
}

Esse padrão é extremamente comum em Go, especialmente ao trabalhar com funções que retornam um resultado e um erro. Ele mantém o escopo das variáveis de erro restrito ao bloco onde são relevantes, evitando poluição do escopo externo.


O for: único laço, múltiplas formas

Go tem apenas a palavra-chave for para todos os tipos de repetição. Ela assume formas diferentes dependendo de como é usada.

Forma clássica com três componentes — equivalente ao for do C:

for i := 0; i < 5; i++ {
    fmt.Println(i)
}

Forma com apenas a condição — equivalente ao while de outras linguagens:

n := 1
for n < 100 {
    n *= 2
}
fmt.Println(n) // 128

Laço infinito — sem condição alguma, equivalente ao while(true):

contador := 0
for {
    contador++
    if contador >= 3 {
        break
    }
}
fmt.Println(contador) // 3

for range — para iterar sobre slices, arrays, maps, strings e channels:

linguagens := []string{"Go", "Python", "Rust", "Java"}

for i, lang := range linguagens {
    fmt.Printf("%d: %s\n", i, lang)
}

Quando o índice não é necessário, usa-se _:

for _, lang := range linguagens {
    fmt.Println(lang)
}

Iterando sobre uma string com range, Go itera sobre os runes, não os bytes — o que significa que caracteres multibyte são tratados corretamente:

for i, r := range "Olá" {
    fmt.Printf("índice %d: %c\n", i, r)
}
// índice 0: O
// índice 1: l
// índice 2: á  ← ocupa 2 bytes, mas é um único rune

Iterando sobre um map:

capitais := map[string]string{
    "Brasil":    "Brasília",
    "Argentina": "Buenos Aires",
    "Chile":     "Santiago",
}

for pais, capital := range capitais {
    fmt.Printf("%s → %s\n", pais, capital)
}

A ordem de iteração sobre maps em Go é intencionalmente aleatória. O compilador até randomiza a ordem em cada execução para evitar que programadores dependam de uma ordem não garantida.


break e continue

O break interrompe o laço imediatamente. O continue pula para a próxima iteração:

for i := 0; i < 10; i++ {
    if i == 3 {
        continue  // pula o 3
    }
    if i == 7 {
        break     // para no 7
    }
    fmt.Println(i)
}
// Imprime: 0 1 2 4 5 6

Go também suporta labels para break e continue em laços aninhados, permitindo sair de um laço externo a partir de um laço interno:

externo:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if i == 1 && j == 1 {
            break externo  // sai do laço externo
        }
        fmt.Printf("i=%d j=%d\n", i, j)
    }
}

O switch em Go

O switch do Go é mais poderoso e mais seguro do que o de C ou Java. A diferença mais importante: não há fall-through automático. Cada case executa apenas seu próprio bloco e para — não é necessário escrever break ao final de cada caso.

package main

import "fmt"

func main() {
    dia := 3

    switch dia {
    case 1:
        fmt.Println("Segunda-feira")
    case 2:
        fmt.Println("Terça-feira")
    case 3:
        fmt.Println("Quarta-feira")
    case 4:
        fmt.Println("Quinta-feira")
    case 5:
        fmt.Println("Sexta-feira")
    case 6, 7:
        fmt.Println("Final de semana")
    default:
        fmt.Println("Dia inválido")
    }
}

Note que um case pode listar múltiplos valores separados por vírgula, como case 6, 7.


Inicialização no switch

Assim como o if, o switch aceita uma instrução de inicialização:

switch os := runtime.GOOS; os {
case "linux":
    fmt.Println("Sistema Linux")
case "darwin":
    fmt.Println("Sistema macOS")
default:
    fmt.Printf("Outro sistema: %s\n", os)
}

switch sem expressão

Quando o switch não recebe uma expressão, ele se comporta como uma cadeia de if/else if, avaliando cada case como uma expressão booleana independente. Esse padrão é muito mais legível do que um longo encadeamento de if:

nota := 78

switch {
case nota >= 90:
    fmt.Println("Conceito A")
case nota >= 80:
    fmt.Println("Conceito B")
case nota >= 70:
    fmt.Println("Conceito C")
case nota >= 60:
    fmt.Println("Conceito D")
default:
    fmt.Println("Reprovado")
}

fallthrough explícito

Se o comportamento de queda entre casos for necessário, Go exige que seja declarado explicitamente com a palavra-chave fallthrough. Ela força a execução do próximo case independentemente de sua condição:

x := 1

switch x {
case 1:
    fmt.Println("caso 1")
    fallthrough
case 2:
    fmt.Println("caso 2 — executado por fallthrough")
case 3:
    fmt.Println("caso 3 — não executado")
}
// Imprime:
// caso 1
// caso 2 — executado por fallthrough

O fallthrough é raro no código Go idiomático. Seu uso deve ser acompanhado de um comentário explicando a intenção.


defer: execução adiada

Embora não seja estritamente uma estrutura de controle de fluxo, o defer merece introdução aqui por influenciar diretamente a ordem de execução. Uma instrução defer agenda a execução de uma função para o momento em que a função atual retornar — independentemente de como ela retornar, seja por um retorno normal ou por um pânico:

package main

import "fmt"

func main() {
    fmt.Println("início")
    defer fmt.Println("executado por defer")
    fmt.Println("fim")
}
// Saída:
// início
// fim
// executado por defer

Quando múltiplos defer são empilhados, eles executam em ordem LIFO — o último declarado é o primeiro a executar:

for i := 1; i <= 3; i++ {
    defer fmt.Println(i)
}
// Imprime: 3, 2, 1

O caso de uso mais comum do defer é garantir o fechamento de recursos como arquivos, conexões de banco de dados ou locks de mutex, logo após sua abertura:

arquivo, err := os.Open("dados.txt")
if err != nil {
    log.Fatal(err)
}
defer arquivo.Close()  // será executado quando a função retornar

// ... trabalha com o arquivo

Esse padrão garante que o recurso seja sempre liberado, mesmo que a função retorne por um caminho de erro inesperado.


Resumo do que foi coberto

Este artigo percorreu todas as estruturas de controle do Go. O if com inicialização embutida, as quatro formas do for, o switch sem fall-through automático e com expressões booleanas, e o defer para execução adiada. Com esses recursos, é possível expressar qualquer lógica de controle de fluxo de forma clara e idiomática em Go.


Referências e leituras complementares


Próximo artigo: Artigo 06 — Funções: declaração, múltiplos retornos e variádicas


you asked

Sim


claude response