15 Desvirtuação das repetições
A estrutura do for
, embora seja bem objetiva indicando iniciação, condição e incremento (seus três elementos de operação), ela é bastante flexível do ponto de vista de seu uso pelo programador.
Um indicador dessa liberdade dada ao programador é que todos os elementos são opcionais, além de permitirem outros elementos.
15.0.1 Laços infinitos
Em termos de argumentos opcionais, o trecho de código seguinte pode ser considerado.
for (;;)
("Faça algo!\n"); printf
Para esta repetição, não há iniciação, nem condição, nem incremento. Seu comportamento, assim, funciona como um “repita o comando para sempre”, ou seja, um laço infinito.
Laços infinitos, embora não se apresentem como uma solução algorítmica em si, não são incomuns em programas. Por exemplo, o programa que gerencia um aparelho eletrônico simples como um forno de micro-ondas não foi feito para terminar: ele inicia ao se ligar o aparelho na tomada e só para quando o plugue for retirado.
Embora o esquema for (;;)
seja usado, recomenda-se que laços infinitos usem while (true)
para dar essa ênfase.
Na prática, basta não ter a condição de continuidade especificada para se ter a repetição infinita. Segue outro exemplo simples com repetições infinitas usando o for
, que faz a contagem cíclica de 0 a 9 (contagem modular).
for (int i = 0;; i = (i + 1) % 10)
("%d ", i); printf
15.0.2 Término forçado for
Enquanto se escreve um programa, o uso da repetição com for
intuitivamente indica que um determinado número de repetições vai ocorrer. Muitos programadores deturpam essa percepção artificialmente interrompendo a repetição.
Ao considerar o código seguinte, a expectativa é que a leitura seja feita cinco vezes. Na repetição, porém, há um if
que altera o valor de i
de forma a terminar a repetição.
/*
Leitura e apresentação de valores inteiros
Requer: uma sequência de até 5 valores inteiros não negativos ou uma
sequência encerrada por um valor negativo
Assegura: a apresentação de cada valor lido na tela
*/
#include <stdio.h>
int main(void) {
("Digite até 5 valores inteiros não negativos:\n");
printffor(int i = 0; i < 5; i++) {
char entrada[160];
("> ");
printf(entrada, sizeof entrada, stdin);
fgetsint valor;
(entrada, "%d", &valor);
sscanf
(" +-- digitado %d.\n", valor);
printfif (valor < 0)
= 5;
i }
return 0;
}
Digite até 5 valores inteiros não negativos:10
>
+-- digitado 10.0
>
+-- digitado 0.-5
> +-- digitado -5.
O programa não está incorreto no sentido de que encerra as leituras ao encontrar um valor negativo. O problema é que esse encerramento prematuro em função do valor lido é mascarado e exige por parte de outro programador que analise os comandos uma atenção extra para entender que um “para i de 1 até 5” não será sempre de 1 a 5.
Em programas mais complexos e com mais variáveis, o uso desse expediente de término forçado pode ficar mascarado o suficiente para que outro programador, ao fazer uma modificação, sequer note essa possibilidade e introduza um erro ou instabilidade no código.
Neste caso, como o número de vezes da repetição é variável, o código ficaria mais claro usando o do while
, por exemplo. Assim, não há indução no código a um comportamento que o programa não tem e deixa explícitas as duas condições de parada da repetição.
/*
Leitura e apresentação de valores inteiros
Requer: uma sequência de até 5 valores inteiros não negativos ou uma
sequência encerrada por um valor negativo
Assegura: a apresentação de cada valor lido na tela
*/
#include <stdio.h>
int main(void) {
("Digite até 5 valores inteiros não negativos:\n");
printfint i = 0;
int valor;
do {
char entrada[160];
("> ");
printf(entrada, sizeof entrada, stdin);
fgets(entrada, "%d", &valor);
sscanf
(" +-- digitado %d.\n", valor);
printf++;
i} while (valor >= 0 && i < 5);
return 0;
}
Digite até 5 valores inteiros não negativos:10
>
+-- digitado 10.0
>
+-- digitado 0.-5
> +-- digitado -5.
15.1 Laços while
infinitos, mas nem tanto
Não é incomum encontrar códigos em repositórios que utilizem falsos laços infinitos. Segue um programa exemplo desse recurso.
/*
Leitura e apresentação de valores não negativos
Requer: uma sequência de 0 ou mais de inteiros não negativos seguida
por um valor negativo usado como sentinela
Assegura: a apresentação de cada valor da sequência na tela
*/
#include <stdio.h>
#include <stdbool.h>
int main(void) {
while (true) {
char entrada[160];
("> ");
printfint valor;
(entrada, sizeof entrada, stdin);
fgets(entrada, "%d", &valor);
sscanfif (valor < 0)
break;
(" +-- digitado %d.\n", valor);
printf}
return 0;
}
12
>
+-- digitado 12.100
>
+-- digitado 100.18
>
+-- digitado 18.1
>
+-- digitado 1.-5 >
Embora o while (true)
seja um indicador sutil de que talvez não se espere uma repetição infinita, ele requer que o código seja analisado para identificar onde a condição de parada ocorre. No contexto, o comando break
interrompe sumariamente a repetição, desestruturando o código.
Somente se deve usar o brake
para interromper repetições quando essa for realmente a melhor opção, ou seja, quando for uma exceção! Caso contrário, o emprego de while
e do while
são opções melhores.
O programa seguinte é uma versão estruturado com a mesma funcionalidade usando uma condição explícita no while
.
/*
Leitura e apresentação de valores não negativos
Requer: uma sequência de 0 ou mais de inteiros não negativos seguida
por um valor negativo usado como sentinela
Assegura: a apresentação de cada valor da sequência na tela
*/
#include <stdio.h>
int main(void) {
char entrada[160];
// Primeira leitura
("> ");
printf(entrada, sizeof entrada, stdin);
fgetsint valor;
(entrada, "%d", &valor);
sscanf
while (valor >= 0) {
(" +-- digitado %d.\n", valor);
printf
// Próxima leitura
("> ");
printf(entrada, sizeof entrada, stdin);
fgets(entrada, "%d", &valor);
sscanf}
return 0;
}
12
>
+-- digitado 12.100
>
+-- digitado 100.18
>
+-- digitado 18.1
>
+-- digitado 1.-5 >
Uma versão com do while
é apresentada na sequência, com destaque de que a apresentação do valor na tela é feita somente para valores não negativos.
/*
Leitura e apresentação de valores não negativos
Requer: uma sequência de 0 ou mais de inteiros não negativos seguida
por um valor negativo usado como sentinela
Assegura: a apresentação de cada valor da sequência na tela
*/
#include <stdio.h>
int main(void) {
char entrada[160];
int valor;
do {
("> ");
printf(entrada, sizeof entrada, stdin);
fgets(entrada, "%d", &valor);
sscanf
if (valor >= 0)
(" +-- digitado %d.\n", valor);
printf} while (valor >= 0);
return 0;
}
12
>
+-- digitado 12.100
>
+-- digitado 100.18
>
+-- digitado 18.1
>
+-- digitado 1.-5 >
15.2 O comando break
nas repetições
O uso do break
para encerrar repetições não é, em si, um erro de programação ou uma falha em si. Em programas mais complexos, com aninhamento de repetições, a indicação explícita de todas as condições que mantém ou encerram uma repetição pode tornar o código difícil de entender.
O impacto no código das interrupções sumárias com break
podem ser atenuadas usando-se um recurso de programação interessante: variáveis lógicas. As variáveis lógicas acrescentam ao código uma informação importante, que é o significado da variável dado por seu nome.
Uma versão de um programa para a leitura de inteiros até encontrar um valor negativo como o usado na Seção 15.1 é apresentado na sequência.
/*
Leitura e apresentação de valores não negativos
Requer: uma sequência de 0 ou mais de inteiros não negativos seguida
por um valor negativo usado como sentinela
Assegura: a apresentação de cada valor da sequência na tela
*/
#include <stdio.h>
#include <stdbool.h>
int main(void) {
char entrada[160];
int valor;
bool encontrou_sentinela = false;
do {
("> ");
printf(entrada, sizeof entrada, stdin);
fgets(entrada, "%d", &valor);
sscanf
if (valor < 0)
= true;
encontrou_sentinela else
(" +-- digitado %d.\n", valor);
printf} while (!encontrou_sentinela);
return 0;
}
12
>
+-- digitado 12.100
>
+-- digitado 100.18
>
+-- digitado 18.1
>
+-- digitado 1.-5 >
Com o nome adequado, uma variável lógica somente acrescenta significado ao código: o do while
, por exemplo, pode ler entendido como “faça a leitura e apresentação enquanto não encontrar sentinela”.
Para os programadores que evitam novas variáveis para economizar memória (preocupação legítima!), sempre se deve também considerar a clareza do código. Além disso, uma variável bool
em uma máquina com 8GiB de memória principal consome 0,0000002% do total disponível1.
O cálculo considera que
sizeof (bool)
é um byte, valor usado pelo compilador durante a escrita desse livro.↩︎