C# Arrays Multidimensionais e Jagged
Na aula Arrays, a Ana guardou as notas de uma turma numa fileira: uma nota por aluno. Mas cada aluno faz quatro provas. Agora os dados formam uma tabela, com uma linha por aluno e uma coluna por prova, como numa planilha. O C# tem dois jeitos de guardar tabelas: o array retangular (int[,]), em que todas as linhas têm o mesmo tamanho, e o array de arrays (int[][]), que em inglês se chama jagged array, "array dentado", porque as linhas podem ter tamanhos diferentes, como os dentes de um serrote.
- Como criar uma tabela com
int[,]e acessar uma casa comnotas[linha, coluna]. - O que
GetLength(0),GetLength(1),LengtheRankdizem. - Como percorrer a tabela com dois
foraninhados e comforeach. - Como criar um array de arrays (
int[][]) com linhas de tamanhos diferentes. - Qual dos dois escolher, e como seria uma terceira dimensão.
- Os erros mais comuns:
CS0022(vírgula × colchetes),CS0847,CS9174, oLengthno lugar doGetLengthe a linha que ficounull.
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. Antes desta aula, veja Arrays e Laços Aninhados: uma tabela quase sempre é percorrida com dois laços, um dentro do outro.
A ideia: as poltronas do cinema
Num cinema, ninguém diz "poltrona 37". Diz "fila C, poltrona 5": dois números para achar um lugar. Um array comum é uma fileira só, com um número por portinha. Um array de duas dimensões é a sala inteira:
- a primeira coordenada é a linha (a fila);
- a segunda é a coluna (a poltrona dentro da fila);
- as duas começam em 0, como nos arrays comuns.
Num cinema comum, toda fila tem o mesmo número de poltronas: é o array retangular. Num teatro antigo, as filas da frente são mais curtas que as do fundo: é o array de arrays, em que cada fila é uma fileira separada, do tamanho que precisar.
1. O array retangular: int[,]
int[,] notas =
{
{ 7, 8, 6, 9 },
{ 5, 6, 7, 8 },
{ 10, 9, 9, 10 },
};
Console.WriteLine($"Aluno 2, prova 3: {notas[1, 2]}");
Console.WriteLine($"Linhas: {notas.GetLength(0)}");
Console.WriteLine($"Colunas: {notas.GetLength(1)}");
Console.WriteLine($"Total de elementos: {notas.Length}");
Console.WriteLine($"Dimensões: {notas.Rank}");
- int[,]
- "Array de
intcom duas dimensões". A vírgula dentro dos colchetes separa as dimensões:[,]são duas,[,,]seriam três. - { { 7, 8, 6, 9 }, … }
- Os valores, uma linha por par de chaves. São 3 linhas de 4 números: o compilador conta e cria uma tabela 3 × 4. A vírgula depois da última linha é opcional e facilita acrescentar outra.
- notas[1, 2]
- Linha 1 (o segundo aluno), coluna 2 (a terceira prova): 7. Os dois índices vão dentro dos mesmos colchetes, separados por vírgula.
- GetLength(0)
- "Pegar o comprimento" da dimensão 0, a das linhas: 3.
- GetLength(1)
- O comprimento da dimensão 1, a das colunas: 4.
- Length
- O total de casas: 3 × 4 = 12. Cuidado: não é o número de linhas.
- Rank
- "Posto", quantas dimensões: 2.
$ dotnet run grade.cs Aluno 2, prova 3: 7 Linhas: 3 Colunas: 4 Total de elementos: 12 Dimensões: 2
| coluna 0 (prova 1) | coluna 1 (prova 2) | coluna 2 (prova 3) | coluna 3 (prova 4) | |
|---|---|---|---|---|
| linha 0 (aluno 1) | [0, 0] = 7 | [0, 1] = 8 | [0, 2] = 6 | [0, 3] = 9 |
| linha 1 (aluno 2) | [1, 0] = 5 | [1, 1] = 6 | [1, 2] = 7 | [1, 3] = 8 |
| linha 2 (aluno 3) | [2, 0] = 10 | [2, 1] = 9 | [2, 2] = 9 | [2, 3] = 10 |
Para arrays comuns, o guia usa a expressão de coleção com colchetes, [7, 8, 6] C# 12. Ela não funciona com arrays retangulares: int[,] m = [[1, 2], [3, 4]]; dá error CS9174 (veja "Quando dá errado"). Para int[,], use as chaves.
Missão: mostre a nota do aluno 3 na prova 1. Lembre: as duas contagens começam no 0. A saída tem de ser Aluno 3, prova 1: 10.
Aluno 3, prova 1: 10
int[,] notas =
{
{ 7, 8, 6, 9 },
{ 5, 6, 7, 8 },
{ 10, 9, 9, 10 },
};
Console.WriteLine($"Aluno 3, prova 1: {notas[0, 0]}"); // troque os índices
int[,] notas =
{
{ 7, 8, 6, 9 },
{ 5, 6, 7, 8 },
{ 10, 9, 9, 10 },
};
Console.WriteLine($"Aluno 3, prova 1: {notas[2, 0]}");
2. Percorrer a tabela
Com dois for aninhados: o de fora anda pelas linhas, o de dentro pelas colunas de cada linha. A Ana monta o boletim, com a média de cada aluno:
int[,] notas =
{
{ 7, 8, 6, 9 },
{ 5, 6, 7, 8 },
{ 10, 9, 9, 10 },
};
for (int aluno = 0; aluno < notas.GetLength(0); aluno++)
{
int soma = 0;
Console.Write($"Aluno {aluno + 1}:");
for (int prova = 0; prova < notas.GetLength(1); prova++)
{
Console.Write($"{notas[aluno, prova],4}");
soma += notas[aluno, prova];
}
Console.WriteLine($" | média {soma / (double)notas.GetLength(1):F2}");
}
- aluno < notas.GetLength(0)
- O de fora passa pelas 3 linhas.
- int soma = 0;
- Dentro do de fora: zera para cada aluno. Se ficasse lá em cima, as notas de todos se misturariam.
- prova < notas.GetLength(1)
- O de dentro passa pelas 4 colunas da linha atual.
- notas[aluno, prova]
- A casa da linha
aluno, colunaprova. - {…,4}
- Cada nota ocupa 4 espaços, alinhada à direita (o mesmo truque da tabuada em Laços Aninhados).
- :F2
- A média com duas casas decimais (aula Formatar Números).
$ dotnet run boletim.cs Aluno 1: 7 8 6 9 | média 7,50 Aluno 2: 5 6 7 8 | média 6,50 Aluno 3: 10 9 9 10 | média 9,50
Com foreach
O foreach também funciona com int[,]: ele passa por todas as casas, linha por linha, como quem lê um livro. Mas ele não diz em que linha ou coluna está, então serve só para coisas como somar tudo ou contar.
int[,] m = { { 1, 2, 3 }, { 4, 5, 6 } };
foreach (int x in m)
{
Console.Write($"{x} ");
}
Console.WriteLine();
Console.WriteLine(string.Join(", ", m));
$ dotnet run foreach2.cs 1 2 3 4 5 6 System.Int32[,] « o string.Join não abre um int[,]
Repare na última linha: o string.Join, que mostrava um array comum, aqui só mostra o nome do tipo, System.Int32[,]. Para mostrar uma tabela, use os dois for.
Criar com new e encher depois
Como nos arrays comuns, new char[3, 3] cria a tabela com o valor padrão do tipo em todas as casas. A Ana desenha um jogo da velha:
char[,] velha = new char[3, 3];
for (int l = 0; l < 3; l++)
{
for (int c = 0; c < 3; c++)
{
velha[l, c] = '.';
}
}
velha[1, 1] = 'X';
velha[0, 2] = 'O';
for (int l = 0; l < 3; l++)
{
for (int c = 0; c < 3; c++)
{
Console.Write(velha[l, c]);
Console.Write(' ');
}
Console.WriteLine();
}
$ dotnet run velha.cs . . O . X . . . .
- new char[3, 3]
- 3 linhas e 3 colunas. As casas começam com o caractere de código 0, que não aparece na tela; por isso o primeiro laço põe um
'.'em cada uma. - velha[1, 1] = 'X';
- O X no centro (linha 1, coluna 1). O
'O'fica na linha 0, coluna 2: o canto de cima, à direita.
3. O array de arrays: int[][]
A lojinha da Ana anota as vendas de cada dia. Num dia vendeu duas vezes, no outro quatro, num terceiro nenhuma. Uma tabela retangular desperdiçaria espaço e precisaria de um "valor vazio". O array de arrays guarda cada dia como uma fileira separada, do tamanho certo:
int[][] vendas =
[
[120, 80],
[200, 150, 90, 60],
[],
[75],
];
for (int dia = 0; dia < vendas.Length; dia++)
{
int total = 0;
foreach (int venda in vendas[dia])
{
total += venda;
}
Console.WriteLine($"Dia {dia + 1}: {vendas[dia].Length} vendas, total {total}");
}
Console.WriteLine($"Segunda venda do dia 2: {vendas[1][1]}");
- int[][]
- "Array de arrays de
int". Leia de dentro para fora:int[]é uma fileira de números;int[][]é uma fileira de fileiras. - [ [120, 80], [200, 150, 90, 60], [], [75] ]
- Aqui a expressão de coleção C# 12 funciona: cada linha é um array comum, com o seu próprio tamanho. A terceira está vazia.
- vendas.Length
- Quantas linhas (dias): 4. Diferente do
int[,], aqui oLengthé o número de linhas. - vendas[dia]
- Uma linha inteira: um
int[]comum, com tudo o que você já sabe fazer com arrays (Length,foreach). - vendas[dia].Length
- O tamanho daquela linha: 2, 4, 0 e 1.
- vendas[1][1]
- Dois pares de colchetes: o primeiro escolhe a linha (dia 2), o segundo escolhe o elemento dentro dela (a segunda venda): 150.
$ dotnet run jagged.cs Dia 1: 2 vendas, total 200 Dia 2: 4 vendas, total 500 Dia 3: 0 vendas, total 0 Dia 4: 1 vendas, total 75 Segunda venda do dia 2: 150
Missão: some todas as vendas de todos os dias e mostre Total da semana: 775. Use um laço para os dias e outro, dentro dele, para as vendas de cada dia.
Total da semana: 775
int[][] vendas =
[
[120, 80],
[200, 150, 90, 60],
[],
[75],
];
int totalSemana = 0;
// some aqui todas as vendas
Console.WriteLine($"Total da semana: {totalSemana}");
int[][] vendas =
[
[120, 80],
[200, 150, 90, 60],
[],
[75],
];
int totalSemana = 0;
foreach (int[] dia in vendas)
{
foreach (int venda in dia)
{
totalSemana += venda;
}
}
Console.WriteLine($"Total da semana: {totalSemana}");
Criar com new: as linhas começam null
No new int[3][], só o número de linhas vai no new: o segundo par de colchetes fica vazio, porque cada linha terá o seu tamanho. E as linhas ainda não existem: cada uma começa com null e precisa ser criada uma por uma.
int[][] linhas = new int[3][]; linhas[0] = [1]; linhas[1] = new int[2]; Console.WriteLine(linhas[0].Length); Console.WriteLine(linhas[1].Length); Console.WriteLine(linhas[2] is null); Console.WriteLine(linhas[2].Length);
$ dotnet run jagged-new.cs 1 2 True « a linha 2 nunca foi criada Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object. at Program.<Main>$(String[] args) in /home/ana/cs/jagged-new.cs:line 7
A linha 2 nunca recebeu um array, e o linhas[2].Length quebrou com NullReferenceException (código de saída 134). É a mesma pegadinha do new string[3] da aula Arrays: o compilador não avisa.
4. Qual usar
Retangular int[,] | Array de arrays int[][] | |
|---|---|---|
| Forma | Todas as linhas com o mesmo tamanho | Cada linha com o seu tamanho |
| Criar com valores | { { 1, 2 }, { 3, 4 } } | [[1, 2], [3, 4, 5]] |
| Criar vazio | new int[3, 4]: as 12 casas já existem, com 0 | new int[3][]: 3 linhas null, a criar uma por uma |
| Acessar | m[1, 2] | j[1][2] |
| Número de linhas | m.GetLength(0) | j.Length |
| Tamanho de uma linha | m.GetLength(1) (igual para todas) | j[i].Length |
Length | Total de casas (linhas × colunas) | Número de linhas |
| Bom para | Tabuleiros, mapas de jogo, planilhas, imagens: tudo que é retangular | Listas de tamanhos diferentes: vendas por dia, alunos por turma, linhas de um texto |
Na dúvida, se os dados são uma grade de verdade (toda linha tem as mesmas colunas), use int[,]: o tamanho fica garantido e não há linha null. Se as linhas variam, use int[][]. E se o número de linhas ou de elementos muda enquanto o programa roda, o caminho é uma lista de listas (List<List<int>>, aula List<T>).
Mais dimensões
Dá para ter três vírgulas, quatro… O jeito é o mesmo: int[,,] tem três dimensões, e cada casa precisa de três índices. Um prédio com andares, corredores e salas, por exemplo:
int[,,] cubo = new int[2, 3, 4];
cubo[1, 2, 3] = 99;
Console.WriteLine($"{cubo.Rank} dimensões, {cubo.Length} elementos");
Console.WriteLine(cubo[1, 2, 3]);
$ dotnet run tres.cs 3 dimensões, 24 elementos 99
Na prática, mais de duas dimensões é raro, e fica difícil de imaginar. Quando os dados ficam complicados assim, geralmente é melhor criar uma classe que diga o que cada coisa é.
Quando dá errado
error CS0022: Número incorreto de índices dentro de []; esperado 2
$ cat errado1.cs
int[,] m = { { 1, 2 }, { 3, 4 } };
Console.WriteLine(m[1][0]);
$ dotnet run errado1.cs
/home/ana/cs/errado1.cs(2,19): error CS0022: Número incorreto de índices dentro de []; esperado 2
Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: o m é retangular (int[,]) e quer os dois índices nos mesmos colchetes. O m[1] sozinho tem um índice só. Em inglês: Wrong number of indices inside []; expected 2.
Como consertar: m[1, 0]. O contrário também acontece: num int[][], j[1, 0] dá o mesmo CS0022, com esperado 1, e o certo é j[1][0].
error CS0847: Inicializador de matriz de comprimento "3" é esperado
$ cat errado3.cs
int[,] m =
{
{ 1, 2, 3 },
{ 4, 5 },
};
$ dotnet run errado3.cs
/home/ana/cs/errado3.cs(4,5): error CS0847: Inicializador de matriz de comprimento "3" é esperado
Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: num array retangular, toda linha tem de ter o mesmo número de valores. A primeira tem 3, então a da linha 4 do arquivo também precisava de 3.
Como consertar: complete a linha. Se as linhas têm mesmo tamanhos diferentes, troque por um array de arrays: int[][] m = [[1, 2, 3], [4, 5]];.
error CS9174: Não é possível inicializar o tipo 'int[*,*]' com uma expressão de coleção porque o tipo não é construível.
O que significa: você escreveu int[,] m = [[1, 2], [3, 4]];. A expressão de coleção com colchetes não sabe criar arrays retangulares. (O int[*,*] é o jeito interno de escrever "array de duas dimensões".)
Como consertar: use as chaves, int[,] m = { { 1, 2 }, { 3, 4 } };, ou mude o tipo para int[][], se as linhas podem variar.
IndexOutOfRangeException num laço com m.Length
$ cat errado4.cs
int[,] m = { { 1, 2, 3 }, { 4, 5, 6 } };
for (int l = 0; l < m.Length; l++)
{
Console.WriteLine(m[l, 0]);
}
$ dotnet run errado4.cs
1
4
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
at Program.<Main>$(String[] args) in /home/ana/cs/errado4.cs:line 4
O que significa: num int[,], o Length é o total de casas (2 × 3 = 6), e não o número de linhas (2). Quando l chegou a 2, não havia mais linha. O programa terminou com o código 134.
Como consertar: use m.GetLength(0) para as linhas e m.GetLength(1) para as colunas.
NullReferenceException num array de arrays
O que significa: o array foi criado com new int[3][] e uma das linhas nunca recebeu o seu array: ela ainda é null.
Como consertar: crie todas as linhas antes de usar (linhas[i] = new int[tamanho];, num laço), ou crie tudo de uma vez com os valores: int[][] linhas = [[1], [2, 3], []];.
Referência rápida
| Escrita | O que faz |
|---|---|
int[,] m = { { 1, 2, 3 }, { 4, 5, 6 } }; | Tabela 2 × 3 com valores |
int[,] m = new int[2, 3]; | Tabela 2 × 3, tudo 0 |
m[1, 2] | Linha 1, coluna 2 |
m.GetLength(0) / m.GetLength(1) | Número de linhas / de colunas |
m.Length / m.Rank | Total de casas / número de dimensões |
int[][] j = [[1, 2], [3, 4, 5]]; | Array de arrays com linhas de tamanhos diferentes |
int[][] j = new int[3][]; | 3 linhas, todas null até você criar |
j[1][2] | Linha 1, elemento 2 |
j.Length / j[i].Length | Número de linhas / tamanho da linha i |
int[,,] c = new int[2, 3, 4]; | Três dimensões; c[1, 2, 3] |
Exercícios
1. A casa certa
Num int[,] mapa, complete para pegar a casa da linha 4, coluna 7 (contando do 0):
2. Número de colunas
Complete para saber quantas colunas tem o int[,] tabela:
3. Total de casas
Quanto vale new int[5, 6].Length?
4. A Ana guarda os alunos de cada turma; uma turma tem 30, outra 25, outra 32. Qual tipo serve melhor?
5. Com int[][] j = [[1, 2], [3, 4, 5]];, quanto vale j[1][2]?
6. Num int[,] m de 3 linhas e 4 colunas, quanto vale m.Length?
Missão final: as poltronas livres
Missão: o mapa da sala de cinema tem 'L' nas poltronas livres e 'O' nas ocupadas. Percorra a tabela com dois laços (usando GetLength) e conte as livres. A saída tem de ser Livres: 5 de 12.
Livres: 5 de 12
char[,] sala =
{
{ 'L', 'O', 'O', 'L' },
{ 'O', 'O', 'L', 'L' },
{ 'L', 'O', 'O', 'O' },
};
int livres = 0;
// conte aqui as poltronas 'L'
Console.WriteLine($"Livres: {livres} de {sala.Length}");
char[,] sala =
{
{ 'L', 'O', 'O', 'L' },
{ 'O', 'O', 'L', 'L' },
{ 'L', 'O', 'O', 'O' },
};
int livres = 0;
for (int fila = 0; fila < sala.GetLength(0); fila++)
{
for (int poltrona = 0; poltrona < sala.GetLength(1); poltrona++)
{
if (sala[fila, poltrona] == 'L')
{
livres++;
}
}
}
Console.WriteLine($"Livres: {livres} de {sala.Length}");
Aqui o sala.Length é usado do jeito certo: ele é o total de poltronas, 3 × 4 = 12. Um foreach (char p in sala) também contaria as livres, mas a missão pede os dois laços, porque no próximo passo (mostrar "fila 2, poltrona 3 está livre") você vai precisar saber onde cada poltrona fica.
Resumo
- Um array de duas dimensões guarda uma tabela: cada casa tem uma linha e uma coluna, as duas contadas a partir do 0.
- O retangular
int[,]tem todas as linhas do mesmo tamanho. Crie com chaves ({ { 1, 2 }, { 3, 4 } }) ounew int[3, 4], acesse comm[l, c], e meça comGetLength(0)eGetLength(1). OLengthé o total de casas. - O array de arrays
int[][]é uma fileira de fileiras, cada uma do seu tamanho. Crie com[[1, 2], [3, 4, 5]], acesse comj[l][c], e meça comj.Lengthej[l].Length. Comnew int[3][], as linhas começamnull. - Percorra com dois
foraninhados: o de fora nas linhas, o de dentro nas colunas. - Vírgula para o
[,], dois pares de colchetes para o[][]: misturar dáCS0022.