Un puntero es una variable que contiene la dirección de una variable
Fácil, ¿no?. En realidad no es mas que eso, sin embargo si uno no lo tiene claro es probable que entre en un estado de pánico y confusión debido a que no se sabe si se tiene el valor de la variable o la dirección donde ésta se ubica.
Punteros y Direcciones
Veamos la representación y organización de los datos en la memoria primaria (RAM para los amigos). Típicamente se organizan en celdas contiguas de memoria, con una dirección asignada a cada una de ellas. La idea es poder utilizar una celda o un grupo de ellas de ser necesario.
Comúnmente un byte es un char, 2 bytes representan un short integer y 4 bytes representan un integer. Un puntero es un grupo de celdas (en la mayoría de los PCs modernos 4) que almacena una dirección de memoria.
El operador unario & asigna la dirección de una variable; por ejemplo, sea c un char y p un puntero, entonces la operación
p = &c
asigna la dirección de c a la variable p, se dice entonces que p apunta a c. Notar que el operador & sólo debe ser aplicado sobre variables, jamás sobre expresiones, constantes o registros.
Se tiene además el operador * con el cual se obtiene lo apuntado por el puntero en otras palabras, si se tiene lo anterior entonces se cumple que:
*p == c
Declaraciones
Para declarar un puntero se le debe informar a C que es lo que uno desea almacenar en memoria, por lo tanto se le informa el tipo de lo almacenado, por ejemplo:
char *p; (puntero a char)
int *p; (puntero a int)
float *p; (puntero a float)
La forma general de la declaración de un puntero es tipo *variable;
Ejemplo (Pequeño ejercicio mental, si lo entienden van bien encaminados):
int x=1, y=2; int *ip; /* ip es un puntero a int*/
ip = &x; /* ip apunta a la dirección de x */ y = *ip; /* y = 1 */ *ip = 0; /* x = 0 */ |
Aritmética de punteros
Una de las gracias de los punteros de C radica en que, al ser manejados como una variable más, son válidas todas las operaciones aritméticas de enteros sobre ellos. Lo malo de esto (porque no todo puede ser bueno en la vida) es que el programador puede perder el control sobre ellos dentro de un programa ocasionando más de un dolor de cabeza a quienes realizan los programas.
Hay un viejo lema que reza: mas peligroso que puntero perdido en C.
Por lo tanto, si ip es un puntero a una dirección de memoria, entonces ip++ hará que ese puntero apunte a la dirección siguiente e ip-- hará que apunte a la anterior.
ERRORES COMUNES
Se debe tener mucho cuidado al manejar punteros y diferenciar bien entre lo apuntado y el que apunta al utilizar la aritmética, por ejemplo en el caso anterior en que ip apunta a x se puede tener además que:
*ip=*ip+10; /* x = 11 se aumenta el valor de lo apuntado */
y = *ip +1 /* y = 12 nuevamente se utiliza lo apuntado*/ |
Lo entretenido es cuando se tiene:
*ip++; |
¿Se aumenta lo apuntado o la dirección de la memoria?... la respuesta es...
DEPENDE DEL COMPUTADOR!!!!!!!! (música de suspenso de fondo)
Así que para evitarse malos ratos se utilizan paréntesis:
(*ip)++; /* Aumenta el valor de lo apuntado en 1 */
*(ip++); /* Lo que hace esto es entregar lo apuntado por ip y luego cambiar el puntero a la dirección siguiente */
*(++ip); /* Cambia el puntero a la dirección siguiente y entrega lo apuntado */ |
Otro error común al utilizar punteros es creer que por copiar punteros se crea una copia de lo apuntado, por ejemplo:
int x=10; int *px, *py;
px = &x; /* *px = 10 */ py = px; /* *py = 10 */
(*px)++; /* *px = 11 y OJO *py = 11, porque apuntan a la misma dirección*/ |
cast
Otra de las gracias de C es que uno lo puede engañar haciéndole creer que lo que apunta es en realidad otra cosa, a ese engaño se le conoce como cast.
Por ejemplo, si uno no sabe el tipo de lo que se apuntará con anterioridad se puede definir como void *, y luego con el cast se le informa al computador el tipo de lo almacenado. Por ejemplo:
void *p; /* puntero a cualquier cosa *7 int i; char c;
... /* bla, bla, bla */
if (condicion) { p = (void *) &i; } else { p = (void *) &c; };
... /* más bla, bla, bla */
if (condicion) { i = (int) *p; } else { c = (char) *p; }; |
En el ejemplo anterior se utiliza el cast (void *) para que se pueda asignar a p la dirección de las variables, y luego se utilizan los casts de los tipos respectivos para recuperar los datos.