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." ) ;
}
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.
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.
-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.
MÁS
SOBRE LAS FUNCIONES EN C++
RESUMEN
DE LA SESIÓN
·
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.
Comentarios
Publicar un comentario