Un puntero no es más que una dirección de memoria. Lo que tiene de especial es que normalmente un puntero tendrá un tipo de datos asociado: por ejemplo, un “puntero a entero” será una dirección de memoria en la que habrá almacenado (o podremos almacenar) un número entero.
Vamos a ver qué símbolo usamos en C para designar los punteros:
int num; /* "num" es un número entero */
int *pos; /* "pos" es un "puntero a entero" (dirección de
memoria en la que podremos guardar un entero) */
Es decir, pondremos un asterisco entre el tipo de datos y el nombre de la variable. Ese asterisco puede ir junto a cualquiera de ambos, también es correcto escribir
int* pos;
Esta nomenclatura ya la habíamos utilizado aun sin saber que era eso de los punteros. Por ejemplo, cuando queremos acceder a un fichero, hacemos
FILE* fichero;
Antes de entrar en más detalles, y para ver la diferencia entre trabajar con “arrays” o con punteros, vamos a hacer dos programas que pidan varios números enteros al usuario y muestren su suma. El primero empleará un “array” (una tabla, de tamaño predefinido) y el segundo empleará memoria que reservaremos durante el funcionamiento del programa.
El primero podría ser así:
/*---------------------------*/
/* Ejemplo en C nº 71: */
/* C071.C */
/* */
/* Sumar varios datos */
/* Version 1: con arrays */
/* */
/* Curso de C, */
/* Nacho Cabanes */
/*---------------------------*/
#include
int main() {
int datos[100]; /* Preparamos espacio para 100 numeros */
int cuantos; /* Preguntaremos cuantos desea introducir */
int i; /* Para bucles */
long suma=0; /* La suma, claro */
do {
printf("Cuantos numeros desea sumar? ");
scanf("%d", &cuantos);
if (cuantos>100) /* Solo puede ser 100 o menos */
printf("Demasiados. Solo se puede hasta 100.");
} while (cuantos>100); /* Si pide demasiado, no le dejamos */
/* Pedimos y almacenamos los datos */
for (i=0; i
Los más avispados se pueden dar cuenta de que si sólo quiero calcular la suma, lo podría hacer a medida que leo cada dato, no necesitaría almacenar todos. Vamos a suponer que sí necesitamos guardarlos (en muchos casos será verdad, si los cálculos son más complicados). Entonces nos damos cuenta de que lo que hemos estado haciendo hasta ahora no es eficiente:
La solución es reservar espacio estrictamente para lo que necesitemos, y eso es algo que podríamos hacer así:
/*---------------------------*/
/* Ejemplo en C nº 72: */
/* C072.C */
/* */
/* Sumar varios datos */
/* Version 2: con punteros */
/* */
/* Curso de C, */
/* Nacho Cabanes */
/*---------------------------*/
#include
#include
int main() {
int* datos; /* Necesitaremos espacio para varios numeros */
int cuantos; /* Preguntaremos cuantos desea introducir */
int i; /* Para bucles */
long suma=0; /* La suma, claro */
do {
printf("Cuantos numeros desea sumar? ");
scanf("%d", &cuantos);
datos = (int *) malloc (cuantos * sizeof(int));
if (datos == NULL) /* Si no hay espacio, avisamos */
printf("No caben tantos datos en memoria.");
} while (datos == NULL); /* Si pide demasiado, no le dejamos */
/* Pedimos y almacenamos los datos */
for (i=0; i
Este fuente es más difícil de leer, pero a cambio es mucho más eficiente: funciona perfectamente si sólo queremos sumar 5 números, pero también si necesitamos sumar 120.000 (y si caben tantos números en la memoria disponible de nuestro equipo, claro).
Vamos a ver las diferencias:
En primer lugar, lo que antes era int datos[100] que quiere decir “a partir de la posición de memoria que llamaré datos, querré espacio para a guardar 100 números enteros”, se ha convertido en int* datos que quiere decir “a partir de la posición de memoria que llamaré datos voy a guardar varios números enteros (pero aún no sé cuantos)”.
Luego reservamos el espacio exacto que necesitamos, haciendo datos = (int *) malloc (cuantos * sizeof(int)); Esta orden suena complicada, así que vamos a verla por partes:
La forma de guardar los datos que teclea el usuario también es distinta. Cuando trabajábamos con un “array”, hacíamos scanf("%d", &datos[i]) (“el dato número i”), pero con punteros usaremos scanf("%d", datos+i) (en la posición datos + i). Ahora ya no necesitamos el símbolo “ampersand” (&). Este símbolo se usa para indicarle a C en qué posición de memoria debe almacenar un dato. Por ejemplo, float x; es una variable que podremos usar para guardar un número real. Si lo hacemos con la orden “scanf”, esta orden no espera que le digamos en qué variable deber guardar el dato, sino en qué posición de memoria. Por eso hacemos scanf("%f", &x); En el caso que nos encontramos ahora, int* datos ya se refiere a una posición de memoria (un puntero), por lo que no necesitamos & para usar “scanf”.
Finalmente, la forma de acceder a los datos también cambia. Antes leíamos el primer dato como datos[0], el segundo como datos[1], el tercero como datos[2] y así sucesivamente. Ahora usaremos el asterisco (*) para indicar que queremos saber el valor que hay almacenado en una cierta posición: el primer dato será *datos, el segundo *(datos+1), el tercero será *(datos+2) y así en adelante. Por eso, donde antes hacíamos suma += datos[i]; ahora usamos suma += *(datos+i);
También aparece otra orden nueva: free. Hasta ahora, teníamos la memoria reservada estáticamente, lo que supone que la usábamos (o la desperdiciábamos) durante todo el tiempo que nuestro programa estuviera funcionando. Pero ahora, igual que reservamos memoria justo en el momento en que la necesitamos, y justo en la cantidad que necesitamos, también podemos volver a dejar disponible esa memoria cuando hayamos terminado de usarla. De eso se encarga la orden “free”, a la que le debemos indicar qué puntero es el que queremos liberar.