No es un bug, es una característica no documentada

miércoles, 14 de enero de 2015

Programación. Punteros en C

16:49 Posted by Inazio , No comments

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 comentarios:

Publicar un comentario