Arrays

Entendiendo los arrays y su funcionamiento en C#

Arrays


Entendiendo los arrays y su funcionamiento en C#

« Volver al inicio

¿Qué son los arrays?

En una frase corta, un array podemos decir que es un conjunto de valores contenidos en un único identificador.

Hasta ahora hemos visto tipos de datos escalares o individuales, es decir, un espacio de memoria donde se almacena un único valor de un tipo específico. Una variable hasta ahora la entendíamos por una “cajita” abstracta en la memoria RAM donde podías guardar un valor (numérico, literal, booleano…) que podía variar a lo largo de la ejecución de un programa. Visualmente, una variable a sería algo así:

Caja variable

De una forma un poco más abstracta, algo así que ocupa en memoria:

IdentificadorValor
a1

Esto sería una representación visual de cómo se almacenaría internamente una variable int a inicializada a 1. Es un valor único porque la variable a sólo puede tener un sólo número a la vez. Es decir, si a esta variable le asignamos otro valor, por ejemplo a = 7, el valor antiguo se perderá, si no lo hemos guardado antes en otra variable.

Esto puede representar un problema si por ejemplo queremos almacenar en una variable varias edades. Hasta ahora la solución que conocemos es la de crear muchas variables, como edad1, edad2, edad3, etc:

IdentificadorValor
edad121
edad237
edad342
edad419
edad535

Pero es una solución poco práctica si por ejemplo queremos iterar sobre cada uno de los valores. Necesitaríamos una variable que pueda contener más de un valor a la vez. Para eso están los arrays.

IdentificadorValor_0Valor_1Valor_2Valor_3Valor_4
edades2137421935

O bien, lo podemos visualizar como una lista:

int[] edades =

ÍndiceValor
021
137
242
319
435

Esta última tabla parece un poco más abstracta, pero representa exactamente lo mismo que la anterior: una variable que contiene más de un valor al que podemos acceder por un número de índice que se cuenta desde 0.

Así pues, un array a podemos definirlo como una “caja” que ocupa espacio en memoria, pero que en lugar de tener un único compartimento, tiene varios compartimentos para guardar cosas en ellas. Por ejemplo, una caja de huevos sería un array de tamaño 6:

Caja array

¿Cómo declarar una array?

Sabiendo ya la diferencia entre variables escalares y arrays, que vienen a ser como listas, o mejor dicho contenedores de varios valores, hay varias formas de declarar una array en C#.

Si queremos simplemente inicializar un array de enteros llamado edades sin especificar aún su tamaño, podemos hacerlo así:

int[] edades;

Date cuenta que estamos declarando un tipo de datos int[] con esos dos corchetes, no es de tipo int normal como vimos hasta ahora. Esos dos corchetes son los que indican que estamos declarando una variable array de enteros.

Esto significa que este array sólo puede contener números enteros ya que la hemos declarado de tipo int[]. Si quisiéramos crear un array de cadenas de texto, lo haríamos así:

string[] nombres;

Y lo mismo con el resto de tipos de datos: float[], double[], byte[], long[], etc.

Un consejo útil: nombra las arrays en plural, ya que con esa información sabemos que se trata de una variable con múltiples valores. Dejaremos las variables escalares o individuales con nombres en singular siempre que sea posible.

Tarde o temprano, tendremos que especificar el tamaño del array antes de poder utilizarlo, ya que un array tiene siempre un tamaño fijo. ¿O acaso has visto alguna vez que una caja de cartón de 6 huevos se pudiera incrementar a más espacios? 😉

Volvemos al ejemplo de las edades. Si tenemos previsto guardar un total de 5 edades en el array edades se lo especificaremos al programa así:

int[] edades;		//Primero la declaración
edades = new int[5];	//Luego la inicialización

O bien, si estamos 100% seguros desde el principio del programa, lo podemos inicializar a la vez que lo declaramos:

int[] edades = new int[5];	//Inicializamos y declaramos en una misma línea

Estas líneas de código ya nos han creado un array vacío de 5 espacios:

int[] edades =

ÍndiceValor
0
1
2
3
4

Pero ahora, ¿cómo asignamos un valor a cada uno de los elementos de edades? Primero hay que recordar que en informática es habitual contar desde 0, por lo que los elementos de los arrays se empiezan a contar con el índice 0.

Así, si queremos inicializar el índice 0 de edades a 21, lo podemos hacer así:

edades[0] = 21;

Y si queremos imprimir por pantalla su valor, lo podemos hacer así:

Console.WriteLine(edades[0]);

//Imprimirá: 21

Nuestra tabla de edades ahora mismo está así:

int[] edades =

ÍndiceValor
021
1
2
3
4

Vamos a asignar el resto de valores (recordamos que al índice 0 de edades ya le hemos asignado un valor):

edades[1] = 37;
edades[2] = 42;
edades[3] = 19;
edades[4] = 35;

Tras ejecutar este código (y el anterior), la lista de edades nos quedaría así:

int[] edades =

ÍndiceValor
021
137
242
319
435

Luego podemos trabajar con cada uno de los elementos de los arrays como si se tratasen de variables individuales:

// Código previo:
int[] edades = new int[5];
edades[0] = 21;
edades[1] = 37;
edades[2] = 42;
edades[3] = 19;
edades[4] = 35;

// Trabajamos con el array:
int edadMayor = edades[2];
int edadMenor = edades[3];

Cabe mencionar que también podemos inicializar un array sin necesidad de especificar su número de elementos si conocemos sus valores iniciales. Esto lo podemos hacer en una misma línea utilizando llaves. Así, podemos representar la tabla anterior en una sola línea de código:

int[] edades = { 21, 37, 42, 19, 35 };

Console.WriteLine(edades[3]);	// Imprimirá: 19

¿Cómo recorrer un array?

La forma más fácil de acceder a un elemento de array ya la hemos visto antes. Si queremos mostrar la lista de edades, lo podemos hacer así:

Console.WriteLine(edades[0]);
Console.WriteLine(edades[1]);
Console.WriteLine(edades[2]);
Console.WriteLine(edades[3]);
Console.WriteLine(edades[4]);

Pero esto es poco práctico sobre todo si trabajamos con arrays grandes. Este código lo podemos abreviar con un for:

for(int i = 0; i < 5; i++) {
  Console.WriteLine(edades[i]);
}

Si nos fijamos bien:

  1. Primero inicializamos la variable entera i a 0. Esta se usará como índice para recorrer el array.
  2. Luego comprobamos que i sea menor que 5, ya que es el tamaño del array que hemos definido más arriba:
int[] edades = new int[5];
  1. Si se cumple lo anterior, imprimimos el valor del índice i en la lista edades. Si no, salimos del bucle.
  2. Incrementamos i y volvemos al paso 2.

La ventaja que tenemos recorriendo un array con for es que podemos modificar los valores de dentro del array a medida que lo recorremos.

Por ejemplo, si queremos sumar 5 a cada una de las edades que tenemos guardadas:

for(int i = 0; i < 5; i++) {
  edades[i] = edades[i] + 5;	// O bien edades[i] += 5;
}

Tras ejecutar este código, la tabla nos quedaría así (con los valores en negrita):

int[] edades =

ÍndiceValor antiguoValor actual
02126
13742
24247
31924
43540

Tenemos otra forma de recorrer los arrays que también nos puede resultar muy útil: con foreach. Aunque esto sólo nos lo reservaremos para poder leer o utilizar el valor de cada elemento de array.

foreach(int edad in edades) {
  Console.WriteLine(edad);
}

Lo traducimos a nuestro idioma:

Para cada valor llamado edad dentro del array: edades {
  Imprimir: edad
}

Es decir:

  1. Creamos temporalmente una variable entera llamada edad que tomará el valor de edades elemento por elemento, por cada iteración que hagamos.
  2. Imprimimos la edad por consola.
  3. Si aún quedan elementos en el array, volvemos al paso 1.

Es decir, este bloque hará exactamente lo mismo que lo que hemos hecho antes con el bucle for para imprimir las edades por consola, pero con una sintaxis más simple. Nos ahorramos el declarar una variable i para tener que acceder a cada elemento por cada iteración.

Problema del foreach: no podremos modificar directamente los elementos de cada variable, ya que aunque cambiemos el calor de edad dentro del foreach, no se modificará dicho valor dentro del array. Necesitaríamos crear un int i dentro del bucle que se autoincrementase por cada iteración para poder modificar los valores de edades apoyándonos con dicha i… pero para eso mejor hacer un for.

¿Cuánto mide un array?

Antes hemos iterado sobre la lista edades con for de la siguiente forma:

for(int i = 0; i < 5; i++) {
  Console.WriteLine(edades[i]);
}

Si nos fijamos bien, parece que nos hemos inventado ese “5” de la nada. ¿Y si nos equivocamos y la lista tiene menos elementos?

Para ello podemos hacer uso del método Length. Dicho método, presente en todos los arrays, nos devolverá el número de elementos que tiene el array donde lo utilicemos.

De esto ya hice un poco de spoiler en el tema anterior de Estructuras de control al final de la sección de los foreach. Para utilizarlo sobre la tabla de edades, tendremos que hacerlo así:

int longitud = edades.Length;	// longitud vale 5

Con esto ganamos más control a la hora de controlar el tamaño de nuestros arrays cuando vayamos a iterar sobre ellos por ejemplo con un for:

for(int i = 0; i < edades.Length; i++) {
  Console.WriteLine(edades[i]);
}

Arrays sobredimensionados

Los arrays en C# tienen un problema, y es que cuando los creamos sin asignar un valor, estos se inicializan con un valor por defecto, según el tipo de dato que se vaya a guardar.

Por ejemplo, a la hora de declarar un array de 10 enteros de la siguiente forma:

int[] enteros = new int[10];

Dicho array ya contendrá enteros inicializados a 0, ya que es el valor por defecto de los elementos vacíos de los arrays de enteros:

Console.WriteLine(enteros[4]);  //Imprimirá 0

En otros lenguajes de programación como C o C++, lo que puede ocurrir es que tengamos “basurilla” en cada valor de array no inicializado. En otros lenguajes, por defecto nos asigna un valor nulo en cada elemento de array vacío, lo cual nos puede servir también para controlar el último dato almacenado en una variable.

En C#, como todos los arrays de enteros se inicializan a 0, ¿cómo sabemos dónde está el último valor que le hemos asignado? Aquí estamos entrando en el problema de los arrays sobredimensionados.

int[] enteros =

ÍndiceValor
00
10
20
30
40
50
60
70
80
90

Pero tampoco existen problemas sin soluciones en C#. Para poder controlar dónde se guardó un dato por última vez en un array, podemos usar una variable “puntero”:

int[] enteros = new int[10];  // Creamos el array
int puntero = 0;              // No hemos guardado nada todavía, estamos en la posición 0

El puntero será el encargado de decir al programa: “aquí es donde tienes que guardar el próximo dato”. Lo inicializamos a cero puesto que el array lo tenemos declarado sin inicializar. Si lo hubiéramos inicializado, entonces debíamos haberle asignado un valor manualmente:

int[] enteros = new int[10];  // Creamos el array
enteros[0] = 24;  // Asignamos valores
enteros[1] = 53;
enteros[2] = 12;
int puntero = 3;  // El siguiente índice será el 3

Ese valor puntero irá incrementando o decreciendo según si vamos almacenando o borrando datos en el array. Nos lo tenemos que imaginar como una “flechita” que se irá moviendo hacia arriba o hacia abajo en nuestra lista según le vamos agregando o quitando valores a nuestro array.

for(int i = puntero; i < enteros.Length; i++) {
  Console.Write("Introduce un dato nuevo: ");
  enteros[i] = Convert.ToInt32(Console.ReadLine());
  puntero++;
}

Este código irá preguntando al usuario por más números hasta que se llegue al final del array. Si nos fijamos, el valor puntero irá incrementando cada vez que agreguemos un dato.

Si aún no entendemos el propósito de los “punteros” de los array, aquí tenéis un gif aclaratorio con nuestra lista dimensionada a 10 elementos: Array y puntero Lo importante es que visualicemos cómo el puntero “<-” va desplazándose a medida que va incrementando o decreciendo el valor puntero al añadir o quitar valores al array, de forma que podamos gestionar dónde queremos guardar el próximo dato nuevo en el programa.

También tenemos que fijarnos cómo en cierto momento a los valores 2 y 3 les asignamos otro valor sin que varíe el puntero. Y también tenemos que fijarnos que al valor 6 le asignamos 0 y el puntero avanza, porque, ¿y si queremos guardar también ceros en el array? De ahí lo que comentaba la importancia de poder saber controlar nuestro array en cada momento.

Los cuatro mandamientos de las variables “puntero” de los array son:

  1. El puntero sumará 1 cada vez que añadamos un elemento nuevo al array
  2. El puntero irá restando 1 por cada elemento que quitemos al array.
  3. El valor puntero no cambiará cada vez que editemos un valor que esté presente en medio del array.
  4. Un puntero nunca podrá ser ni menor que 0, ni mayor que el tamaño del array, ya que si no estaremos intentando acceder a una parte de la memoria que no pertenece al array, lo cual resulta en error durante la ejecución del programa. Lo tenemos que controlar dentro del programa.

Dicho esto, dejo una tabla de cómo se inicializan los elementos de los arrays vacíos por cada tipo, por si las dudas:

Tipo de datoValor por defecto
byte[], sbyte[], short[], ushort[], int[], uint[], long[], ulong[], float[], double[], decimal[]0
char[]'\0' (carácter nulo), equivalente a (char)0
string[]null
bool[]false
comments powered by Disqus