viernes, 20 de julio de 2012

Punteros en C++ - Parte I : Introducción

C++ es el lenguaje preferido de muchos programadores, debido a su versatilidad y potencia para expresar instrucciones a un computador. Sin embargo, uno de los principales problemas al aprender este lenguaje es la facilidad de incurrir en errores. Mas aún, si lo que queremos es usar de manera directa el modelo de memoria del lenguaje mediante el uso de punteros, vamos a tener muchísimos problemas.

En este post explicaré, de manera introductoria, cómo entender el manejo de memoria usando C++. La idea es lograr que hayan menos dolores de cabeza en el mundo :).

Voy a asumir en este post que conoces cómo compilar un código en C++, además de algunas características básicas como tipos de datos, funciones, etc. Empezaré la explicación con un código muy simple como se muestra a continuación:

#include <iostream>
#include <cstdlib>

using namespace std;

int main(int argc, char* argv[]){
 
 int var = 10;
 cout << var << endl;
 cout << &var << endl;
 
 return EXIT_SUCCESS;
}

En este código se presenta una función main clásica. En la línea 8 creamos una variable entera var que almacena un valor de 10.  Internamente lo que sucede es que cuando el programa se ejecute, el sistema operativo le va a asignar a nuestro programa un lugar en la memoria RAM para poder almacenar nuestra variable var. Imaginemos que la memoria RAM está compuesta por un conjunto de celdas en donde es posible almacenar información. Una posible configuración de la memoria después de crear la variable var se muestra en la siguiente figura:


El valor 10 se almacena en una celda que está en la dirección 0xbfc36158. Entonces, cada vez que usamos la variable var, internamente estamos usando esa celda de memoria asignada. Es por eso que la instrucción de la línea 9, toma el valor en la celda de memoria y lo imprime en pantalla. Un detalle importante es que en C++, uno puede saber cuál es la dirección de memoria en donde está almacenada una variable, para eso se emplea el operador &. Cuando uno aplica el operador & a una variable (como en la línea 10 del código de arriba), éste nos entrega la dirección de memoria en donde reside la variable. En el ejemplo, nuestro programa imprimirá en pantalla el valor de la variable y la dirección de memoria en donde está almacenada.

Y dónde están los punteros y cómo se relaciona a todo lo que hemos visto?
Pues, si podemos imprimir una dirección de memoria, entonces podemos almacenarla! Un puntero es "una variable que almacena una dirección de memoria". Así de simple! Veamos ahora el siguiente código:

#include <iostream>
#include <cstdlib>

using namespace std;

int main(int argc, char* argv[]){
 
 int var = 10;
 cout << var << endl;
 cout << &var << endl;
 
    int* p = &var;
 cout << p << endl;
 cout << &p << endl;
        
    return EXIT_SUCCESS;
}
Hemos añadido tres líneas de código al primer programa. En la línea 12, creamos el puntero p (usando la sintaxis <tipo-de-dato>*); osea una variable que va a almacenar una dirección de memoria. Hay que tener en cuenta que cuando se crea un puntero, hay que informar explícitamente acerca del tipo de dato que está almacenado en la dirección de memoria a almacenar. Por ejemplo, nuestro puntero p almacenará una dirección de memoria, en la cual se almacena un número entero (en este caso, el valor 10). El valor que le asignamos al puntero es la dirección de la variable var. La línea 13 imprime el valor almacenado en p (y por lo tanto, imprime lo mismo que la línea 10).

La pregunta es: Si un puntero es una variable, entonces está almacenada también en una celda de memoria, la cual a su vez también tiene una dirección? La respuesta es SI. Para graficar esto, veamos la siguiente figura:

Ahora la variable p (un puntero), posee una celda de memoria en donde está almacenando la dirección de la variable var, y por consiguiente tiene una dirección de memoria también. Para extraer la dirección de memoria de un puntero, usamos el operador &, como en la línea 14 del programa.

A estas alturas, resulta lógico pensar en la posibilidad de volver a almacenar la dirección del puntero p en otra variable. Una variable que almacena la dirección de memoria en donde se almacena un puntero, se le llama un puntero a un puntero (o mas comúnmente puntero doble). El siguiente código muestra cómo es posible hacer esto:

#include <iostream>
#include <cstdlib>

using namespace std;

int main(int argc, char* argv[]){
 
 int var = 10;
 cout << var << endl;
 cout << &var << endl;
 
    int* p = &var;
 cout << p << endl;
 cout << &p << endl;
        
    int** p2 = &p;
 cout << p2 << endl;
 cout << &p2 << endl;

    return EXIT_SUCCESS;
}
La línea 16 crea una variable en donde se va a almacenar la dirección de un puntero. La sintaxis usa ahora un doble asterisco para denotar que es un puntero doble. Y así uno podría crear cadenas de punteros las veces que queramos, aunque en la práctica no es realmente necesario el uso más allá de los punteros dobles (además de la complejidad que se añade a la comprensión del programa).

Algo interesante es que nuestra información también puede ser manipulada de manera indirecta usando los punteros. Veamos el siguiente código:
#include <iostream>
#include <cstdlib>

using namespace std;

int main(int argc, char* argv[]){
 
 int var = 10;
 cout << var << endl;
 cout << &var << endl;
 
    int* p = &var;
    *p = 12;
    cout << var << endl;

    return EXIT_SUCCESS;
}

En la línea 12, asignamos la dirección de la variable var a un puntero p. En la línea 13, usamos el puntero para cambiar el valor almacenado en la dirección de memoria que almacena el puntero (el operador * se usa para dereferenciar un puntero, osea tener acceso a la información de la dirección de memoria que mantiene el puntero). Finalmente, cuando volvemos a imprimir la variable var, vemos que su valor cambió, y lo hicimos indirectamente usando el puntero p.

Espero que este post haya sido de utilidad. En el siguiente post, explicaré las razones del uso de punteros y daré algunos consejos para su uso.

2 comentarios:

  1. Hey Genial explicación de como usar Punteros !
    creo que ni en todos los libros que leido de C++ han podido escribir una forma tan sencilla de entender la manipulación de los punteros, Gracias por hacer este post!

    ResponderEliminar
    Respuestas
    1. Discúlpame si no he continuado los post, estuve bastante complicado de tiempo, pero espero pronto poder seguir con los tutoriales para el uso de punteros :)

      Saludos

      Eliminar