Pular para conteúdo

Semana 02 — Estruturas condicionais

Nesta semana

Unidade 1 · Seg 17/08 · Qua 19/08 · Sex 21/08

Até agora nossos programas eram uma linha reta: as instruções rodavam sempre, na mesma ordem, do começo ao fim. Nesta semana o programa ganha a capacidade de tomar decisões — escolher caminhos diferentes conforme os dados. Esse é o salto que separa um "calculador" de um programa que reage ao mundo.

Você vai dominar o if, o else, o encadeamento com else if, o aninhamento de condições, a combinação de testes com operadores lógicos e a estrutura de múltipla escolha switch. Como sempre: digite e execute cada exemplo.

🎯 Objetivos de aprendizagem

Ao final desta semana, você deverá ser capaz de:

  • explicar o que é controle de fluxo e por que decisões são essenciais em programação;
  • escrever estruturas if, if/else e cadeias else if corretas e legíveis;
  • combinar condições com os operadores lógicos &&, || e !, entendendo a avaliação em curto-circuito;
  • construir e ler condicionais aninhadas, sabendo quando elas ajudam e quando atrapalham;
  • usar a estrutura switch adequadamente, conhecendo o papel do break e o fenômeno do fall-through;
  • reconhecer e evitar os erros clássicos de decisão (= vs ==, ; após o if, intervalos mal escritos, break esquecido).

📖 Segunda — Estudo do conteúdo

1. Por que um programa precisa decidir?

Muitos problemas do mundo real têm a forma "se… então… senão…":

  • Se a nota for maior ou igual a 6, então o aluno está aprovado, senão reprovado.
  • Se o saldo for suficiente, então autorize a compra, senão recuse.
  • Se o número for par, então faça uma coisa; senão, faça outra.

Em Pensamento Computacional você já desenhou fluxogramas com losangos de decisão. A estrutura condicional é exatamente isso, escrito em C: um teste (uma condição que é verdadeira ou falsa) que determina qual bloco de instruções será executado.

flowchart TD
    A["nota >= 6 ?"] -->|verdadeiro| B["imprime<br/>Aprovado"]
    A -->|falso| C["imprime<br/>Reprovado"]
    B --> D["continua o programa"]
    C --> D

2. Relembrando: verdadeiro, falso e comparações

Antes de decidir, o programa precisa avaliar uma condição. Como vimos na Semana 01, em C:

  • falso é o valor 0;
  • verdadeiro é qualquer valor diferente de zero (a convenção é 1).

Uma comparação produz esse valor lógico. Recapitulando os operadores relacionais:

Operador Significado Exemplo verdadeiro
== igual a idade == 18
!= diferente de resposta != 's'
< menor que nota < 6
> maior que saldo > 0
<= menor ou igual nota <= 10
>= maior ou igual idade >= 18
int n = 7;
printf("%d\n", n > 5);    // 1 (verdadeiro)
printf("%d\n", n == 10);  // 0 (falso)

O erro nº 1 das condicionais: = no lugar de ==

= atribui; == compara. Dentro de um if, trocar um pelo outro muda tudo — e compila sem erro:

if (idade = 18) { ... }   // ERRADO: atribui 18 a idade; a condição vira "18" (verdadeiro SEMPRE)
if (idade == 18) { ... }  // certo: pergunta se idade vale 18
Como idade = 18 resulta em 18 (diferente de zero), o if sempre executa. Bugs assim são difíceis de achar. Leia sempre suas condições em voz alta: "se idade for igual a 18".

3. A estrutura if

A forma mais simples: execute um bloco apenas se a condição for verdadeira.

if (condição) {
    // executa só quando a condição é verdadeira
}

Exemplo:

int idade;
scanf("%d", &idade);

if (idade >= 18) {
    printf("Voce e maior de idade.\n");
}
printf("Fim.\n");   // esta linha roda SEMPRE (está fora do if)

Se idade for 20, imprime as duas linhas; se for 15, imprime só "Fim.".

O ; fantasma depois do if

Um ponto e vírgula logo após a condição cria um bloco vazio — e o que você achava que era o "corpo do if" passa a rodar sempre:

if (idade >= 18);            // <- este ; encerra o if aqui (corpo vazio!)
{
    printf("Maior de idade\n");  // roda SEMPRE, independentemente da idade
}
Nunca coloque ; imediatamente após if (...).

4. ifelse: os dois caminhos

Quando há uma ação para o caso verdadeiro e outra para o falso:

int nota;
scanf("%d", &nota);

if (nota >= 6) {
    printf("Aprovado\n");
} else {
    printf("Reprovado\n");
}

Exatamente um dos dois blocos executa — nunca os dois, nunca nenhum. O else é sempre opcional e, quando presente, "pertence" ao if mais próximo.

5. Blocos, chaves e legibilidade

As chaves { } agrupam várias instruções em um bloco. Se o corpo tem uma única instrução, o C permite omitir as chaves — mas não faça isso:

// Funciona, porém arriscado:
if (aprovado)
    printf("Parabens!\n");

// Recomendado (sempre com chaves):
if (aprovado) {
    printf("Parabens!\n");
}

Por que sempre usar chaves? Porque, sem elas, apenas a primeira linha pertence ao if. Um descuido gera bugs silenciosos:

if (aprovado)
    printf("Parabens!\n");
    printf("Matricula liberada.\n");   // ENGANO: esta linha roda SEMPRE (não está no if!)

Padronize o recuo (indentação)

O C ignora espaços e quebras de linha, mas humanos não. Recue o conteúdo dos blocos (o Code::Blocks faz isso automaticamente). Código bem indentado revela a estrutura de decisões só de bater o olho — e evita a confusão do exemplo acima.

6. else if: escolhendo entre vários caminhos

Quando há mais de duas possibilidades, encadeamos testes com else if:

int nota;
scanf("%d", &nota);

if (nota >= 90) {
    printf("Conceito A\n");
} else if (nota >= 70) {
    printf("Conceito B\n");
} else if (nota >= 50) {
    printf("Conceito C\n");
} else {
    printf("Conceito D\n");
}

As condições são testadas de cima para baixo. A primeira verdadeira executa seu bloco, e todas as demais são ignoradas. O else final (sem condição) é o "caso contrário", que pega tudo o que sobrou.

A ordem dos testes importa!

Como o primeiro verdadeiro "vence", condições mais restritivas devem vir antes. Veja o que acontece se invertermos:

if (nota >= 50) {           // uma nota 95 já cai AQUI...
    printf("Conceito C\n"); // ...e imprime C, errado!
} else if (nota >= 70) {
    printf("Conceito B\n");
} else if (nota >= 90) {
    printf("Conceito A\n");
}
Repare: repare que não precisamos escrever nota >= 70 && nota < 90 no segundo teste — se o programa chegou lá, é porque o primeiro if (>= 90) já falhou.

7. Combinando condições com operadores lógicos

Muitas decisões dependem de mais de uma condição ao mesmo tempo. Para isso usamos os operadores lógicos, já apresentados na Semana 01:

Operador Nome Resultado verdadeiro quando…
&& E (and) ambas as condições são verdadeiras
\|\| OU (or) pelo menos uma é verdadeira
! NÃO (not) inverte o valor lógico
// Pode entrar no brinquedo? Precisa ter 12+ anos E 1,40m+ de altura.
if (idade >= 12 && altura >= 1.40) {
    printf("Pode entrar.\n");
}

// É fim de semana? Sábado (6) OU domingo (7).
if (dia == 6 || dia == 7) {
    printf("Fim de semana!\n");
}

// Nega uma condição:
if (!chovendo) {
    printf("Bom dia para um passeio.\n");
}

Como escrever 'x entre a e b' — NÃO copie da matemática

Na matemática você escreve 0 <= x <= 100. Em C isso não faz o que você quer (é interpretado de forma estranha e quase sempre dá "sempre verdadeiro"). O correto é quebrar em duas comparações ligadas por &&:

if (x >= 0 && x <= 100) {   // certo: "x entre 0 e 100, inclusive"
    printf("Nota valida.\n");
}

Curiosidade 💡 — avaliação em curto-circuito (e por que é útil)

O C avalia && e || da esquerda para a direita e para assim que já sabe o resultado:

  • Em A && B, se A for falso, B nem é avaliado (o resultado já é falso).
  • Em A || B, se A for verdadeiro, B nem é avaliado.

Isso não é só uma otimização: permite proteger um teste com o anterior. Por exemplo, evitar dividir por zero — só testamos a divisão se o divisor não for zero:

if (divisor != 0 && total / divisor > 10) { ... }
Se divisor for 0, o primeiro teste falha e o segundo (a divisão) nunca acontece.

Curiosidade 💡 — as Leis de De Morgan

Negar uma combinação de condições tem uma regra elegante: !(A && B) equivale a (!A || !B), e !(A || B) equivale a (!A && !B). Ou seja, ao "distribuir" o !, o && vira || e vice-versa. Útil para simplificar condições complicadas e deixá-las mais legíveis.

8. Condicionais aninhadas

Um if pode conter outro if no seu corpo. Isso é o aninhamento, útil quando uma decisão só faz sentido depois de outra já ter sido tomada:

int numero;
scanf("%d", &numero);

if (numero >= 0) {
    if (numero == 0) {
        printf("Zero\n");
    } else {
        printf("Positivo\n");
    }
} else {
    printf("Negativo\n");
}

Leia assim: primeiro decidimos se é não-negativo; só então, dentro desse caso, distinguimos zero de positivo.

Aninhar demais é sinal de alerta

Muitos níveis de if dentro de if deixam o código difícil de acompanhar (o chamado "código em escada"). Frequentemente dá para achatar a estrutura usando else if ou combinando condições com &&/||. Compare:

// Aninhado (mais difícil de ler)
if (tem_conta) {
    if (saldo >= valor) {
        printf("Compra autorizada\n");
    }
}
// Achatado com && (mais claro)
if (tem_conta && saldo >= valor) {
    printf("Compra autorizada\n");
}

Curiosidade 💡 — o dangling else (o 'else solto')

Quando há ifs aninhados sem chaves, a qual if um else pertence? A regra do C é: o else sempre casa com o if mais próximo ainda "em aberto". Essa ambiguidade, conhecida como dangling else, é justamente mais um motivo para sempre usar chaves — com elas, não há dúvida possível.

9. switch: múltipla escolha por valor exato

Quando você compara uma mesma variável contra vários valores fixos, uma cadeia de else if fica repetitiva. O switch expressa isso de forma mais limpa:

int opcao;
printf("1-Iniciar 2-Configurar 3-Sair\nEscolha: ");
scanf("%d", &opcao);

switch (opcao) {
    case 1:
        printf("Iniciando...\n");
        break;
    case 2:
        printf("Abrindo configuracoes...\n");
        break;
    case 3:
        printf("Saindo. Ate logo!\n");
        break;
    default:
        printf("Opcao invalida.\n");
}

Como funciona: o switch avalia a expressão e salta para o case cujo valor bate. O break encerra o switch. O default (opcional) trata qualquer valor não previsto.

O break esquecido e o fall-through

Sem break, a execução "escorrega" para o próximo case — e continua até achar um break ou o fim. Isso se chama fall-through:

switch (opcao) {
    case 1:
        printf("Um\n");   // se opcao == 1 e faltar o break...
    case 2:
        printf("Dois\n"); // ...esta linha TAMBÉM executa!
        break;
}
Quase sempre isso é um bug. Salvo raras exceções propositais, coloque break em todo case.

Limitações do switch

O switch só compara igualdade com valores inteiros constantes (inclui char, pois é inteiro). Ele não funciona com intervalos (case > 10: é inválido) nem com double ou strings. Para intervalos e testes mais ricos, use if/else if.

Curiosidade 💡 — fall-through usado de propósito

Há um caso legítimo: agrupar vários valores que compartilham a mesma ação, deixando cases "vazios" caírem no seguinte:

switch (mes) {
    case 1: case 3: case 5: case 7:
    case 8: case 10: case 12:
        printf("31 dias\n");
        break;
    case 4: case 6: case 9: case 11:
        printf("30 dias\n");
        break;
    default:
        printf("Fevereiro: 28 ou 29\n");
}

10. Bônus: o operador ternário ?:

Para decisões simples que apenas escolhem um valor, existe uma forma compacta, o operador condicional ternário:

// condição ? valor_se_verdadeiro : valor_se_falso
int maior = (a > b) ? a : b;      // maior recebe a ou b

printf("%s\n", (nota >= 6) ? "Aprovado" : "Reprovado");

Ele equivale a um if/else que decide um valor. Use com moderação: para lógica mais elaborada, o if/else tradicional é mais legível.

11. Erros comuns desta semana (resumão)

Checklist de armadilhas

  • = (atribui) no lugar de == (compara) dentro do if.
  • ; logo após if (...) — cria corpo vazio.
  • Esquecer as chaves e achar que várias linhas estão no if.
  • Escrever intervalo como 0 <= x <= 100 (use x >= 0 && x <= 100).
  • Ordem errada em cadeias else if (condição genérica antes da específica).
  • break esquecido no switch (fall-through acidental).
  • Usar switch com intervalos, double ou strings (não funciona).

👥 Quarta — Encontro

Chegue tendo estudado e executado os exemplos. No encontro vamos:

  1. Transformar enunciados em árvores de decisão (fluxogramas) e daí em código.
  2. Discutir quando usar else if × switch × &&/|| × aninhamento.
  3. Caçar, ao vivo, os erros da seção 11 — especialmente =/== e break esquecido.

Problema-guia — classificador de IMC: ler peso e altura, calcular o Índice de Massa Corporal (IMC = peso / (altura * altura)) e classificar em faixas.

imc.c
#include <stdio.h>

int main(void) {
    double peso, altura;
    printf("Peso (kg): ");
    scanf("%lf", &peso);
    printf("Altura (m): ");
    scanf("%lf", &altura);

    double imc = peso / (altura * altura);
    printf("IMC = %.1f -> ", imc);

    if (imc < 18.5) {
        printf("Abaixo do peso\n");
    } else if (imc < 25.0) {
        printf("Peso normal\n");
    } else if (imc < 30.0) {
        printf("Sobrepeso\n");
    } else {
        printf("Obesidade\n");
    }
    return 0;
}

Para pensar antes: por que a cadeia usa só imc < X em cada faixa, sem &&? (Dica: reveja a seção 6.)


✍️ Sexta — Estudo dirigido

Exercícios guiados (Beecrowd)

Resolva, em C, na ordem sugerida:

Problema O que exercita
1035 Teste de Seleção 1 if/else com condições combinadas
1038 Lanche switch como tabela de preços
1037 Intervalo cadeia else if com faixas
1041 Coordenadas de um Ponto aninhamento (quadrantes, eixos, origem)
1042 Ordena os 3 Valores condições cuidadosas
1043 Triângulo validação + classificação
1044 Múltiplos uso de % numa condição
1046 Intervalo de Tempo lógica com decisão
1052 Mês switch com default

Desafios para escrever do zero

  1. Sinal do número: ler um inteiro e dizer se é positivo, negativo ou zero.
  2. Par ou ímpar: ler um inteiro e classificar (use % 2).
  3. Maior de três: ler três números e imprimir o maior — faça uma versão com if/else aninhado e outra com o operador ternário.
  4. Classificador de triângulo: ler três lados; primeiro decidir se formam um triângulo (a soma de dois quaisquer > o terceiro); se sim, classificar em equilátero, isósceles ou escaleno.
  5. Calculadora com menu: ler dois números e uma operação (+, -, *, /) como char; usar switch para calcular. Trate a divisão por zero com uma mensagem.
  6. Ano bissexto: ler um ano e dizer se é bissexto (divisível por 4, exceto os divisíveis por 100 que não sejam por 400). Ótimo exercício de &&/||.

Para investigar (curiosidade prática)

  • O que imprime if (5) printf("A\n");? E if (0) printf("B\n");? Relacione com a seção 2 (verdadeiro/falso em C).
  • Reescreva a condição !(idade >= 18 && tem_documento) usando as Leis de De Morgan (seção 7) e confira que o comportamento é o mesmo.

✅ Checklist de autoavaliação

  • Explico o que é controle de fluxo e desenho a decisão como fluxograma.
  • Escrevo if, if/else e cadeias else if corretas, sempre com chaves.
  • Combino condições com &&, ||, ! e sei escrever "x entre a e b".
  • Entendo a avaliação em curto-circuito e sei usá-la para proteger testes.
  • Sei quando aninhar e quando achatar condições.
  • Uso switch com break, conheço o fall-through e as limitações da estrutura.
  • Não caio nas armadilhas =/==, ; após o if e intervalo 0 <= x <= 100.

🔗 Referências e para se aprofundar

  • Material 2026.1 — Aula 02: Estruturas Condicionais.
  • BACKES, André. Linguagem C: completa e descomplicada (estruturas de decisão).
  • KERNIGHAN, B.; RITCHIE, D. The C Programming Language, cap. 3 (controle de fluxo).
  • MIZRAHI, Victorine V. Treinamento em Linguagem C (comandos de decisão).
  • Referência: cppreference — if · switch.
  • Prática: Beecrowd.

Antes de seguir para a Semana 03

Decisões são metade do controle de fluxo; a outra metade é a repetição, que vem na próxima semana. Garanta que você consegue, sozinho(a), escrever um if/else if correto e um switch com break — vamos combiná-los com laços muito em breve.