Funciones Para resolver un problema complejo, suele ser más

Anuncio
Funciones
Para resolver un problema complejo, suele ser más sencillo dividirlo en problemas más
pequeños e ir resolviéndolos uno a uno, que intentar resolverlo de entrada de manera
completa. Esta técnica se conoce como divide and conquer, donde resolviendo todas las
partes se puede obtener la solución integral.
El lenguaje C nos permite hacer esto mediante el uso de funciones.
Podemos definir una función como una porción o bloque de código, que realiza una tarea
determinada.
Las funciones sirven para:
– modularizar el programa
– encapsular tareas definidas
– hacer que el programa sea más fácil de interpretar
– poder reutilizar código, evitando repeticiones
Existen dos tipos de funciones: las que provee la biblioteca standard de C y las definidas
por el programador.
De las primeras, ya hemos estado usando algunas: printf, scanf, sqrt, pow. Ahora
vamos a enfocarnos en las segundas.
Cuando uno escribe una función, lo que busca es que esa función realice una tarea
simple y específica.
Para crear y luego utilizar una función es necesario declararla, definirla y por último
llamarla.
La declaración implica escribir el prototipo de la función, es decir, el molde.
tipo_dato_retorno nombre_de_la_funcion(parámetros);
Acá especificamos:
– nombre_de_la_funcion: dará una idea de la tarea que llevará a cabo
– parámetros: datos que necesita recibir la función para realizar la tarea
– tipo_dato_retorno: el resultado de la tarea
Hasta acá sólo damos una idea de la función, diciendo qué va a hacer, qué necesita para
hacerlo y qué devolverá de resultado, pero no especificamos cómo va a hacerlo. Esto
último forma parte de la definición de la función.
tipo_dato_retorno nombre_de_la_funcion(parámetros)
{
// pasos para realizar la tarea y obtener el resultado
}
Lo único que resta es llamarla.
Cosas importantes a tener en cuenta:
– el prototipo y la definición siempre deben coincidir. Si no coinciden, el compilador
va a considerar que se trata de dos funciones distintas.
– no es necesario que una función retorne un valor. En este caso, en el prototipo
aparecerá void como tipo_dato_retorno, y no le devolverá nada a quien la
llamó.
– no es necesario tampoco que una función reciba parámetros. En este caso, entre
los () no se escribirá nada.
– los parámetros se pasan "por valor". Es decir, la función va a recibir el valor que
está dentro de la variable pasada como parámetro, no la variable en sí. Más
adelante se verá que cuando el parámetro es un arreglo, el comportamiento es
distinto.
– una función cualquiera no conoce las variables que se utilizan en el resto del
programa, a menos que se las pasen como parámetro.
– cada vez que una función se ejecuta, sus variables locales (son propias de la
función, definidas en el cuerpo de la misma) se resetean, o sea, una función no
tiene "memoria", cada vez que es llamada, es como si fuese llamada por primera
vez.
Empecemos por un ejemplo sencillo:
"Se requiere escribir un programa que calcule la potencia de dos números enteros."
Sin funciones, la cosa la escribiríamos así:
#include <stdio.h>
int main()
{
int base, exponente;
int pot = 1;
int i;
printf("Ingresar la base y el exponente: ");
scanf("%d %d", &base, &exponente);
for (i = 0; i < exponente; i++)
{
pot = pot * base;
}
printf("La potencia es: %d\n", pot);
return 0;
}
En lugar de hacer todo dentro del main(), queremos empezar a delegar tareas. Algo
"delegable" sería el cálculo de la potencia. Definamos entonces una función potencia.
Esta función tendría que:
– recibir dos números enteros, base y exponente
– elevar la base al valor del exponente
– retornar el resultado de la potencia
Partiendo de este análisis, la cosa quedaría así:
#include <stdio.h>
int potencia(int bas, int exp);
DECLARACIÓN
int main()
{
int base, exponente;
int pot;
printf("Ingresar la base y el exponente: ");
scanf("%d %d", &base, &exponente);
pot = potencia(base, exponente);
LLAMADO
printf("La potencia es: %d\n", pot);
return 0;
}
int potencia(int b, int e)
{
int i;
int resultado = 1;
for (i = 0; i < e; i++)
{
resultado = resultado * b;
}
DEFINICIÓN
return resultado;
}
Fíjense varias cosas:
– No es necesario que los nombres asignados a los parámetros en la declaración y
en la definición sean los mismos. De hecho, podríamos incluso no asignarles
nombre en la declaración, pero es una buena práctica hacerlo, ya que aclara a qué
se refiere cada uno.
– La variable i que antes formaba parte del main(), ahora forma parte de la función.
Se convirtió en una variable local a la función, ya que sólo ella la conoce y
necesita.
– La variable pot que antes guardaba el resultado del cálculo, ahora guarda el
resultado de la función, o sea, su valor de retorno.
– Los parámetros que recibe la función ( b y e) son utilizados como variables
comunes dentro de la función, como si hubiesen sido definidas allí. La
particularidad que tienen, es que ya vienen con un valor dentro.
– La declaración de la función debe ir siempre antes de que sea llamada, por lo que
se ubica antes del main().
– La definición de la función puede ir tanto antes como después del main(), es
indistinto.
Dijimos al principio que una de las razones para utilizar funciones es evitar repetir código.
Pensemos en este ejercicio:
"Escribir un programa que solicite el ingreso de tres números enteros impares positivos y
luego calcule su promedio."
Una forma de resolver esto sería, por cada número requerido, ingresarlo y validarlo: esto
lo estaríamos haciendo tres veces, o sea, estaríamos repitiendo el mismo código tres
veces.
Para evitar esto, podemos crear una función que encapsule el ingreso y la validación de
un número, y llamar a esa función tres veces.
Entonces, nuestra función cargar_entero_impar_positivo():
– solicita el ingreso de un número entero impar positivo
– valida el número ingresado: si no es correcto, vuelve a solicitar un número
– devuelve el número ingresado ya validado
Por su lado, el main() llamará tres veces a esta función, una por cada número que
necesita.
La solución sería la siguiente:
#include <stdio.h>
int cargar_entero_impar_positivo();
int main()
{
int num1, num2, num3;
double promedio;
num1 = cargar_entero_impar_positivo();
num2 = cargar_entero_impar_positivo();
num3 = cargar_entero_impar_positivo();
promedio = (num1 + num2 + num3) / 3.0;
printf("El promedio de los numeros ingresado es %.2lf\n", promedio);
return 0;
}
int cargarEnteroImparPositivo()
{
int num;
printf("Ingrese un numero entero impar positivo: ");
do
{
scanf("%d", &num);
if (num <= 0 || num % 2 == 0)
{
printf("Error. Ingrese un numero entero impar positivo");
}
} while (num <= 0 || num % 2 == 0);
return num;
}
Fíjense cómo el main() queda bastante simple. De hecho, a simple vista se puede
entender lo que hace. Ese es otro beneficio del uso de funciones.
Vayamos a otro ejemplo:
"Escribir un programa que cuente las letras ingresadas hasta el ingreso de un '.' "
La estrategia sería la siguiente:
– solicitar el ingreso de letras
– contar la cantidad de letras ingresadas (sólo las letras)
– informar la cantidad de letras ingresadas
Pensando en dividir el problema en subproblemas, podríamos, por ejemplo, pasarle a una
función la responsabilidad de ingresar el texto mientras va contando las letras ingresadas.
El planteo sería algo así:
int main()
{
int cantLetras;
printf("Ingrese un texto (punto para finalizar): ");
cantLetras = ingresar_texto();
printf("\nEl texto tiene %d letras.", cantLetras);
return 0;
}
Acá el main() se encarga de pedir el ingreso de letras y mostrar la cantidad ingresada,
mientras que delega la tarea de ingresar las letras y contarlas en la función
ingresar_texto().
Concentrémosnos un poco en la función ingresar_texto().
¿Necesita que el main() le pase algún dato? No. Entonces, la función va sin parámetros.
¿Qué tiene que hacer esta función? Ir tomando cada caracter que se ingresa por teclado,
fijarse si es una letra, y de ser así, contarla.
¿Qué tiene que devolverle al main() como resultado? La cantidad de letras que contó.
En base a esto, el prototipo de la función sería:
int ingresar_texto();
Y su definición sería:
int ingresar_texto()
{
char caracter;
int contador = 0;
caracter = getche();
while (caracter != '.')
{
if ((caracter >= 'A' && caracter <= 'Z') || (caracter >= 'a' && caracter <= 'z'))
{
contador++;
}
caracter = getche();
}
return contador;
}
Vayamos un paso más allá. En lugar de que la función ingresar_texto() sea la
encargada de chequear que el caracter ingresado sea una letra, deleguemos esa tarea a
otra función. Esta nueva función tendría que:
– recibir un caracter
– fijarse si es o no letra
– devolver verdadero o falso, según sea el caso
Recordemos que en C, el valor de verdad VERDADERO se representa con el número
entero 1, y el valor FALSO con el número entero 0.
Entonces, el prototipo sería:
int es_letra(char caract);
Y su definición:
int es_letra(char caract)
{
if ((caract >= 'A' && caract <= 'Z') || (caract >= 'a' && caract <= 'z'))
{
return 1;
}
else
{
return 0;
}
}
Juntando todas estas partes, el problema se resolvería así:
#include <stdio.h>
#include <conio.h>
int ingresar_texto();
int es_letra(char caract);
int main()
{
int cantLetras;
printf("Ingrese un texto (punto para finalizar): ");
cantLetras = ingresar_texto();
printf("\nEl texto tiene %d letras.", cantLetras);
return 0;
}
int ingresar_texto()
{
char caracter;
int contador = 0;
caracter = getche();
while (caracter != '.')
{
if (es_letra(caracter) == 1)
{
contador++;
}
caracter = getche();
}
return contador;
}
int es_letra(char caract)
{
if ((caract >= 'A' && caract <= 'Z') || (caract >= 'a' && caract <= 'z'))
{
return 1;
}
else
{
return 0;
}
}
Fíjense cómo una función definida por nosotros puede llamar a otra función, también
definida por nosotros. Es decir, el llamado a funciones no es propiedad exclusiva del
main(). De hecho, int main() es también una función que una vez finalizada retorna un
entero.
Generación de números aleatorios
El lenguaje C permite generar series de números aleatorios mediante el uso de dos
funciones: rand() y srand().
Uso de rand()
Esta función retorna un número pseudo aleatorio, comprendido entre 0 y RAND_MAX
El ejemplo siguiente genera una serie de 10 números al azar:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i;
for (i = 0; i < 10; i++)
{
printf("Numero generado al azar #%d: %d\n", i, rand());
}
return 0;
}
Es posible definir un rango particular dentro del cual se van a generar los números,
definiendo el límite inferior y el superior del intervalo deseado:
rand() % (lim_sup ­ lim_inf + 1) + lim_inf
Si se quiere generar un número aleatorio entre 2010 y 2015, se puede especificar:
rand() % (2015 ­ 2010 + 1) + 2010, donde:
indica el piso del intervalo
define la longitud del intervalo
genera el número pseudo aleatorio
Recordemos que el operador % (módulo) obtiene el resto de una división, y puede tomar
sólo ciertos valores. Por ejemplo, con num%5, se obtienen valores que van entre 0 y 4, con
num%9 se obtienen valores que van de 0 a 8.
Fíjense que la longitud del intervalo generado es igual a num y siempre el valor mínimo es
0 y el valor máximo es num­1. Ahora bien, en general nosotros necesitamos otros
intervalos o rangos, por ejemplo de 3 a 8 (no de 0 a 5).
Ahí entra en juego la última parte de la "fórmula": la suma del límite inferior. Con eso
logramos correr o desplazar el intervalo para que empiece en 3 en lugar de en 0.
Aplicando esto, con el ejemplo anterior se pueden obtener números aleatorios dentro del
rango 2010-2015, incluídos ambos límites.
¿Por qué se dice que los números generados por la función rand() son pseudo
aleatorios? Para entender esto es necesario observar el comportamiento del programa
anterior ejecutándolo varias veces. Resultado: las series son siempre las mismas.
Para evitar obtener en ejecuciones sucesivas la misma serie, se utiliza la función
srand().
Uso de srand()
Esta función genera una semilla a partir de la cual rand() generará la serie numérica. Para
cada semilla rand() generará una serie específica de números aleatorios.
Usualmente, se utiliza como semilla el momento de ejecución del programa: dado que
éste no se repite, se puede asegurar que cada serie será distinta y única.
Algo a tener en cuenta: srand() se llama una sola vez en todo el programa, usualmente
desde el main().
Modificando el ejemplo anterior:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
int i;
srand(time(NULL));
for (i = 0; i < 10; i++)
{
printf("Numero generado al azar #%d: %d\n", i, rand());
}
return 0;
}
Analicen los resultados y compárenlos.
Descargar