Saltar para o conteúdo
Topos Uranos

Resumo

Um programa que apenas encadeia instruções faz sempre o mesmo, quaisquer que sejam os seus dados; para que responda a eles, precisa decidir. Esta aula estuda as decisões de C++: if e else, que escolhem entre dois caminhos segundo uma condição; as cadeias de else if, que escolhem entre vários, e nas quais a ordem das condições decide que caso cada ramo atende; as decisões aninhadas e a armadilha do else pendente; e switch com enum class, que escolhem entre valores discretos com nome, com um compilador que avisa se falta um caso ou se um cai no seguinte. O conceito que unifica tudo é a análise por casos: uma decisão correta parte o conjunto das entradas em casos disjuntos que o cobrem por completo, e cada caso se verifica com um teste no seu interior e outro em cada uma das suas bordas.

Objetivos de aprendizagem

  1. Escrever decisões simples, encadeadas e aninhadas que atendam todos os casos de uma especificação.
  2. Verificar que os casos de uma cadeia de if e else if são disjuntos e exaustivos, e projetar uma entrada de teste por caso.
  3. Usar switch com enum class para valores discretos, e explicar o que o compilador adverte se falta um caso.
  4. Detectar o else pendente e a queda entre casos de um switch, e corrigi-los.

if e else

A forma de uma decisão

A aula Do problema ao algoritmo apresentou três formas com as quais se constrói todo algoritmo: a sequência, a decisão e a repetição. Até aqui os programas do curso foram sequências, salvo algum if antecipado na unidade 1; esta aula escreve em C++ a segunda forma. Uma decisão avalia uma condição, isto é, uma expressão de tipo bool como as da aula Relações e lógica, e executa um bloco se a condição é verdadeira e outro se é falsa:

Código C++
if (condição) {    // executa-se se a condição é verdadeira} else {    // executa-se se a condição é falsa}

A condição vai sempre entre parênteses. A parte else é opcional: sem ela, se a condição é falsa nada se faz e o programa segue com a instrução posterior à decisão. Em ambos os casos os dois caminhos se reúnem no final, de modo que a decisão, vista de fora, é uma só instrução com uma entrada e uma saída, como exigia a aula 1.1.

ExemploO resto euclidiano

A aula Aritmética mostrou que a % b tem o sinal do dividendo, de modo que com a=−7a = -7 e b=2b = 2 vale −1-1, ao passo que o resto euclidiano, que nunca é negativo, vale 1; e anunciou que a maneira segura de corrigi-lo é somar bb só quando o resto é negativo. O programa supõe b>0b > 0.

Programa em C++
#include <iostream>#include <print>​int main(){    int a{};    int b{};    std::cin >> a >> b;    int r{a % b};    if (r < 0) {        r = r + b;    }    std::println("Resto euclidiano de {} por {}: {}", a, b, r);}
Entrada do programa
-7 2
Saída
Resto euclidiano de -7 por 2: 1

A variável r já não é constante, porque a decisão pode alterá-la. A figura segue os dois caminhos possíveis.

Demonstração

  1. A execução chega à decisão com r já calculado, e a primeira coisa que faz é avaliar a condição r < 0: o losango é o único ponto do programa onde os caminhos se separam.

  2. Se a condição é verdadeira, como com −7-7 e 2, executa-se o bloco do if: r passa de −1-1 a 1.

  3. Se é falsa, como com 7 e 2, o bloco é saltado por inteiro, e r conserva o seu valor.

  4. Ambos os caminhos se reúnem no mesmo ponto, e dali em diante o programa segue igual nos dois casos: a instrução que escreve o resultado executa-se sempre.

Com else, a decisão escolhe entre duas ações que se excluem. A condição de ano bissexto da aula Relações e lógica produz um bool; uma decisão converte-o numa conduta do programa.

Programa em C++
#include <iostream>#include <print>​int main(){    int year{};    std::cin >> year;    const bool isLeap{(year % 4 == 0 && year % 100 != 0) || year % 400 == 0};    if (isLeap) {        std::println("{} é bissexto: fevereiro tem 29 dias.", year);    } else {        std::println("{} não é bissexto: fevereiro tem 28 dias.", year);    }}
Entrada do programa
1900
Saída
1900 não é bissexto: fevereiro tem 28 dias.

Dar nome à condição, como isLeap, separa o que se decide do que se faz com a decisão.

Chaves sempre

A gramática de C++ permite omitir as chaves quando o bloco tem uma só instrução: if (r < 0) r = r + b; é válido. Ora, sem chaves a decisão governa só a instrução seguinte, e o que o leitor julga governado depende da indentação, que o compilador ignora. Se isto é assim, toda modificação posterior (acrescentar uma segunda instrução, comentar a primeira) pode mudar o significado sem que o texto o mostre. Daí se segue a regra do curso: o bloco de um if ou de um else leva sempre chaves, ainda que contenha uma só instrução.

O erro mais curto de todos confirma a regra. Um ponto e vírgula depois dos parênteses é uma instrução vazia, e a decisão governa essa instrução vazia; o bloco que segue já não pertence ao if e executa-se sempre.

Programa em C++
#include <print>​int main(){    const int stock{12};    if (stock == 0);    {        std::println("Produto esgotado");    }    std::println("Unidades disponíveis: {}", stock);}
Avisos do compilador
main.cpp: In function ‘int main()’:main.cpp:6:20: warning: suggest braces around empty body in an ‘if’ statement [-Wempty-body]    6 |     if (stock == 0);      |                    ^main.cpp:6:5: warning: this ‘if’ clause does not guard... [-Wmisleading-indentation]    6 |     if (stock == 0);      |     ^~main.cpp:7:5: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the ‘if’    7 |     {      |     ^
Saída
Produto esgotadoUnidades disponíveis: 12

O programa anuncia que o produto está esgotado embora restem 12 unidades. O GCC dá dois avisos: -Wempty-body sugere chaves em volta do corpo vazio, e -Wmisleading-indentation assinala que o bloco está indentado como se o if o governasse. O outro erro clássico, if (x = 0) em vez de if (x == 0), foi visto na aula Compilar, executar e configurar: a atribuição vale zero, que se converte em false, e -Wparentheses avisa. Em ambos os casos o programa compila; sem os avisos, o erro passaria despercebido.

As chaves têm mais uma consequência: uma variável declarada dentro do bloco de um if existe só ali. Se um valor calculado num ramo é usado depois da decisão, a variável declara-se antes, como r no exemplo do resto; o escopo será estudado na aula Parâmetros e escopo.

O operador condicional

Quando os dois ramos de uma decisão só escolhem um valor, C++ oferece uma forma mais breve: condição ? a : b vale a se a condição é verdadeira e b se é falsa, e só avalia o elemento escolhido. Diferentemente de if, que é uma instrução, o operador condicional é uma expressão: tem um valor e um tipo, e pode ir onde vai qualquer expressão, por exemplo na inicialização de uma constante, que um if não permitiria sem renunciar a const.

Programa em C++
#include <iostream>#include <print>​int main(){    int a{};    int b{};    std::cin >> a >> b;    const int larger{a > b ? a : b};    std::println("Maior: {}", larger);    std::println("{} {}", b, b == 1 ? "unidade" : "unidades");    const auto mixed{a > b ? a : 0.5};    std::println("Tipo comum: {:.1f}", mixed);    std::println("Sem parênteses: {}", 10 + a > b ? 1 : 0);    std::println("Com parênteses: {}", 10 + (a > b ? 1 : 0));}
Entrada do programa
7 3
Saída
Maior: 73 unidadesTipo comum: 7.0Sem parênteses: 1Com parênteses: 11

As duas primeiras linhas mostram o uso que esclarece: escolher o maior e fazer uma palavra concordar com o número, sem repetir a instrução inteira em dois ramos. A terceira mostra que a expressão tem um só tipo, qualquer que seja o ramo escolhido: se os dois resultados são de tipos distintos, convertem-se a um tipo comum com as conversões aritméticas usuais da aula Aritmética; aqui, um int e um double dão double, e o 7 escolhido guarda-se como 7.0. As duas últimas mostram a precedência: o operador condicional está entre os de prioridade mais baixa, de modo que sem parênteses a condição é 10 + a > b e o valor, 1, em vez de 11. Quando o condicional faz parte de uma expressão maior, vai entre parênteses.

O operador obscurece assim que se lhe pede mais do que escolher um valor. Aninhado, como em a > b ? (a > c ? a : c) : (b > c ? b : c), obriga a reconstruir mentalmente uma decisão aninhada sem as chaves que a tornam legível; e usado pelos seus efeitos, como em ok ? std::println("sim") : std::println("não"), é um if disfarçado. A regra do curso é usá-lo só para escolher entre dois valores breves, sem aninhá-lo; todo o resto se escreve com if.