Correspondencia entre arrays y punteros
En muchos aspectos, existe una equivalencia entre arrays y punteros. De hecho, cuando declaramos un array estamos haciendo varias cosas a la vez:
-Declaramos un puntero del mismo tipo que los elementos del array.
-Reservamos memoria para todos los elementos del array. Los elementos de un array se almacenan internamente en la memoria del ordenador en posiciones consecutivas.
-Se inicializa el puntero de modo que apunte al primer elemento del array.
Las diferencias entre un array y un puntero son dos:
-Que el identificador de un array se comporta como un puntero constante, es decir, no podemos hacer que apunte a otra dirección de memoria.
-Que el compilador asocia, de forma automática, una zona de memoria para los elementos del array, cosa que no hace para los elementos apuntados por un puntero corriente.
Ejemplo:
int vector[10];
int *puntero;
puntero = vector; /* Equivale a puntero = &vector[0]; (1)
esto se lee como "dirección del primer elemento de vector" */
(*puntero)++; /* Equivale a vector[0]++; (2) */
puntero++; /* puntero equivale a asignar a puntero el valor &vector[1] (3) */ |
¿Qué hace cada una de estas instrucciones?:
En (1) se asigna a puntero la dirección del array, o más exactamente, la dirección del primer elemento del array vector.
En (2) se incrementa el contenido de la memoria apuntada por puntero, que es vector[0].
En (3) se incrementa el puntero, esto significa que apuntará a la posición de memoria del siguiente elemento int, y no a la siguiente posición de memoria. Es decir, el puntero no se incrementará en una unidad, como tal vez sería lógico esperar, sino en la longitud de un int, ya que puntero apunta a un objeto de tipo int.
Análogamente, la operación:
puntero = puntero + 7; |
No incrementará la dirección de memoria almacenada en puntero en siete posiciones, sino en 7*sizeof(int).
Otro ejemplo:
struct stComplejo {
float real, imaginario;
} Complejo[10];
stComplejo *pComplejo; /* Declaración de un puntero */
pComplejo = Complejo; /* Equivale a pComplejo = &Complejo[0]; */
pComplejo++; /* pComplejo == &Complejo[1] */ |
En este caso, al incrementar pComplejo avanzaremos las posiciones de memoria necesarias para apuntar al siguiente complejo del array Complejo. Es decir avanzaremos sizeof(stComplejo) bytes.
La correspondencia entre arrays y punteros también afecta al operador []. Es decir, podemos usar los corchetes con punteros, igual que los usamos con arrays. Pero incluso podemos ir más lejos, ya que es posible usar índices negativos.
Por ejemplo, las siguientes expresiones son equivalentes:
*(puntero + 7);
puntero[7]; |
De forma análoga, el siguiente ejemplo también es válido:
int vector[10];
int *puntero;
puntero = &vector[5];
puntero[-2] = puntero[2] = 100; |
Evidentemente, nunca podremos usar un índice negativo con un array, ya que estaríamos accediendo a una zona de memoria que no pertenece al array, pero eso no tiene por qué ser cierto con punteros.
En este último ejemplo, puntero apunta al sexto elemento de vector, de modo que puntero[-2] apunta al cuarto, es decir, vector[3] y puntero[2] apunta al octavo, es decir, vector[7].
Operaciones con punteros
La aritmética de punteros es limitada, pero en muchos aspectos muy interesante; y aunque no son muchas las operaciones que se pueden hacer con los punteros, cada una tiene sus peculiaridades.
Asignación
Ya hemos visto cómo asignar a un puntero la dirección de una variable. También podemos asignar un puntero a otro, esto hará que los dos apunten a la misma dirección de memoria:
int *q, *p;
int a;
q = &a; /* q apunta a la dirección de a */
p = q; /* p apunta al mismo sitio, es decir,
a la dirección de a */ |
Sólo hay un caso especial en la asignación de punteros, y es cuando se asigna el valor cero. Este es el único valor que se puede asignar a cualquier puntero, independientemente del tipo de objeto al que apunte.
Operaciones aritméticas
Podemos distinguir dos tipos de operaciones aritméticas con punteros. En uno de los tipos uno de los operandos es un puntero, y el otro un entero. En el otro tipo, ambos operandos son punteros.
Ya hemos visto ejemplos del primer caso. Cada unidad entera que se suma o resta al puntero hace que este apunte a la dirección del siguiente objeto o al anterior, respectivamente, del mismo tipo.
El valor del entero, por lo tanto, no se interpreta como posiciones de memoria física, sino como posiciones de objetos del tipo al que apunta el puntero.
Por ejemplo, si sumamos el valor 2 a un puntero a int, y el tipo int ocupa cuatro bytes, el puntero apuntará a la dirección ocho bytes mayor a la original. Si se tratase de un puntero a char, el puntero avanzará dos posiciones de memoria.
Las restas con enteros operan de modo análogo.
Con este tipo de operaciones podemos usar los operadores de suma, resta, preincremento, postincremento, predecremento y postdecremento. Además, podemos combinar los operadores de suma y resta con los de asignación: += y -=.
En cuanto al otro tipo de operaciones aritméticas, sólo está permitida la resta, ya que la suma de punteros no tiene sentido. Si la suma o resta de un puntero y un entero da como resultado un puntero, la resta de dos punteros dará como resultado, lógicamente, un entero. Veamos un ejemplo:
int vector[10];
int *p, *q;
p = vector; /* Equivale a p = &vector[0]; */
q = &vector[4]; /* apuntamos al 5º elemento */
cout << q-p << endl; |
El resultado será 4, que es la "distancia" entre ambos punteros.
Generalmente, este tipo de operaciones sólo tendrá sentido entre punteros que apunten a objetos del mismo tipo, y más frecuentemente, entre punteros que apunten a elementos del mismo array.
Comparación entre punteros
Comparar punteros puede tener sentido en la misma situación en la que lo tiene restar punteros, es decir, averiguar posiciones relativas entre punteros que apunten a elementos del mismo array.
Podemos usar los operadores <, <=, >= o > para averiguar posiciones relativas entre objetos del mismo tipo apuntados por punteros.
Existe otra comparación que se realiza muy frecuente con los punteros. Para averiguar si estamos usando un puntero nulo es corriente hacer la comparación:
if(NULL != p) |
Lo expuesto en el capítulo 9 sobre conversiones implícitas a bool también se aplica a punteros, de modo que podemos simplificar esta sentencia como:
if(p)
|
Y también:
if(NULL == p) |
O simplemente:
if(!p) |
Nota: No es posible comparar punteros de tipos diferentes, ni aunque ambos sean nulos. |
Comentarios