F010 Funciones y Enteros

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 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.
 
Es importante destacar que no existe una forma única de resolver un problema concreto y que una no tiene por qué ser mejor que otra, aunque con frecuencia se aplican criterios de eficiencia o elegancia para seleccionar una solución.

Una función es un bloque de código reutilizable que realiza una tarea específica, lo que evita duplicar líneas de instrucciones. En Arduino y C++, las funciones ayudan a fragmentar un problema general en partes más pequeñas y manejables.

Para crear una función es obligatorio declararla primero, especificando el tipo de dato que devolverá y los parámetros que recibe. La instrucción return se encarga de enviar el resultado final a la parte del programa que llamó a la función.

  • Funciones con retorno: Si una función devuelve un valor, se debe especificar su tipo (como bool para verdaderos/falsos o int para enteros) y finalizar con un return.

  • Funciones vacías (void): Si la función ejecuta su tarea sin devolver ningún valor, se declara como void. Ejemplos comunes de esto son las funciones obligatorias setup() y loop().

Una función puede recibir múltiples argumentos separados por comas, pero tiene una limitación estricta: solo puede devolver un único valor a través de return.

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);
}

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.

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 instrucción 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. 
bool Primo( int n){
  for ( int i = 2 ; i < n ; i++){
    if ( n % i == 0){     // Si el resto es 0 entonces es divisible.
      Serial.print ( String(n) + " es divisible por: " + String(i)) ;
      return(false) ;
     
     }
   }
   return (true) ;
}
void setup(){
Serial.begin(115200);
}

Veamos cuantos primos hay hasta el, digamos 1024, Descargar
bool control = true ;
int maximo = 1024 ;

void setup(){
  Serial.begin(115200);
}
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 aquí es que no ha encontrado ningún divisor
}

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

bool control = true ;
int maximo = 1024 ;
int contador = 1 ;

void setup(){
  Serial.begin(115200);
}
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)== true ){
        if (contador++ % 8 == 0){
        Serial.println( String(x)+"," ) ;
      } else{
          Serial.print( String(x) +","+ '\t') ;
        }
      }
    }  
  }
  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 aquí es que no ha encontrado ningún divisor
}

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

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 tenemos una línea que conviene comentar:
if (contador++ % 8 == 0){

a una variable se le añaden dos símbolos más al nombre, significa que primero se use su valor actual en la instrucción en curso, n 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 contador-- y  --contador 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.  ¿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.

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

De hecho en Arduino C++ hay varios tipos de distintos tamaños para manejar enteros. 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.

Desbordamiento u Overflow
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 cuidado.

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++

 

No hay comentarios:

Publicar un comentario