Punteros en C
Los punteros es algo que cada
lenguaje de programación trata de una forma específica, aunque lo que se puede
en uno, normalmente se suele poder hacer en otro de una u otra manera.
Es por ello, que al ser algo
específico del lenguaje de programación, que vamos a verlo para el caso
específico de C, aunque algunas cosas podrían ser generalizables a otros
lenguajes (pero muchas no)
Variables estáticas y dinámicas
En general, al programar,
puedo hacer uso de dos tipos de variables:
è Estáticas: La memoria que utilizan se
reserva en tiempo de compilación. Ventaja: Su sencillez
è Dinámicas: La memoria se reserva en tiempo
de ejecución. Ventajas: Flexibilidad (estoy definiendo una matriz de cualquier
tamaño) y permiten la creación de estructuras de datos dinámicas (que es un
mecanismo más complejo que el visto hasta ahora con las estructuras estáticas).
int *matriz
…
matriz=malloc(sizeof(int)*5*5);
Para utilizar variables
dinámicas es preciso disponer de un mecanismo para acceder a la memoria del
ordenador. Ese mecanismo es precisamente el uso de punteros.
Concepto de puntero
Cuando tengo una variable, hay
tres datos que están relacionados en ella:
è Tipo: Directamente relacionado con el tamaño
del espacio que se reserva en memoria para ella
è Valor que contiene
è Dirección: Ubicación en memoria
Un puntero es una variable que
almacena la dirección de memoria de otra variable. Es como una flecha que
apunta a otra variable.
Punteros: dos operadores
è Operador dirección (&): Nos devuelve la
dirección de una variable
è Operador contenido o indirección (*): Nos
permite acceder al contenido de una variable a la que está apuntando un puntero
Declaración de punteros
Hay que indicar el tipo de la
variable a la que apuntará, el nombre del puntero, y utilizar el símbolo * para
indicar que es un puntero.
Ejemplo:
int valor;
int *puntero;
puntero=&valor;
valor=5;
*puntero=5; /* Las dos instrucciones hacen lo mismo */
Más sobre punteros
¿Cuál es la diferencia?
puntero=5; /* Modifico a donde apunta el puntero */
*puntero=5; /* Modifico el valor de la variable a la que apunta el
puntero */
Hay que inicializar los
punteros para que apunten a una dirección de memoria antes de utilizarlos.
int *puntero;
*puntero=7;
¿Dónde apunte el puntero? ¿Qué
estoy modificando? Exacto, no lo sé ni yo, así que esto al sistema operativo no
le va a gustar y seguro que se enfada (abortándome el programa).
Valor NULL: Es un puntero que
se usa para indicar que un puntero no apunta a ningún sitio:
int *puntero=NULL;
¿Qué operaciones puedo hacer
con datos apuntados? Las mismas que con el correspondiente tipo de datos.
Operaciones básicas con punteros
Asignación:
è Hacer que el puntero apunte a una dirección
de memoria
è p1=p2; Ojo, p1 pasa a contener la dirección
de memoria contenida en p2.
è Implicaciones:
·
Los
vectores hay que copiarlos componente a componente
·
Si
copiamos el puntero, no hemos copiado la variable
Comparación:
è Ver si dos punteros apuntan al mismo lugar
è p1==p2 no es lo mismo que *p1==*p2;
Suma, resta:
è Se utiliza para recorrer estructuras de
datos.
è p1++ (apuntará al siguiente carácter de una
cadena)
Inicialización del valor de un puntero
Tengo dos operaciones:
è Asignar la dirección de otra variable
del programa:
int
valor;
int
*puntero;
puntero=&valor;
è Pedir al sistema memoria para una variable
nueva:
Es precisamente la opción empleada para
utilizar variables dinámicas. C ofrece funciones para obtener memoria del
sistema operativo de forma dinámica y liberarla cuando ya no es necesaria
Generación y destrucción de variables dinámicas
#include<stdlib.h> /* necesario para asignación dinámica de
memoria */
…
struct fecha {
int dia;
int mes;
int agno;
};
struct fecha *pFecha;
…
pFecha=(struct fecha *) malloc (sizeof(struct fecha));
if (pFecha==NULL){
printf(“No hay
suficiente memoria \n”);
}
else {
…
free(pFecha);
…
};
…
Malloc y Free
pFecha=(struct fecha *) malloc
(sizeof(struct fecha));
Malloc reserva los bytes que
indiquemos en el parámetro que le pasamos (Precisamente para ello usamos la
función sizeof pasándole como parámetro el tipo correspondiente. Así reservamos
espacio justo para una variable de ese tipo).
Malloc devuelve un puntero genérico
(o NULL si no hay memoria suficiente).
Tras llamar a malloc hay que
hacer una conversión explícita de tipos para convertir ese puntero genérico en
un puntero específico al tipo de datos que estamos manejando (en este caso en
un puntero a struct fecha).
free(pFecha);
Free libera la memoria a la
que apunta el puntero que le pasamos como parámetro.
Liberará más o menos memoria
en función del tipo de datos del que sea el puntero que le pasamos.
Paso de parámetros por referencia a una función
Los punteros entre otras cosas
dan soporte para poder pasar parámetros por referencia a una función:
…
float perim, area;
circulo(radio, &perim, &area);
…
void circulo (float r, float *p, float *a){
*p=2*Pl*r;
*a=Pl*r*r;
}
Si la función llamara a otra
función y hubiera que pasar de nuevo “a” por referencia, no habría que poner “&”
delante de “a”, ya que “a” es ya un puntero y por tanto una dirección.
Punteros y vectores
char *p, c, v[5]; /* Definimos un puntero a caracter, un caracter, y
un vector de 5 caracteres */
c=*p; /* Asigno a c lo apuntado por p */
p=&c; /* Asigno a p la dirección de c */
/* Al definir un vector v[5] se queda guardada la dirección inicial del
vector en la constante v */
p=v;
p=&v[0]; /* Esta línea y la anterior son equivalentes */
/* Del mismo modo p+4 es exactamente lo mismo que &p[4] */
/* Y *(v+4) equivale a v[4] */
Recorriendo vectores con punteros
Le estaríamos sumando a p 1:
char *p:
p=p+1;
Le estaríamos sumando a p 4:
int *p;
p=p+1; /*Sí, uno vale por cuatro */
Es decir, estás sumando
unidades de datos, no bytes
Aclarando un poco las cosas
int vector[4];
El compilador reserva cuatro
enteros consecutivos en memoria.
Almacena en la variable vector
la dirección del primer elemento del vector.
Acceso a los elementos:
vector[0] *(vector)
vector[1] *(vector+1)
vector[2] *(vector+2)
vector[3] *(vector+3)
La variable vector es como un
puntero, con la única diferencia de que se ha reservado además espacio para sus
componentes.
Paso de vectores como parámetros
Cuando pasamos un vector a un
función, lo estamos pasando implícitamente por referencia, ya que un vector y
un puntero, a fin de cuentas, es lo mismo (o casi).
Como parámetro actual ponemos
la variable del vector (que como acabamos de ver no es más que un puntero).
Como parámetro formal, hasta
ahora hemos hecho apaños ya que no sabíamos muy bien lo que eran los punteros
ni su relación con los vectores, pero a partir de ahora basta con poner un
puntero al tipo de que sean las componentes del vector. Dentro de la función
podré acceder a las “componentes de ese puntero” sin ningún problema.
Recordar que al ser un paso
por referencia y al estar psaando punteros, todos los cambios que haga en el
vector quedarán hechos en el vector original.
Recordar que al ser un paso
por referencia y al estar pasando punteros, todos los cambios que haga en el
vector quedarán hecho en el vector original.
Recordar además que existe un
motivo importante para que un vector se pase siempre por referencia, y es que
de hacerse por valor se estarían duplicando las necesidades de memoria.
Paso de parámetros. Ejemplo
main(){
char cadena[50];
char nueva[50];
…
quitarEspacios(cadena,
nueva);
/* cadena y nueva son ya
direcciones */
}
void quitarEspacios (char *origen, char *destino) {
…
destino[3]=origen[7];
…
}
Punteros y matrices
Es la misma idea que cuando
hemos hablado de vectores y punteros.
Una matriz es un puntero al
primer elemento más una reserva de espacio para todos los elementos.
¿Cómo se almacena una matriz
en C? Por filas. El orden de los elemento en memoria es el siguiente
(suponiendo matriz de 3x3):
m[0][0]
m[0][1]
m[0][2]
m[1][0]
m[1][1]
m[1][2]
m[2][0]
m[2][1]
m[2][2]
¿Puedo hacer esto?
#define FILAS 3
#define COLUMNAS 3
…
int matriz[FILAS][COLUMNAS];
mifuncion(matriz);
…
void mifuncion(int *m){
…
m[1][0]=…
…
}
No. Motivo: Cuando quiero
pasar una matriz como parámetro a una función tengo que pasar además del
puntero, las dimensiones de esa matriz ya que el puntero no tiene ninguna
información acerca de cuantas columnas tiene la matriz y por tanto no sabe dónde
terminan las filas.
Cuando he puesto m[1][0], sintácticamente
es correcto, pero el compilador en ese punto como no sabe cómo de larga es una
fila (es decir, cuantas columnas tiene la matriz), no tiene ni idea de a que
componente de la matriz tiene que acceder porque no sabe cuanto desplazarse en
memoria.
Es decir, existe m[1][0], m[0][2]…
¿y m[0][1000]? ¿O paramos en m[0][29]? Si no sé como de larga es una fila, no sé
dónde está almacenado en memoria la componente [1][0] y por tanto no puedo
acceder así a los elementos guardados en una matriz.
¿Cómo lo hago entonces?
Pasando puntero (m), número de filas (f) y número de columnas (c).
El acceso a la componente [1][0]
se hace como (m+c*1+0), o como m[c*1+0] (sí, accediendo a la matriz como si
fuera un vector, menudo lio, jeje. Bienvenidos a C).
Paso de matrices como parámetros
#define FILAS 3
#define COLUMAS 3
…
int matriz[FILAS][COLUMNAS];
mifuncion(matriz,FILAS,COLUMNAS);
…
void mifuncion(int *m, int f, int c){
…
(m+c*2+1)=…
/* Equivalente a m[2][1]
o a m[c*2+1] */
…
}
Punteros y registros
->: Operador especial para
acceder a los campos de un registro que es apuntado por un puntero.
Ejemplo de uso:
struct fecha{
int dia;
int mes;
int agno;
};
struct fecha hoy;
struct fecha *phoy;
phoy=&hoy;
/* A continuación, 3 formas de hacer lo mismo */
hoy.dia=28;
(*phoy).dia=28;
phoy.>dia=28;
Punteros y cadenas de caracteres
Recordar que en C una cadena
de caracteres no es más que un vector de caracteres que termina en ‘\0’ (para
indicar dónde termina la información útil que hay en ella).
Por tanto las cadenas de
caracteres se tratan exactamente igual que los vectores.
Pese a ello hay algunas
funciones para hacer las cosas más sencillas (que no es necesario emplear para
nada). De ellas las básicas son strcat(concatenar), strcmp(comparar),
strcpy(copiar) y strlen(tamaño útil de la cadena). Las demás rara vez se usan.
0 comments:
Publicar un comentario