martes, 19 de octubre de 2010

Punteros en C++

Podríamos decir que el tema de punteros en C/C++ es uno de los mas oscuros. Por experiencia personal, puedo decir que es de los capítulos que mas "asusta" leer en cualquier libro de programación. Pero, un mal uso de punteros, puede provocar cualquier desastre: Por ello es imprescindible usarlos "con cabeza". Para poder entender este texto, es necesario tener conocimientos básicos de programación, aunque intentaré aclarar incluso los conceptos mas simples. En esta primera entrada incluyo la "teoría", lo básico para comprender conceptos. Próximamente, crearé otra con mas contenido práctico.

Y sin mas preámbulos, nos lanzamos a la teoría. Antes de nada debemos comprender 2 conceptos básicos: Variable y Memoria.

Una variable es una estructura de datos que se encarga, como bien os imagináis, a almacenar datos. Al ser variable, el dato almacenado puede cambiar a lo largo de la ejecución del programa. El valor es guardado en una zona específica de la memoria del ordenador.

La memoria de un ordenador, la podemos imaginar como un conjunto de celdas, donde almacenamos todos los datos. Cada dato ocupa una o mas celdas contiguas de memoria. El número de celdas de memoria requeridas para almacenar un dato depende de su tipo de dato.

Vamos a imaginar que en un programa, tenemos una variable llamada Contador, la cual almacena el dato 5 (Entero). Al compilar el programa, el propio compilador asignará las celdas de memoria para ese dato. Podemos acceder a ese dato si conocemos la "dirección" de la primera celda de memoria del dato.

Los Punteros, son variables que representan una dirección (En vez de un dato). Para entender mejor este concepto, voy a usar un símil: Las hojas de cálculo: Imaginemos que tenemos una hoja de cálculo, donde tenemos almacenados varios datos:


Como vemos, cada dato ocupa una celda (Igual que cada variable puede ocupar 1 o varias celdas). A su vez, cada dato tiene un valor. Por ejemplo, valor 005 o valor 018 (Igual que las variables tienen valor). Cada celda, tiene un nombre: Por ejemplo, el dato 007 está almacenado en la celda A8. Y el dato 016 está almacenado en la celda B7. Por lo tanto, la variable Dato con valor 028 está almacenada en la dirección C9. Por lo tanto, la función de un puntero dentro de este símil, sería apuntar al nombre de la celda (Dirección de memoria).

Vamos a ver un ejemplo, aplicando lo anterior a la memoria, para comprender mejor estos conceptos:

En nuestro programa, declaramos la variable int Contador; Nuestra variables estaría en la memoria de la siguiente forma:


Una vez le asignemos un valor a nuestra variable, quedará así (Suponiendo que le asignamos valor 10):


Ahora vamos a plasmar esto en código, para que sea aún mas comprensible. Como siempre, todos los ejemplos son escritos, compilados y ejecutados bajo un entorno GNU/Linux. (IDE Geany, compilador GCC).
    int Contador = 10;  /* Asignamos valor 10 a variable Contador */
    int *miPuntero = &Contador;
   
    printf("Variable = %d\nPuntero = %p", Contador, miPuntero);
Antes de explicar el código, voy a explicar los operadores de punteros:

El operador & (Operador Dirección) Proporciona dirección del operando.
El operador * (Operador Indirección) Opera sobre una variable puntero.

En el código, primero declaramos la variable Contador. Seguidamente, declaramos la variable miPuntero (Para declarar variables puntero, antes del nombre ponemos el operador indirección) a la cual asignamos la dirección de memoria de la variable contador (Usamos el operador dirección, pues como dije, este nos proporciona la dirección del operando).

La variable miPuntero, ahora "apunta" a la dirección de memoria donde está almacenado Contador. Podemos observar la salida del programa ejemplo1:
serch@serch-server:~/Escritorio$ ./ejemplo1
Variable = 10
Puntero = 0xbff8a06c
Como vemos, Puntero (miPuntero) es la dirección de memoria de Variable (Contador).  Gráficamente, podemos expresarlo así:

&Contador se refiere a la dirección de memoria de Contador.

Una equivalencia, es que Contador y *miVariable valen lo mismo: Al usar *miVariable, nos estamos refiriendo al valor apuntado: Esto lo podemos observar haciendo estos cambios en el programa:
printf("Variable = %d\nPuntero = %d\n", Contador, *miPuntero);

Ahora que ya conocemos el funcionamiento básico de los punteros, voy a explicar lo básico de las Referencias. Son exclusivas de C++, y algo mas cómodas que los punteros. Las referencias son un "duplicado" de una variable. De manera que, cualquier operación que se realice sobre una referencia, se produce sobre la variable original. Las referencias deben ser asignadas en el momento de la declaración de la variable original. Podemos ver el ejemplo de código con ejemplo2

Como vemos, creamos una referencia (Con el operador &) y le asignamos el valor 5. Asignamos un valor a la referencia, e imprimimos el valor de la variable original. Podemos observar la salida del programa:
serch@serch-server:~/Escritorio$ ./ejemplo2
Variable = 5
Por ahora termina aquí la explicación de Punteros (Y referencia) que básicamente es teoría, en una próxima entrada incluiré mas práctica: Aritmética de punteros, punteros a funciones, punteros y arrays, asignación dinámica de memoria...



Enlaces externos: ejemplo1 , ejemplo2

2 comentarios:

  1. Serch, tienes que mirarte Python y como gestiona las variables como punteros.
    Por ejemplo, cuando tienes un registro (un tipo de variable) y lo asignas a otra variable, esta no es nada más que un puntero, sin tener que especificar.

    Por ejemplo:

    variable = Persona(nombre="variable", dni="54684265s", telefono="666666666")

    otra_variable = variable


    En este caso, en la primera asignacion hemos asignado con el nombre variable tres bloques de memoria con referencia Persona (para referirse a alguna seria: Persona.nombre)

    Y en la seguna variable (otra_variable) no se crean otra vez los bloques de memoria, sino que es un puntero a la memoria de variable, por lo tanto si cambias algo en otra_variable se cambia en variable. Esto puede ser un problema a veces, pero en otras es muy funcional.

    UN saludo ;)

    ResponderEliminar
  2. Pero yo no estudio Python si no C xD Algún día me lo miraré si, pero por ahora, ni tiempo.

    ResponderEliminar