C# while
Imagine mostrar os números de 1 a 100 escrevendo cem Console.WriteLine. Ou somar as vendas de um mês inteiro, dia por dia. Computadores são ótimos para repetir, e as instruções que repetem um trecho de código se chamam laços (em inglês, loops). O mais simples deles é o while ("enquanto"): ele repete um bloco enquanto uma condição for verdadeira. Nesta aula você também vai ver o perigo de todo laço, o laço infinito, e como testá-lo sem medo.
- Como o
whilefunciona: testa a condição, roda o bloco, testa de novo… - As três peças de um laço com contador: início, condição e passo.
- Como contar para trás e como acumular uma soma.
- Como repetir quando você não sabe quantas vezes vai ser.
- O laço infinito: por que acontece e como parar (Ctrl+C e
timeout). - Os erros mais comuns, com as mensagens reais do compilador.
Todos os programas desta aula rodaram com o .NET 10 (C# 14) na máquina fictícia da Ana, na pasta ~/cs, com dotnet run arquivo.cs.
A ideia: "enquanto tiver louça, lave"
Depois do almoço, a Ana olha a pia: enquanto tiver louça suja, ela pega um prato e lava. Antes de cada prato, ela olha de novo. Quando a pia fica vazia, ela para e vai fazer outra coisa. Ela não precisa saber, antes de começar, quantos pratos são.
O while faz exatamente isso:
- Testa a condição ("tem louça?").
- Se for
true, roda o bloco (lava um prato) e volta ao passo 1. - Se for
false, sai do laço e o programa continua depois dele.
Cada passagem pelo bloco se chama uma volta (ou iteração).
1. O primeiro while
int i = 1;
while (i <= 5)
{
Console.WriteLine($"Volta {i}");
i++;
}
Console.WriteLine($"Fim! i agora vale {i}");
$ dotnet run voltas.cs Volta 1 Volta 2 Volta 3 Volta 4 Volta 5 Fim! i agora vale 6
- int i = 1;
- O início: cria o contador antes do laço. O nome
ié tradição para contadores (de "índice"). - while (i <= 5)
- A condição, entre parênteses, como no
if. Tem de darbool. É testada antes de cada volta. - { … }
- O corpo do laço: o que se repete. Chaves sempre, pelo mesmo motivo da aula de if e else.
- i++;
- O passo: soma 1 ao contador (aula ++ e --). Sem ele,
ificaria valendo 1 para sempre (seção 5). - Fim! i agora vale 6
- O laço parou quando
ichegou a 6, porque6 <= 5éfalse. Por isso, depois do laço,ivale 6, e não 5.
Volta por volta
| Teste | i antes | i <= 5? | O que acontece |
|---|---|---|---|
| 1º | 1 | true | mostra Volta 1, i vira 2 |
| 2º | 2 | true | mostra Volta 2, i vira 3 |
| 3º | 3 | true | mostra Volta 3, i vira 4 |
| 4º | 4 | true | mostra Volta 4, i vira 5 |
| 5º | 5 | true | mostra Volta 5, i vira 6 |
| 6º | 6 | false | sai do laço |
Cinco voltas, mas seis testes: o último teste é o que dá false e encerra o laço.
Missão: o programa mostra a tabuada do 7, mas só até o 3. Mude o que for preciso para ele ir de 7 x 1 até 7 x 10.
7 x 1 = 7 7 x 2 = 14 … 7 x 10 = 70
int i = 1;
while (i <= 3)
{
Console.WriteLine($"7 x {i} = {7 * i}");
i++;
}
int i = 1;
while (i <= 10)
{
Console.WriteLine($"7 x {i} = {7 * i}");
i++;
}
2. Contar para trás
Para uma contagem regressiva, o contador começa no maior valor, a condição usa >= e o passo é --:
int s = 10;
while (s >= 1)
{
Console.Write($"{s} ");
s--;
}
Console.WriteLine("Feliz Ano Novo!");
$ dotnet run regressiva.cs 10 9 8 7 6 5 4 3 2 1 Feliz Ano Novo!
O Console.Write (sem o "Line") não pula linha, por isso os números ficaram lado a lado, cada um seguido de um espaço (aula Saída).
3. Acumular: a soma de 1 a 100
Um laço também serve para juntar resultados. A ideia é ter uma variável "acumuladora" que começa em 0 e recebe um pouco a cada volta, como um cofrinho:
int n = 1;
int soma = 0;
while (n <= 100)
{
soma += n;
n++;
}
Console.WriteLine($"1 + 2 + ... + 100 = {soma}");
$ dotnet run soma.cs 1 + 2 + ... + 100 = 5050
- int soma = 0;
- O acumulador começa em zero, antes do laço. Se fosse criado dentro do laço, voltaria a zero a cada volta.
- soma += n;
- O mesmo que
soma = soma + n;(aula Operadores de Atribuição). Na 1ª volta, soma vira 1; na 2ª, 3; na 3ª, 6; … - {soma}
- O resultado é mostrado depois do laço, uma vez só.
4. Quando você não sabe quantas voltas
Até aqui, dava para saber de antemão quantas voltas seriam (5, 10, 100). Para isso, a próxima aula, for, é até mais prática. O while brilha quando o número de voltas depende do que acontece dentro do laço. A Ana guarda R$ 150 por mês e quer saber em quantos meses chega a R$ 1.000:
decimal guardado = 0m;
decimal porMes = 150m;
decimal meta = 1000m;
int meses = 0;
while (guardado < meta)
{
guardado += porMes;
meses++;
Console.WriteLine($"Mês {meses}: R$ {guardado:N2}");
}
Console.WriteLine($"Meta atingida em {meses} meses!");
$ dotnet run poupanca.cs Mês 1: R$ 150,00 Mês 2: R$ 300,00 Mês 3: R$ 450,00 Mês 4: R$ 600,00 Mês 5: R$ 750,00 Mês 6: R$ 900,00 Mês 7: R$ 1.050,00 « 1.050 não é < 1.000: o laço para aqui Meta atingida em 7 meses!
Ninguém escreveu "7" no programa: o laço descobriu sozinho. Mude porMes para 200m e ele para no mês 5; mude a meta para 5000m e ele vai até o mês 34. O dinheiro está em decimal, como sempre neste guia (aula float, double e decimal).
O laço que não roda nenhuma vez
Como a condição é testada antes da primeira volta, se ela já começa false, o corpo nunca roda:
int estoque = 0;
while (estoque > 0)
{
Console.WriteLine("Vendendo uma unidade...");
estoque--;
}
Console.WriteLine("Estoque vazio.");
$ dotnet run zero.cs Estoque vazio. « 0 > 0 é false: zero voltas
Isso é uma qualidade: o while nunca faz uma volta "a mais" quando não devia. Quando você precisa que o corpo rode pelo menos uma vez (por exemplo, para mostrar um menu antes de perguntar se a pessoa quer sair), existe o do while, a próxima aula.
5. O laço infinito
Se a condição nunca ficar falsa, o laço nunca termina. Isso se chama laço infinito, e o motivo mais comum é esquecer o passo. Neste programa falta o i++: i vale 1 para sempre, e 1 <= 5 é sempre true.
int i = 1;
Console.WriteLine("Calculando os dobros...");
while (i <= 5)
{
int dobro = i * 2;
}
Console.WriteLine("Pronto!");
O compilador não reclama: um laço infinito é um programa válido (às vezes ele é até de propósito, como num jogo que roda até o jogador sair). O problema só aparece ao rodar: o programa mostra Calculando os dobros... e fica parado, com o processador trabalhando sem parar, sem nunca chegar ao Pronto!. No terminal, aperte Ctrl+C para interromper o programa.
Como testar sem medo: timeout
Quando você suspeita que um programa pode travar, rode-o com o comando timeout do Linux, que dá um prazo em segundos e encerra o programa se ele passar disso. Foi assim que os laços infinitos deste guia foram testados. Dê um prazo folgado: o primeiro dotnet run de um arquivo novo gasta alguns segundos só para compilar.
$ timeout 15 dotnet run infinito.cs Calculando os dobros... « e nada mais por 15 segundos $ echo $? 124
O echo $? mostra o código de saída do último comando. O 124 é o jeito do timeout dizer "o prazo acabou e eu tive que encerrar o programa". Se o programa tivesse terminado sozinho a tempo, o código seria o dele (normalmente 0).
Um laço infinito também não termina sozinho na caixa "Experimente". Enquanto o programa roda, o botão Rodar vira ■ Parar: clique nele, e o terminal da caixa mostra ^C e [código de saída: 130], como o Ctrl+C no terminal. Se você não parar, o editor para o programa sozinho depois de 15 segundos e avisa que ele rodou demais (laço infinito?). Antes de rodar, confira se o laço tem um passo que leva a condição a ficar falsa.
Laço infinito de propósito: while (true)
Às vezes é mais fácil escrever "repita para sempre" e decidir a saída lá dentro, com break ("quebrar"), que sai do laço na hora:
int vidas = 3;
while (true)
{
Console.WriteLine($"Vidas: {vidas}");
vidas--;
if (vidas == 0)
{
break;
}
}
Console.WriteLine("Fim de jogo.");
$ dotnet run dado.cs Vidas: 3 Vidas: 2 Vidas: 1 Fim de jogo.
O break e o seu parente continue têm uma aula só para eles: break e continue.
6. O ; depois do while
A armadilha do if (…); também existe aqui, e é pior: vira um laço infinito silencioso.
int i = 1;
while (i <= 5);
{
Console.WriteLine($"Volta {i}");
i++;
}
Console.WriteLine("Fim!");
$ timeout 15 dotnet run pv.cs /home/ana/cs/pv.cs(2,15): warning CS0642: Instrução empty possivelmente incorreta $ echo $? 124 « nem "Volta 1" apareceu
- O
;logo depois do)é uma instrução vazia, e ela virou o corpo do laço: "enquantoi <= 5, não faça nada". - Como o corpo não faz nada,
inunca muda, e o laço nunca termina. - O bloco
{ … }de baixo ficou fora do laço e nunca é alcançado.
O aviso CS0642 aponta a coluna 15 da linha 2: o ;. Toda vez que vir esse aviso, apague o ponto e vírgula.
Formas diferentes + qual usar
| Laço | Testa a condição | Quando usar |
|---|---|---|
while (cond) { … } | Antes de cada volta (pode rodar 0 vezes) | Quando o número de voltas depende do que acontece: "até juntar R$ 1.000", "enquanto houver itens". |
do { … } while (cond); | Depois de cada volta (roda pelo menos 1 vez) | Menus e perguntas que precisam aparecer antes do teste. Aula do while. |
for (int i = 0; i < n; i++) { … } | Antes de cada volta | Quando você sabe quantas voltas: de 1 a 10, cada posição de um array. Aula for. |
foreach (var x in lista) { … } | (não tem condição) | Passar por todos os itens de uma coleção. Aula foreach. |
while (true) { … break; … } | Dentro, com break | Quando a decisão de sair fica no meio do corpo. |
Quando dá errado
O que significa: a condição nunca fica false. Causas comuns: esqueceu o passo (i++), o passo vai para o lado errado (i-- numa condição i <= 5), ou há um ; depois do while (…).
Como consertar: aperte Ctrl+C. Depois confira as três peças: o contador começa certo? A condição é a certa? O passo existe, está dentro das chaves e leva a condição a ficar falsa?
warning CS0642: Instrução empty possivelmente incorreta
O que significa: há um ; logo depois do ) do while. O laço tem corpo vazio e, quase sempre, nunca termina.
Como consertar: apague o ;.
error CS0029: Não é possível converter implicitamente tipo "int" em "bool"
$ cat intcond.cs
int vidas = 3;
while (vidas)
{
vidas--;
}
$ dotnet run intcond.cs
/home/ana/cs/intcond.cs(2,8): error CS0029: Não é possível converter implicitamente tipo "int" em "bool"
Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: como no if, a condição do while tem de ser bool. Um número sozinho não serve.
Como consertar: while (vidas > 0).
error CS0165: Uso de variável local não atribuída "i"
$ cat semvalor.cs
int i;
while (i <= 3)
{
Console.WriteLine(i);
i++;
}
$ dotnet run semvalor.cs
/home/ana/cs/semvalor.cs(2,8): error CS0165: Uso de variável local não atribuída "i"
Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: o contador foi declarado sem valor inicial, e a condição tenta lê-lo. Em C, isso compilaria (no máximo com um aviso) e o contador começaria com um valor qualquer, o "lixo" da memória; o C# recusa.
Como consertar: int i = 1; (ou o valor de início que você quiser).
error CS0103: O nome "quadrado" não existe no contexto atual
$ cat dentro.cs
int i = 1;
while (i <= 3)
{
int quadrado = i * i;
i++;
}
Console.WriteLine(quadrado);
$ dotnet run dentro.cs
/home/ana/cs/dentro.cs(7,19): error CS0103: O nome "quadrado" não existe no contexto atual
Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: uma variável criada dentro das chaves do laço só existe lá dentro (e é criada de novo a cada volta).
Como consertar: se você precisa do valor depois do laço, declare a variável antes dele: int quadrado = 0;, e dentro do laço só faça quadrado = i * i;.
O que significa: o famoso erro "por um" (em inglês, off-by-one): < no lugar de <=, ou o contador começando em 0 em vez de 1. i = 1; while (i < 5) dá só 4 voltas.
Como consertar: faça a tabela "volta por volta" da seção 1 para os primeiros e os últimos valores.
Referência rápida
| Forma | O que faz |
|---|---|
while (cond) { … } | Repete o bloco enquanto cond for true. Testa antes de cada volta. |
int i = 1; while (i <= n) { …; i++; } | Conta de 1 a n. |
int i = n; while (i >= 1) { …; i--; } | Conta de n até 1. |
soma += x; dentro do laço | Acumula (com soma criada antes, valendo 0). |
while (true) { … if (…) { break; } } | Repete até um break. |
| Ctrl+C | Interrompe o programa no terminal. |
timeout 15 dotnet run x.cs | Roda com prazo de 15 segundos (código 124 se estourar). |
Exercícios
1. Enquanto
Complete com a palavra do laço:
2. Quantas voltas?
Quantas vezes o corpo deste laço roda? int i = 0; while (i < 4) { i++; }
3. O passo
Complete o passo para a contagem regressiva terminar:
4. O que acontece com int i = 1; while (i <= 5) { Console.WriteLine(i); }?
5. Com int n = 10;, quantas vezes roda while (n < 5) { n++; }?
6. O que quer dizer o código de saída 124 depois de timeout 15 dotnet run x.cs?
Missão final: a poupança da Ana
Missão: a Ana guarda R$ 150 por mês e quer chegar a R$ 1.000. Escreva um while que, enquanto o guardado for menor que a meta, some o valor do mês ao guardado e conte mais um mês. (Cuidado: se esquecer o guardado += porMes;, o laço fica infinito e você vai ter que recarregar a página.) A saída deve ser:
Meta atingida em 7 meses: R$ 1.050,00
decimal guardado = 0m;
decimal porMes = 150m;
decimal meta = 1000m;
int meses = 0;
// escreva o while aqui
Console.WriteLine($"Meta atingida em {meses} meses: R$ {guardado:N2}");
decimal guardado = 0m;
decimal porMes = 150m;
decimal meta = 1000m;
int meses = 0;
while (guardado < meta)
{
guardado += porMes;
meses++;
}
Console.WriteLine($"Meta atingida em {meses} meses: R$ {guardado:N2}");
Depois, mude o porMes para 275m: em quantos meses ela chega lá? E se a meta for 0m? (Zero meses: o laço não roda nenhuma vez.)
Resumo
while (condição) { … }repete o bloco enquanto a condição fortrue. A condição é testada antes de cada volta, então o laço pode rodar 0 vezes.- Laço com contador: início antes (
int i = 1;), condição nos parênteses, passo no fim do corpo (i++). - Para acumular, crie o acumulador antes do laço, valendo 0, e some dentro dele.
- O
whileé o laço certo quando o número de voltas depende do que acontece dentro dele. - Sem o passo, ou com
;depois dowhile (…)(CS0642), o laço é infinito. Pare com Ctrl+C; teste comtimeout(código 124). - A condição tem de ser
bool(CS0029), o contador precisa de valor inicial (CS0165) e variáveis criadas no corpo não existem depois dele (CS0103).