domingo, 2 de octubre de 2011

Aclaraciones de Punteros C

Me gustaría hacer una serie de aclaraciones acerca de los punteros. Son cosas que quizás no quedasen demasiado claras en la primera parte de punteros en C. A raíz de ciertas preguntas y después de releer la anterior entrada he decidido detenerme más en el concepto y uso de punteros.

En primer lugar me gustaría hablar acerca de que es un puntero. Un puntero es una variable que almacena una dirección de memoria. Desde esta variable podemos acceder al valor almacenado en la dirección de memoria apuntada. Debemos especificar el tipo de valor que contiene la dirección apuntada (int, double, char,...), esto se realiza en la declaración del puntero. El puntero se debe declarar (tipo) *nombrePuntero, ejemplo: int *punt;  Para entender este concepto lo mejor sera ver un ejemplo con un esquema.

Supongamos que declaramos una variable "int num1 = 5;" y un puntero de tipo int "int *punt;". Aunque declaremos el puntero con '*', debemos tener en cuenta que para almacenar una dirección de memoria usaremos "punt = ". Veamos como queda el esquema de la memoria ahora mismo:
Podemos ver como en la dirección de memoria 0xFFFFFFF0 se encuentra almacenada la variable num1, mientras que en la 0xFFFFFFF1 encontramos el puntero punt. La flecha nos indica que cuando accedamos a punt estaremos guardando y accediendo al valor almacenado en 0xFFFFFFF1. A continuación haremos que la variable punt apunte a num1. Para esto debemos saber como tomar la dirección de memoria de una variable. Esto se hace mediante &nombreVariable , es decir, si usamos &num1 estaremos obteniendo 0xFFFFFFF0. Por tanto para que punt1 apunte a num1 deberemos realizar: punt = &num1; .Una vez realizado esto podemos apreciar como queda el esquema de la memoria:
Viendo esto podemos apreciar como el contenido de la variable punt contiene como valor la dirección de memoria de num1. A partir de ahora podremos acceder al valor almacenado en num1 desde punt. Para realizar el acceso debemos escribir *punt . Por tanto a partir de ahora con *punt podemos guardar valores en num1 y también leer el valor que contiene.
En el esquema vemos como *punt esta apuntando al valor de num1. Como hemos podido comprobar podemos acceder al valor de la dirección de memoria almacenada en un puntero a partir del '*'. Debemos también pensar en el paso de parámetros a una función que reciba un puntero. Supongamos una función: void funPunt(int *puntRec); .Para llamar a esta función deberemos mandar una dirección de memoria como parámetro. Suponiendo que tenemos las anteriores variables del ejemplo y queremos mandar la dirección de memoria de num1 podriamos llamar a la función de dos maneras: 1ª  funPunt(&num1);  De esta manera mandamos la dirección de memoria de num1 y podemos acceder a ella desde funPunt. La 2ª manera sería funPunt(punt); Como ya hemos visto el puntero punt contiene como valor la dirección de memoria de num1 por tanto es similar a la 1ª forma.

Quizá os preguntéis ¿Y para que me sirve a mi esto? Pues una vez aclarado el concepto y el uso de variables de puntero pasaremos a ver las utilidades de los punteros. Una de las funciones más importantes es la de el paso por referencia en funciones y poder modificar su valor en el interior, esto lo veremos en esta misma entrada. Otra de las funciones es una utilización más cercana de la memoria y eficiente de la memoria empleada en nuestros programas. Además sirven para la gestión dinámica de memoria (lo veremos en la segunda parte de punteros). Esto son algunos ejemplos de los usos que les podemos dar a los punteros.

Por último me dispongo a mostrar un ejemplo de paso por referencia de variables. Para entender esto debemos saber que al pasar una variable a una función su ámbito es tan solo para la función (es como una nueva variable). Por tanto si modificamos el valor en la función, no cambiara su valor fuera. ¿Y si necesitamos modificar el valor de una variable en una función? Esto se soluciona mediante punteros. Utilizando un puntero podemos modificar el valor de una variable pasada por parámetro dentro de la función. Esto es posible debido a que el puntero recibe la dirección de memoria de la variable accederemos a esa dirección de memoria y modificaremos su valor. Veamos un código de ejemplo:
Podemos apreciar las diferencias de las dos funciones. En una suma se pasa como valor (se copia su valor a sum), mientras que en la que usamos el puntero recibe la dirección de memoria de suma. Por tanto en el interior de la función sumaRef guardamos en la dirección apuntada por el puntero sum el valor de la suma. Es decir, que en main al ejecutar sumar la variable suma seguira con el valor '0' mientras que al ejecutar la sumarRef el valor de la variable suma del main cambia y pasa a contener el valor de la suma de ambos números. Si lo ejecutamos podremos apreciarlo mejor. El resultado mostrado por pantalla es:
Hemos podido apreciar en este resultado lo anteriormente mencionado. Este paso por referencia resulta muy útil. Podemos ver otro ejemplo en la anterior entrada en el primer ejemplo en la función dividir. En esta función no se podría hacer dos return uno del cociente y otro del resto. Por consiguiente al pasar las variables por referencia modificamos el valor de ambas variables dentro de la función.

Una vez más pido que si tenéis alguna duda me consultéis. Si como en este caso la misma duda surge varias veces realizare otra entrada resolviendo estas dudas. Espero que os sirva de utilidad a todos.

2 comentarios:

  1. Está muy bien explicado, ya me quedan más claros los %&$&/¨Ç*^de los punteros xDD.

    ResponderEliminar
  2. #1 Gracias David. Me alegro que te sirvan de ayuda estas aclaraciones acerca de los punteros.

    ResponderEliminar