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.