C# Laços Aninhados

Com um laço, a Ana já sabe repetir uma linha: cinco asteriscos, a tabuada do 7, as notas de um aluno. Mas muita coisa no mundo vem em linhas e colunas: a tabuada inteira, um tabuleiro de xadrez, os assentos de um cinema, os dias de um calendário. Para isso, a ferramenta é colocar um laço dentro de outro. Isso se chama laço aninhado (em inglês, nested loop: "laço no ninho", um dentro do outro, como as bonecas russas). Não há nenhuma palavra nova nesta aula: só o for, o while e o foreach que você já conhece, combinados.

Você vai aprender
  • Como um for dentro de outro for funciona, volta por volta.
  • A diferença entre Console.Write e Console.WriteLine na hora de desenhar linhas e colunas.
  • Como desenhar retângulos e triângulos, montar a tabuada em grade e listar combinações.
  • Quantas vezes o laço de dentro roda (e por que isso cresce tão rápido).
  • Por que o break só sai do laço de dentro, e três jeitos de sair dos dois.
  • Os erros mais comuns: o mesmo nome nos dois laços (CS0136), usar a variável de dentro fora dele (CS0103), a quebra de linha no lugar errado e o ++ na variável errada.

Todos os programas foram rodados com o .NET 10 (dotnet --version = 10.0.112, C# 14) na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cs, com dotnet run arquivo.cs (aula Rodar com dotnet run). Antes desta aula, é bom ter visto o for e o break e continue.


A ideia: o relógio de ponteiros

Olhe um relógio. O ponteiro dos minutos dá uma volta inteira, de 0 a 59, e só então o ponteiro das horas anda um passo. Aí os minutos começam tudo de novo, do zero. Para as horas andarem de 1 até 3, os minutos dão três voltas completas.

  • O ponteiro das horas é o laço de fora: anda devagar, um passo de cada vez.
  • O ponteiro dos minutos é o laço de dentro: a cada passo do de fora, ele dá uma volta inteira, do começo ao fim.

Essa é a regra de ouro desta aula: para cada volta do laço de fora, o laço de dentro roda do começo ao fim.


1. O primeiro laço aninhado

Para ver a ordem das voltas, o programa abaixo só mostra o valor das duas variáveis em cada passo:

for (int i = 1; i <= 3; i++)
{
    for (int j = 1; j <= 2; j++)
    {
        Console.WriteLine($"i = {i}, j = {j}");
    }
}
for (int i = 1; i <= 3; i++)
O laço de fora, o ponteiro das horas: i vale 1, 2 e 3.
{ … }
O corpo do laço de fora. Tudo o que está entre essas chaves se repete 3 vezes, incluindo o outro for.
for (int j = 1; j <= 2; j++)
O laço de dentro, o ponteiro dos minutos. Cada vez que o programa chega nesta linha, ele cria um j novinho, que começa em 1 e vai até 2.
Console.WriteLine($"i = {i}, j = {j}");
Mostra as duas variáveis (o $"…{…}" é a interpolação da aula Interpolação). O de dentro enxerga o i do de fora sem problema nenhum.
i e j
São os nomes tradicionais: i para o laço de fora, j para o de dentro (e k para um terceiro). Quando os laços têm um sentido, prefira nomes que digam o que são: linha e coluna, como nos próximos exemplos.
$ dotnet run voltas.cs
i = 1, j = 1
i = 1, j = 2      « o de dentro deu uma volta inteira com i = 1
i = 2, j = 1      « o de fora andou um passo; o j recomeçou do 1
i = 2, j = 2
i = 3, j = 1
i = 3, j = 2

Repare: o i só muda depois que o j termina. E o j recomeça do 1 todas as vezes. O laço de fora deu 3 voltas, o de dentro deu 2 voltas a cada uma delas, e a linha do meio rodou 3 × 2 = 6 vezes.

Passo a passo

PassoO que aconteceSaída
1i = 1. Chega no for de dentro: cria j = 1. 1 <= 2? Sim.i = 1, j = 1
2j++ → 2. 2 <= 2? Sim.i = 1, j = 2
3j++ → 3. 3 <= 2? Não: o de dentro termina. Volta para o de fora.—
4i++ → 2. 2 <= 3? Sim. Chega no for de dentro de novo: um j novo, valendo 1.i = 2, j = 1
…O mesmo para i = 2 e i = 3.…
fimi++ → 4. 4 <= 3? Não: o de fora termina, e o programa acaba.—

2. Linhas e colunas: Write e WriteLine

Para desenhar, a gente precisa de duas formas de escrever (aula Saída):

  • Console.Write("*") ("escrever") põe o asterisco e fica na mesma linha: o próximo texto sai colado nele.
  • Console.WriteLine() ("escrever linha"), sem nada dentro dos parênteses, só pula para a linha de baixo.

Com isso, a receita de qualquer desenho é: o laço de fora cuida das linhas; o de dentro, das colunas de cada linha; e, quando o de dentro termina, uma quebra de linha.

for (int linha = 1; linha <= 3; linha++)
{
    for (int coluna = 1; coluna <= 5; coluna++)
    {
        Console.Write("*");
    }
    Console.WriteLine();
}
Linha do códigoO que faz
for (int linha = 1; linha <= 3; linha++)Repete 3 vezes: uma por linha do desenho.
for (int coluna = 1; coluna <= 5; coluna++)Em cada linha, repete 5 vezes: uma por coluna.
Console.Write("*");Desenha um asterisco e continua na mesma linha. Roda 3 × 5 = 15 vezes.
Console.WriteLine();Fica fora do laço de dentro e dentro do de fora: roda uma vez por linha, depois dos 5 asteriscos, e pula para baixo.
$ dotnet run retangulo.cs
*****
*****
*****
O lugar da linha é o que manda

Num laço aninhado, a mesma linha de código faz coisas bem diferentes conforme a chave em que ela está. Antes de escrever, pergunte: "isto acontece uma vez por coluna (dentro do de dentro), uma vez por linha (depois do de dentro, ainda no de fora) ou uma vez só (fora de tudo)?". A seção "Quando dá errado" mostra o que sai com o WriteLine no lugar errado.

Missão: o programa desenha uma linha de 6 asteriscos. Coloque tudo dentro de outro for para desenhar 4 linhas de 6 asteriscos, um retângulo.

******
******
******
******
for (int coluna = 1; coluna <= 6; coluna++)
{
    Console.Write("*");
}
Console.WriteLine();
for (int linha = 1; linha <= 4; linha++)
{
    for (int coluna = 1; coluna <= 6; coluna++)
    {
        Console.Write("*");
    }
    Console.WriteLine();
}

3. O laço de dentro pode depender do de fora

No retângulo, toda linha tinha 5 colunas. Mas a condição do laço de dentro pode usar a variável do de fora. Troque o coluna <= 5 por coluna <= linha: a linha 1 ganha 1 asterisco, a linha 2 ganha 2, e assim por diante.

for (int linha = 1; linha <= 5; linha++)
{
    for (int coluna = 1; coluna <= linha; coluna++)
    {
        Console.Write("*");
    }
    Console.WriteLine();
}
$ dotnet run triangulo.cs
*
**
***
****
*****
coluna <= linha
O "tamanho" do laço de dentro muda a cada volta do de fora. Com linha = 1, o de dentro dá 1 volta; com linha = 5, dá 5. No total, 1 + 2 + 3 + 4 + 5 = 15 asteriscos.

Missão: vire o triângulo de cabeça para baixo: a primeira linha com 5 asteriscos e a última com 1. Dica: mude só a condição do laço de dentro. Quantos asteriscos a linha 1 tem de ter? E a linha 5?

*****
****
***
**
*
for (int linha = 1; linha <= 5; linha++)
{
    for (int coluna = 1; coluna <= linha; coluna++)
    {
        Console.Write("*");
    }
    Console.WriteLine();
}
for (int linha = 1; linha <= 5; linha++)
{
    for (int coluna = 1; coluna <= 6 - linha; coluna++)
    {
        Console.Write("*");
    }
    Console.WriteLine();
}

Outra solução que funciona: deixar o de dentro como está e fazer o de fora andar para trás, for (int linha = 5; linha >= 1; linha--).


4. A tabuada em grade

Na aula for, a Ana mostrou a tabuada de um número. Com dois laços, ela monta a grade inteira: o de fora escolhe a linha, o de dentro a coluna, e cada casa é linha * coluna.

for (int linha = 1; linha <= 5; linha++)
{
    for (int coluna = 1; coluna <= 5; coluna++)
    {
        Console.Write($"{linha * coluna,4}");
    }
    Console.WriteLine();
}
linha * coluna
A conta de cada casa: na linha 3, coluna 4, sai 12.
{…,4}
O ,4 dentro das chaves da interpolação é o alinhamento: o número ocupa 4 espaços, encostado à direita. Sem ele, o 5 e o 25 teriam larguras diferentes e as colunas ficariam tortas (mais em Formatar Números).
$ dotnet run tabuada.cs
   1   2   3   4   5
   2   4   6   8  10
   3   6   9  12  15
   4   8  12  16  20
   5  10  15  20  25

Experimente: aumente a tabuada para 10 × 10 (troque os dois 5 por 10). Depois tente {linha * coluna,3} e {linha * coluna,6} e veja as colunas mudarem de largura. O que acontece com {linha * coluna}, sem o ,4?

for (int linha = 1; linha <= 5; linha++)
{
    for (int coluna = 1; coluna <= 5; coluna++)
    {
        Console.Write($"{linha * coluna,4}");
    }
    Console.WriteLine();
}

5. Combinações: tudo com tudo

Laços aninhados também servem para listar todas as combinações de duas coisas. A Ana quer saber quantas senhas de 2 letras dá para fazer só com A, B e C. Um char também pode ser a variável de um for, porque cada letra é um número por baixo e o ++ passa para a próxima (aula char e bool):

int total = 0;
for (char primeira = 'A'; primeira <= 'C'; primeira++)
{
    for (char segunda = 'A'; segunda <= 'C'; segunda++)
    {
        Console.Write($"{primeira}{segunda} ");
        total++;
    }
    Console.WriteLine();
}
Console.WriteLine($"Total: {total} senhas");
$ dotnet run senhas.cs
AA AB AC
BA BB BC
CA CB CC
Total: 9 senhas
int total = 0;
Fica fora dos dois laços: é criado uma vez só e vai somando. Se ficasse dentro do de fora, voltaria a zero a cada linha.
char primeira = 'A'; primeira <= 'C'; primeira++
A primeira letra: A, B e C.
$"{primeira}{segunda} "
As duas letras coladas, e um espaço para separar da próxima senha.
total++;
Conta uma senha por volta do laço de dentro: 3 × 3 = 9.

6. Quantas vezes o de dentro roda?

A conta é sempre a mesma: voltas do de fora × voltas do de dentro. Com laços pequenos, isso nem se nota. Com laços grandes, cresce muito rápido:

int voltasDeFora = 0;
int voltasDeDentro = 0;
for (int i = 0; i < 1000; i++)
{
    voltasDeFora++;
    for (int j = 0; j < 1000; j++)
    {
        voltasDeDentro++;
    }
}
Console.WriteLine($"O laço de fora rodou {voltasDeFora} vezes.");
Console.WriteLine($"O de dentro rodou {voltasDeDentro} vezes.");
$ dotnet run contar.cs
O laço de fora rodou 1000 vezes.
O de dentro rodou 1000000 vezes.     « mil vezes mil: um milhão

Um milhão de voltas ainda é rápido para o computador (uma fração de segundo). Mas três laços de 1000, um dentro do outro, já dariam um bilhão. Quando um programa com laços aninhados fica lento, a primeira coisa a fazer é multiplicar as voltas e ver se dá para fazer menos.


7. break só sai do laço de dentro

A Ana quer descobrir dois números de 1 a 9 cuja soma é 10 e cujo produto é 24. Ela testa todas as duplas e usa o break (aula break e continue) para parar quando achar:

// Dois números de 1 a 9 com soma 10 e produto 24
for (int a = 1; a <= 9; a++)
{
    for (int b = 1; b <= 9; b++)
    {
        if (a + b == 10 && a * b == 24)
        {
            Console.WriteLine($"Achei: {a} e {b}");
            break;
        }
    }
}
Console.WriteLine("Fim da busca");
$ dotnet run busca.cs
Achei: 4 e 6
Achei: 6 e 4      « ué: o break não parou tudo?
Fim da busca

O break sai só do laço em que ele está, o mais de dentro. Quando achou 4 e 6, ele parou o laço do b, mas o laço do a continuou: 5, 6 (e achou 6 e 4), 7, 8, 9. O C# não tem um "break dos dois laços". Há três jeitos de resolver.

Jeito 1: uma variável bool de aviso

bool achou = false;
for (int a = 1; a <= 9 && !achou; a++)
{
    for (int b = 1; b <= 9; b++)
    {
        if (a + b == 10 && a * b == 24)
        {
            Console.WriteLine($"Achei: {a} e {b}");
            achou = true;
            break;
        }
    }
}
Console.WriteLine("Fim da busca");
bool achou = false;
Uma "bandeira" (em inglês, flag), criada fora dos dois laços: "ainda não achei".
a <= 9 && !achou
O de fora só continua se ainda houver números e se ainda não achou (! é o "não", aula Operadores Lógicos).
achou = true; break;
Levanta a bandeira e sai do de dentro. O de fora confere a condição, vê !achou falso e termina também.
$ dotnet run busca2.cs
Achei: 4 e 6
Fim da busca

Jeito 2: goto

O C# tem o goto ("vá para"), que pula direto para um rótulo: um nome seguido de dois-pontos, como fim:. Sair de laços aninhados é um dos poucos usos aceitos dele:

for (int a = 1; a <= 9; a++)
{
    for (int b = 1; b <= 9; b++)
    {
        if (a + b == 10 && a * b == 24)
        {
            Console.WriteLine($"Achei: {a} e {b}");
            goto fim;
        }
    }
}
fim:
Console.WriteLine("Fim da busca");
$ dotnet run busca3.cs
Achei: 4 e 6
Fim da busca

Funciona, mas o goto deixa o caminho do programa difícil de seguir quando é usado à vontade, e muita gente o evita. Use só para isto, e com um rótulo de nome claro.

Jeito 3: um método com return

O jeito mais limpo é colocar a busca num método (uma função) e usar return, que sai do método inteiro, com todos os laços de uma vez. Você vai ver isso em Métodos e Valor de Retorno.

Missão: este é o busca.cs, que acha a resposta duas vezes. Faça ele parar na primeira: a saída tem de ser só Achei: 4 e 6 e Fim da busca. Use a bandeira bool ou o goto.

Achei: 4 e 6
Fim da busca
for (int a = 1; a <= 9; a++)
{
    for (int b = 1; b <= 9; b++)
    {
        if (a + b == 10 && a * b == 24)
        {
            Console.WriteLine($"Achei: {a} e {b}");
            break;
        }
    }
}
Console.WriteLine("Fim da busca");
bool achou = false;
for (int a = 1; a <= 9 && !achou; a++)
{
    for (int b = 1; b <= 9; b++)
    {
        if (a + b == 10 && a * b == 24)
        {
            Console.WriteLine($"Achei: {a} e {b}");
            achou = true;
            break;
        }
    }
}
Console.WriteLine("Fim da busca");

E o continue?

Com o continue vale a mesma regra: ele pula para a próxima volta do laço em que está. Aqui, ele pula as casas em que a linha e a coluna são iguais (a "diagonal"):

for (int linha = 1; linha <= 3; linha++)
{
    for (int coluna = 1; coluna <= 3; coluna++)
    {
        if (linha == coluna)
        {
            continue;
        }
        Console.Write($"({linha},{coluna}) ");
    }
    Console.WriteLine();
}
$ dotnet run continue.cs
(1,2) (1,3)
(2,1) (2,3)
(3,1) (3,2)     « (1,1), (2,2) e (3,3) foram puladas

O continue pulou só o resto daquela volta do laço de dentro. O Console.WriteLine() do de fora rodou normalmente nas três linhas.


8. Misturando laços diferentes

Qualquer laço pode ficar dentro de qualquer outro: for dentro de foreach, while dentro de for, do while dentro de while. Aqui, um foreach passa pelas letras de um texto e um while repete cada letra 3 vezes:

foreach (char letra in "ANA")
{
    int vezes = 0;
    while (vezes < 3)
    {
        Console.Write(letra);
        vezes++;
    }
    Console.Write(' ');
}
Console.WriteLine();
$ dotnet run letras.cs
AAA NNN AAA
foreach (char letra in "ANA")
O de fora: letra vale 'A', 'N' e 'A'.
int vezes = 0;
Fica dentro do de fora, antes do while: volta a zero para cada letra. No for isso acontece sozinho (o int j = 0 faz parte dele); no while, quem zera é você. Se esta linha ficasse lá em cima, fora de tudo, só a primeira letra seria repetida.
Console.Write(' ');
Um espaço depois de cada grupo, uma vez por letra.

Quantos níveis?

Dá para pôr um terceiro laço dentro do segundo, e um quarto dentro do terceiro. Mas, a partir de três, o código fica difícil de ler, e a quantidade de voltas explode (seção 6). Se você precisar de muitos níveis, é sinal de que um pedaço deveria virar um método separado. Para guardar dados em linhas e colunas, existe o array de duas dimensões, que você vai ver em Arrays Multidimensionais, quase sempre percorrido com dois for aninhados, exatamente como nesta aula.


Quando dá errado

error CS0136: o mesmo nome nos dois laços
for (int i = 1; i <= 3; i++)
{
    for (int i = 1; i <= 2; i++)
    {
        Console.WriteLine(i);
    }
}
$ dotnet run mesmo-nome.cs
/home/ana/cs/mesmo-nome.cs(3,14): error CS0136: Um local ou um parâmetro denominado "i" não pode ser declarado neste escopo porque esse nome é usado em um escopo delimitador de local para definir um local ou parâmetro

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que significa: "uma variável local chamada i não pode ser criada aqui, porque esse nome já é usado num escopo de fora". O laço de dentro está dentro do corpo do de fora, onde o i já existe. Se o C# deixasse, você não teria como falar do i de fora. Em inglês: A local or parameter named 'i' cannot be declared in this scope because that name is used in an enclosing local scope. A posição (3,14) é linha 3, coluna 14: o segundo int i.

Como consertar: dê um nome diferente à variável de dentro: j, ou melhor, um nome com sentido (linha e coluna). Dois laços um depois do outro (não um dentro do outro) podem usar o mesmo i à vontade: cada um tem o seu (aula Escopo).

error CS0103: usar a variável do laço de dentro fora dele
for (int linha = 1; linha <= 2; linha++)
{
    for (int coluna = 1; coluna <= 3; coluna++)
    {
        Console.Write("*");
    }
    Console.WriteLine($" ({coluna} colunas)");
}
$ dotnet run fora.cs
/home/ana/cs/fora.cs(7,28): error CS0103: O nome "coluna" não existe no contexto atual

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que significa: "o nome coluna não existe aqui" (The name 'coluna' does not exist in the current context). A variável criada no for só vive dentro dele. Na linha 7, o laço de dentro já terminou e o coluna sumiu. O contrário funciona: o de dentro enxerga o linha do de fora.

Como consertar: se você precisa do valor depois do laço, guarde-o numa variável criada antes dele (por exemplo, int colunas = 3; lá em cima, usada no for e no WriteLine).

Tudo numa linha só: faltou o Console.WriteLine()
for (int linha = 1; linha <= 3; linha++)
{
    for (int coluna = 1; coluna <= 5; coluna++)
    {
        Console.Write("*");
    }
}
$ dotnet run sem-quebra.cs
***************$          « 15 asteriscos colados, e até o prompt grudou neles

O que significa: o programa desenhou as 3 linhas, mas nada mandou pular de linha. O computador não sabe que você "queria" um retângulo.

Como consertar: coloque Console.WriteLine(); depois da chave que fecha o laço de dentro, ainda dentro do de fora.

Um asterisco por linha: o Console.WriteLine() dentro do laço errado
for (int linha = 1; linha <= 2; linha++)
{
    for (int coluna = 1; coluna <= 3; coluna++)
    {
        Console.Write("*");
        Console.WriteLine();
    }
}
$ dotnet run quebra-dentro.cs
*
*
*
*
*
*

O que significa: o WriteLine ficou dentro do laço de dentro e rodou a cada coluna, 2 × 3 = 6 vezes.

Como consertar: passe-o para baixo da chave } do laço de dentro. Uma boa indentação (os 4 espaços de cada nível) ajuda a ver onde cada linha está.

Asteriscos sem fim: o ++ na variável errada
for (int linha = 1; linha <= 3; linha++)
{
    for (int coluna = 1; coluna <= 5; linha++)
    {
        Console.Write("*");
    }
    Console.WriteLine();
}
$ dotnet run i-errado.cs
*********************************************************************************
*********************************************************************************
*********************************************************************************   « a tela se enche sem parar
^C                « a Ana aperta Ctrl+C
$ echo $?
130

O que significa: o laço de dentro faz linha++ em vez de coluna++. O coluna fica em 1 para sempre, 1 <= 5 é sempre verdade, e o laço nunca termina: um laço infinito. O compilador não avisa nada, porque o código é válido. O Ctrl+C interrompe o programa, e o código de saída 130 quer dizer "interrompido pelo Ctrl+C".

Como consertar: confira as três partes de cada for: a variável que é criada, a que é testada e a que anda têm de ser a mesma. Copiar e colar um for para fazer o de dentro é a origem mais comum deste erro.


Referência rápida

SituaçãoComo fica
Laço dentro de laçofor (…) { for (…) { … } }: para cada volta do de fora, o de dentro roda inteiro
Quantas vezes o de dentro rodavoltas do de fora × voltas do de dentro (3 × 5 = 15)
Desenhar linhas e colunasde fora = linhas; de dentro = colunas com Console.Write; Console.WriteLine() depois do de dentro
Triânguloa condição de dentro usa a variável de fora: coluna <= linha
Colunas alinhadas$"{valor,4}": ocupa 4 espaços, encostado à direita
Nomesi, j, k, ou nomes com sentido (linha, coluna); nunca o mesmo nome nos dois (CS0136)
break / continueValem só para o laço em que estão (o mais de dentro)
Sair dos dois laçosbandeira bool na condição do de fora, goto rotulo;, ou return dentro de um método
Contador que zera a cada linhaCrie-o dentro do de fora, antes do de dentro
Contador do totalCrie-o fora dos dois laços

Exercícios

1. Quantas vezes?

Quantas vezes o Console.Write("*") roda neste código?

for (int i = 0; i < 4; i++)
{
    for (int j = 0; j < 3; j++)
    {
        Console.Write("*");
    }
}
Roda vezes.

2. O triângulo

Complete a condição do laço de dentro para cada linha ter tantos asteriscos quanto o seu número (linha 1: *; linha 2: **…):

for (int coluna = 1; ; coluna++)

3. Pular de linha

Que comando, colocado logo depois do laço de dentro, faz o desenho pular para a próxima linha?

4. Num laço aninhado, o break que está dentro do laço de dentro…

5. O que este código mostra?

for (int i = 1; i <= 2; i++)
    for (int j = 1; j <= 2; j++)
        Console.Write($"{i}{j} ");

6. O compilador mostrou error CS0136: Um local ou um parâmetro denominado "i" não pode ser declarado neste escopo. O que aconteceu?

Missão final: o tabuleiro de xadrez

Missão: o programa desenha 4 linhas de 8 casas, todas #. Faça as casas alternarem entre # (escura) e . (clara), como num tabuleiro. Dica: some a linha e a coluna. Quando a soma é par ((linha + coluna) % 2 == 0), a casa é escura; quando é ímpar, é clara.

#.#.#.#.
.#.#.#.#
#.#.#.#.
.#.#.#.#
for (int linha = 0; linha < 4; linha++)
{
    for (int coluna = 0; coluna < 8; coluna++)
    {
        Console.Write('#');
    }
    Console.WriteLine();
}
for (int linha = 0; linha < 4; linha++)
{
    for (int coluna = 0; coluna < 8; coluna++)
    {
        if ((linha + coluna) % 2 == 0)
        {
            Console.Write('#');
        }
        else
        {
            Console.Write('.');
        }
    }
    Console.WriteLine();
}

Por que a soma funciona? Andar uma casa para o lado ou para baixo muda a soma em 1, e ela passa de par para ímpar (ou o contrário): é exatamente o que faz a cor trocar. A casa (0, 0) tem soma 0, par, e começa escura. Com o operador ternário (aula Operador Ternário), o if inteiro cabe numa linha: Console.Write((linha + coluna) % 2 == 0 ? '#' : '.');.


Resumo

  • Um laço aninhado é um laço dentro do corpo de outro. Para cada volta do de fora, o de dentro roda do começo ao fim, como os ponteiros de um relógio.
  • O corpo do de dentro roda voltas de fora × voltas de dentro vezes. Isso cresce rápido: 1000 × 1000 = um milhão.
  • Para desenhar, o de fora cuida das linhas, o de dentro das colunas (Console.Write), e um Console.WriteLine() fecha cada linha, depois do de dentro.
  • A condição do de dentro pode usar a variável do de fora (coluna <= linha): é assim que se faz um triângulo.
  • Cada laço precisa de uma variável com nome próprio (CS0136), e a variável do de dentro não existe fora dele (CS0103).
  • break e continue valem só para o laço mais de dentro. Para sair dos dois, use uma bandeira bool, um goto ou um return num método.
  • Qualquer laço pode ficar dentro de qualquer outro (foreach, while, for). No while, lembre de zerar o contador do de dentro a cada volta do de fora.