Resumo
A aula Laços ensinou a escrever uma repetição; esta ensina a escrevê-la bem e a saber por que está correta. Começa pelos padrões com que se constrói a maioria dos laços (o contador, o acumulador, o máximo e o mínimo, a sentinela e o flag), cada um definido pelo que sua variável significa depois de cada iteração. Essa observação conduz ao conceito central: o invariante, uma afirmação sobre as variáveis que vale antes da primeira iteração e depois de cada uma, e que, junto com a condição de saída, implica o resultado. É uma indução sobre o número de iterações, e com ela um laço se projeta ao contrário: primeiro o que deve valer no final, depois o invariante, por último o corpo. A terminação se justifica com a variante, já apresentada na aula Do problema ao algoritmo, e a aula mostra três laços que não terminam: um com condição real, um com contador sem sinal e um cujo contador com sinal estoura, o que é comportamento indefinido. Encerra com o primeiro contato com o depurador do Visual Studio Code, que detém o programa em uma linha e mostra suas variáveis iteração a iteração.
Objetivos de aprendizagem
- Reconhecer em um laço os padrões de contador, acumulador, máximo e sentinela, e usá-los para projetar laços novos.
- Enunciar o invariante de um laço simples e verificar que ele se conserva em uma iteração qualquer.
- Justificar a terminação de um laço com uma quantidade inteira que decresce e é limitada.
- Acompanhar a execução de um laço com o depurador do Visual Studio Code, com um ponto de interrupção e a visão de variáveis.
Padrões
O que uma variável significa
A maioria dos laços não se inventa: monta-se com umas poucas peças que reaparecem em todos os programas. Cada peça é uma variável com uma inicialização antes do laço, uma atualização dentro dele e, sobretudo, um significado que vale depois de cada iteração. Conhecer as peças pelo significado, e não pela forma, é o que permite combiná-las sem erro.
- Um contador vale quantos dados foram processados (ou quantos cumprem uma condição). Inicializa-se em 0, porque antes da primeira iteração nenhum foi processado, e atualiza-se com
++count, só quando há algo a contar. - Um acumulador vale a combinação dos dados processados: sua soma, seu produto. Inicializa-se no elemento neutro da operação (0 para a soma, 1 para o produto), porque a soma de nenhum dado é 0 e o produto de nenhum dado é 1.
- Um máximo vale o maior dos dados processados, e um mínimo, o menor. O máximo de nenhum dado não existe: por isso se inicializa com o primeiro dado, e não com um número escolhido ao acaso.
- Uma sentinela é um valor da entrada que não é um dado, mas o sinal de que os dados terminaram. O padrão exige ler uma vez antes do laço e outra no final do corpo, como fez a aula Laços ao somar até o zero, de modo que a condição examine cada valor antes que seja processado, e a sentinela nunca seja contada como dado.
- Um flag é um
boolque diz se algo ocorreu alguma vez entre os dados processados. Inicializa-se emfalsee passa atruena primeira vez que ocorre; nenhuma iteração posterior o devolve afalse.
Um exemplo com todas as peças
Um registro de chuva anota os milímetros caídos a cada dia. O programa lê primeiro um limiar, e depois as medidas de vários dias, terminadas por um número negativo, que não é uma medida possível e serve de sentinela; escreve quantas medidas há, sua média, o máximo, quantos dias superaram o limiar e se houve algum dia sem chuva.
#include <iostream>#include <print>int main(){ double threshold{}; std::cin >> threshold; int count{0}; // contador: medidas processadas int aboveCount{0}; // contador: medidas acima do limiar double total{0.0}; // acumulador: soma das medidas double largest{0.0}; // máximo: só vale se count > 0 bool dryDay{false}; // flag: houve um dia sem chuva double value{}; std::cin >> value; while (std::cin && value >= 0.0) { if (count == 0 || value > largest) { largest = value; } ++count; total += value; if (value > threshold) { ++aboveCount; } if (value == 0.0) { dryDay = true; } std::cin >> value; } if (count == 0) { std::println("Não há medidas."); } else { std::println("Medidas: {}; média: {:.2f} mm", count, total / count); std::println("Máximo: {:.1f} mm; dias acima de {:.1f} mm: {}", largest, threshold, aboveCount); std::println("Houve dias sem chuva: {}", dryDay ? "sim" : "não"); }}104.5 0 12.5 21.5 7.5 -1Medidas: 5; média: 9.20 mmMáximo: 21.5 mm; dias acima de 10.0 mm: 2Houve dias sem chuva: simQuatro decisões merecem comentário. A condição count == 0 || value > largest inicializa o máximo com o primeiro dado sem uma leitura à parte: na primeira iteração o contador vale 0 e a atribuição ocorre sempre; nas seguintes, só se o dado supera o máximo. A comparação value == 0.0 é exata, porque o valor foi lido e não calculado: o algarismo 0 se converte no zero exato, e a advertência da aula Os números de ponto flutuante contra == se refere a resultados de operações. A condição do laço inclui std::cin, de modo que uma entrada que acaba sem sentinela também termina o laço. E a média só é calculada se há medidas: com count igual a 0, total / count seria , que em ponto flutuante não é um erro, mas o valor “não é um número”, que o programa escreveria sem aviso.
Demonstração
Antes do laço: o limiar é 10, os contadores e o acumulador valem 0, o flag é falso, e a primeira medida, 4.5, já foi lida. O máximo vale 0, mas esse valor não significa nada enquanto
countfor 0.Primeira iteração:
counté 0, de modo que 4.5 passa a ser o máximo; o contador sobe para 1 e o total para 4.5. Lê-se 0.Segunda iteração: 0 não supera o máximo, mas levanta o flag. Lê-se 12.5.
Terceira iteração: 12.5 é o novo máximo e supera o limiar:
aboveCountsobe para 1. Lê-se 21.5.Quarta iteração: 21.5 é o novo máximo e também supera o limiar. Lê-se 7.5.
Quinta iteração: 7.5 não muda nem o máximo nem
aboveCount; o total chega a 46. Lê-se .A condição examina , que é negativo: o laço termina sem processá-lo. A média é .
Observe-se que em cada linha da figura cada variável cumpre seu significado em relação às medidas processadas até ali: total é sua soma, largest seu máximo, dryDay diz se houve um zero entre elas. Essa propriedade, que vale depois de cada iteração, tem nome.
Carregando o conteúdo…