INGENIERIA INFORMATICA Lenguajes de Programación E.P.S. PRACTICAS de Lenguaje C Tema: Funciones Funciones Preparación Previa Editar + Compilar + Depurar errores Sintácticos y Semánticos Declarar variables con tipos simples y usarlas en expresiones simples Entrada/Salida reducida con printf y scanf Iniciación y motivación a la programación estructurada Estructuras de Control Arrays y estructuras Objetivos de la Sesión Principales Concepto de función Declaración y definición de funciones sencillas Argumentos de una función. Paso por valor Variables locales y globales Secundarios Aprender a analizar el código desarrollado por otras personas Aprender a modificar el código desarrollado por otras personas Motivación Las aplicaciones informáticas que habitualmente se utilizan, incluso a nivel de informática personal, suelen contener decenas y aún cientos de miles de líneas de código fuente. A medida que los programas se van desarrollando y aumentan de tamaño, se convertirían rápidamente en sistemas poco manejables si no fuera por la modularización, que es el proceso consistente en dividir un programa muy grande en una serie de módulos mucho más pequeños y manejables. A estos módulos se les ha denominado de distintas formas (subprogramas, subrutinas, procedimientos, funciones, etc.) según los distintos lenguajes. El lenguaje C hace uso del concepto de función (function). Sea cual sea la nomenclatura, la idea es sin embargo siempre la misma: dividir un programa grande en un conjunto de subprogramas o funciones más pequeñas que son llamadas por el programa principal; éstas a su vez llaman a otras funciones más específicas y así sucesivamente. La división de un programa en unidades más pequeñas o funciones presenta –entre otras– las ventajas siguientes: 1. Modularización. Cada función tiene una misión muy concreta, de modo que nunca tiene un número de líneas excesivo y siempre se mantiene dentro de un tamaño manejable. Además, una misma función (por ejemplo, un producto de matrices, una resolución de un sistema de ecuaciones lineales, ...) puede ser llamada muchas veces en un mismo programa, e incluso puede ser reutilizada por otros programas. Cada función puede ser desarrollada y comprobada por separado. 2. Simplificación de código y localización de errores. En la medida en que una misma función es utilizada muchas veces, el número total de líneas de código del programa disminuye, y también lo hace la probabilidad de introducir errores en el programa. Una función de C es una porción de código o programa que realiza una determinada tarea. Una función está asociada con un identificador o nombre, que se utiliza para referirse a ella desde el resto del programa. En toda función utilizada en C hay que distinguir entre su definición, su declaración y su llamada. Para explicar estos conceptos hay que introducir los conceptos de valor de retorno y de argumentos. Declaración De la misma manera que en C es necesario declarar todas las variables, también toda función debe ser declarada antes de ser utilizada en la función o programa que realiza la llamada. En C la declaración de una función se debe hacer de la siguiente forma: Mediante una declaración explícita, previa a la llamada. Esta es la práctica más segura y la que hay que tratar de seguir siempre. La declaración de la función se hace mediante el prototipo de la función, bien fuera de cualquier bloque, bien en la parte de declaraciones de un bloque. La forma general del prototipo de una función es la siguiente: tipo_valor_de_retorno nombre_funcion(lista de tipos de argumentos); Esta forma general coincide sustancialmente con la primera línea de la definición –el encabezamiento-, con dos pequeñas diferencias: en vez de la lista de argumentos formales o parámetros, en el prototipo basta incluir los tipos de dichos argumentos. Se pueden incluir también identificadores a continuación de los tipos, pero son ignorados por el compilador. Además, una segunda diferencia es que el prototipo termina con un carácter (;). Cuando no hay argumentos formales, se pone entre los paréntesis la palabra void, y se pone también void precediendo al nombre de la función cuando no hay valor de retorno. Es decir, cuando una función es ejecutada, puede devolver al programa que la ha llamado un valor (el valor de retorno), cuyo tipo debe ser especificado en el encabezamiento de la función (si no se especifica, se supone por defecto el tipo int). Si no se desea que la función devuelva ningún valor, el tipo del valor de retorno deberá ser void. Los prototipos permiten que el compilador realice correctamente la conversión del tipo del valor de retorno, y de los argumentos actuales a los tipos de los argumentos formales. La declaración de las funciones mediante los prototipos suele hacerse al comienzo del fichero, después de los #define e #include. En muchos casos –particularmente en programas grandes, con muchos ficheros y muchas funciones–, se puede crear un fichero (con la extensión .h) con todos los prototipos de las funciones utilizadas en un programa, e incluirlo con un #include en todos los ficheros en que se utilicen dichas funciones. Definición de una función La definición de una función consiste en la definición del código necesario para que ésta realice las tareas para las que ha sido prevista. La definición de una función se debe realizar en alguno de los ficheros que forman parte del programa. La forma general de la definición de una función es la siguiente: tipo_valor_de_retorno nombre_funcion(lista de argumentos con tipos) { declaración de variables y/o de otras funciones codigo ejecutable return (expresión); // optativo } La primera línea recibe el nombre de encabezamiento (header) y el resto de la definición – encerrado entre llaves– es el cuerpo (body) de la función. La sentencia return permite devolver el control al programa que llama. Si no hay ningún return, el control se devuelve cuando se llega al final del cuerpo de la función. La palabra clave return puede ir seguida de una expresión, en cuyo caso ésta es evaluada y el valor resultante devuelto al programa que llama como valor de retorno (si hace falta, con una conversión previa al tipo declarado en el encabezamiento). Los paréntesis que engloban a la expresión que sigue a return son optativos. Recordad que en el programa principal si queremos hacer uso de este valor deberemos guardarlo en una variable. Por ejemplo, el siguiente código utiliza la función valor_abs para calcular el valor absoluto de una variable. #include <stdio.h> double valor_abs(double); // declaración void main (void) { double z, y; y = -30.8; z = valor_abs(y) + y*y; // llamada en una expresion } double valor_abs(double x) { double absX; if (x < 0.0) absX = -x; else absX = x; return absX; } La función valor_abs() recibe un valor de tipo double (x). El valor de retorno de dicha función (el valor absoluto de y), es introducido en la expresión aritmética que calcula z. Ejercicio 1: Compilad y ejecutad el código anterior. Debugad paso a paso (F7) para ver el funcionamiento de una llamada a una función. Mirad como cambia el valor de las variables. Ejercicio 2: Dado el siguiente programa, realizar uno de equivalente que utilice tres funciones, es decir, una función para cada párrafo. (Recordar incluir todo los programas que escribais con vuestra práctica) #include <stdio.h> void main() { /* Primer párrafo de la explicación. */ puts("Este es un programa en C que ilustra el tipico"); puts("ejemplo de programa no estructurado"); /* Fin del primer párrafo de la explicación. */ /* Segundo párrafo de explicación. */ puts("Cuando el programa se ejecuta, el usuario no"); puts("puede decir si el programa esta o no estructurado,"); puts("solo puede hacerlo el programador."); /* Fin del segundo párrafo de explicación. */ /* Tercer párrafo de explicación.. */ puts("Por tanto, un programa en C estructurado solo es"); puts("util al programador, al jefe del programador,"); puts("al profesor del programador, a aquellos que "); puts("necesiten modificar el programa y a aquellos"); puts("que convivan con el programador mientras este"); puts("intenta encontrar errores en el programa"); /* Fin del tercer párrafo de explicación. */ } Paso de argumentos por valor La manera más simple de pasar los argumentos a una función es copiándolos ( paso por valor). Los argumentos con que se llama a la función son copiados en las variables especificadas en la definición de la función. Por lo tanto, los cambios que la función realiza no se trasmiten a las variables (originales) del programa que la ha llamado: lo que ha recibido son copias. Aunque se llamen igual son variables diferentes, ya que el espacio en memoria que se usa para guardar las variables del main() y para una función no es el mismo. Intuitivamente podéis pensar que nuestras variables tienen nombre y apellido (= el nombre de la función a la que pertenecen). La siguiente tabla representa la gestión de memoria cuando ejecutamos el programa del ejemplo: Ejemplo: float valor_abs(float ); main() { float x,y; y=2 ; x=valor_abs(y) ; x=x+1; } MEMORIA DEL MAIN A B C G A B C float valor_abs(float y) { float x; D if(y<0) x=-y; else x=y; D y=x+2; E return y; F Declaración variables: y= Õ x= æ Inicalización de y: y= -2 x= æ Llamamos a valor_abs con el valor de y: y= -2 x= æ y=-2 x= æ E y=-2 x= æ F y= -2 x= 2 G y= -2 x= 3 } Ejercicio 3: Es correcto el siguiente programa? #include <stdio.h> void permutar(double, double); /* declaración */ void main(void) { double a=1.0, b=2.0; printf("a = %lf, b = %lf\n", a, b); permutar(a, b); printf("a = %lf, b = %lf\n", a, b); } void permutar(double x, double y) { double temp; temp = x; x = y; y = temp; } MEMORIA FUNCIONES AUXILIARES Inicio de la valor_abs: y= -2 x= Đ y=-2 x= 2 y= 0 memoria de x= 2 Volvemos a main devolviendo x. Fin de la memoria de valor_abs Fijaos que después de retornar al main( ), las variables pertenecientes a la función valor_abs han dejado de existir. Són variables locales a la función. No uséis nunca una variable local para controlar un proceso del main. Variables locales y globales Acabamos de ver que las variables de las funciones auxiliares de nuestro programa sólo tienen existencia dentro del cuerpo de la función. Las únicas que sobreviven durante todo el programa son las variables del main. Sin embargo, el valor de las variables declaradas dentro de una función sólo puede modificarse dentro del cuerpo de dicha función. Si queremos que una variable / definición de un nuevo tipo de datos pueda ser usada por cualquier función, debemos hacer una declaración/ definición global, eso es, fuera del main (donde declaramos las funciones). Usaremos definiciones globales para nuestras estructuras. NO ES DE BUEN PROGRAMADOR USAR VARIABLES GLOBALES. Nosotros sólo utilizaremos variables globales momentáneamente para evitar el paso por referencia. Ejercicio 4: Crea un programa que utilice el programa endevina, pero ahora utilizando una función que realice todo el calculo del juego endevina. Ejercicio 5: Crea un programa que realice el calculo de la siguiente función: f(x) = (2x + a(x+1))/(x-1) Con a un entero y x un float introducidos por el usuario. Primero hazlo sin utilizar ninguna función y luego utilizando una función que haga el cálculo. Es decir que la declaración de la función sea double funcion(double, int); Ejercicio 6: Definid globalmente la estructura de CD’s de la práctica anterior, así como el array de 10 CD’s. Modificad el programa de la práctica anterior añadiendo las siguientes funciones: Una para la entrada de datos de los CD’s. Otra que englobe el cuerpo del while. Deberá leer el grupo, comparar y peguntar al usuario si quiere otro grupo. La respuesta será retornada al main. Ejercicio 7: Crear un programa que pida un numero (decimal) al usuario y que calcule el cuadrado del numero y su raíz cuadrada (función sqrt de C), mostrando siempre el resultado por pantalla. Utilizar dos funciones, una para realizar el cuadrado y la otra para realizar la raíz cuadrada. En el interior de estas funciones es donde se debe imprimir por pantalla el resultado de cada operación. Tened en cuenta que no se puede realizar una raíz cuadrada de un numero negativo. Controlad este caso.