Arrays: Guardando Muitos Valores Juntos

Arrays: Guardando Muitos Valores Juntos

Como guardar muitos valores sob um único nome: o array, uma fileira de posições numeradas com tamanho fixo. A aula cobre criação, acesso por índice, a propriedade Length, o percurso com laço, e o erro clássico de acessar fora dos limites por causa da contagem que começa em zero.
Linguagem C#

• • 11 min de leitura

Até agora, cada variável guardava um único valor: uma idade, um nome, um preço. Mas o mundo real está cheio de conjuntos de valores: as notas de uma turma inteira, os nomes de todos os convidados de uma festa, os preços de todos os produtos de uma loja. Guardar cada um desses em sua própria variável seria impraticável — imagine declarar nota1, nota2, nota3... até nota40 para uma turma de quarenta alunos, e depois somá-las uma a uma. Precisamos de uma forma de guardar muitos valores relacionados sob um único nome, tratando-os como um conjunto. A ferramenta fundamental para isso é o array (lê-se "arrêi"), e é o que abre esta fase. Com ele, quarenta notas cabem numa única variável, e processá-las todas se torna questão de poucas linhas.

A ideia: uma fileira de gavetas numeradas

Lembra da metáfora da gaveta, lá na Aula #04, em que uma variável era uma gaveta com nome? Um array estende essa imagem: em vez de uma gaveta isolada, imagine uma fileira de gavetas idênticas, todas sob um mesmo nome, distinguidas por um número de posição. O array como um todo tem um nome (por exemplo, notas); cada gaveta individual dentro dele é acessada por sua posição na fileira. Todas as gavetas de um mesmo array guardam valores do mesmo tipo — um array de números guarda só números, um array de textos guarda só textos —, o que mantém o conjunto coerente.

Essa estrutura tem duas características que definem o array e valem gravar desde já. Primeira: o array tem um tamanho fixo, definido quando ele é criado — uma fileira de dez gavetas tem dez gavetas, nem mais nem menos, e esse número não muda. Segunda: as gavetas são numeradas a partir do zero — a primeira posição é a 0, a segunda é a 1, e assim por diante. Essa contagem a partir do zero, que mencionamos ao falar de strings na Aula #09, reaparece aqui e é universal em programação; a estranheza inicial dá lugar rapidamente à naturalidade.

Criando um array

Há duas formas comuns de criar um array. A primeira, quando você já conhece os valores, lista-os entre chaves:

// Um array de inteiros, criado já com seus valores:
int[] notas = { 8, 6, 9, 7, 10 };

Vamos ler essa declaração. O int[] (com os colchetes) é o tipo: significa "um array de inteiros" — os colchetes transformam "um inteiro" em "vários inteiros". O notas é o nome do array. E os valores entre chaves são o conteúdo inicial: cinco notas, ocupando as posições 0 a 4. Assim como um número tem tipo int e um texto tem tipo string, um array de inteiros tem tipo int[], e um array de textos tem tipo string[]:

string[] nomes = { "Ana", "Beto", "Carla" };   // um array de três textos

A segunda forma cria um array vazio de um tamanho definido, para preenchê-lo depois:

// Cria um array de 5 inteiros, todos inicialmente com valor 0:
int[] pontuacoes = new int[5];

Aqui, new int[5] cria uma fileira de cinco gavetas de inteiros, todas começando com o valor padrão (para inteiros, zero). A palavra new (em inglês, "novo") sinaliza a criação de algo novo; o [5] define o tamanho. Este formato é útil quando você sabe quantos valores vai guardar, mas ainda não sabe quais — você cria o espaço e o preenche em seguida.

Acessando os valores: o índice

Para ler ou modificar um valor específico dentro do array, usamos o nome do array seguido da posição entre colchetes — essa posição chama-se índice:

int[] notas = { 8, 6, 9, 7, 10 };

Console.WriteLine(notas[0]);   // exibe 8 — o primeiro valor, na posição 0
Console.WriteLine(notas[2]);   // exibe 9 — o terceiro valor, na posição 2

notas[1] = 5;                  // MODIFICA o valor na posição 1 (era 6, vira 5)
Console.WriteLine(notas[1]);   // exibe 5

Preste muita atenção à numeração, pois é onde iniciantes mais tropeçam. notas[0] é o primeiro valor (8), não o segundo. notas[2] é o terceiro (9). Como a contagem começa em zero, a posição de um elemento é sempre "quantos vêm antes dele": o primeiro tem zero antes, então é 0; o terceiro tem dois antes, então é 2. Um array de cinco elementos tem, portanto, índices válidos de 0 a 4 — não de 1 a 5. O índice também serve para modificar um valor: notas[1] = 5 guarda 5 na segunda gaveta, exatamente como uma atribuição comum, só que direcionada a uma posição do array.

Descobrindo o tamanho: Length

Todo array sabe o seu próprio tamanho, acessível pela propriedade Length (a mesma que usamos com strings, na Aula #09):

int[] notas = { 8, 6, 9, 7, 10 };
Console.WriteLine(notas.Length);   // exibe 5 — o array tem cinco elementos

O Length é imensamente útil, sobretudo ao percorrer o array com um laço, pois nos diz até onde ir. E aqui mora uma relação que confunde no início mas é lógica: um array de Length igual a 5 tem índices válidos de 0 a 4 — ou seja, o último índice válido é sempre Length - 1, e não Length. O tamanho é 5, mas a última posição é 4, porque começamos a contar do zero. Guardar essa relação — "último índice = tamanho menos um" — evita muitos erros.

O erro clássico: acessar fora dos limites

Já que os índices vão de 0 a Length - 1, o que acontece se você tentar acessar uma posição que não existe — como notas[5] num array de cinco elementos (cujo último índice é 4)? O programa quebra, com um erro de execução chamado, em inglês, IndexOutOfRangeException — "exceção de índice fora do intervalo":

int[] notas = { 8, 6, 9, 7, 10 };
// Console.WriteLine(notas[5]);   // QUEBRA: não existe posição 5 (índices vão de 0 a 4)

Este é um dos erros mais comuns de quem começa com arrays, e quase sempre nasce de esquecer a contagem a partir do zero — tentar acessar notas[5] pensando no "quinto elemento", quando o quinto está na posição 4. Felizmente, é um erro de execução com mensagem clara (você aprendeu a lê-las na Aula #15): a mensagem aponta exatamente o problema. A lição preventiva: ao acessar arrays, lembre-se sempre de que os índices válidos vão de 0 até Length - 1, e desconfie de qualquer acesso que possa ultrapassar esse limite.

Percorrendo um array com laço

Raramente acessamos posições de um array uma a uma pelo número; o mais comum é percorrê-lo com um laço, processando cada elemento. Com o laço for da Aula #13 e o Length, isso é direto:

int[] notas = { 8, 6, 9, 7, 10 };

// Percorre do índice 0 até Length-1, exibindo cada nota com sua posição:
for (int i = 0; i < notas.Length; i++)
{
    Console.WriteLine($"Nota na posição {i}: {notas[i]}");
}

Observe a estrutura, que é o padrão de percurso de array com for: o laço começa em i = 0 (o primeiro índice) e continua enquanto i < notas.Length (note o "menor que", não "menor ou igual" — pois o último índice válido é Length - 1, então paramos antes de Length). A cada volta, notas[i] acessa o elemento da posição atual. Este padrão — for de 0 a Length, acessando array[i] — é tão frequente que vale memorizá-lo. Ele permite somar todos os valores, encontrar o maior, contar quantos satisfazem uma condição, e mais.

Você deve estar lembrando do foreach que espiamos na Aula #14 — e sim, ele também percorre arrays, de forma ainda mais simples quando não precisamos da posição: foreach (int nota in notas) Console.WriteLine(nota);. O for é necessário quando queremos a posição (i) ou modificar elementos; o foreach é mais limpo quando só queremos visitar cada valor. Retomaremos essa comparação na aula sobre processamento de coleções.

Guardar muitos valores sob um único nome deixou de exigir uma variável para cada um. O array é uma fileira de posições numeradas, criada com um tamanho fixo, em que cada elemento é alcançado pelo seu índice e o total é conhecido por Length. Combinado com um laço, ele permite processar cem ou mil valores com a mesma quantidade de código que processaria dois.

O erro clássico de acessar uma posição inexistente ficou compreendido pela causa: com a contagem começando em zero, a última posição de um array de cinco elementos é a de índice quatro, e não cinco. É um deslize frequente, e a mensagem de erro que ele produz agora é reconhecível de imediato.

Fontes e leituras recomendadas

Exercícios

Exercício 1

Crie um array de inteiros com cinco valores à sua escolha. Exiba o primeiro e o último elemento usando índices (lembre que o último índice é Length - 1). Depois, modifique o valor do meio (posição 2) e exiba o array inteiro com um laço for.

Ver resposta

✓ Resposta: Exemplo:

int[] numeros = { 10, 20, 30, 40, 50 };
Console.WriteLine(numeros[0]);              // primeiro: 10
Console.WriteLine(numeros[numeros.Length - 1]); // último: 50
numeros[2] = 99;                            // modifica o do meio
for (int i = 0; i < numeros.Length; i++)
    Console.WriteLine(numeros[i]);          // 10, 20, 99, 40, 50

O último elemento é acessado com numeros.Length - 1 (índice 4), e a modificação da posição 2 troca o 30 por 99.

Exercício 2

Explique, com suas palavras, por que um array de tamanho 5 tem índices válidos de 0 a 4, e não de 1 a 5. O que aconteceria se você tentasse acessar a posição 5, e por quê?

Ver resposta

✓ Resposta: Um array de tamanho 5 tem índices de 0 a 4 porque a contagem das posições começa em zero: o primeiro elemento está na posição 0, o segundo na 1, e assim por diante, de modo que o quinto (e último) está na posição 4. Há cinco posições (0, 1, 2, 3, 4), mas a numeração vai até 4, não até 5. Tentar acessar a posição 5 quebra o programa com uma IndexOutOfRangeException, porque essa posição não existe — o array só tem gavetas de 0 a 4, e pedir a de número 5 é pedir algo fora dos limites do conjunto.

Exercício 3

Crie um array de textos com os nomes de quatro frutas. Percorra-o com um laço for que exiba cada fruta precedida do seu número de posição (por exemplo, "Posição 0: maçã"). Depois, faça o mesmo com um foreach e compare qual foi mais simples e por quê.

Ver resposta

✓ Resposta: Exemplo com for:

string[] frutas = { "maçã", "banana", "uva", "pera" };
for (int i = 0; i < frutas.Length; i++)
    Console.WriteLine($"Posição {i}: {frutas[i]}");

Com foreach:

foreach (string fruta in frutas)
    Console.WriteLine(fruta);

O foreach foi mais simples para apenas exibir cada fruta, pois dispensa gerenciar índice e tamanho. Porém, ele não dá acesso à posição — por isso, para exibir "Posição 0: ...", o for foi necessário, já que precisávamos do número do índice (i). A escolha depende de precisar, ou não, da posição.

Exercício 4

Escreva um programa que crie um array de cinco notas, e use um laço for para calcular e exibir a soma de todas elas (dica: use uma variável soma iniciada em 0 e acumule notas[i] a cada volta). Como você calcularia a média a partir daí?

Ver resposta

✓ Resposta: Exemplo:

int[] notas = { 8, 6, 9, 7, 10 };
int soma = 0;
for (int i = 0; i < notas.Length; i++)
    soma = soma + notas[i];
Console.WriteLine($"Soma: {soma}");   // 40

A cada volta, o elemento da posição i é adicionado a soma, que acumula o total (40). Para a média, bastaria dividir a soma pela quantidade de elementos: double media = (double)soma / notas.Length; — usando notas.Length (5) como divisor. (A conversão para double garante casas decimais na divisão, evitando a divisão inteira da Aula #06.)

Exercício 5

Sem código: explique a diferença entre criar um array com int[] valores = { 1, 2, 3 }; e com int[] valores = new int[3];. Em que situação cada forma é mais apropriada, e o que contém o array em cada caso logo após a criação?

Ver resposta

✓ Resposta: Com int[] valores = { 1, 2, 3 };, cria-se o array já com valores conhecidos — três posições contendo 1, 2 e 3 —, apropriado quando você conhece o conteúdo de antemão. Com int[] valores = new int[3];, cria-se um array vazio de tamanho 3 — três posições, todas contendo o valor padrão (0, para inteiros) —, apropriado quando você sabe quantos elementos terá mas ainda não quais, pretendendo preenchê-los depois. Logo após a criação, a primeira forma contém os valores dados; a segunda contém zeros, aguardando serem preenchidos.

Comentários

Mais em Linguagem C#

A List: Uma Coleção que Cresce e Encolhe
A List: Uma Coleção que Cresce e Encolhe

A coleção que resolve a rigidez do array: a List, que cresce e encolhe…

Armadilhas Comuns: O Mapa dos Perigos
Armadilhas Comuns: O Mapa dos Perigos

As seis armadilhas que pegam todo programador C#: confundir atribuição com…

Projeto: Um Sistema de Formas de Pagamento
Projeto: Um Sistema de Formas de Pagamento

Um projeto integrador que reúne toda a fase de objetos num sistema de formas…