Desenvolvimento - C#
Arrays em C# - Teoria e prática
Veja neste artigo uma abordagem geral sobre arrays, falando desde sua definição teórica até sua utilização em exemplos práticos na linguagem C#, explicando como são armazenados e acessados na memória.
por Joel RodriguesDefinição
“Arrays podem ser definidos como séries, coleções ou listas de elementos, geralmente de um mesmo tipo de dados, armazenados de forma sequencial”. Assim como esta definição, o array é uma das estruturas de dados de maior simplicidade, tanto em termos de compreensão teórica do seu funcionamento, quanto a nível de implementação. Porém, esta considerável simplicidade não reduz sua importância na computação, posto que em muitas situações, é a solução mais prática para resolução de problemas comuns no “mundo da programação”.
Quando se busca tratar com listas de objetos (chamando de “objetos” os elementos do vetor, não apenas instâncias de classes, como também e principalmente tipos-valor) de um determinado tipo-valor de dado, o uso de arrays pode ser a melhor alternativa, uma vez que o acesso aos elementos é consideravelmente rápido, pois os valores são armazenados diretamente no stack. A forma como esta estrutura é tratada na memória é comentada a seguir.
Vetores, matrizes e outras N dimensões
Arrays podem ser tanto listas simples e sequenciais, como coleções compostas, ou seja, podem possuir tanto apenas uma como várias dimensões. Um array de uma dimensão é chamado VETOR, enquanto que um com várias dimensões é chamado de MATRIZ. Esta definição é semelhante àquela estudada na álgebra, e como tal, só conseguimos visualizar o que seriam arrays de até três dimensões.
Nos vetores, cada elemento é identificado por um único índice que indica sua posição na lista (podemos considerar como uma única LINHA). Já nas matrizes, os elementos são identificados a partir de dois ou mais índices que, em conjunto, indicam a posição do item na coleção. Para o caso de duas dimensões, consideramos que os itens referem-se às LINHAS e COLUNAS. Para dimensões iguais ou superiores a três, não damos nenhuma nomenclatura específica aos índices.
Observação: em C#, assim como na maioria das linguagens de programação, os índices de listas são “zero-based”, ou seja, começam a contar do zero (0) e não do um (1) como estamos acostumados em nosso dia-a-dia.
Figura 1: Ilustração de vetor de números inteiros com índices no topo
Figura 2: Ilustração de matriz de letras com índices em destaque
Figura 3: Ilustração de array tridimensional com índices em destaque
Nas figuras 1, 2 e 3, representamos os arrays de 1, 2 e 3 dimensões, respectivamente. No primeiro exemplo, temos um vetor onde cada elemento consiste de um número inteiro e cujo índice é mostrado no topo. Por exemplo, o elemento de índice 0 possui o valor 9.
Na Figura 2, ilustramos uma matriz (duas dimensões) de letras, cujos índices das linhas (horizontais) e colunas (verticais) são exibidos em destaque no topo e no lado esquerdo, respectivamente. Como já foi dito, cada elemento é identificado por dois índices (linhas e colunas), por exemplo, o elemento de índices 1 e 1 tem o valor “Q”, enquanto o elemento na posição (3,2) vale “J”.
A Figura 3, por sua vez, ilustra o que seria um array tridimensional. Neste exemplo, porém, não definimos valores para os elementos, apenas demos cores diferentes a alguns itens para que possamos referenciá-los. Neste caso, cada elemento é identificado por três índices. Na figura, o elemento de índices (0,0,0) é o de cor laranja, enquanto o elemento (0, 2, 1) é o verde, o elemento amarelo (no topo), possui índices (2, 1, 1).
Observação: tanto para matrizes quanto para estruturas tridimensionais, indicamos os índices citando primeiramente a linha, seguida da coluna e então as demais dimensões.
Observação: conforme foi comentado anteriormente, não podemos ilustrar e visualizar estruturas com mais de três dimensões, ou fazer referências a objetos de nosso cotidiano. O estudo de outras dimensões no universo real é feito, na física avançada.
Armazenamento e acesso à memória
Quando declaramos um array de um determinado tipo, é reservada uma região sequencial na memória com capacidade bastante para comportar todos os itens da lista. Ou seja, uma quantidade de bytes múltipla do tamanho do tipo é dedicada ao armazenamento dos itens do array. Por exemplo, sabendo que uma variável do tipo int ocupa 4 bytes na memória, se declararmos um array com capacidade para 3 elementos deste tipo, estaremos reservando 4x3=12 bytes na memória (de forma sequencial).
Figura 4: Ilustração de região de memória utilizada por um array
Na Figura 4, temos uma ilustração do que seria uma região da memória de 20 bytes sequenciais. Os quadros em vermelhos indicam uma possível forma de alocação de espaço para armazenamento do array citado no parágrafo anterior. Obervemos que foram reservados 12 bytes, do 002 ao 013. Este espaço só é liberado quando o array é destruído (ou por processo de liberação dinâmica de memória, que não veremos neste artigo, mas que é frequentemente utilizado por programas em C).
Tanto o array como um todo quanto cada elemento individualmente possuem um endereço de memória no qual são buscadas suas informações quando o acesso a elas é requisitado pelo sistema. Este endereço é dado pela localização do primeiro byte da sequencia, ou seja, o endereço do array é o byte 001 na memória. Como cada elemento ocupa 4 bytes, seus endereços são 001, 006 e 010.
Quando acessamos um elemento de um array, sua posição na memória é localizada através do endereço do array como um todo e a quantidade de bytes ocupada pelo tipo do elemento em conjunto com seu índice. Por exemplo, caso desejássemos acessar o segundo elemento do array acima, sua posição seria dada por: 002 + 4x1 = 006. Onde: 002 é o endereço do array; 4 é a quantidade de bytes ocupada por um inteiro; e 1 é o índice do elemento que queremos acessar.
Observação: em alguns casos, são reservados mais alguns bytes além dos explicados aqui. Isso é feito por questões internas de comportamento do array, que às vezes requer espaço extra para realizar certas operações.
Arrays “Jagged”
“Os chamados 'jagged arrays' são um tipo a mais de array, onde cada elemento é um vetor”. Definição simples, utilização também.
Exemplificando talvez fique mais simples de compreender: supondo que temos um array que armazena as turmas de uma escola e, para cada turma, temos um número diferente de alunos dos quais precisamos armazenar o código. Poderíamos usar uma matriz onde as linhas representariam a turma e as colunas representariam os alunos, porém, nesse estaríamos utilizando um número fixo de alunos, igual para todas as turmas, o que não atende a nossa necessidade. Aqui entram os jagged arrays, podemos ter um array que comporte a quantidade de turmas (um número conhecido) e cada elemento é um vetor, cujo comprimento varia e podemos definir individualmente. Adiante veremos como declarar e inicializar os vários tipos de vetores em C#.
Arrays em C#: declaração
A declaração de arrays na linguagem C# é bastante simples. Vejamos as listagens 1, 2 e 3.
Listagem 1: Declaração de um vetor em C#
//Estrutura geral: tipo[] nome; int[] numeros; // Vetor de inteiros string[] nomes; // Vetor de strings
Listagem 2: Declaração de uma matriz em C#
//Estrutura geral: tipo[,] nome; int[,] matriz2dimensoes; decimal[, ,] matriz3dimensoes;
Listagem 3: Declaração de array jagged em C#
//Estrutura geral: tipo[][] nome; int[][] vetorDeInteiros; float[][] vetorDeDecimais;
A declaração não é um processo complexo, a parte mais importante, eu diria, é a inicialização, o que veremos a seguir.
Arrays em C#: inicialização
A inicialização de vetores na linguagem C# pode ser feita de duas formas: no momento da declaração e após a declaração. Veremos nas listagens 5, 6 e 7 como proceder com essa inicialização no momento da declaração.
Listagem 5: Inicialização de vetor junto com a declaração
int[] impares = new int[] { 1, 3, 5, 7, 9 }; //ou int[] impares = { 1, 3, 5, 7, 9 };
Listagem 6: Inicialização de matriz junto com a declaração
int[,] matriz = new int[,] { { 1, 2 }, { 3, 4 } }; //ou int[,] matriz = { { 1, 2 }, { 3, 4 } };
Listagem 7: Inicialização de jagged array junto com a declaração
int[][] jagged1 = new int[][] { new int[] {0,1,2,3}, new int[] {4,5,6,7,8,9}, new int[] {10,20,30,40} }; //ou int[][] jagged1 = { new int[] {0,1,2,3}, new int[] {4,5,6,7,8,9}, new int[] {10,20,30,40} };
Caso os valores não sejam definidos no momento de sua declaração, podemos acessar cada elemento posteriormente e atribuir-lhes valor, o que é exemplificado nas listagens 8, 9 e 10.
Listagem 8: Inicializando elementos de um vetor individualmente
int[] vetor = new int[3]; vetor[0] = 10; vetor[1] = 25; vetor[2] = 99;
Listagem 9: Inicializando elementos de uma matriz individualmente
string[,] matriz = new string[2, 2]; matriz[0, 0] = "C#"; matriz[0, 1] = "Delphi"; matriz[1, 0] = "Java"; matriz[1, 1] = "VB.NET";
Listagem 10: Inicialização dos elementos de um jagged array inividualmente
int[][] jagged = new int[3][]; jagged[0] = new int[] { 0, 1, 2, 3}; jagged[1] = new int[] { 4, 5, 6, 7, 8, 9 }; jagged[2] = new int[] { 10, 20};
Como vimos, inicializar os elementos de um array não é um processo complicado. A forma como se vai fazer tal inicialização depende, geralmente, da situação. Vejamos agora alguns exemplos práticos da utilização dessa estrutura de dados tão simples e, ao mesmo tempo, tão útil no mundo da programação de computadores.
Arrays em C#: exemplos práticos
Na Listagem 11 utilizaremos um vetor para armazenar 10 números informados pelo usuário e, ao final, exibir aqueles que estão abaixo da média do conjunto.
Listagem 11: Exemplo prático com vetor
int[] valores = new int[10]; decimal soma = 0, media = 0; for (int i = 0; i < 10; i++) { Console.WriteLine(String.Format("Informe o elemento de índice {0}:", i)); valores[i] = int.Parse(Console.ReadLine()); soma += valores[i]; } media = soma / 10; for (int i = 0; i < 10; i++) { if(valores[i] < media) Console.WriteLine(String.Format("O elemento de índice {0}, cujo valor é {1}, está abaixo da média.", i, valores[i])); }
Na Listagem 12, utilizaremos uma matriz para montar uma “cartela de bingo”, onde as casas são informadas pelo usuário.
Listagem 12: Exemplo prático com matrizes
int[,] bingo = new int[5,5]; for (int i = 0; i < 5; i++ ) { for (int j = 0; j < 5; j++) { Console.WriteLine(String.Format("Informe o elemento ({0},{1})", i, j)); bingo[i, j] = int.Parse(Console.ReadLine()); } }
Por fim, utilizaremos um array jagged para armazenar turmas com vários alunos cada uma, obervemos então a Listagem 13.
Listagem 13: Exemplo prático com array jagged
Console.WriteLine("Informe o número de turmas: "); int num_turmas = int.Parse(Console.ReadLine()); string[][] turmas = new string[num_turmas][]; for (int i = 0; i < num_turmas; i++ ) { Console.WriteLine("Informe a quantidade de alunos da turma {0}:", i); int num_alunos = int.Parse(Console.ReadLine()); turmas[i] = new string[num_alunos]; for (int j = 0; j < num_alunos;j++ ) { Console.WriteLine("Informe o nome do aluno {0}:", j); turmas[i][j] = Console.ReadLine(); } }
Nos exemplos acima, utilizamos apenas o laço FOR, mas as demais estruturas de iteração como WHILE e FOREACH podem ser igualmente utilizadas, a escolha por cada uma delas dependerá da situação.
Concluímos aqui este artigo que teve por objetivo fazer uma abordagem geral sobre arrays, tratando desde sua definição teórica, a forma como são armazenados na memória, como se dá o acesso a eles e, finalizando, vimos alguns exemplos práticos de utilização.
Agradeço a atenção do leitor. Até a próxima oportunidade.