Quando o Programa Decide: if, else e switch

[342] Quando o Programa Decide: if, else e switch

Trocar == por = dentro de um if compila, roda e faz a coisa errada em silêncio; há duas defesas contra isso. Além dela, o critério de verdade do C, em que zero é falso e todo o resto é verdadeiro, o curto-circuito dos operadores lógicos, o ternário, e o switch com seu break obrigatório.
Linguagem C

8 min de leitura

Até agora, nossos programas seguiam sempre o mesmo caminho, da primeira à última linha. Mas um programa útil precisa escolher: cobrar frete se o pedido for abaixo de um valor, mostrar "aprovado" se a nota passar de sete, encerrar se o usuário digitar "sair". Hoje damos ao C o poder de decidir. E, como sempre, vamos descobrir uma ou duas armadilhas que a linguagem reserva para os desatentos.

A decisão mais simples: if

O if executa um bloco apenas se uma condição for verdadeira:

#include <stdio.h>

int main(void) {
    int nota = 8;

    if (nota >= 7) {
        printf("Aprovado!\n");
    }

    return 0;
}

A condição fica entre parênteses. Se ela for verdadeira, o bloco entre chaves roda; se não, é ignorado. Você pode omitir as chaves quando o bloco tem uma única linha, mas não faça isso. Chaves sempre presentes evitam uma classe inteira de bugs difíceis — voltaremos a esse ponto.

Os dois caminhos: if e else

O else cobre o caso contrário:

#include <stdio.h>

int main(void) {
    int idade = 16;

    if (idade >= 18) {
        printf("Maior de idade.\n");
    } else {
        printf("Menor de idade.\n");
    }

    return 0;
}

E, para mais de dois caminhos, encadeamos else if:

#include <stdio.h>

int main(void) {
    int nota = 6;

    if (nota >= 9) {
        printf("Excelente\n");
    } else if (nota >= 7) {
        printf("Aprovado\n");
    } else if (nota >= 5) {
        printf("Recuperação\n");
    } else {
        printf("Reprovado\n");
    }

    return 0;
}

O C testa as condições de cima para baixo e para na primeira verdadeira. Por isso a ordem importa: se você colocasse nota >= 5 antes de nota >= 9, ninguém jamais seria "Excelente".

Verdadeiro e falso, à moda do C

Aqui está um traço fundamental da linguagem: o C não tinha, originalmente, um tipo booleano. Para ele, zero é falso e qualquer coisa diferente de zero é verdadeira. Isso significa que uma condição não precisa ser uma comparação:

#include <stdio.h>

int main(void) {
    int estoque = 3;

    if (estoque) { // verdadeiro porque 3 != 0
        printf("Ainda há %d unidades.\n", estoque);
    }

    return 0;
}

Desde o C99, existe o cabeçalho <stdbool.h>, que traz bool, true e false por comodidade — mas por baixo dos panos continuam sendo 1 e 0. Use-o para deixar a intenção clara:

#include <stdio.h>
#include <stdbool.h>

int main(void) {
    bool logado = true;
    if (logado) {
        printf("Bem-vindo de volta.\n");
    }
    return 0;
}

Operadores de comparação e lógicos

Para construir condições, você combina operadores relacionais e lógicos:

==   igual a            &&   E (and)
!=   diferente de       ||   OU (or)
<    menor que          !    NÃO (not)
>    maior que
<=   menor ou igual
>=   maior ou igual

Exemplo combinando os dois:

#include <stdio.h>

int main(void) {
    int idade = 25;
    double renda = 3500.0;

    if (idade >= 18 && renda >= 2000.0) {
        printf("Elegível para o crédito.\n");
    } else {
        printf("Não elegível.\n");
    }

    return 0;
}

Os operadores && e || têm avaliação em curto-circuito: em a && b, se a já for falso, o b nem é avaliado (o resultado já está decidido). Isso não é um detalhe — é uma técnica útil para proteger operações, como veremos ao estudar ponteiros.

A armadilha clássica: = em vez de ==

Este é possivelmente o erro mais famoso do C. Comparação é == (dois sinais). Um único = é atribuição, e o C aceita atribuição dentro de um if sem reclamar:

int x = 5;

if (x = 0) {        // ATRIBUI 0 a x — e 0 é falso!
    printf("nunca entra aqui\n");
}
// x agora vale 0, e você nem percebeu

O código compila, roda, e faz a coisa errada em silêncio. Duas defesas: compile sempre com gcc -Wall (ele avisa), e adote o hábito de escrever a constante à esquerda — if (0 == x) — pois aí um = acidental vira erro de compilação.

Veja o aviso aparecer: no Compiler Explorer, o campo ao lado do compilador aceita as mesmas flags da linha de comando. Cole o if com =, compile sem nada e depois com -Wall: só o segundo reclama do valor usado como condição. Acrescente -Wextra e deixe os dois ligados desde já.

O operador ternário: um if que devolve valor

Para escolhas simples entre dois valores, há um atalho — o operador ? ::

#include <stdio.h>

int main(void) {
    int idade = 20;
    const char *status = (idade >= 18) ? "adulto" : "menor";
    printf("Status: %s\n", status);
    return 0;
}

Leia como: "se idade >= 18, o valor é "adulto"; senão, "menor"". É elegante para casos curtos, mas não abuse — encadear ternários vira código ilegível rapidamente.

Escolha entre muitos casos: switch

Quando você compara uma mesma variável contra vários valores fixos, o switch é mais claro que uma escada de else if:

#include <stdio.h>

int main(void) {
    int dia = 3;

    switch (dia) {
        case 1:
            printf("Domingo\n");
            break;
        case 2:
            printf("Segunda\n");
            break;
        case 3:
            printf("Terça\n");
            break;
        default:
            printf("Outro dia\n");
            break;
    }

    return 0;
}

Três pontos merecem atenção. O break ao fim de cada case é obrigatório na prática: sem ele, a execução "escorrega" para o próximo caso (o chamado fall-through). O default cobre qualquer valor não previsto — como o else final. E o switch só funciona com valores inteiros ou caracteres, nunca com double ou strings.

O fall-through, aliás, às vezes é intencional — para agrupar casos:

#include <stdio.h>

int main(void) {
    char c = 'e';

    switch (c) {
        case 'a':
        case 'e':
        case 'i':
        case 'o':
        case 'u':
            printf("Vogal\n");
            break;
        default:
            printf("Consoante\n");
    }

    return 0;
}

Aqui a ausência de break nos primeiros casos é proposital: qualquer vogal cai no mesmo bloco.

Um programa que junta tudo

#include <stdio.h>

int main(void) {
    double valor = 150.0;
    int itens = 4;
    double frete;

    if (valor >= 200.0) {
        frete = 0.0;
    } else if (itens >= 3) {
        frete = 15.0;
    } else {
        frete = 25.0;
    }

    const char *faixa = (valor >= 200.0) ? "frete grátis" : "frete cobrado";

    printf("Pedido: R$ %.2f, %d itens\n", valor, itens);
    printf("Frete: R$ %.2f (%s)\n", frete, faixa);
    printf("Total: R$ %.2f\n", valor + frete);

    return 0;
}

Saída:

Pedido: R$ 150.00, 4 itens
Frete: R$ 15.00 (frete cobrado)
Total: R$ 165.00

O que vem a seguir

Agora o programa sabe decidir. No próximo artigo, ele vai aprender a repetir — e é aí que o computador realmente mostra sua força, fazendo milhões de vezes aquilo que faríamos uma só. Vamos aos laços while, do-while e for, e às palavras break e continue, que controlam o fluxo de dentro de um laço.

Fontes e leituras recomendadas

Exercícios

Exercício 1

Escreva um programa que leia (ou fixe) um número inteiro e imprima "par" ou "ímpar". Dica: use o operador resto %.

Ver resposta

✓ Resposta: O resto da divisão por 2 é 0 para pares:

#include <stdio.h>
int main(void) {
    int n = 7;
    if (n % 2 == 0) printf("par\n");
    else            printf("ímpar\n");
    return 0;
}

Exercício 2

Usando else if, classifique uma temperatura em: "congelando" (< 0), "frio" (0 a 15), "agradável" (16 a 25) ou "quente" (> 25).

Ver resposta

✓ Resposta: A ordem das faixas precisa ser coerente (do menor para o maior, aproveitando o corte anterior):

#include <stdio.h>
int main(void) {
    double t = 22.0;
    if (t < 0)        printf("congelando\n");
    else if (t <= 15) printf("frio\n");
    else if (t <= 25) printf("agradável\n");
    else              printf("quente\n");
    return 0;
}

Exercício 3

Reescreva a classificação de vogal/consoante do artigo usando if/else if em vez de switch. Qual das duas versões você acha mais legível, e por quê?

Ver resposta

✓ Resposta: Uma versão com if:

if (c=='a' || c=='e' || c=='i' || c=='o' || c=='u')
    printf("Vogal\n");
else
    printf("Consoante\n");

O switch com fall-through costuma ser mais legível quando há muitos valores discretos e explícitos (fica claro que são cinco casos agrupados). O if com || é mais compacto, mas a linha cresce e fica mais fácil errar um operador. Ambos são corretos; a escolha é de estilo.

Exercício 4

Explique o que este trecho imprime e por quê: int x = 5; if (x = 0) printf("A\n"); else printf("B\n");

Ver resposta

✓ Resposta: Imprime B. O if (x = 0) atribui 0 a x (não compara). O resultado da atribuição é 0, que é falso, então o else roda e imprime "B". De quebra, x agora vale 0. É exatamente a armadilha do = no lugar de ==.

Exercício 5

Escreva um switch que receba um caractere de operação ('+', '-', '*', '/') e dois números double, e imprima o resultado da conta correspondente. Trate a divisão por zero.

Ver resposta

✓ Resposta:

#include <stdio.h>
int main(void) {
    char op = '/';
    double a = 10.0, b = 0.0;
    switch (op) {
        case '+': printf("%.2f\n", a + b); break;
        case '-': printf("%.2f\n", a - b); break;
        case '*': printf("%.2f\n", a * b); break;
        case '/':
            if (b != 0.0) printf("%.2f\n", a / b);
            else          printf("Erro: divisão por zero\n");
            break;
        default:  printf("Operação inválida\n");
    }
    return 0;
}

Aqui o if (b != 0.0) protege a conta antes de dividir — a mesma disciplina defensiva que o C sempre pede de você.

Comentários

Mais em Linguagem C

Manipulação de Strings sem Sofrimento
Manipulação de Strings sem Sofrimento

O strtok escreve na string que recebe, trocando cada delimitador por um…

typedef e o Design de Tipos Legíveis
typedef e o Design de Tipos Legíveis

Esconder um asterisco dentro de um apelido faz PInt a, b; declarar dois…

Depurando de Verdade com o GDB
Depurando de Verdade com o GDB

Espalhar printf, recompilar, rodar, apagar os printf e repetir é o método que…