5.- ARRAYS

 5.1 INTRODUCCION

Muchas aplicaciones requieren el procesado de múltiples datos que tienen características comunes. En tales situaciones es a menudo conveniente colocar los datos en un array, donde todos comparten el mismo nombre. Los datos individuales pueden ser caracteres, enteros, números en coma flotante, etc. Pero todos tienen que ser del mismo tipo y con el mismo tipo de almacenamiento.
Cada elemento del array es referido especificando el nombre del array seguido por uno o más índices, con cada índice encerrado entre corchetes. Cada índice debe ser expresado como un entero no negativo: una constante entera, una variable entera o una expresión entera más compleja.
En un array x de n elementos los elementos del array son x[0], x[1],...,x[n-1].
El número de índices determina la dimensionalidad del array.

5.2 DEFINICION DE UN ARRAY

Al definirse, cada array debe acompañarse de una especificación de tamaño (número de elementos). En términos generales, un array unidimensional puede expresarse como:
             tipo_almacenam tipo_dato nombre_array[expresion_entera_positiva]

Los array automáticos, a diferencia de las variables automáticas, no pueden ser inicializados. Sin embargo las definiciones de arrays estáticos y externos pueden incluir, si se desea, la asignación de valores iniciales. Los valores iniciales deben aparecer en el orden en que serán asignados a los elementos individuales del array, encerrados entre llaves y separados por comas. La forma general es:
             tipo_almacenam tipo_dato nombre_array[expresión] = {valor1,valor2,...,valorn};

La presencia de la expresión, que indica el número de elementos del array, es opcional cuando los valores iniciales están presentes.
Todos los elementos del array que no tienen asignados valores iniciales explícitos serán puestos automáticamente a cero.

5.3 PROCESAMIENTO DE UN ARRAY

En C no se permiten operaciones que impliquen arrays completos. Así, si a y b son dos arrays similares (mismo tipo de datos, misma dimensionalidad y mismo tamaño), operaciones de asignación, comparación,...m, deben realizarse elemento por elemento.

 5.4 PASO DE ARRAYS A FUNCIONES

El nombre de un array se puede usar como argumento de una función, permitiendo así que el array completo sea pasado a la función. Sin embargo la manera en que se pasa difiere mucho de la de una variable ordinaria.
Para pasar un array a una función, el nombre del array debe aparecer sólo, sin corchetes o índices, como un argumento actual dentro de la llamada a la función. El correspondiente argumento formal se escribe de la misma manera, pero debe ser declarado como un array dentro de la declaración de argumentos formales.  Cuando se declara un array unidimensional como un argumento formal, el array se escribe con un par de corchetes vacíos.
Hemos visto que los argumentos son pasados a la función por valor cuando los argumentos son variables ordinarias. Sin embargo, cuando se pasa un array a una función, los valores de los elementos del array no son pasados a la función. En vez de esto, el nombre del array se interpreta como la dirección del primer elemento del array. Esta dirección se asigna al correspondiente argumento formal cuando se llama a la función. El argumento formal se convierte por tanto en un puntero al primer elemento del array (más sobre esto cuando hablemos de punteros). Los argumentos pasados de esta forma se dicen que son pasados por referencia en vez de por valor. Por tanto, si un elemento del array es alterado dentro de la función, esta alteración será reconocida en todo el ámbito de definición del array.

5.5 ARRAYS MULTIDIMENSIONALES

Los arrays multidimensionales son definidos prácticamente de la misma manera que los arrays unidimensionales, excepto que se requiere un par de corchetes para cada índice. En general:
              tipo_almacenm tipo_dato nombre_array [expresión 1][expresión 2]..[expresión n]

Recordar que tipo_almacenamiento es opcional; los valores por defecto son automáticos para arrays definidos dentro de una función y externos para los arrays definidos fuera de una función.
Los arrays multidimensionales se procesan de la misma manera que los array unidimensionales, sobre la base de elemento a elemento. Sin embargo, se requiere algún cuidado cuando se pasan arrays multidimensionales a una función. En particular, las declaraciones de argumentos formales dentro de la definición de función deben incluir especificaciones explícitas de tamaño en todos los índices excepto en el primero.

 5.6 ARRAYS Y CADENAS DE CARACTERES

Una cadena de caracteres se representa por un array unidimensional de caracteres. Cada carácter de la cadena se almacena en un elemento del array.
Algunos problemas requieren que los caracteres de la cadena sean procesados individualmente. No obstante, hay muchos otros problemas en los que se requiere que las cadenas se procesen como entidades completas. Tales problemas pueden simplificarse considerablemente usando funciones especiales de biblioteca orientadas a cadenas.

gets: Su forma general es gets(cadena_de_caracteres) y lo que hace es tomar de teclado un texto (hasta que se pulse enter) y guardarlo en cadena_de_caracteres.
En la cadena automáticamente se guarda como último elemento el carácter nulo:'\0'.

puts: Su forma general es puts(cadena_de_caracteres) y lo que hace escribir en pantalla el contenido de cadena_de_caracteres terminando con un salto de línea.

Las funciones especificadas a continuación para el tratamiento de cadenas de caracteres se encuentran en el fichero de cabecera string.h:

strcmp: Su forma general strcmp(cadena1,cadena2) y lo que hace es comparar alfabéticamente dos cadenas. Devuelve:
                        - un valor negativo si cadena1 precede alfabéticamente a cadena2.
                       
- el valor 0 si las dos cadenas son idénticas.
                        - un valor positivo si cadena2 precede alfabéticamente a cadena1.

strcpy: Su forma general es strcpy(cadena1,cadena2) y lo que hace es copiar el valor de cadena2 en cadena1.

 strlen: Su forma general es strlen(cadena) y retorna el número de caracteres de la cadena.


6.- PUNTEROS

6.1 CONCEPTOS BÁSICOS

Dentro de la memoria de la computadora cada dato almacenado ocupa una o más celdas contiguas de memoria. El número de celdas de memoria requeridas para almacenar un dato depende de su tipo.
Si v es una variable que representa un determinado dato, el compilador automáticamente asigna celdas de memoria para ese dato. El dato puede ser accedido si conocemos su localización (la dirección) de la primera celda de memoria. La dirección de v se determina mediante &v, donde & es un operador unario, llamado operador dirección, que proporciona la dirección del operando. Si la dirección de v se le asigna a otra variable 
pv = &v; esta nueva variable es un puntero a v: representa la dirección de v y no su valor. El dato representado por v puede ser entonces accedido mediante *pv, donde * es un operador unario, llamado operador indirección, que opera sólo sobre una variable puntero.

Los operadores monarios & y * son miembros del mismo grupo de precedencia que los otros operadores monarios: -,++,--,!,sizeof y (tipo). Hay que recordar que este grupo de operadores tiene mayor precedencia que el de los operadores aritméticos y la asociatividad de los operadores monarios es de derecha a izquierda.
El operador dirección (&) sólo puede actuar sobre variables ordinarias o elementos simples de un array.
El operador indirección (*) sólo puede actuar sobre operandos que sean punteros.
Las variables puntero pueden apuntar a variables numéricas o de caracteres, arrays, funciones u otras variables puntero. (También pueden apuntar a otros tipos de estructuras de datos que se verán posteriormente). Por tanto a una variable puntero se le puede asignar la dirección de una variable ordinaria. También se le puede asignar la dirección de otra variable puntero, siempre que ambas apunten al mismo tipo de dato.

6.2 DECLARACIÓN DE PUNTEROS

Los punteros, como cualquier otra variable, deben ser declarados antes de ser usados. Cuando una variable puntero es definida, el nombre de la variable debe ir precedida por un *. Este identifica que la variable es un puntero. El tipo de dato que aparece en la declaración se refiere al tipo de objeto del puntero, el tipo de dato que se almacena en la dirección representada por el puntero, en vez del puntero mismo. Así, una declaración de puntero general es:
                                                          tipo_dato *ptvar;

donde ptvar es la variable puntero y tipo_dato el tipo de dato apuntado por el puntero.
Dentro de la declaración, una variable puntero puede ser inicializada asignándole la dirección de otra variable (ésta debe estar previamente declarada).

6.3 PASO DE PUNTEROS A UNA FUNCIÓN

A menudo los punteros son pasados a las funciones como argumentos. Esto permite que datos de la porción de programa desde el que se llama a la función sean accedidos por la función, alterados dentro de ella y devueltos de forma alterada. Este uso de los punteros se conoce como paso, de argumentos por dirección o referencia.
Cuando los punteros son usados como argumento de una función, es necesario tener cuidado con la declaración de los argumentos formales dentro de la función. Los argumentos formales que sean punteros deben ir precedidos por un asterisco.

            Ejemplo:
           
void f1(int,int);
            void f2(int *,int *);
            void main()
             {
              int u=1,v=3;
              f1(u,v);
              printf("después de la llamada a f1:  u=%d v=%d\n",u,v);
              f2(&u,&v);
              printf("después de la llamada a f2:  u=%d v=%d\n",u,v);
             }

            void f1(int u,int v)
           
 {
               u=0;
               v=0;
               return;
             }

            void f2(int *pu,int *pv)
             {
               *pu=0;
               *pv=0;
               return;
             }

La salida del programa:  después de la llamada a f1:  u=1 v=3
                                           después de la llamada a f2:  u=0 v=0

El nombre de un array es un puntero al array, representa la dirección del primer elemento del array. Por tanto, un nombre de array se trata como un puntero cuando se pasa a una función y no hay que preceder el array con el & dentro de la llamada a función.
Hay que recordar que la función scanf requiere que sus argumentos vayan precedidos por &, puesto que necesita la dirección de los elementos que van a ser leídos. Los & proporcionan un medio para acceder a las direcciones de variables ordinarias. Los & no son necesarios con nombres de arrays ya que estos mismos representan direcciones.
Una función también puede devolver un puntero a la parte llamante del programa. Para hacer esto, la definición de la función y cualquier declaración de la función debe indicar que la función devolverá un puntero. Esto se realiza precediendo la función con un *. He aquí un boceto de un programa que ilustra esto:

            double *f1(double []);
           
void main()
             {
                double z[10];
                double *pz;
                pz = f1(z);
             }

            double *f1(double z[])
             {
                double *pf;
                 pf=....;
                return(pf);
             }

6.4 PUNTEROS Y ARRAYS UNIDIMENSIONALES

El nombre de un array es realmente un puntero al primer elemento de ese array. Sin embargo, si x es un array undimensional, la dirección del primer elemento puede ser expresada tanto como &x[0] o simplemente como x. La dirección del elemento (i+1) se puede expresar como &x[i] o como (x+i).
En la expresión (x+i) x representa una dirección e i un número entero. Además x es el nombre de un array cuyos elementos pueden ser caracteres, enteros, números en coma flotante, etc. Por tanto, no estamos simplemente añadiendo valores numéricos. Más bien estamos especificando una localización que está i elementos del array detrás del primero, con lo cual la expresión (x+i) es una representación simbólica de una dirección en vez de una expresión aritmética.
Si &x[i] y (x+i) representan la dirección del i-ésimo elemento de x, x[i] y *(x+i) representan el contenido de esa dirección: el valor del i-ésimo elemento de x.
Ya que un nombre de array es en realidad un puntero a su primer elemento, es posible definir un array como una variable puntero en vez de como un array convencional. La definición convencional de un array produce la reserva de un bloque fijo de memoria al principio de la ejecución del programa, mientras que esto no ocurre si se representa el array mediante una variable puntero. En este caso, se necesita algún tipo de asignación inicial de memoria antes de que los elementos del array sean procesados. Tales tipos de reserva se realizan mediante la función malloc.
Si el array se define como una variable puntero, no se le pueden asignar valores iniciales numéricos.

                        int *x;
                       
x = (int *) malloc (10 * sizeof(int));

La función malloc devuelve un puntero a carácter y, en este caso, reserva un bloque de memoria para 10 enteros.
A una variable puntero a carácter sí se le puede asignar una cadena como una parte de la declaración de la variable ( y además sin tener que se declarada static).

6.5 OPERACIONES CON PUNTEROS

Ya se ha dicho que un valor entero se puede sumar al nombre de un array para acceder a uno de sus elementos. El valor entero es interpretado como el índice del array; representa la localización relativa del elemento deseado con respecto al primero. Esto funciona porque todos los elementos del array son del mismo tipo y por tanto cada elemento ocupa un mismo número de celdas de memoria. El número de celdas que separan a dos elementos del array dependerá del tipo de datos del array, pero de esto se encarga el compilador automáticamente.
Este concepto se puede extender a variables puntero. En particular, un valor entero puede ser sumado o restado a una variable puntero, pero el resultado de la operación debe ser interpretado con cuidado. Supongamos que px es una variable puntero que representa la dirección de una variable x. Se pueden escribir expresiones com ++px, --px, (px+3). Cada expresión representa una dirección localizada a cierta distancia de la posición original representada por px. La distancia exacta será el producto de la cantidad entera por el número de bytes que ocupa cada elemento al que apunta px.
Resumen de operaciones que se pueden realizar sobre punteros:

1. A una variable puntero se le puede asignar la dirección de una variable ordinaria (pv=&v).
2. A una variable puntero se le puede asignar el valor de otra variable puntero, siempre que ambas apunten al mismo tipo de dato.
3. A una variable puntero se le puede asignar el valor nulo (NULL).
4. Una cantidad entera puede ser sumada o restada a una variable puntero.
5. Una variable puntero puede ser restada de otra con tal de que ambas apunten a elementos del mismo array.
6. Dos variable puntero pueden ser comparadas siempre que ambas apunten a datos del mismo tipo.
7. No se permiten operaciones aritméticas con punteros. Así una variable puntero no puede ser multiplicada por una constante, dos punteros no pueden sumarse, etc.

6.6 PUNTEROS Y ARRAYS MULTIDIMENSIONALES

Un array bidimensional es en realidad una colección de arrays unidimensionales. Por tanto, se puede definir un array bidimensional como un puntero a un grupo contiguo de arrays unidimensionales. Una declaración de array bidimensional puede ser escrita como    tipo_dato (*ptvar) [expresión2]
en vez de                                                               tipo_dato array [expresión1][expresión2]

Los paréntesis que rodean al puntero deben estar presentes.
Ejemplo:
Suponer que x es un array bidimensional de enteros con 10 filas y 20 columnas. Podemos declara x como:

int (*x)[20]   en vez de  int x[10][20];

En la primera declaración x se define como un puntero a un grupo contiguo de array unidimensionales de 20 elementos enteros. Así x apunta al primero de los arrays de 20 elementos, que es en realidad la primera fila (fila 0) del array bidimensional original. Del mismo modo (x+1) apunta al segundo array de 20 elementos, y así sucesivamente.
El elemento de la fila 2 columna 5 es accedido  +(*(x+2)+5)
(x+2) es un puntero a la fila 2
*(x+2) es el objeto de ese puntero y refiere a toda la fila. Como la fila 2 es un array unidimensional *(x+2) es realmente un puntero al primer elemento de la fila 2.
(*(x+2)+5) es un puntero al elemento 5 de la fila 2.
El objeto de este puntero  *(*(x+2)+5) refiere al elemento en la columna 5 de la fila 2.

6.7 ARRAYS DE PUNTEROS

Un array multidimensional puede ser expresado como un array de punteros en vez de como un puntero a un grupo contiguo de arrays. En términos generales un array bidimensional puede ser definido como un array unidimensional de punteros escribiendo      tipo_dato *nom_array[expresión1]
en vez de
                                                           tipo_dato nom_array[expresión1][expresión2]

Notar que el nombre del array precedido por un asterisco no está cerrado entre paréntesis. Se asocia primero el par de corchetes con nom_array definiéndolo como un array. El asterisco que lo precede establece que el array contendrá punteros.
Ejemplo:
Suponer que x es un array bidimensional de 10 filas y 20 columnas. Se puede definir x como un array unidimensional de punteros escribiendo 
int *x[10];
Aquí x[0] apunta al primer elemento de la primera fila, x[1] al primer elemento de la segunda fila, y así sucesivamente. Notar que el número de elementos dentro de cada fila no está especificado explícitamente. Un elemento individual del array, tal com x[2][5] puede ser accedido escribiendo   *(x[2]+5)
En esta expresión x[2] es un puntero al primer elemento en la fila 2, de modo que (x[2]+5) apunta al elemento 5 de la fila 2. El objeto de este puntero, *(x[2]+5), refiere, por tanto, a x[2][5].
Los arrays de punteros ofrecen un método conveniente para almacenar cadenas. En esta situación cada elemento del array es un puntero que indica dónde empieza cada cadena.


Indice

Ejercicios del Bloque 2

Inicio