|
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.
|