Saltar para o conteúdo
Topos Uranos

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

  1. Reconhecer em um laço os padrões de contador, acumulador, máximo e sentinela, e usá-los para projetar laços novos.
  2. Enunciar o invariante de um laço simples e verificar que ele se conserva em uma iteração qualquer.
  3. Justificar a terminação de um laço com uma quantidade inteira que decresce e é limitada.
  4. 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 bool que diz se algo ocorreu alguma vez entre os dados processados. Inicializa-se em false e passa a true na primeira vez que ocorre; nenhuma iteração posterior o devolve a false.

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.

Programa em C++
#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");    }}
Entrada do programa
104.5 0 12.5 21.5 7.5 -1
Saída
Medidas: 5; média: 9.20 mmMáximo: 21.5 mm; dias acima de 10.0 mm: 2Houve dias sem chuva: sim

Quatro 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 0/00/0, que em ponto flutuante não é um erro, mas o valor “não é um número”, que o programa escreveria sem aviso.

ExemploAs variáveis iteração a iteração

Demonstração

  1. 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 count for 0.

  2. 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.

  3. Segunda iteração: 0 não supera o máximo, mas levanta o flag. Lê-se 12.5.

  4. Terceira iteração: 12.5 é o novo máximo e supera o limiar: aboveCount sobe para 1. Lê-se 21.5.

  5. Quarta iteração: 21.5 é o novo máximo e também supera o limiar. Lê-se 7.5.

  6. Quinta iteração: 7.5 não muda nem o máximo nem aboveCount; o total chega a 46. Lê-se −1-1.

  7. A condição examina −1-1, que é negativo: o laço termina sem processá-lo. A média é 46/5=9.246 / 5 = 9.2.

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.