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
- Escrever decisões simples, encadeadas e aninhadas que atendam todos os casos de uma especificação.
- Verificar que os casos de uma cadeia de
ifeelse ifsão disjuntos e exaustivos, e projetar uma entrada de teste por caso. - Usar
switchcomenum classpara valores discretos, e explicar o que o compilador adverte se falta um caso. - Detectar o
elsependente e a queda entre casos de umswitch, 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:
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.
A aula Aritmética mostrou que a % b tem o sinal do dividendo, de modo que com e vale , ao passo que o resto euclidiano, que nunca é negativo, vale 1; e anunciou que a maneira segura de corrigi-lo é somar só quando o resto é negativo. O programa supõe .
#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);}-7 2Resto euclidiano de -7 por 2: 1A variável r já não é constante, porque a decisão pode alterá-la. A figura segue os dois caminhos possíveis.
Demonstração
A execução chega à decisão com
rjá calculado, e a primeira coisa que faz é avaliar a condiçãor < 0: o losango é o único ponto do programa onde os caminhos se separam.Se a condição é verdadeira, como com e 2, executa-se o bloco do
if:rpassa de a 1.Se é falsa, como com 7 e 2, o bloco é saltado por inteiro, e
rconserva o seu valor.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.
#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); }}19001900 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.
#include <print>int main(){ const int stock{12}; if (stock == 0); { std::println("Produto esgotado"); } std::println("Unidades disponíveis: {}", stock);}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 | { | ^Produto esgotadoUnidades disponíveis: 12O 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.
#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));}7 3Maior: 73 unidadesTipo comum: 7.0Sem parênteses: 1Com parênteses: 11As 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.
Carregando o conteúdo…