Introducción a los punteros
Esteban MonteroApuntes16 de Octubre de 2015
2.564 Palabras (11 Páginas)117 Visitas
Introducción a los punteros
Los puntos que detallaremos son:
- Definición del concepto de puntero.
- Declaración de punteros.
- Operadores de punteros: & y *.
- Aritmética de punteros.
- Asignación dinámica de memoria.
- Punteros y arrays.
- Inicializaciones de punteros.
- Punteros a funciones.
- Tipo void y punteros.
- Declaraciones curiosas.
El concepto de puntero es importantísimo en la programación en C. Un puntero contiene una dirección de memoria. Cuando una variable contiene la dirección de otra variable se dice que la primera variable apunta a la segunda.
Variables punteros
La forma general para declarar una variable puntero es:
tipo *nombre;
donde tipo es cualquier tipo válido de C (también llamado tipo base) y nombre es el nombre de la variable puntero.
Operadores de punteros
Existen dos operadores especiales de punteros: & y *. Estos dos operados son monarios y no tienen nada que ver con los operadores binarios de multiplicación (*) y de and a nivel de bits (&).
Operador &
& es un operador monario que devuelve la dirección de memoria de su operando.
Ejemplo:
#include
main (void)
{
int x = 10;
printf (" x = %d\n &x = %p\n",
x, &x);
}
Salida del ejemplo anterior:
x = 10
&x = 8FBC:0FFE
Operador *
El operador * es el complemento de &. Es un operador monario que devuelve el valor de la variable localizada en la dirección que sigue.
Ejemplo 1:
#include
main (void)
{
int x = 10;
printf (" x = %d\n", x);
printf (" *&x = %d", *&x);
}
Salida de ejemplo 1:
x = 10
*&x = 10
Aritmética de punteros
Existen 4 operadores que realizan operaciones aritméticas que pueden utilizarse con punteros:
+, -, ++, --
Ejemplo
#include
#define imprimir_p printf ("\np = %p", p);
main (void)
{
int *p;
imprimir_p;
printf ("\tsizeof(*p) = %d", sizeof(*p));
p++; imprimir_p;
p -= 5; imprimir_p;
}
/* SALIDA DEL EJEMPLO:
p = 76F69E34 sizeof(*p) = 4
p = 76F69E38
p = 76F69E24
*/
Ojo : ¿Cuánto es (76F69E38 – 14) en hexadecimal?
En el ejemplo anterior se aprecia que si hacemos p++; no aumenta el valor de p en 1 sino que aumenta en 4, que es el tamaño en bytes de un int, es decir, el tamaño del objeto al que apunta.
Por lo tanto, la sentencia p++ hay que interpretarla de la siguiente forma: «p apunta al siguiente elemento del tipo base». Lo mismo se puede decir de los demás operadores aritméticos aplicados a los punteros.
Supóngase que queremos hacer un programa que lea n valores enteros introducidos por teclado por el usuario, los almacene en un vector y los imprima en orden inverso.
Una solución es:
#include
main (void)
{
#define NMAX 100 /* número máximo de elementos */
int v[NMAX]; /* vector */
int n = 0; /* número de elementos introducidos */
int varaux; /* variable auxiliar */
register int i; /* índice */
do
{
printf ("\nIntroduce número de valores a leer (1-%d): ", NMAX);
scanf ("%d", &n);
} while (n < 1 || n > NMAX);
for (i = 0; i < n; i++)
{
printf ("Introduce valor %d: ", i);
scanf ("%d", &varaux);
v[i] = varaux;
}
printf ("\n\nValores en orden inverso:\n");
for (i = n - 1; i >= 0; i--)
printf ("%d ", v[i]);
}
Si el usuario introduce como valor de n, el valor 10, estaremos desperdiciando, si un int ocupa 2 bytes, 90*2 bytes de memoria. Además, el usuario no puede introducir más de NMAX valores. Estas restricciones vienen impuestas porque el tamaño de un array en la declaración ha de ser una expresión constante. La asignación de memoria en este caso se dice que es estática porque se determina en el momento de la compilación. Cuando la asignación de memoria se determina en tiempo de ejecución se dice que es asignación dinámica.
Veamos cómo se haría el programa anterior con asignación dinámica y luego pasamos a explicarlo:
#include
#include
main (void)
{
int *v; /* vector */
int n = 0; /* número de elementos introducidos */
int varaux; /* variable auxiliar */
register int i; /* índice */
printf ("\nIntroduce número de valores a leer: ");
scanf ("%d", &n);
v = (int *) malloc (n * sizeof (int));
if (v == NULL)
printf ("Memoria insuficiente.");
else
{
for (i = 0; i < n; i++)
{
printf ("Introduce valor %d: ", i);
scanf ("%d", &varaux);
v[i] = varaux;
}
printf ("\n\nValores en orden inverso:\n");
for (i = n - 1; i >= 0; i--)
printf ("%d ", v[i]);
free (v);
}
}
La primera sentencia de main() es:
int *v;
En esta declaración estamos declarando v como un puntero a entero.
La siguiente línea «extraña» para nostros es:
v = (int *) malloc (n * sizeof (int));
La función malloc reserva memoria; acepta como argumento los bytes de memoria a reservar y devuelve un puntero al primer byte de la zona de memoria reservada; los bytes de memoria solicitados los reserva en un espacio de memoria contiguo. Si no hay suficiente memoria, devuelve NULL. Un puntero que tiene el valor NULL es un puntero que no apunta a ningún sitio.
El prototipo de esta función se encuentra en el fichero malloc.h (de ahí el incluir este fichero en nuestro ejemplo) y es el siguiente:
void *malloc (unsigned int bytes);
Vemos que devuelve un puntero a void; esto quiere decir que devuelve un puntero que apunta a cualquier tipo base, o dicho de otro modo, un puntero que apunta a una dirección de memoria sin tener tipo base.
Veamos otra vez la llamada a esta función en nuestro ejemplo:
v = (int *) malloc (n * sizeof (int));
Al valor devuelto por la función malloc (puntero a void) siempre se le realiza un moldeado (recordad que esto se hacía con: (tipo)) para adecuarlo al tipo base de nuestro puntero que va a apuntar a esa zona de memoria reservada. En nuestro caso el molde es:
(int *) /* puntero a entero */
El argumento que le pasamos a malloc ha de ser el número de bytes de memoria a reservar. Esto siempre se hace siguiendo la fórmula:
numero_de_elementos * sizeof (tipo_de_cada_elemento)
que traducido a nuestro caso queda:
n * sizeof (int)
Otra forma de hacer lo mismo es:
n * sizeof (*v)
que suele ser muy corriente. Las dos formas son equivalentes.
La memoria asignada por malloc se desasigna con la función free(). Esta memoria asignada no se desasigna al salir del bloque de código en que fue asignada como ocurre con las variables locales sino con la función free (liberar) o al terminar el programa. Por lo tanto, siempre que asignemos memoria con malloc, tenemos que desasignarla con free cuando ya no nos sea necesaria.
...