Capítulo 7. Programación modular. Conceptos básicos sobre funciones. 1. Introducción. 2. Estructura de un programa dividido en módulos. 2.1. Reglas de ámbito de las funciones. 2.2. Devolución de un valor: return(). 3. Variables globales y locales. 3.1. Operador de resolución de ámbito (::). 3.2. Parámetros de funciones. 3.3. Paso de parámetros: por valor y por referencia. 4. Funciones de escape: exit() y abort(). 1 1. Introducción. Generalmente los problemas a resolver informáticamente mediante un algoritmo se abordan aplicando la programación modular, que también se denomina diseño descendente de programas o top-down. Este consiste en descomponer el problema en partes más pequeñas y menos complejas, las cuales pueden ser divididas de nuevo en otras partes y así sucesivamente hasta que se considere que el desarrollo y mantenimiento de cada parte sean suficientemente sencillos. Usar el diseño descendente o modular tiene ventajas como: - El programa queda estructurado en módulos, lo que facilita la comprensión y el mantenimiento del mismo. Los módulos pueden ser programados de forma independiente, por lo que pueden hacerlo distintas personas, ganándose por tanto en rapidez. Los módulos pueden ser compartidos por diferentes programas. Un módulo debe ser independiente del resto del programa, de esta manera se consigue su total portabilidad entre distintos programas. 2. Estructura de un programa dividido en módulos. Cuando se emplea la programación modular surgen los conceptos de programa principal y funciones o módulos: - - El programa principal describe la solución completa del problema, realizando principalmente llamadas a las distintas funciones para que se ejecuten. Las funciones realizan las operaciones que se les encomiende, para lo cual podrán llamar a otras funciones. Cada función tendrá un nombre para poder identificarla. Al hablar de módulo o función hay que distinguir entre lo que se denomina definición de función y llamada a la función. La definición de una función es el cuerpo de la misma, es decir las instrucciones que la componen. Por otro lado, la llamada a la función es escribir el nombre de la misma en el punto del programa donde queremos que sea ejecutada. Veamos la sintaxis en pseudocódigo con un ejemplo: Ej. Programa PRINCIPAL Inicio. <Sentencia1> <Sentencia2> FUNC() //Llamada a la función FUNC <Sentencia3> ... Fin. Función FUNC() <SentenciaF1> //Definición de la función FUNC 2 <SentenciaF2> <SentenciaF3> ... FinFunción El orden de ejecución de las instrucciones cuando se realiza una llamada a una función es el siguiente: se ejecutan las instrucciones de la función y cuando ésta finaliza se continúa ejecutando el programa que llamó a la función, justamente por la instrucción siguiente a la llamada a la función. Se suele decir que llamar a una función es un salto con retorno. En el ejemplo anterior el orden sería: Sentencia1 Sentencia2 Llamada a FUNC -----> Se ejecuta: SentenciaF1 SentenciaF2 SentenciaF3 ... Sentencia3 <--------- Retorna al programa ... Para usar funciones en C, la sintaxis es la siguiente: #include <...> ... tipo FUNC(void); //Declaración de la función FUNC void main(void) //Programa principal { <Entorno> //Declaración de variables de main <Sentencia1> <Sentencia2> FUNC() // Llamada a la función FUNC <Sentencia3> ... } tipo FUNC(void) //Definición de la función FUNC { <Entorno> //Declaración de variables locales <SentenciaF1> <SentenciaF2> <SentenciaF3> } Como vemos, en lenguaje C además de existir la llamada y la definición de la función, también se escribe la declaración de la misma, justo antes del bloque principal main. El tipo de la función es el tipo del valor que devuelve la función, como vamos a ver a continuación. 3 2.1. Reglas de ámbito de las funciones. Las reglas de ámbito hacen referencia al conocimiento que una función tiene de otra función del programa. En C el código y los datos de una función son privados, de forma que las demás funciones no pueden interactuar con partes del código de una función ni con sus datos (locales). Por otra parte, existen las variables globales de un programa, que como veremos más adelante son accesibles desde cualquier función. Estas variables globales deben ser evitadas. En C, todas las funciones están en el mismo nivel de ámbito, o sea que no se pueden declarar funciones dentro de funciones. No existe el concepto de que una función pertenezca a otra. Cualquier función puede realizar una llamada a cualquier otra función del programa. 2.2. Devolución de un valor: return(). Una función tiene la capacidad de devolver un valor en el propio nombre de la función. Este valor es devuelto al módulo que realizó la llamada a la función. Si una función devuelve un valor, cuando se realiza la llamada a la función el nombre de ésta podrá usarse en cualquier expresión o condición, como cualquier otra variable del tipo del valor que devuelve, por ejemplo: SUMA = TOTAL + FUNC() si ( FUNC() == 0 ) //Llamada y asignación //Llamada y condición Por el contrario, no está permitido usar el nombre de la función a la izquierda de una asignación, por ejemplo: FUNC()++; FUNC() *= 5; //FUNC() = FUNC() + 1 //FUNC() = FUNC() * 5 Es erróneo. Es erróneo. Por tanto, después de realizar la llamada a la función, al finalizar su ejecución se puede devolver un valor al punto donde fue llamada la función, de modo que al retornar a dicho punto la primera acción que se realiza es usar ese valor en la expresión donde esté incluida la función. Veamos un ejemplo: Ej. Teclear 10 valores y visualizar la media: Programa PRINCIPAL Inicio. Escribir “Programa que calcula la media” Resultado = MEDIA() //Llamada a la función MEDIA //La variable Resultado recoge el valor que //devuelva la función MEDIA. Escribir “La media es:”, Resultado Fin. Función MEDIA() //Definición de la función MEDIA Suma = 0 Para I de 1 a 10 Inc.1 4 Escribir “Teclee Valor:” Leer Valor Suma = Suma + Valor FinPara Devolver(Suma/10) //Devuelve un valor en función MEDIA FinFunción La sentencia Devolver(), además de devolver un valor, fuerza la finalización de la función, retornando en ese momento al módulo que la llamó. Por tanto, si hubiera más sentencias después de Devolver() no se ejecutarían. Es decir, la ejecución de una función puede finalizar por dos motivos: por llegar a la sentencia FinFunción; o por llegar a una sentencia Devolver(). Una función puede tener varias sentencias Devolver(), pero es aconsejable que sólo haya una para así controlar fácilmente dónde finaliza la ejecución de la función. En C se devuelve un valor desde una función con la sentencia return(), que también provoca la finalización de la función. Veamos cómo sería el ejemplo anterior: Ej. Teclear 10 valores y visualizar la media: #include <stdio.h> #include <conio.h> float MEDIA(void); //Declaración de la función: hay //que indicar el tipo que devuelve, float. void main(void) { float Resultado; clrscr(); gotoxy(5,5);printf(“Programa que calcula media”); Resultado = MEDIA() //Llamada a la función MEDIA gotoxy(5,7);printf(“La media es: %.2f”, Resultado); // Podría haberse usado directamente: // gotoxy(5,7);printf(“La media es: %.2f”, MEDIA() ); } float MEDIA(void)//Definición de la función MEDIA. Hay { //que indicar el tipo que devuelve: float int I, Valor; float Suma; Suma = 0; for (I = 1; I <= 10; I++) { gotoxy(5,9);printf(“Teclee valor %d:”, I); clreol();scanf(“%d”, &Valor);fflush(stdin); Suma += Valor } return(Suma/10) //Devuelve la media en tipo float } 5 Como vemos, la función tiene un tipo (float) que será el tipo del valor que devuelve. Este tipo se coloca delante del nombre de la función tanto en la definición como en la declaración. <tipo> <nombre_función> ( void ) Devolver un valor no es obligatorio para una función. Cuando una función en C no devuelve un valor podrá usarse el tipo void, no tendrá sentencia return(...) y además en la llamada no se usará el nombre de la función dentro de ninguna expresión ni condición. Generalmente, en los distintos lenguajes de programación se denominan procedimientos a las funciones que no devuelven valores. 3. Variables globales y locales. Una variable global es accesible desde cualquier punto del programa, incluidas las funciones. Se suele decir que su ámbito se extiende al programa principal y a todos sus módulos o funciones. Una variable local es accesible sólo desde el módulo o función donde ha sido declarada, por lo que su ámbito está restringido a ese módulo. Las variables globales deben evitarse por los llamados efectos laterales, es decir la modificación de una variable global dentro de una función puede afectar a otra parte del programa, donde no se esperaba esa modificación, lo cual ocurre sobre todo en programas grandes. Otro inconveniente de estas variables es que ocupan memoria durante todo el tiempo que dure la ejecución del programa completo. Además, cuando una función usa alguna variable global, no es una función totalmente independiente, con lo que pierde portabilidad. Por el contrario, la modificación de una variable local sólo afectará a la función a la que pertenece. Además la memoria que ocupan las variables locales se libera cuando finaliza la ejecución de la función donde esté declarada, quedando libre más memoria para otras variables del programa que sigue en ejecución. En pseudocódigo para indicar que una variable es global se declarará antes de “Inicio” y una variable local se declarará dentro del módulo al que corresponda. En C las variables globales se declaran antes del bloque principal, de la forma: #include <stdio.h> ... <tipo> <Var.Global1>; <tipo> <Var.Global2>; ... 6 void main(void) { ... } En C las variables locales se declaran dentro de una función, siendo locales a esa función, de la forma: #include <stdio.h> ... int FUNC(void); void main(void) { <tipo> <Var.Local1>; //Locales de la función main. <tipo> <Var.Local2>; //No se pueden usar en FUNC, ... //ni en ninguna otra función. } int FUNC(void) { <tipo> <Var.Local11>; //Locales de la función FUNC. <tipo> <Var.Local12>; //No se pueden usar en main, ... //ni en ninguna otra función. } Si una variable local de una función tiene el mismo nombre que una variable global del programa, cuando se use el nombre de esa variable dentro de la función se hace referencia a la variable local. Ej. Pseudocódigo. Valor //Valor es variable global Inicio. Valor = 150 //Se usa la var. Valor global. FUNC_1() FUNC_2() Fin. Función FUNC_1() Contador //Variable local //Usa vari. Valor global para Contador de 1 a Valor Inc.1 Escribir “*” //Visualiza 150 asteriscos finpara FinFunción Función FUNC_2() Contador //Variable local Valor //Variable Valor local. //En esta función, la variable global //Valor no se usa, se usa la local. 7 Valor = 10 para Contador de 1 a Valor Inc.1 Escribir “*” //Visualiza 10 asteriscos finpara FinFunción 3.1. Operador de resolución de ámbito (::). En C existe una forma de referenciar una variable global en una función que contenga una variable local con el mismo nombre que la global. Para ello se utiliza el llamado el operador de resolución de ámbito, :: (es decir, dos signos de dos puntos seguidos). Al colocar este operador delante del nombre de la variable, se accederá a la variable global. En el ejemplo anterior, en la función FUNC_2() podría escribirse: Ej. #include <stdio.h> #include <conio.h> int Valor; //Variable Valor global. void FUNC_2(void); void main(void) { Valor = 150; FUNC_2(); } void FUNC_2(void) { int Contador; //Variable local int Valor; //Variable Valor local. Valor = 10; //La vari. local Valor vale 10. for (Contador = 1; Contador <= ::Valor; Contador++) printf(“*”); //Visualiza 150 asteriscos. //Se accede a Valor global (::Valor), vale 150. } 3.2. Parámetros de funciones. Como las variables globales no deben usarse, tiene que emplearse un método para que haya comunicación entre una función y otra o entre una función y el programa principal. Dicho de otro modo, los datos de entrada de una función pueden venir del módulo llamante (no sólo de teclado u otro dispositivo de entrada). Como se ha visto anteriormente, con la instrucción return sólo se puede devolver un resultado desde una función. Pero existen situaciones en la que una función debe devolver más de un resultado al módulo que realizó la llamada. Esto se puede realizar y se explicará en un capítulo posterior. Los parámetros o variables de enlace son variables usadas para pasar datos a una función en el momento de su llamada y para obtener resultados de la función cuando finalice. 8 Ej. Calcular factorial de un número: Pseudocódigo. Inicio. N //Variables locales del programa principal Resultado Escribir “Teclee un número:” Leer N Resultado = FACTORIAL(N) //Pasa N como parámetro Escribir “El resultado es:”, Resultado Fin. Función FACTORIAL(Limite) I F //En Limite recibe el valor //de N como dato de entrada //Variables locales F = 1 para I de 1 a Limite Inc.1 F = F * I finpara Devolver(F) FinFunción Al hablar de parámetros debe hacerse la siguiente distinción: - - Parámetros formales: son los parámetros o variables locales usados en la función para recibir datos de entrada (variable Limite en el ejemplo anterior) o devolver resultados de salida. Estas variables son locales en la función, que junto con las variables locales declaradas dentro de la función, son las que puede usar la función en sus sentencias. Parámetros actuales: son las variables del módulo llamante usadas en la llamada a la función para enviar datos (variable N en el ejemplo anterior) y recoger resultados de la función. El método para recoger resultados en los parámetros de una función será comentado en un capítulo posterior. El número de parámetros formales debe coincidir con el de actuales, así como el tipo de cada parámetro formal con su actual correspondiente. En C los parámetros formales se colocan entre paréntesis después del nombre de la función en su declaración y definición, separados por comas e indicando el tipo de cada uno, es decir: <tipo> <Función>( <tipo> <pf1>, <tipo> <pf2>,... ) 9 En C los parámetros actuales se colocan entre paréntesis después del nombre de la función en su llamada separados por comas, sin indicar tipo, es decir: <Función>( <pa1>, <pa2>,... ) //Llamada a la función El ejemplo anterior en C quedaría: Ej. Calcular factorial de un número: #include <stdio.h> #include <conio.h> long FACTORIAL(int); //Declaración de función. Sólo es //necesario poner los tipos de void main(void) //de los parámetros (int). { int N; //Variable locales del long Resultado; //programa principal clrscr(); printf(“Teclee un número:”); scanf(“%d”, &N);fflush(stdin); Resultado = FACTORIAL(N); //Pasa N como parámetro printf(“\nEl factorial es: %ld”, Resultado); } long FACTORIAL(int Limite) //En Limite recibe el valor { //de N como dato de entrada int I; //Variables locales long F; F = 1; for (I = 1; I <= Limite; I++) F = F * I; return(F) } Como vemos, en la declaración de la función no es obligatorio indicar los nombres de los parámetros, pero sí los tipos separados por comas. El nombre de un parámetro formal puede ser el mismo que el de su parámetro actual correspondiente, pero siempre debe tenerse en cuenta que son variables distintas. 3.3. Paso de parámetros: por valor y por referencia. El envío de datos y la recepción de resultados de una función se llama paso de parámetros. Este puede realizarse de dos modos: - paso por valor: un parámetro pasado por valor sólo podrá ser usado por la función para recibir un dato de entrada a la misma. 10 - paso por referencia: un parámetro pasado por referencia podrá ser usado por la función tanto para recibir un dato de entrada a la misma como para devolver un resultado de salida de la función, que lo podrá usar el módulo llamante. Este tipo de parámetro será explicado en un capítulo posterior. Ej. Cálculo de la longitud de una circunferencia. Pseudocódigo. Inicio. Radio Longitud //Declarar 2 variables: Radio y Longitud. Escribir “Teclee radio:” Leer Radio Longitud = Calcular_Longitud(Radio) //Radio se pasa por valor Escribir “La longitud es: “, Longitud Fin. Función Calcular_Longitud(R) //R por valor L //Declarar vari. L local PI = 3.1416 L = 2 * PI * R return(L) //Devuelve L FinFunción En el paso por valor, el parámetro formal será una nueva variable donde se copia el valor del parámetro actual correspondiente al comenzar a ejecutarse la función. Puede decirse que el parámetro formal es una copia del parámetro actual, que sería el original. La función no tiene acceso al original, por tanto puede modificarlo, trabaja con la copia y la puede modificar, pero esta copia se destruye al finalizar la función, ya que es una variable local de la función. En el ejemplo anterior R es una copia de Radio. Si la función modificara la variable R, no afectaría a Radio. La variable R se destruye al finalizar la función. El parámetro actual en un paso por valor puede ser una expresión, es decir no tiene por qué ser siempre una simple variable. En ese caso, el parámetro formal recoge como valor el resultado de la expresión. En C el paso por valor se realiza como se ha visto hasta ahora, sin indicar nada especial. Veamos el ejemplo anterior en C: Ej. Cálculo de la longitud de una circunferencia. #include <stdio.h> #include <conio.h> #define PI 3.1416 float CALCULAR_LONGITUD(int); void main(void) { int Radio; float Longitud; //Variable de tipo float. 11 clrscr(); printf(“Teclee el radio:”); scanf(“%d”, &Radio);fflush(stdin); Longitud = CALCULAR_LONGITUD(Radio); //Radio se pasa por valor. printf(“\nLa longitud es: %.4f”, Longitud); } float CALCULAR_LONGITUD(int R) { //R es una copia de Radio y es local. float L; L = 2 * PI * R; return(L); } 4. Funciones de escape: exit() y abort(). Existen funciones para finalizar la ejecución del programa, por ejemplo cuando se da la ocurrencia de algún estado de alarma. Una de ellas es la función exit(), que está declarada en stdlib.h. Esta función provoca la finalización del programa y regresa al sistema operativo, cerrando todos los ficheros previamente, por lo que la última información actualizada quedará grabada en los ficheros abiertos. Su prototipo es: void exit ( int código_de_vuelta) El código_de_vuelta es el valor que devuelve al sistema operativo, que podrá ser comprobado por ejemplo con los errorlevel de un fichero .BAT del MS-DOS. El valor 0 normalmente indica finalización normal del programa y un valor distinto de 0 suele usarse para indicar finalización con algún error. Un ejemplo de su uso, como veremos más adelante, se da cuando se intenta reservar memoria de forma dinámica, con malloc, y no hay memoria suficiente. En tal caso se puede usar exit(1). Otra función que aborta la ejecución del programa y regresa al sistema operativo es abort(). Visualiza el mensaje "Abnormal program termination". Está declarada en stdlib.h. Esta función no cierra los ficheros abiertos, por lo que la información que aún no se hubiera grabado se perdería. Su prototipo es: void abort (void); 12 EJERCICIOS - CAPITULO 7: Realizar los siguientes algoritmos en pseudocódigo y en C, utilizando funciones en todos ellos: 1. Realizar el diseño de la siguiente factura para cada artículo tecleado. El programa finalizará cuando el código de artículo tecleado sea cero. Para cada artículo se tecleará código, nombre y precio, y a continuación una serie de cantidades hasta que la cantidad tecleada sea cero, y se irá construyendo la factura con el formato indicado. Si se llena la pantalla, no se permitirá seguir con la misma factura, sino que se comenzará con otro artículo, solicitando su código. Si la cantidad es mayor de 10 se aplicará un descuento del 15%. Una función se encargará de mostrar el diseño inicial, sin datos, que será utilizada cada vez que se vaya a comenzar con un nuevo artículo. VENTAS DE ARTÍCULOS ------------------DATOS ARTÍCULO: CODIGO (0=FIN): ----NOMBRE........: ----------------PRECIO........: -----CODIGO -----XXXX DATOS VENTAS: CANTIDAD.: ---(0=FIN) NOMBRE PRECIO CANTIDAD DESC. --------------- ------- -------- ----XXXXXXXXXXX XXXXX XXXX XX% XX X% XXX XX% X X% TOTAL --------XXXXX.XX XXX.XX XXXX.XX XXX.XX --------TOTAL: XXXXXX.XX 2. Realizar un programa que solicita DNI, nombre y saldo (puede ser negativo y con decimales) de una serie de personas, hasta que el DNI tecleado sea 0. Al final mostrará el DNI y el saldo de las personas con mayor y menor saldo. Una función se encargará de realizar un diseño de pantalla como el siguiente. Cada vez que se vaya a teclear un nuevo DNI, se borrará la pantalla completa y se volverá a visualizar el diseño de pantalla. MAYOR/MENOR SALDO ---------------------------------------DNI (O=FIN): __________ NOMBRE.....: _____________________ SALDO......: ________ SALDO MAYOR: ________ MENOR: ________ 3. Realizar un programa que solicite el DNI y la edad de una serie de alumnos, hasta que se responda N a la pregunta "DESEA INTRODUCIR ALUMNO 13 (S/N)?". Al final mostrará la media de las edades. Se realizará una función que permita teclear solamente S o N y devuelve la letra tecleada de esas dos. Sólo se visualizará la letra tecleada cuando sea correcta, S o N, usando getch(). La función no finalizará hasta que la letra pulsada sea correcta. También se admitirán en minúsculas, s o n. 4. Realizar un programa que calcula el factorial de cada número que se teclee. Cuando el número tecleado sea 0, finalizará el programa. Una función debe solicitar el número, calcular su factorial y devolverlo al programa principal. El factorial de un número N es: N! = 1 * 2 * 3 * ...* (N-1) * N. 5. Realizar un programa que finalice cuando se pulse la opción 4 de un menú de 4 opciones. Una función debe visualizar dicho menú y devuelve al programa la opción elegida. Las opciones serán: 1)Longitud circunferencia: 2r; 2)Area del círculo: r2; 3)Volumen de la esfera: 4/3r3; 4)Salir. Realizar una función para cada una de las tres primeras opciones. Se tecleará el radio y se visualizará el resultado dentro de cada función. 6. Realizar un programa que solicite el DNI y 5 notas correctas (entre 0 y 10) de una serie de alumnos, hasta que se responda N a la pregunta "INTRODUCIR ALUMNO (S/N)?". Para cada alumno debe visualizarse su nota media. Utilizar la función realizada anteriormente, que controla que se teclee sólo S o N y no otra letra y devuelva la opción pulsada. Además, si una nota tecleada no es correcta, otra función debe encargarse de mostrar el mensaje de error correspondiente y de borrarlo. 7. Modificar el programa del ejercicio 3 para que la posición (columna y fila) de pantalla donde teclear la S o la N sea pasada como 2 parámetros a la función. 8. Modificar el programa del ejercicio 4 para que el número se teclee en el bloque main. 9. Modificar el programa del ejercicio 5 para que se teclee el radio y se visualice el resultado en el bloque main. 10. Modificar el programa del ejercicio 6 para que la posición (columna y fila) de pantalla donde mostrar el mensaje de error sea pasada como 2 parámetros a la segunda función. 11. Realizar un programa que llama a una función para que visualice un rectángulo en pantalla, cuyo tipo de línea (simple, doble o sin línea) es pasado como parámetro. La posición de pantalla también se le pasa como parámetro, así como el carácter de relleno del rectángulo. Por tanto en el programa principal se teclearán 4 datos que serán pasados como parámetros a la función. 12. Teniendo en cuenta que la función random(LIMITE) devuelve un número aleatorio entre 0 y LIMITE–1, teclear una serie de valores LIMITE hasta que 14 se teclee un cero para finalizar el programa. A cada valor LIMITE tecleado se le aplicará la función random y a continuación se solicitará al usuario que teclee números hasta que acierte el valor aleatorio generado por random. Para cada número que teclee el usuario se visualizará si es mayor o menor que el valor aleatorio, para que pueda acertarlo más fácilmente. 13. Teclear una serie de parejas de números hasta que se responda ‘N’ a la pregunta “Teclear números (S/N)?”. Para cada pareja de números visualizar la potencia del primero elevado al segundo, XY = X * X *...* X, o sea multiplicar la X un número Y de veces. Los valores se teclean en el programa principal, una función debe calcular XY y devuelve el resultado al programa principal. 14. Función que devuelve el resultado de la expresión X1 + X2 + ... + XY, donde X y Y son enteros tecleados en el bloque main (. Utilizar la función realizada en el ejercicio anterior (C ofrece la función pow(X,Y) que devuelve XY y está en math.h). Realizar otra función para la expresión: 11 + 22 + ... + NN, donde N es tecleado en el bloque main. 15. Sabiendo que un euro son 166,386 pesetas, realizar un conversor de euros a pesetas y viceversa. 15