En esta lección vamos a continuar con lo que vimos en Variables en Arduino, así que es importante que si no has leído ese post lo hagas ahora ya que partiremos de ahí para aprender sobre punteros.
Como vimos en “Variables en Arduino” el uso de variables nos permite almacenar ciertos datos y operar con ellos a lo largo del código de nuestro proyecto. Estos datos se guardan en un lugar concreto de la memoria de nuestra placa controladora, es decir, asociada a la variable en la que creemos que tendremos un contenido (valor que almacenamos) y una dirección de memoria que nos indica donde se guarda dicho valor. Pero, ¿cómo podemos saber dicha dirección? Para ello se recurre al operador & que se puede traducir al lenguaje natural como “la dirección de”.
Vamos a aplicar lo que aprendimos sobre comunicación por el Puerto Serie para mostrar por el monitor serial un ejemplo para ver la diferencia entre contenido y dirección de memoria:
1 2 3 4 5 6 7 8 9 10 11 |
void setup() { Serial.begin(9600); } void loop() { int a = 100; Serial.print("Valor contenido en a: "); Serial.println(a); //imprimimos el valor de a Serial.print("Dirección de memoria de la variable a: "); Serial.println((unsigned int)(&a)); //imprimimos la direccion de a delay(3000); } |
Como puedes ver, para poder imprimir la dirección de memoria tenemos que hacer una pequeña modificación. La memoria de la placa Zum Core es de 32 kb, por lo que serán necesarias 32.768 direcciones de memoria distintas. Para cubrir todas las combinaciones posibles, las direcciones de memoria se codifican con 2 bytes (16 bits). Es por eso que al imprimir la dirección de memoria donde está almacenada la variable se hace lo que se llama una conversión de tipo (casting) que hace que el valor de &a sea interpretado como una variable de tipo entero sin signo (unsigned int) y no un entero (int), ya que en éste no caben direcciones de memoria suficientes. Pero, ¿podemos guardar esta dirección para poder trabajar con ella?
Punteros
Los punteros son un tipo de variables especialmente preparados para almacenar una dirección física de un objeto en el mapa de memoria. Podemos imaginar los punteros como una marca que apunta a otra variable (o posición de memoria), a través de la cual también podremos acceder a la información guardada en la posición a la que apunta el puntero. Por esto es importante que el puntero que declaremos sea del mismo tipo que la variable a la que va a apuntar. Un momento, ¿cómo se declaran los punteros?
Los punteros se declaran de la siguiente manera:
1 |
{tipo} * {nombre_puntero}; |
Donde tipo es el tipo de variable a la que va a apuntar el puntero y nombre_puntero es el nombre por el que llamaremos a la variable puntero que se está declarando
Veamos algunos ejemplos:
1 2 3 |
int * p_entero; float * p_float; char * p_caracter; |
Como hemos dicho antes, todas las variables tienen una dirección de memoria asociada en donde se guarda la información que contiene la variable y, como podrás imaginar, los punteros no son diferentes. Un puntero tiene asociada una dirección de memoria en donde guarda a su vez otra dirección de memoria de otra variable a la que apunte. ¿Que lío no? Vamos a verlo con un ejemplo. Antes de que copies el código en tu IDE de Arduino te reto a que recuerdes lo que vimos en el post sobre la LCD y hagas lo mismo que hago yo aquí pero mostrando la información por la pantalla de la LCD.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
void setup() { Serial.begin(9600); } void loop() { int a = 100; int * puntero_a = &a; //el contenido de puntero_a es igual a la dirección de a Serial.print("Valor contenido en a: "); Serial.println(a); //Imprime a Serial.print("Direccion de memoria de la variable a: "); Serial.println((unsigned int)(&a)); //Imprime la dirección de a Serial.print("El valor contenido en la direccion de memoria apuntada por el puntero_a: "); Serial.println(*puntero_a); //imprime el valor que hay en la dirección contenida en el puntero Serial.print("La direccion contenida en el puntero: "); Serial.println((unsigned int)(puntero_a)); //imprime el valor contenido en el puntero (dirección de a) Serial.print("La direccion del puntero: "); Serial.println((unsigned int)(&puntero_a)); //imprime la dirección de puntero_a (dirección donde está guardada la dirección de a) Serial.println(); delay(3000); } |
Esto es lo que me ha salido a mí, aunque a ti te pueden salir otros valores para las direcciones de memoria.
Por si no ha quedado del todo claro vamos a representarlo gráficamente (recuerda que son variables tipo int y unsigned int por lo que ocupan 2 bytes o 16bits):
Valor Decimal | Valor Binario | |
100 | 00000000 | 01100100 |
2294 | 00001000 | 11110110 |
2292 | 00001000 | 11110100 |

Los punteros, como una variable más, pueden definirse como variables globales o locales tal y como se explicó en la lección sobre Variables en Arduino
Aritmética de punteros
La aritmética de punteros nos permite sumar/restar un valor entero al puntero, de forma que podemos mover el puntero y recorrer la memoria de una forma determinada por el tipo de variable con que se declara el puntero y el valor entero con que se opere. De forma genérica si tenemos:
1 2 |
{tipo} *{nombre_puntero}; {nombre_puntero} = {nombre_puntero} + n; |
Será como desplazar nuestro puntero n veces el número de bytes definido por el tipo de variable con que se declara el puntero; volviendo al ejemplo anterior, si tenemos:
1 2 3 4 5 6 7 8 9 |
void setup() {} void loop() { int a = 100; int * puntero_a = &a; //puntero_a = 2294 puntero_a = puntero_a + 5; //puntero_a = puntero_a + 5*2bytes (int ocupa 2 bytes) = 2304 puntero_a --; // puntero_a = puntero_a-1 = puntero_a - 1*2bytes = 2302 } |
Es equivalente al resto de tipos de variables, a continuación puedes ver gráficamente este tipo de operaciones:
Para operar con punteros podemos utilizar la misma sintaxis que con el resto de variables:
1 2 3 4 5 6 7 8 9 10 11 |
int * p1; //Suma p1 = p1+n; p1 +=n; //equivalente a p1=p1+n; p1 ++; //equivalente a p1=p1+1; //Resta p1= p1-n; p1 -=n; //equivalente a p1=p1-n; p1--; //equivalente a p1=p1-1; |
¿Y para que sirven? (¿Y para qué no?)
1 2 3 4 |
int a = 100; int *p1; p1 = &a; char *p2 = 0; //Se puede usar el valor NULL |
Los punteros son muy útiles para recorrer información con una estructura de datos determinada, ya que podremos ir desplazando el puntero por la memoria con esa misma estructura pudiendo acceder, leer y escribir los datos de forma ordenada. En nuestro caso, para programar en Arduino por norma general no trabajaremos con ficheros de información por lo que esta aplicación no nos interesa demasiado. El uso de punteros nos permite utilizar estructuras de datos complejas, como pueden ser vectores, estructuras, etc., que explicaremos y usaremos en otras lecciones, además permite trabajar con variables dinámicas, que se crean y destruyen durante la ejecución del programa, aunque esto último es más avanzado.