Iniciante⏱️ 30 min

05. Instruções de Controlo de Fluxo: Seleção

Domina as estruturas de tomada de decisão em C: a instrução if simples e completa, if's encadeados, o problema do 'else pendente' e a seleção múltipla com switch e break.

Instruções de Controlo de Fluxo: Seleção

Até ao momento, os nossos programas seguiam um fluxo linear: cada instrução era executada estritamente em sequência, uma a seguir à outra. Contudo, a capacidade de tomar decisões com base no valor de variáveis é o alicerce de qualquer algoritmo inteligente.

Nesta aula — a primeira parte do estudo sobre Controlo de Fluxo —, vamos explorar a fundo as instruções de seleção:

  1. A instrução de seleção simples e bilateral: if e if ... else.
  2. Estruturas com decisões múltiplas: if's encadeados.
  3. A ambiguidade clássica do “Else Pendente” (Dangling Else) e a sua resolução com blocos de chavetas {}.
  4. A instrução de seleção múltipla: switch, a palavra-chave break e o agrupamento de casos (fall-through).
  5. Exercícios práticos interativos com soluções ocultas que podes revelar com um clique.

1. A Instrução de Seleção Simples: if … else

A instrução if permite condicionar a execução de uma instrução à verificação de uma dada condição lógica.

1. Forma Simples do if

Executa a instrução associada apenas se a condição for verdadeira (diferente de zero):

if (condição) 
    instrucao;

2. Forma Completa do if ... else

Permite bifurcar o fluxo de execução: se a condição for verdadeira executa instrucao1; se for falsa (igual a zero) executa instrucao2:

if (condição)
    instrucao1;
else
    instrucao2;

Regras Sintáticas Essenciais

  1. Parênteses Obrigatórios: Em C, a condição associada ao if tem de estar obrigatoriamente entre parênteses: if (idade >= 18).
  2. Instruções Simples vs. Compostas:
    • Se precisares de executar apenas uma instrução, as chavetas são opcionais (mas recomendadas por clareza).
    • Se precisares de executar duas ou mais instruções, deves agrupá-las obrigatoriamente num bloco de código delimitado por chavetas { ... }.
// Bloco de instruções compostas:
if (saldo >= preco) {
    saldo = saldo - preco;
    printf("Compra aprovada! Saldo restante: %.2f\n", saldo);
} else {
    printf("Saldo insuficiente para efetuar a compra!\n");
}

2. Exemplo Prático: Verificação de Maioridade

Eis a implementação clássica da verificação de maioridade legal:

#include <stdio.h>

int main(void) {
    int idade;

    printf("Digite a idade da pessoa: ");
    if (scanf("%d", &idade) != 1) {
        printf("Erro na leitura da idade!\n");
        return 1;
    }

    if (idade >= 18) {
        printf("Pessoa e' maior de idade.\n");
    } else {
        printf("Pessoa e' menor de idade.\n");
    }

    return 0;
}

3. Decisões Múltiplas com if's Encadeados

Quando um problema envolve mais de duas opções alternativas (por exemplo: determinar se um número é positivo, negativo ou nulo), encadeamos vários if's através da construção else if:

if (condição1)
    instrucao1;
else if (condição2)
    instrucao2;
else if (condição3)
    instrucao3;
    ...
else
    instrucaoN; // Executado se nenhuma das condições anteriores for satisfeita

Exemplo: Classificação de um Número

#include <stdio.h>

int main(void) {
    int num;

    printf("Digite um numero: ");
    scanf("%d", &num);

    if (num > 0) {
        printf("O numero e' positivo.\n");
    } else if (num < 0) {
        printf("O numero e' negativo.\n");
    } else {
        printf("O numero e' igual a zero.\n");
    }

    return 0;
}

4. O Problema Clássico do “Else Pendente” (Dangling Else)

Considera o seguinte fragmento de código com múltiplos if's aninhados:

if (condição1)
    if (condição2)
        if (condição3) 
            instrucao1;
        else 
            instrucao2;
    else 
        instrucao3;
else 
    instrucao4;

À primeira vista, a indentação visual sugere a quem cada else pertence. Mas como é que o compilador de C resolve isto na ausência de chavetas?

⚠️ Regra Fundamental de C:
O else é sempre associado ao if mais próximo que ainda não tenha um else associado, independentemente da forma como o código está indentado no editor!

A Ambiguidade na Prática

Imagina este código:

// Código potencialmente enganoso:
if (condicao1)
    if (condicao2)
        instrucao1;
else
    instrucao3; // A qual if pertence este else?

Mesmo que tenhas alinhado o else com o primeiro if (condicao1), o compilador vai associá-lo ao segundo if (condicao2)!

A Solução Definitiva: Usar Chavetas { ... }

Para contrariar a regra de associação por proximidade e garantir que o compilador entende exatamente a tua intenção lógica, usa sempre chavetas explicitamente:

// Solução inequívoca:
if (condicao1) {
    if (condicao2) {
        instrucao1;
    }
} else {
    // Este else pertence inequivocamente ao primeiro if!
    instrucao3;
}

5. A Instrução de Seleção Múltipla: switch

Quando a seleção da instrução a executar depende da comparação de um valor de tipo ordinal (int ou char) com várias opções discretas constantes, a linguagem C disponibiliza uma estrutura muito mais limpa e legível: o switch.

Sintaxe Geral do switch

switch (seletor) {
    case const1:
        instrucoes1;
        break;
    case const2:
        instrucoes2;
        break;
    ...
    case constN:
        instrucoesN;
        break;
    default:
        instrucoesPadrao;
}

Como Funciona o switch?

  1. O compilador avalia a expressão seletor.
  2. O valor é comparado sequencialmente com cada constante associada aos blocos case.
  3. Quando encontra uma constante coincidente, a execução do programa salta diretamente para essa linha e continua a executar.
  4. O bloco default é opcional e é executado apenas quando o seletor não coincide com nenhum dos casos testados.
  5. As constantes de cada case têm de ser todas diferentes entre si e devem ser valores constantes conhecidos em tempo de compilação (não podem ser variáveis ou intervalos como x > 10).

6. A Palavra-Chave break e o Efeito Fall-Through

A instrução break provoca a saída imediata do bloco switch, saltando para a instrução seguinte à chaveta de fecho }.

💥 Atenção ao Efeito Fall-Through:
Se omitires o break no final de um case, o programa continuará a executar o código do case seguinte, mesmo que o valor não coincida!

Tirar Partido do Fall-Through: Agrupamento de Casos

Por vezes, queremos que várias opções diferentes executem o mesmo bloco de código. O C permite agrupar múltiplos case em cascata:

switch (seletor) {
    case const1:
    case const2:
        instrucoesComuns_1_e_2;
        break;
        
    case const3:
    case const4:
        instrucoesComuns_3_e_4;
        break;
        
    default:
        instrucoesDefault;
}

7. Exemplos Práticos de Aplicação do switch

Exemplo 1: Leitura do Estado Civil (char com maiúsculas e minúsculas)

Repara como agrupamos 's' e 'S' sem duplicar o código:

#include <stdio.h>

int main(void) {
    char estado_civil;

    printf("Qual o estado civil (S/C/V/D)? ");
    scanf(" %c", &estado_civil);

    switch (estado_civil) {
        case 's':
        case 'S':
            printf("Solteiro(a)\n");
            break;
            
        case 'c':
        case 'C':
            printf("Casado(a)\n");
            break;
            
        case 'v':
        case 'V':
            printf("Viuvo(a)\n");
            break;
            
        case 'd':
        case 'D':
            printf("Divorciado(a)\n");
            break;
            
        default:
            printf("Estado Civil Invalido!\n");
    }

    return 0;
}

Exemplo 2: Dia da Semana (Dias Úteis vs. Fim de Semana)

Neste exemplo, o seletor é um número inteiro de 1 a 7:

#include <stdio.h>

int main(void) {
    int dia;

    printf("Digite um valor de 1 a 7 (1=Domingo ... 7=Sábado): ");
    scanf("%d", &dia);

    switch (dia) {
        case 1:
        case 7:
            printf("Fim de semana!\n");
            break;

        case 2:
        case 3:
        case 4:
        case 5:
        case 6:
            printf("Dia de semana (dia de trabalho).\n");
            break;

        default:
            printf("Valor invalido! Deve ser um número entre 1 e 7.\n");
    }

    return 0;
}

8. Exercícios Práticos com Soluções Interativas

Coloca os teus conhecimentos em prática! Tenta resolver os exercícios de forma autónoma antes de clicares para ver a solução.

Exercício 1: Classificador de Triângulos com Validação Geométrica

Enunciado: Escreve um programa que leia os três lados de um triângulo (a, b, c). O programa deve primeiro verificar se os lados formam de facto um triângulo válido (a soma de quaisquer dois lados tem de ser estritamente maior que o terceiro lado). Se for válido, classifica-o em:

  • Equilátero: Todos os três lados iguais.
  • Isósceles: Dois lados iguais e um diferente.
  • Escaleno: Todos os lados diferentes entre si.
👉 Ver Solução Proposta
#include <stdio.h>

int main(void) {
    float a, b, c;

    printf("Introduz os três lados do triângulo (separados por espaço): ");
    if (scanf("%f %f %f", &a, &b, &c) != 3 || a <= 0 || b <= 0 || c <= 0) {
        printf("Entrada inválida! Os lados devem ser números positivos.\n");
        return 1;
    }

    // 1. Validação da desigualdade triangular
    if ((a + b > c) && (a + c > b) && (b + c > a)) {
        // 2. Classificação
        if (a == b && b == c) {
            printf("Triângulo Equilátero (3 lados iguais).\n");
        } else if (a == b || a == c || b == c) {
            printf("Triângulo Isósceles (2 lados iguais).\n");
        } else {
            printf("Triângulo Escaleno (3 lados distintos).\n");
        }
    } else {
        printf("As medidas introduzidas NÃO formam um triângulo válido!\n");
    }

    return 0;
}

Explicação:

  • A condição (a + b > c) && (a + c > b) && (b + c > a) garante a validade geométrica do triângulo.
  • Usamos if ... else if ... else encadeado para testar primeiro a igualdade tripla (a == b && b == c) e depois as igualdades parciais.

Exercício 2: Mini-Calculadora com switch

Enunciado: Cria uma calculadora que leia dois números reais (num1 e num2) e um caractere que represente a operação matemática pretendida ('+', '-', '*', '/'). Utiliza a instrução switch para calcular e apresentar o resultado da operação. No caso da divisão, garante que o divisor não é zero antes de calcular.

👉 Ver Solução Proposta
#include <stdio.h>

int main(void) {
    double num1, num2, resultado;
    char operacao;

    printf("Introduz a expressão (exemplo: 12.5 + 4.2): ");
    // O espaço antes de %c é crucial para descartar espaços ou quebras de linha anteriores
    if (scanf("%lf %c %lf", &num1, &operacao, &num2) != 3) {
        printf("Formato de entrada inválido!\n");
        return 1;
    }

    switch (operacao) {
        case '+':
            resultado = num1 + num2;
            printf("Resultado: %.2lf\n", resultado);
            break;

        case '-':
            resultado = num1 - num2;
            printf("Resultado: %.2lf\n", resultado);
            break;

        case '*':
            resultado = num1 * num2;
            printf("Resultado: %.2lf\n", resultado);
            break;

        case '/':
            if (num2 != 0.0) {
                resultado = num1 / num2;
                printf("Resultado: %.4lf\n", resultado);
            } else {
                printf("Erro Matemático: Divisão por zero impossível!\n");
            }
            break;

        default:
            printf("Operador '%c' não reconhecido! Usa +, -, * ou /.\n", operacao);
    }

    return 0;
}

Explicação:

  • O switch (operacao) inspeciona diretamente o código ASCII do caractere introduzido.
  • Na divisão, um if (num2 != 0.0) aninhado previne erros graves de divisão por zero.
  • Cada caso termina com break para impedir a execução acidental dos casos seguintes.

Exercício 3: Conversor de Notas Qualitativas

Enunciado: Lê uma nota inteira entre 0 e 20 valores e atribui uma classificação descritiva de acordo com a seguinte escala:

  • Menor que 10: "Reprovado"
  • De 10 a 13: "Suficiente"
  • 14 ou 15: "Bom"
  • 16 ou 17: "Muito Bom"
  • De 18 a 20: "Excelente"
  • Qualquer outro valor: "Nota Inválida"
👉 Ver Solução Proposta
#include <stdio.h>

int main(void) {
    int nota;

    printf("Introduz a nota do aluno (0 a 20): ");
    if (scanf("%d", &nota) != 1) {
        printf("Entrada inválida!\n");
        return 1;
    }

    if (nota < 0 || nota > 20) {
        printf("Nota inválida! A nota tem de estar compreendida entre 0 e 20.\n");
    } else if (nota < 10) {
        printf("Classificação: Reprovado\n");
    } else if (nota <= 13) {
        printf("Classificação: Suficiente\n");
    } else if (nota <= 15) {
        printf("Classificação: Bom\n");
    } else if (nota <= 17) {
        printf("Classificação: Muito Bom\n");
    } else {
        printf("Classificação: Excelente\n");
    }

    return 0;
}

Explicação:

  • Ao encadear os else if, aproveitamos o facto de que cada teste já garante que a condição anterior era falsa (por exemplo, quando o código chega a nota <= 13, já sabemos com certeza que nota >= 10).

Resumo da Aula 05

  • A instrução if (condição) bifurca a execução; as condições requerem obrigatoriamente parênteses.
  • Se um if ou else contiver mais de uma instrução, deve ser delimitado por chavetas { ... }.
  • O “Else Pendente” associa-se sempre ao if mais próximo que não tenha else; usam-se chavetas para alterar este comportamento.
  • O switch é a estrutura ideal para seleção baseada em valores inteiros ou caracteres (int/char).
  • A palavra-chave break é mandatória para evitar a continuação involuntária da execução (fall-through).
  • O agrupamento de múltiplos case consecutivos permite partilhar o mesmo bloco de código entre várias condições.