bclose

Funciones y enteros

Definiendo funciones y diferentes tipos de enteros
tienda online prometec

Objetivos

.

 
    • Operar con enteros.
    • Aprender a definir funciones en Arduino.
    • Trabajar el pensamiento lógico ( y algorítmico)
    • Diferentes tipos de enteros en C++.

 

Material requerido.

ArduinoUNO  Arduino Uno o similar.Un PC con el entorno de Arduino correctamente instalado y configurado.

 

 

La primera función: Calculando si un número es primo

.

Ya hemos comentado antes, que programar es un poco como andar en bici, se aprende  pedaleando y a programar… programando.  Hay que ir aprendiendo la sintaxis del lenguaje, C++ en nuestro caso, pero también aprendiendo a resolver problemas lógicos y partirlos en instrucciones.

Hacer cursos de programación (o de andar en bici) está bien, pero al final hay que ponerse a programar y tener problemas, porque solo teniéndolos y resolviéndolos, solo o con ayuda, se aprende. No se puede aprender a nadar sólo estudiando.

Con un cierto temblor de manos, vamos a centrarnos en esta sesión en algunos ejemplos clásicos de programación, como son el cálculo de números primos para entrenar esta capacidad de búsqueda de algoritmos prácticos para resolver problemas más o menos abstractos y para presentar algunos conceptos adicionales.

 
  • Es importante destacar que no existe una forma única de resolver un problema concreto y que una no tiene porque ser mejor que otra, aunque con frecuencia se aplican criterios de eficiencia o elegancia para seleccionar una solución.
 

Esta sesion va a requerir un esfuerzo un poco mayor que las anteriores porque vamos a empezar a entrenar un musculo poco usado,el cerebro, en una tarea poco frecuente, pensar. Y esto es algo que exige un poco e esfuerzo, pero es necesario para avanzar.

Supongamos que queremos crear un programa que nos devuelva true o false según que el número que le pasamos sea primo o no y a la que podamos llamar varias veces sin copiar el código una y otra vez. La llamaremos Primo () y queremos utilizarla de la siguiente manera: Si el numero n que le pasamos es primo nos tiene que devolver true y en caso contrario que devuelva false, o sea queremos que nos devuelva un valor bool.

Esto es lo que llamamos una función.

En realidad, ya hemos utilizado varias funciones que Arduino trae predefinidas como el Serial.print() o abs() , o Serial.available() y se las reconoce por esa apertura y cierre de paréntesis.

C++ nos ofrece todas las herramientas para crear nuestras propias funciones y es algo muy útil porque nos ayuda a organizar  un problema general en trozos o funciones más pequeñas y más fáciles de manejar.

Para definir una función así, tenemos que declararla primero y describirle a C++ que hacer:

     bool Primo( int x) // int x representa el parámetro que pasaremos a esta función
         {
                Aquí va lo que tiene que hacer
                …………
                return( bool);
         }

Declaramos la función Primo () como bool, o sea va a devolver un valor bool y por eso en algún punto tendremos que usar la instrucción return( true) o return( false) para devolver un resultado a quien la llame. Si devolviera un entero habría que definirla como int Primo( int x).

 
  • Si una función no va a devolver ningún valor, sino que simplemente realiza su trabajo y finaliza sin mas entonces hay que declararla como void (vacía). Ya cononocemos dos funciones así : setup() y loop()
 

Veamos cómo podría ser el código de la función Primo():

     bool Primo( int n)
         {
            for ( int i = 2 ; i <n ; i++)
                {
                   if ( n % i == 0) // Si el resto es 0 entonces es divisible.
                       { 
                          Serial.println ( String(n) +" es divisible por: " + String(i)) ;
                          return(false) ;
                       }
                 }
           return (true) ;
 }

Para saber si un número es o no primo basta con dividirlo por todos los números positivos  menores que él y mayores que 1. En el ejemplo dividimos el número n empezando en 2 y finalizando en n-1.

Si encontramos un valor de i que devuelve resto 0, entonces es divisible (no es primo), devolvemos false con return y volvemos a la intruccion que llamo a la función. Si no hallamos ningún divisor, al finalizar el for devolvemos true y listo. Este es el método de fuerza bruta y sin duda es mejorable pero de momento nos sirve.

Para usar Primo hay que pasarle un entero. Recordad que al definir la función dijimos  bool Primo (int n) donde n representa el valor que queremos probar. Así pues Descargar:

     void loop() // Prog_8_1
          { 
              int x = 427 ; // El número a probar
              bool p = Primo(x);
             if (p )
                 Serial.print( String(x) + " Es primo.") ;
             else
                 Serial.print( String(x) + " No es primo." ) ;
          }

Veamos cuantos primos hay hasta el, digamos 1024, Descargar:

      bool control = true ; // Prog_8_2
      int maximo = 1024 ;

      void loop()
          { 
              if ( control) // Solo es para que no repita una y otra vez lo mismo
                  { 
                       Serial.println( "Los numeros primos hasta el " + String( maximo)) ;
                       for ( int x = 2 ; x < maximo ; x++)
                             { 
                                bool p = Primo(x);
                                if (p ) Serial.println( x) ; // No hay inconveniente en escribirlo seguido
                             }
                  }
              control = false ;
          }

      bool Primo( int n)
          { 
              for ( int i = 2 ; i <n ; i++)
                   { 
                        if ( n % i == 0) // Si el resto es 0 entonces es divisible.
                        return(false) ;
                   }
              return (true) ; // Si llega aqui es que no ha encontrado ningun divisor
          }

Aunque el programa funciona correctamente la salida no es muy presentable( Recordad que nos gusta ser elegantes). Vamos a formatearla. Para ello usaremos el carácter tabulador que se representa como ‘\t’ y una coma después.  Descargar:

     bool control = true ; //Prog_8.3
     int maximo = 1024 ;
     int contador = 1 ;

     void loop()
          {  
              if ( control)             // Solo es para que no repita una y otra vez lo mismo
                   {
                        Serial.println( "Los numeros primos hasta el " + String( maximo)) ;
                        for ( int x = 2 ; x < maximo ; x++)
                              {
                                 if (Primo(x) )
                                    if ( contador++ % 8 == 0)
                                         Serial.println( String(x)+"," ) ;
                                    else
                                         Serial.print( String(x) +","+ '\t') ; 
                              } 
                   }
              control = false ;
          }

Ahora el programa formatea la salida de una forma un poco  más presentable y cómoda de leer.

Salida de consola

 

Para conseguirlo, hemos añadido una coma y un tabulador a cada número excepto a uno de cada 8 que añadimos intro. También tenemosuna línea que conviene comentar:

if ( contador++ % 8 == 0)

Cuando a una variable se le añaden dos símbolos mas al nombre, significa que primero se use su valor actual en la instrucción en curso, ene este caso en el if, y después se incremente en 1 su valor.

Si hubiéramos escrito:

if ( ++contador % 8 == 0)

Querría decir que queremos incrementar su valor antes de utilizarlo. Esta notación es muy habitual en C++ y conviene reconocerla. También podemos usar contador- – y  – -contador para decrementar.

 

El tipo entero

.

Este sería un buen momento para preguntarnos hasta donde podría crecer máximo en el programa anterior. Le asignamos un valor de 1024, pero ¿Tiene un entero límite de tamaño?

La respuesta es afirmativa. Los enteros int en Arduino C++ utilizan 16 bits por lo que el máximo seria en principio 216 = 65.536,  Pero como el tipo int usa signo, su valor está comprendido entre
-32.768 y +32.767.

De hecho en Arduino C++ hay varios tipos de distintos tamaños para manejar enteros: 

Tipo Descripción Valor
int Entero con signo, 16 bits entre -32,768 y 32,767
unsigned int Entero sin signo, 16 bits 216 – 1 ; de 0 hasta 65.535
long Entero con signo, 32 bits 232 – 1 ,Desde  -2.147.483,648 hasta 2.147.483.647
unsigned long Entero sin signo, 32 bits Desde 232 – 1 ; 0 a 4.294.967.295
byte Entero sin signo, 8 bits 28 de 0 hasta 255

 

Todos estos tipos representan enteros con y sin signo y se pueden utilizar para trabajar con números realmente grandes pero no sin límite.

De hecho C++ tiene la fea costumbre de esperar que nosotros llevemos el cuidado de no pasarnos metiendo un valor que no cabe en una variable. Cuando esto ocurre se le llama desbordamiento (overflow) y C++ ignora olímpicamente el asunto, dando lugar a problemas difíciles de detectar si uno no anda con tiento.

Prueba este a calcular esto en un programa:

int i = 32767 ;
Serial.println ( i+1);

Enseguida veras que si i=32767 y le incrementamos en 1, para C++ el resultado es negativo. Eso es porque sencillamente no controla el desbordamiento. También es ilustrativo probar  el resultado de

int i = 32767 ;
Serial.println (2*   i + 1);

Que según Arduino es -1.

 
  • Esto no es un error, sino que se decidió así en su día y C++ no controla los desbordamientos, así que mucho cuidado, porque este tipo de errores pueden ser muy complicados de detectar

 

Más sobre las funciones en C++

.

 Cuando se declara una función se debe especificar que parámetro va a devolver. Así:

Instrucción Significa
int Funcion1() Indica que va a devolver un entero
String Funcion2() Indica que va a devolver un String.
unsigned long Funcion3() Indica que va a devolver un long sin signo
void Funcion4() No va a devolver valores en absoluto

Una función puede devolver cualquier tipo posible en C++, pero sólo puede devolver un único valor mediante la instrucción return(). Expresamente se impide devolver más de un parámetro. Si se requiere esto, existen otras soluciones que iremos viendo.

 
  • Este problema se puede resolver usando variables globales o pasando valores por referencia, y lo trataremos en futuras sesiones.

 

Lo que sí está permitido es pasar varios argumentos a una función:

int Funcion5 ( int x , String s , long y)

Aquí declaramos que vamos a pasar a Funcion5,  tres argumentos en el orden definido, un entero un String y por ultimo un long.

Resumen de la sesión

.

 
    • Hemos definido una función propia para saber si un número es primo.
    • Vimos que el tipo entero tiene un límite de tamaño.
    • Conocimos tipos con mayor y menor capacidad para manejar números enteros mas o menos grandes, pero que todos siguen teniendo un límite de tamaño.
    • El efecto de desbordamiento de tipos es clave y debe ser tenido muy en cuanta cuando operamos con enteros.
    • Hemos ido jugando con problemas lógicos y hemos visto algunas soluciones que os pueden ayudar a plantear las vuestras propias.

 

 

(47) Comments

  • Hola muchas gracias por subir estos tutoriales, aparte de ir relizando estas practicas estoy tratando de hacer otros ejercicios aplicando lo aprendido, aunque tengo dudas sobre cuando utilizar parentesis con el if y con el for espero me o pudieras aclarar. Saludos

    • Hola David, si lo que va dentro del if o el for es sólo una instrucción no necesita paréntesis, ya que interpreta que la siguiente línea es la que entra en el bucle. Si necesitas meter varias instrucciones tienes que ponerlas entre paréntesis después del for o el if. Un saludo.

  • Hola,

    ¿se podría crear una función en un archivo diferente, y después llamar a esa función en el programa principal? Por ejemplo, crear una función y poder utilizarla en distintos programas, sin tener que escribirla literalmente en cada programa. Sé que en otros lenguajes sí se puede y la extensión del archivo es diferente y después es necesario declarar dicha función a la hora de utilizarla en el programa principal, pero soy muy principiante de C++ y no sé cómo se podría hacer aquí.

    Gracias.

    • Hola Jaime, podrías crear una librería, incluirla en el IDE y después usar funciones suyas en cualquier programa después de incluirla. Un saludo.

  • Puff, una sesión un poco dura para mi, veo que esto se puede complicar muchísimo.

    • Tranqui Chopi, que el objetivo era mostraros un programa con varias funciones. Pero por lo demas primero hay que ir aprendiendo a andar antes de correr

    • Pablo David Aranda Rodríguez

    Buenas noches,
    Estoy desarrollando un codigo para que, en caso de que el numero introducido sea primo, se encienda un LED que esta en los pines 30-31, y en caso de que no lo sea, encienda uno que esta en los pines 38-39(estos LEDs son de distinto color). Ademas, me escribirá en pantalla si es primo o no, pero no entiendo porque, numeros que no son primos, me los calcula como primos, y viceversa, ademas de nunca encenderse el LED de los pines 38-39.
    void setup() {
    // put your setup code here, to run once:
    Serial.begin(9600);
    pinMode(30,OUTPUT);
    pinMode(31,OUTPUT);
    pinMode(38,OUTPUT);
    pinMode(39,OUTPUT);
    }

    void loop() {
    bool b = true;
    if (Serial.available()) {
    int n = Serial.read();
    if (b == Primo(n)){
    Serial.println(” Es primo”);
    digitalWrite(31 , LOW);
    digitalWrite(30 , HIGH);
    delay(1000);
    digitalWrite(30 ,LOW);
    }
    else if (b != Primo(n)){
    Serial.println(” No es primo”);
    digitalWrite(39 , LOW);
    digitalWrite(38 , HIGH);
    delay(1000);
    digitalWrite(38 ,LOW);
    }
    }
    }
    bool Primo(int x) {
    for (int i = 2 ; i < x; ) {
    if ( x % i == 0) {
    return(false);
    }
    else {
    return(true);
    }
    i = i + 1;
    }
    }

    • Hola Pablo, utiliza la función Serial.parseInt() en vez de Serial.read() para leer el puerto serie. Un saludo.

  • Muchas gracias admin por la aclaración.
    Estoy intentando hacer el primer ejercicio de esta página, el de los números primos, pero no me sale me da un error. La función bool primo(int n), donde se pone? Dentro de la función void loop o en void setup, o en ninguno de los dos, una función a parte. Me pierdo un poco con C++. El mensaje de error que me da es el siguiente:
    Arduino:1.6.12 (Mac OS X), Tarjeta:”Arduino/Genuino Uno”

    /var/folders/m6/6k7sfhd97rs84nsqdb976t6c0000gn/T//ccsCHLxJ.ltrans0.ltrans.o: In function `main’:
    ccsCHLxJ.ltrans0.o:(.text.startup+0x86): undefined reference to `setup’
    ccsCHLxJ.ltrans0.o:(.text.startup+0x8a): undefined reference to `loop’
    collect2: error: ld returned 1 exit status
    exit status 1
    Error compilación en tarjeta Arduino/Genuino Uno.

    Este reporte podría tener más información con
    “Mostrar salida detallada durante la compilación”
    opción habilitada en Archivo -> Preferencias.

Give a Reply

WordPress Anti-Spam by WP-SpamShield