Punteros: Serial Arduino – Parte III

Punteros
Continuamos con la serie de publicaciones sobre Serial Arduino, de las cuales ya llevamos 2:
En esta ocasión el tema que trataremos será punteros. Punteros o también llamados apuntadores son tipo de variables que mejoran la eficiencia del compilador en lo que respecta al acceso a los datos en la memoria. Son muy populares debido a las importantes aplicaciones en el direccionamiento de estructura de datos como Listas, Colas y Arboles binarios. Gran parte de las librerías en C se implementan utilizando punteros.

Punteros son Variables.Cuando el compilador declara una variable, se establece una reserva de memoria en Bytes para la misma de acuerdo a si la misma es un Int, Char,Boolenan, Float, etc.  Cuando utilizamos dicha variable en un programa, el compilador accede a ella mediante su nombre a la dirección de memoria donde se encuentra el valor de dicha variable.  Es muy común que los programadores utilicen funciones o métodos o procesos o las tan viejas llamadas rutinas, para dividir el trabajo en partes, lo que se llama programación estructurada. Dichos métodos aceptan parámetros de entrada y pueden ser pasados por valor o por referencia.

En parámetros pasados por valor a la función, esta recibe una cópia de dichos argumentos sin afectar su valor original .En parámetros pasados por referencia a la función, esta recibe la dirección de memoria de los argumentos y por lo tanto pueden modificar su valor original. En esta clase de procesos se utilizan Punteros. En la parte 2 de Serial Arduino <enlace>, vimos los conceptos de Vectores y Cadenas y sus diferencias. Nos falta el eslabón de los Punteros para ver como se relacionan los Punteros y las Cadenas.

Un Puntero es una dirección de memoria. Por ejemplo cuando enviamos un correo postal, la carta posee un Puntero que es la dirección del destinatario. Cuando se llama por teléfono, el número es también un puntero del teléfono destino, ambos Punteros indican o Apuntan a una información.

  • Un Puntero es una variable como cualquier otra
  • Un Puntero contiene una Dirección de memoria.
  • Esa Dirección contiene el Dato apuntado por el Puntero
  • Un Puntero apunta al Dato cuya Dirección de memoria dicho Puntero contiene .
Punteros a int y float
Punteros a int y float

En la tabla de la figura se puede apreciar el Concepto. La Memoria podemos pensarla como posiciones sucesivas 1,2……10, en este caso. La variable entera Int a ocupar la dirección 3 y aloja el valor 1000. La variable Float b ocupa la dirección 8 y aloja el valor 578.3. Es decir que cada dirección tiene su contenido. El contenido de la dirección 3 es 1000, el contenido de la dirección 8 es 578.45. Ambas  direcciones se asocian a las variables a y b.

Veamos ahora las variables Puntero. *Ptr1 se aloja en la dirección 9 y su contenido es 3 que es otra dirección de memoria, y como el contenido de 3 es 1000 que es el valor de a, entonces *Ptr1 Apunta a la variable a. De la misma manera *Ptr2 Apunta a la variable b. Es tan sencillo bello como lo indicado.

Declaración y asignación de Punteros

 <Tipo de dato apuntado>< * Nombre puntero >;

 En el caso de Ptr1 y Ptr2 apuntan a un Int y a un Float por lo tanto podemos declararlo como :

int *Ptr1;  // Declaraciones
float *Ptr2;

Pero aquí están declarados y no apuntan a nada, apuntan a Null. Para apuntarlos específicamente a las variables se utiliza la siguiente simbología:

Int a=1000;
Float b= 578.45;
Ptr1 =&a;       // Asignaciones una vez declarados los punteros
Ptr2=&b;

También podemos Declarar e inicializar al mismo tiempo

int *Ptr1=&a; // Declaración e inicialización juntas
float *Ptr2=&b;

(&) Se llama operador de Dirección y Obtiene la dirección de una variable
(*)  Se llama operador de Indirección , Define la variable Puntero y accede al   
   contenido Apuntado

Ptr1=&a; lo que hace es que el Ptr1 definido previamente se cargue con la dirección de la variable a, esto es &a , con este proceso ya Ptr1 apuntará a la variable a. Podemos acceder a la variable por medio del Puntero utilizando el Operador de Indirección. Cuando haga referencia a *Ptr1 estaré accediendo al valor de la variable apuntada, en este caso de int a y de esta manera se puede leer o modificar dicho valor.

Ejemplo de la tabla Punteros
Ejemplo de la tabla Punteros

En este programa se ve lo desarrollado hasta ahora, Tener en cuenta que a través de los punteros hemos modificado su valor:

Modificando Contenido con Punteros
Modificando Contenido con Punteros

Cuando el Puntero está ya declarado y asignado dicho puntero, es decir, que apunta a un Dato válido, accedemos a ese Dato mediante *Ptr, sea para lectura ( int x = *Ptr) o para su modificación ( *Ptr=0;)

Vemos como usando el Operador de Indirección (*) , hemos modificado las variables en sus contenido de manera indirecta mediante un Puntero de Referencia.  En el programa anterior se podría también hacer referencia de la siguiente manera:

Serial.println(“Valor de a=”+String(*Ptr1));
Serial.println(“Valor de b=”+String(*Ptr2)); Dado que *Ptr hace referencia al contenido apuntado.

Veamos el caso de Declaración e inicialización por separado:

Declaración y Posterior Asignación Punteros
Declaración y Posterior Asignación Punteros

Lo más confuso que quizás pueda resultar es la simbología, dado que (*) aparece tanto en la declaración como en el acceso al contenido. Cuando el Puntero ya se ha definido , el (*) representa el Contenido de la memoria apuntada por el Puntero.

Se puede apuntar prácticamente a cualquier tipo de variable:

Int edad=50;
Int *Pedad=& edad;    // Puntero entero apuntando a la variable entera edad.

char *p;  // Puntero a char
char alfa=’A’; // Variable char
p=&alfa;            // Puntero a char que se inicializa apuntando a la variable char alfa

char disco[] =”Los Beatles”; // variable cadena
char *c;  // Puntero a char
c=&disco;  // Puntero a la Cadena disco.

char c; // Variable char
char *P; Puntero a char
P=&c;  // Puntero a char c.
En este caso el puntero apunta a la dirección de la variable c, y no interesa si c está inicializada. Ejemplo de alfabeto:

Alfabeto con Punteros
Alfabeto con Punteros

También existen Puntero que apuntan a Punteros utilizando (**), por ejemplo:

char c=’z’;
char *pc=&c;
char **ppc=&pc,
char ***pppc=&ppc;
***pppc=’m’; // Asigna a variable c la ‘m’ vía tres punteros.

Los Punteros como son variables pueden en determinado momento del programa apuntar a cualquier dato siempre y cuando coincida su tipo. Por ejemplo si tengo dos variables tipo Int A y B, y un puntero Int *P, el Puntero P puede en determinado momento apuntar a A o B.

 Se puede incluso armar Arrays[] de Punteros, donde cada elemento del Array[] sea un puntero. El tema es muy extenso pero pretendo dar las nociones fundamentales para poder trabajar con ellos. Incluso pueden apuntar a Estructuras , y a Funciones.

Punteros y la relación con las cadenas

 Ya recorrido la mayoría de los conceptos, vamos a la frutilla del Postre que es Punteros con Cadenas.

Lo más importante es que el nombre de un Array[] tipo Cadena como los ya vistos en la Parte 2, representan una CONSTANTE PUNTERO. No se puede cambiar el nombre a un Array[] ya que no se pueden modificar Constantes en un programa. Vale decir que el Nombre de un Array[] en un Puntero Constante a dicho Array[], de allí la dualidad Array[] y Punteros.

Ejemplo:

Char alfabeto[]=”ABCDEFGHIJKLMNOPQRSTUVWXYZ”; // Una cadena
Char *p;  // Puntero a char

Ahora apuntamos p a la cadena, mediante estas 2 formas válidas:

p=&alfabeto[0];  // Al primer elemento , Primera forma
p=alfabeto;  // Segunda forma sabiendo que el nombre del Array es un  puntero en sí mismo.

En ambas formas el Puntero p  Apuntará a la letra A

Punteros y Arrays
Punteros y Arrays

Puedo declarar otro puntero p2 que apunte al caracter “L”:
Char *p2=&alfabeto[11] ; // Puntero a char

Vamos a probar esto:

Punteros con Cadenas
Punteros con Cadenas

Imprime:        Puntero p apunta a A
Puntero p2 apunta a L

Ahora bien, si p=alfabeto apunta al primer elemento de la cadena o Array[], será factible poder hacer p+1 para que apunte al elemento siguiente , letra B y poder recorrer todo la cadena usando punteros.

Accediendo a Cadenas con Punteros
Accediendo a Cadenas con Punteros
Salida código anterior
Salida código anterior

Vemos como p actúa de Índice del elemento. En  Serial Arduino Parte II vimos que justamente el índice del elemento del Array[] es justamente un Puntero, aquí lo acabamos de comprobar.

Como además sabemos que toda cadena termina en un carácter Null = ‘\0’, podemos evitar el ciclo for y además armar una función por ejemplo que nos diga la longitud de la cadena:

Evitando ciclo For con Punteros a Cadenas
Evitando ciclo For con Punteros a Cadenas

La salida es : La longitud de la cadena es 26

Observar  que no he declarado ningún Puntero, a excepción del argumento de la función, que como voy a pasarle un puntero a Cadena y además se que el nombre de la cadena o cualquier Array[] es un PUNTERO A CONSTANTE, aprovecho este hecho para manejar la cadena con dicho Puntero. While(*cad) lo que está haciendo es en realidad verificando el fin de cadena o carácter Null.

While(*cad!=’\0’)

{}

Aritmética de Punteros

 El Nombre de Array[] es un puntero constante y no se puede modificar y un Puntero es una variable que si se puede modificar.

Int v[10]; // Array[] de enteros
Int *p; // puntero a enteros
p=v;  // apunta al primer elemento v[0]
p++; // apunta a v[1] ahora p=1;
p+=6; // apunta al octavo elemento o v[7]
*p++; // Incrementa el contenido de v[7]
*p=25; // modifica v[7]=25.

NO SE PUEDE:

  • Sumar Punteros
  • Multiplicar Punteros
  • Dividir Punteros

Ejemplo de Aplicación convertir caracteres de una cadena, Minúsculas a Mayúsculas

Procesando Cadenas con Punteros
Procesando Cadenas con Punteros
Salida del programa anterior
Salida del programa anterior

Notar que en este caso la función convertir debe alterar los caracteres de la cadena original, razón por la cual se tuvo que crear un puntero e inicializarlo apuntándolo a la cadena, es decir no puedo usar en nombre de cadena como puntero porque este , como sabemos es Constante, además que la función acepte un Puntero variable.
Lo importante es que se pudo alterar una variable cadena original, esto es lo que al principio denominamos como pasar parámetros por referencia y no por valor, notar además que la función print siempre llama a imprimir a la variable cadena lo cual indica que se ha modificado. Las funciones de cadena del ANSI C e incluso C++ y C# utilizan punteros para procesar cadenas, archivos, arboles, listas, pilas, colecciones, etc.

Por ejemplo ,la implementación de la función strln(cadena) , que retorna la longitud de una cadena:

 

El nombre de la cadena directamente es un Puntero Constante *s , crea otro puntero *cp del tipo char y le asigna una copia del Puntero , ambos apuntan al elemento [0] de la cadena o primer carácter. Mientras no encuentre el Null de fin de cadena va incrementando el puntero cp recorriendo la cadena. Cuando llega al Null resta las dirección del puntero cp de la dirección  del elemento [0] contenido en el puntero s , obteniendo asi la longitud de la misma.

Esta función es mas avanzada de comparar cadenas. Strmcmp(cadena1,cadena2)

Si bien es mas elaborada el concepto es el mismo, devuelve un r>0, <0 o =0 de acuerdo al orden alfabético. S1 y s2 son dos punteros constantes, es decir que la función acepta los nombres de las cadenas directamente, las cuales son tomadas por los punteros s1 y s2.

r = (unsigned char)*s1 – (unsigned char)*s2 ) lo que hace es restar los caracteres entre cadenas. Al inicio s1 y s2 apuntan al primero de cada cadena, el resultado puede ser 0 si son iguales , >0 o <0 de acuerdo al orden alfabetico. Por ejemplo si “HOLA” se compara con “Hola”, el ASCII(H)=72 y r=0 cuyo ASCII(0)=Null. Luego !r , es el NOT de r es decir 1 o true. Es true o 1 se opera con AND (&&) con *S1 que en este caso es “H” y como no es Null ASCII(0), el While da como resultado true y entra dentro del cuerpo del programa.

Dicho de otra manera, mientras la diferencia entre los elementos de la cadena sea 0, es decir elementos iguales y además el elemento de la cadena1 no sea Null , se pasa a comparar los otros elementos siguientes, por medio de s1++ y s2++. Si los elementos no son iguales r será >0 o <0 y el While se rompe. Por ejemplo “HOLA” con “hola” r=72-104=-32 el While ya no se cumple y retorna el -32, es decir <0 indicando que “hola” está alfabéticamente después de “HOLA”. Si las cadenas son exactamente iguales se retorna 0.

Eso es todo y espero que les sea de utilidad.

 

¿Te gustó? ¡Comparte!Share on LinkedInShare on FacebookTweet about this on TwitterEmail this to someoneShare on RedditShare on TumblrShare on Google+Pin on PinterestBuffer this page
  • Gustavo Circelli

    Hola, disculpas por no responder a tiempo, recién hoy llega a mi correo el aviso de comentario: Lo que deberías hacer es guardar en una variable tipo String usando el objetos String de Arduino como se vio en la parte II y luego usar las funciones de cadena para detectar la posición previa al dato buscado, en este caso “-” previo al 77. Si tus datos vienen simpre, en este caso precedidos por el “-” podrías usar TuCadena.indexOf(‘-‘) que te trae la posición del guión. Luego podras saber que las posiciones que le siguen son el dato que búscas. usando TuCadena.charAt(pos)

  • Metaconta

    Que punterazo. 😉