Un arreglo multidimensional puede ser visto en varias formas en C, por ejemplo:
Un arreglo de dos dimensiones es un arreglo de una dimensión, donde cada uno de los elementos es en sí mismo un arreglo.
Por lo tanto, la notación
a[n][m]
nos indica que los elementos del arreglo están guardados renglón por renglón.
Cuando se pasa una arreglo bidimensional a una función se debe especificar el número de columnas -- el número de renglones es irrelevante.
La razón de lo anterior, es nuevamente los apuntadores. C requiere conocer cuantas son las columnas para que pueda brincar de renglón en renglón en la memoria.
Considerando que una función deba recibir int a[5][35]
, se puede declarar el argumento de la función como:
f( int a[][35] ) { ..... }
o aún
f( int (*a)[35] ) { ..... }
En el último ejemplo se requieren los parénteis (*a)
ya que [ ]
tiene una precedencia más alta que *
.
Por lo tanto:
int (*a)[35];
declara un apuntador a un arreglo de 35 enteros, y por ejemplo si hacemos la siguiente referencia a+2
, nos estaremos refiriendo a la dirección del primer elemento que se encuentran en el tercer renglón de la matriz supuesta, mientras que
int *a[35];
declara un arreglo de 35 apuntadores a enteros.
Ahora veamos la diferencia (sutil) entre apuntadores y arreglos. El manejo de cadenas es una aplicación común de esto.
Considera:
char *nomb[10];
char anomb[10][20];
En donde es válido hacer nomb[3][4]
y anomb[3][4]
en C.
Sin embargo:
- -
anomb
es un arreglo verdadero de 200 elementos de dos dimensiones tipo char
.
- -
- El acceso de los elementos
anomb
en memoria se hace bajo la siguiente fórmula 20*renglon + columna + dirección_base
- -
- En cambio
nomb
tiene 10 apuntadores a elementos.
NOTA: si cada apuntador en nomb
indica un arreglo de 20 elementos entonces y solamente entonces 200 chars estarán disponibles (10 elementos).
Con el primer tipo de declaración se tiene la ventaja de que cada apuntador puede apuntar a arreglos de diferente longitud.
Considerar:
char *nomb[] = { "No mes", "Ene", "Feb", "Mar", .... };
char anomb[][15] = { "No mes", "Ene", "Feb", "Mar", ... };
Se puede indicar que se hace un manejo más eficiente del espacio haciendo uso de un arreglo de apuntadores y usando un arreglo bidimensional.

Apuntadores Un apuntador es una
variable que contiene la dirección en memoria de otra variable. Se pueden tener apuntadores a cualquier tipo de variable.
El operador
unario o
monádico &
devuelve la dirección de memoria de una variable.
El operador de
indirección o
dereferencia *
devuelve el ``contenido de un objeto apuntado por un apuntador''.
Para declarar un apuntador para una variable entera hacer:
int *apuntador;
Se debe asociar a cada apuntador un tipo particular. Por ejemplo, no se puede asignar la dirección de un
short int
a un
long int
.
Para tener una mejor idea, considerar el siguiente código:
main()
{
int x = 1, y = 2;
int *ap;
ap = &x;
y = *ap;
x = ap;
*ap = 3;}
Cuando se compile el código se mostrará el siguiente mensaje:
warning: assignment makes integer from pointer without a cast
.
Con el objetivo de entender el comportamiento del código supongamos que la variable
x
esta en la localidad de la memoria
100
,
y
en
200
y
ap
en
1000
.
Nota: un apuntador es una variable, por lo tanto, sus valores necesitan ser guardados en algún lado.
int x = 1, y = 2;
int *ap;
ap = &x;
100 | 200 | 1000 |
x | 1 | y | 2 | ap | 100 |
Las variables
x
e
y
son declaradas e inicializadas con
1
y
2
respectivamente,
ap
es declarado como un apuntador a entero y se le asigna la dirección de
x
(
&x
). Por lo que
ap
se carga con el valor
100
.
y = *ap;
100 | 200 | 1000 |
x | 1 | y | 1 | ap | 100 |
Después
y
obtiene el contenido de
ap
. En el ejemplo
ap
apunta a la localidad de memoria 100 -- la localidad de
x
. Por lo tanto,
y
obtiene el valor de
x
-- el cual es 1.
x = ap;
100 | 200 | 1000 |
x | 100 | y | 1 | ap | 100 |
Como se ha visto C no es muy estricto en la asignación de valores de diferente tipo (apuntador a entero). Así que es perfectamente legal (aunque el compilador genera un aviso de cuidado) asigna el valor actual de
ap
a la variable
x
. El valor de
ap
en ese momento es
100
.
*ap = 3;
100 | 200 | 1000 |
x | 3 | y | 1 | ap | 100 |
Finalmente se asigna un valor al contenido de un apuntador (
*ap
).
Importante: Cuando un apuntador es declarado apunta a algún lado. Se debe inicializar el apuntador antes de usarlo. Por lo que:
main()
{
int *ap;
*ap = 100;
}
puede generar un error en tiempo de ejecución o presentar un comportamiento errático.
El uso correcto será:
main()
{
int *ap;
int x;
ap = &x;
*ap = 100;
}
Con los apuntadores se puede realizar también aritmética entera, por ejemplo:
main()
{
float *flp, *flq;
*flp = *flp + 10;
++*flp;
(*flp)++;
flq = flp;}
NOTA: Un apuntador a cualquier tipo de variables es una dirección en memoria -- la cual es una dirección entera, pero un apuntador NO es un entero.
La razón por la cual se asocia un apuntador a un tipo de dato, es por que se debe conocer en cuantos bytes esta guardado el dato. De tal forma, que cuando se incrementa un apuntador, se incrementa el apuntador por un ``bloque'' de memoria, en donde el bloque esta en función del tamaño del dato.
Por lo tanto para un apuntador a un char, se agrega un byte a la dirección y para un apuntador a entero o a flotante se agregan 4 bytes. De esta forma si a un apuntador a flotante se le suman 2, el apuntador entonces se mueve dos posiciones float que equivalen a 8 bytes.
A continuación se muestran dos errores comunes que se hacen con los apuntadores.
- No asignar un apuntador a una dirección de memoria antes de usarlo
int *x
*x = 100; lo adecuado será, tener primeramente una localidad física de memoria, digamos int y;
int *x, y;
x = &y;
*x = 100;
- Indirección no válida Supongamos que se tiene una función llamada
malloc() la cual trata de asignar memoria dinámicamente (en tiempo de ejecución), la cual regresa un apuntador al bloque de memoria requerida si se pudo o un apuntador a nulo en otro caso.
char *malloc() -- una función de la biblioteca estándar que se verá más adelante. Supongamos que se tiene un apuntador char *p
Considerar:
*p = (char *) malloc(100): /* pide 100 bytes de la memoria */
*p = 'y'; Existe un error en el código anterior. ¿Cuál es?
El * en la primera línea ya que malloc regresa un apuntador y *p no apunta a ninguna dirección.
El código correcto deberá ser:
p = (char *) malloc(100); Ahora si malloc no puede regresar un bloque de memoria, entonces p es nulo, y por lo tanto no se podrá hacer:
*p = 'y'; Un buen programa en C debe revisar lo anterior, por lo que el código anterior puede ser reescrito como:
p = (char *) malloc(100): /* pide 100 bytes de la memoria */
if ( p == NULL )
{
printf("Error: fuera de memoria\n");
exit(1);
}
*p = 'y';
|