1.INTRODUCCION
La forma más razonable de encarar el desarrollo de un programa complicado es aplicar
lo que se ha dado en llamar "Programación Top - Down" .
Esto implica que, luego de conocer cual es la meta a alcanzar, se subdivide esta en otras
varias tareas concurrentes, por ejemplo :
Leer un teclado, procesar datos, mostrar los resultados .
Luego a estas se las vuelve a dividir en otras menores :
Y así se continúa hasta llegar a tener un gran conjunto de pequeñas y simples
tareas, del tipo de "leer una tecla" ó "imprimir un caracter".
Luego sólo resta abocarse a resolver cada una de ellas por separado.
De esta forma el programador, sólo se las tendrá que ver con diminutas piezas de
programa, de pocas lineas, cuya escritura y corrección posterior es una tarea simple.
Tal es el criterio con que está estructurado el lenguaje C, donde una de sus herramientas
fundamentales són las funciones. Todo compilador comercial trae una gran cantidad de
Librerias de toda índole, matematicas, de entrada - salida, de manejo de textos, de
manejo de gráficos, etc, que solucionan la mayor parte de los problemas básicos de
programación .
Sin embargo será inevitable que en algún momento tenga que crear mis propias funciones,
las reglas para ello son las que desarrollaremos en este capítulo .
Comencemos con algunos conceptos básicos: para hacer que las instrucciones contenidas en
una función, se ejecuten en determinado momento, no es necesario más que escribir su
nombre como una linea de sentencia en mi programa. Convencionalmente en C los nombres de
las funciones se escriben en minúscula y siguen las reglas dadas anteriormente para los
de las variables, pero deben ser seguidos, para diferenciarlas de aquellas por un par de
paréntesis .
Dentro de estos paréntesis estarán ubicados los datos que se les pasan a las funciones.
Está permitido pasarles uno, ninguno ó una lista de ellos separados por comas, por
ejemplo: pow10( a ), getch(), strcmp( s1, s2 ) .
Un concepto sumamente importante es que los argumentos que se les envían a las funciones
son los VALORES de las variables y NO las variables mismas. En otras palabras, cuando se
invoca una función de la forma pow10( a ) en realidad se está copiando en el
"stack" de la memoria el valor que tiene en ese momento la variable a, la
función podrá usar este valor para sus cálculos, pero está garantizado que los mismos
no afectan en absoluto a la variable en sí misma.
Como veremos más adelante, es posible que una función modifique a una variable, pero
para ello, será necesario comunicarle la DIRECCION EN MEMORIA de dicha variable .
Las funciones pueden ó no devolver valores al programa invocante. Hay funciones que tan
sólo realizan acciones, como por ejemplo clrscr(), que borra la pantalla de video, y por
lo tanto no retornan ningun dato de interés; en cambio otras efectuan cálculos,
devolviendo los resultados de los mismos.
La invocación a estos dos tipos de funciones difiere algo, por ejemplo escribiremos :
clrscr() ; c = getch() ;
donde en el segundo caso el valor retornado por la función se asigna a la variable c. Obviamente ésta deberá tener el tipo correcto para alojarla .
2. DECLARACION DE FUNCIONES
Antes de escribir una función es necesario informarle al Compilador los tamaños de los
valores que se le enviarán en el stack y el tamaño de los valores que ella retornará al
programa invocante .
Estas informaciones están contenidas en la DECLARACION del PROTOTIPO DE LA FUNCION.
Formalmente dicha declaración queda dada por :
tipo del valor de retorno nombre_de_la_función(lista de tipos de parámetros)
Pongamos algunos ejemplos :
float mi_funcion(int i, double j ) ; double otra_funcion(void) ; otra_mas(long p) ; void la_ultima(long double z, char y, int x, unsigned long w) ;
El primer término del prototipo da, como hemos visto el tipo del dato retornado por la
función; en caso de obviarse el mismo se toma, por omisión, el tipo int. Sin embargo,
aunque la función devuelva este tipo de dato, para evitar malas interpretaciones es
conveniente explicitarlo .
Ya que el "default" del tipo de retorno es el int, debemos indicar cuando la
función NO retorna nada, esto se realiza por medio de la palabra VOID ( sin valor).
De la misma manera se actúa, cuando no se le enviarán argumentos.
Más adelante se profundizará sobre el tema de los argumentos y sus características.
La declaración debe anteceder en el programa a la definición de la función. Es normal,
por razones de legibilidad de la documentación, encontrar todas las declaraciones de las
funciones usadas en el programa, en el HEADER del mismo, junto con los include de los
archivos *.h que tienen los prototipos de las funciones de Librería.
Si una ó más de nuestras funciones es usada habitualmente, podemos disponer su prototipo
en un archivo de texto, e incluirlo las veces que necesitemos, según se vio en capítulos
previos.
3. DEFINICION DE LAS FUNCIONES
La definición de una función puede ubicarse en cualquier lugar del programa, con sólo
dos restricciones: debe hallarse luego de dar su prototipo, y no puede estar dentro de la
definición de otra función ( incluida main() ). Es decir que a diferencia de Pascal, en
C las definiciones no pueden anidarse.
NOTA: no confundir definición con llamada; una función puede llamar a tantas otras como
desee .
La definición debe comenzar con un encabezamiento, que debe coincidir totalmente con el
prototipo declarado para la misma, y a continuación del mismo, encerradas por llaves se
escribirán las sentencias que la componen; por ejemplo:
#include <stdio.h> float mi_funcion(int i, double j ); /* DECLARACION observe que termina en ";" */ main() { float k ; int p ; double z ; ...........
k = mi_funcion( p, z ); /* LLAMADA a la función */ ........... } /* fin de la función main() */
float mi_funcion(int i, double j ) /* DEFINICION observe que NO lleva ";" */ { float n ................... printf("%d", i ); /* LLAMADA a otra función */ ................... return ( 2 * n ); /* RETORNO devolviendo un valor float */ }
Pasemos ahora a describir más puntualmente las distintas modalidades que adoptan las funciones .
4. FUNCIONES QUE NO RETORNAN VALOR NI RECIBEN PARAMETROS
Veamos como ejemplo la implementacion de una funcion "pausa"
#include <stdio.h> void pausa(void) ;
main() { int contador = 1;
printf("VALOR DEL CONTADOR DENTRO DEL while \n");
while (contador <= 10) { if(contador == 5 ) pausa(); printf("%d\n", contador++); } pausa() ; printf("VALOR DEL CONTADOR LUEGO DE SALIR DEL while: %d", contador) ; return 0; }
void pausa(void) { char c ;
printf("\nAPRIETE ENTER PARA CONTINUAR ") ; while( (c = getchar()) != '\n') ; }
Analicemos lo hecho, en la segunda linea hemos declarado la función pausa, sin valor
de retorno ni parámetros.
Luego esta es llamada dos veces por el programa principal, una cuando contador adquiere el
valor de 5 (antes de imprimirlo) y otra luego de finalizar el loop.
Posteriormente la función es definida. El bloque de sentencias de la misma está
compuesto, en este caso particular, por la definición de una variable c, la impresión de
un mensaje de aviso y finalmente un while que no hace nada, solo espera recibir un
caracter igual a <ENTER>.
En cada llamada, el programa principal transfiere el comando a la función, ejecutandose,
hasta que ésta finalice, su propia secuencia de instrucciones. Al finalizar la función
esta retorna el comando al programa principal, continuandose la ejecución por la
instrucción que sucede al llamado .
Si bien las funciones aceptan cualquier nombre, es una buena técnica de programación
nombrarlas con términos que representen, aunque sea vagamente, su operatoria .
Se puede salir prematuramente de una función void mediante el uso de RETURN, sin que este
sea seguido de ningun parámetro ó valor .
5. FUNCIONES QUE RETORNAN VALOR
Analicemos por medio de un ejemplo dichas funciones :
#include <stdio.h> #include <conio.h>
#define FALSO 0 #define CIERTO 1
int finalizar(void); int lea_char(void) ;
main() { int i = 0; int fin = FALSO; printf("Ejemplo de Funciones que retornan valor\n"); while (fin == FALSO) { i++; printf("i == %d\n", i); fin = finalizar(); } printf("\n\nFIN DEL PROGRAMA........"); return 0; } int finalizar(void) { int c; printf("Otro número ? (s/n) "); do { c = lea_char() ; } while ((c != 'n') && (c != 's')); return (c == 'n'); }
int lea_char(void) { int j ;
if( (j = getch()) >>= 'A' && j <<= 'Z' ) return( j + ( 'a' - 'A') ) ; else return j ; }
Analicemos paso a paso el programa anterior; las dos primeras lineas incluiran, en el
programa los prototipos de las funciones de librería usadas, ( en este caso printf() y
getch() ). En las dos siguientes damos nombres simbólicos a dos constantes que usaremos
en las condiciones lógicas y posteriormente damos los prototipos de dos funciones que
hemos creado.
Podrían haberse obviado, en este caso particular, estas dos últimas declaraciones, ya
que ambas retornan un int (default), sin embargo el hecho de incluirlas hará que el
programa sea más facilmente comprensible en el futuro.
Comienza luego la función main(), inicializando dos variables, i y fin, donde la primera
nos servirá de contador y la segunda de indicador lógico. Luego de imprimir el rótulo
del programa, entramos en un loop en el que permaneceremos todo el tiempo en que fin sea
FALSO.
Dentro de este loop, incrementamos el contador, lo imprimimos, y asignamos a fin un valor
que es el retorno de la función finalizar() .
Esta asignación realiza la llamada a la función, la que toma el control del flujo del
programa, ejecutando sus propias instrucciones.
Saltemos entonces a analizar a finalizar(). Esta define su variable propia, c, (de cuyas
propiedades nos ocuparemos más adelante) y luego entra en un do-while, que efectúa una
llamada a otra función, lea_char(), y asigna su retorno a c iterando esta operativa si c
no es 'n' ó 's', note que: c != 'n' && c != 's' es equivalente a: !( c == 'n' ||
c == 's' ) .
La función lea_char() tiene como misión leer un caracter enviado por el teclado, ( lo
realiza dentro de la expresión relacional del IF ) y salvar la ambigüedad del uso de
mayúsculas ó minúsculas en las respuestas, convirtiendo las primeras en las segundas.
Es facil de ver que, si un caracter esta comprendido entre A y Z, se le suma la diferencia
entre los ASCII de las minúsculas y las mayúsculas ( 97 - 65 = 32 ) para convertirlo, y
luego retornarlo al invocante.
Esta conversión fué incluida a modo de ejemplo solamente, ya que existe una de
Librería, tolower() declarada en ctype.h, que realiza la misma tarea.
Cuando lea_char() devuelva un caracter n ó s, se saldrá del do-while en la función
finalizar() y se retornará al programa principal, el valor de la comparación lógica
entre el contenido de c y el ASCII del caracter n. Si ambos son iguales, el valor
retornado será 1 (CIERTO) y en caso contrario 0 ( FALSO ) .
Mientras el valor retornado al programa principal sea FALSO, este permanecerá dentro de
su while imprimiendo valores sucesivos del contador, y llamadas a las funciones, hasta que
finalmente un retorno de CIERTO ( el operador presionó la tecla n) hace terminar el loop
e imprimir el mensaje de despedida.
Nota: preste atención a que en la función finalizar() se ha usado un do-while .¿Cómo
modificaría el programa para usar un while ?. En la función lea_char se han usado dos
returns, de tal forma que ella sale por uno u otro. De esta manera si luego de finalizado
el else se hubiera agregado otra sentencia, esta jamás sería ejecutada.
En el siguiente ejemplo veremos funciones que retornan datos de tipo distinto al int.
Debemos presentar antes, otra función muy común de entrada de datos: scanf(), que nos
permitirá leer datos completos (no solo caracteres) enviados desde el teclado, su
expresión formal es algo similar a la del printf() ,
scanf("secuencia de control", dirección de la variable ) ;
Donde en la secuencia de control se indicará que tipo de variable se espera leer, por ejemplo :
%d si se desea leer un entero decimal (int) %o " " " " " " octal " %x " " " " " " hexadecimal " %c " " " " " caracter %f leerá un flot %ld leerá un long int %lf leerá un double %Lf leerá un long double
Por "dirección de la variable" deberá entenderse que se debe indicar, en vez
del nombre de la variable en la que se cargará el valor leido, la dirección de su
ubicación en la memoria de la máquina. Esto suena sumamente apabullante, pero por ahora
solo diremos, (más adelante abundaremos en detalles ) que para ello es necesario
simplemente anteponer el signo & al nombre de la misma .
Así, si deseo leer un entero y guardarlo en la variable "valor_leido"
escribiré: scanf("%d",&valor_leido); en cambio si deseara leer un entero y
un valor de punto flotante será: scanf("%d %f", &valor_entero,
&valor_punto_flotante) ;
El tipo de las variables deberá coincidir EXACTAMENTE con los expresados en la secuencia
de control, ya que de no ser así, los resultados son impredecibles.
Por supuesto esta función tiene muchísimas más opciones, ( consulte el Manual de
Librerias de su Compilador, si tiene curiosidad ) sin embargo, por simplicidad, por ahora
nos conformaremos con las antedichas.
El prototipo de scanf() esta declarado en stdio.h .
Usaremos también otra función, ya citada, clrscr(). Recordemos que esta es solo válida
para máquinas tipo PC compatibles y no corre bajo Windows.
Encaremos ahora un programa que nos presente primero, un menú para seleccionar la
conversión de ºC a Fahrenheit ó de centímetros a pulgadas, hecha nuestra elección,
nos pregunte el valor a convertir y posteriormente nos de el resultado .
Si suponemos que las funciones que usaremos en el programa serán frecuentemente usadas,
podemos poner las declaraciones de las mismas, así como todas las contantes que usen, en
un archivo texto, por ejemplo convers.h. Este podrá ser guardado en el subdirectorio
donde están todos los demás (INCLUDE) ó dejado en el directorio activo, en el cual se
compila el programa fuente de nuestro problema. Para variar, supongamos que esto último
es nuestro caso .
CONVERS.H
#include <conio.h> #define FALSO 0 #define CIERTO 1 #define CENT_POR_INCH 25.4
void pausa(void) ; void mostrar_menu(void) ; int seleccion(void) ; void cm_a_pulgadas(void) ; void grados_a_fahrenheit(void) ; double leer_valor(void) ;
Vemos que un Header puede incluir llamadas a otros (en este caso conio.h). Hemos puesto
tambien la definición de todas las constantes que usaran las funciones abajo declaradas.
De dichas declaraciones vemos que usaremos funciones que no retornan nada, otra que
retorna un entero y otra que devuelve un double .
Veamos ahora el desarrollo del programa en sí. Observe que la invocación a conversión.h
se hace con comillas, por haber decidido dejarlo en el directorio activo .
#include <stdio.h> #include "convers.h"
main() { int fin = FALSO;
while (!fin) { mostrar_menu(); switch(seleccion()) { case 1: cm_a_pulgadas(); break; case 2: grados_a_fahrenheit(); break; case 3: fin = CIERTO; break; default: printf("\n¡Error en la Seleccion!\a\a\n"); pausa() ; } } return 0; }
/* Funciones */
void pausa(void) { char c = 0;
printf("\n\n\nAPRIETE ENTER PARA CONTINUAR ") ; while( (c = getch()) != '\r') ; }
void mostrar_menu(void) { clrscr(); printf("\n Menu\n"); printf("---------------------------\n"); printf("1: Centimetros a pulgadas\n"); printf("2: Celsius a Fahrenheit\n"); printf("3: Terminar\n"); }
int seleccion(void) { printf("\nEscriba el número de su Selección: "); return (getche() - '0'); }
void cm_a_pulgadas(void) { double centimetros; /* Guardará el valor pasado por leer_valor() */ double pulgadas ; /* Guardará el valor calculado */
printf("\nEscriba los Centimetros a convertir: "); centimetros = leer_valor(); pulgadas = centimetros * CENT_POR_INCH; printf("%.3f Centimetros = %.3f Pulgadas\n", centimetros, pulgadas); pausa() ; }
void grados_a_fahrenheit(void) { double grados; /* Guardará el valor pasado por leer_valor() */ double fahrenheit ; /* Guardará el valor calculado */
printf("\nEscriba los Grados a convertir: "); grados = leer_valor(); fahrenheit = (((grados * 9.0)/5.0) + 32.0) ; printf("%.3f Grados = %.3f Fahrenheit", grados, fahrenheit); pausa(); }
double leer_valor(void) { double valor; /* Variable para guardar lo leido del teclado */
scanf("%lf", &valor); return valor; }
Veamos que hemos hecho: primero incluimos todas las definiciones presentes en el
archivo convers.h que habiamos previamente creado. Luego main() entra en un loop, que
finalizará cuando la variable fin tome un valor CIERTO, y dentro del cual lo primero que
se hace es llamar a mostrar_menú(), que pone los rótulos de opciones .
Luego se entra en un SWITCH que tiene como variable ,el retorno de la función
selección() (recuerde que tiene que ser un entero), según sea éste se saldrá por
alguno de los tres CASE. Observe que selección() lee el teclado mediante un getche(),
(similar a getch() antes descripta, pero con la diferencia que aquella hace eco del
caracter en la pantalla) y finalmente devuelve la diferencia entre el ASCII del número
escrito menos el ASCII del número cero, es decir, un entero igual numericamente al valor
que el operador quizo introducir .
Si este fue 1, el SWITCH invoca a la función cm_a_pulgadas() y en caso de ser 2 a
grados_a_fahrenheit() .
Estas dos últimas proceden de igual manera: indican que se escriba el dato y pasan el
control a leer_valor(), la que mediante scanf() lo hace, retornando en la variable valor,
un double, que luego es procesado por aquellas convenientemente. Como hasta ahora la
variable fin del programa principal no ha sido tocada, y por lo tanto continua con FALSO
,la iteración del while sigue realizandose, luego que se ejecuta el BREAK de
finalización del CASE en cuestión. En cambio, si la selección() hubiera dado un
resultado de tres, el tercer case, la convierte en CIERTO, con lo que se finaliza el WHILE
y el programa termina.
Vemos en este ejemplo, la posibilidad de múltiples llamados a funciones, una llama a
otra, que a su vez llama a otra, la cual llama a otra, etc ,etc, dando un esquema de flujo
de programa de la forma :
6. AMBITO DE LAS VARIABLES (SCOPE)
VARIABLES GLOBALES
Hasta ahora hemos diferenciado a las variable segun su "tipo" (int, char double,
etc), el cual se refería, en última instancia, a la cantidad de bytes que la
conformaban. Veremos ahora que hay otra diferenciación de las mismas, de acuerdo a la
clase de memoria en la que residen .
Si definimos una variable AFUERA de cualquier función (incluyendo esto a main() ),
estaremos frente a lo denominado VARIABLE GLOBAL. Este tipo de variable será ubicada en
el segmento de datos de la memoria utilizada por el programa, y existirá todo el tiempo
que esté ejecutandose este .
Este tipo de variables son automaticamente inicializadas a CERO cuando el programa
comienza a ejecutarse .
Son accesibles a todas las funciones que esten declaradas en el mismo, por lo que
cualquiera de ellas podrá actuar sobre el valor de las mismas. Por ejemplo :
#include <stdio.h> double una_funcion(void); double variable_global ; main() { double i ; printf("%f", variable_global ); /* se imprimirá 0 */ i = una_funcion() ; printf("%f", i ); /* se imprimirá 1 */ printf("%f", variable_global ); /* se imprimirá 1 */ variable_global += 1 ; printf("%f", variable_global ); /* se imprimirá 2 */ return 0 ; }
double una_funcion(void) { return( variable_global += 1) ; }
Observemos que la variable_global está definida afuera de las funciones del programa,
incluyendo al main(), por lo que le pertenece a TODAS ellas. En el primer printf() del
programa principal se la imprime, demostrandose que está automaticamente inicializada a
cero .
Luego es incrementada por una_funcion() que devuelve ademas una copia de su valor, el cual
es asignado a i ,la que, si es impresa mostrará un valor de uno, pero tambien la
variable_global ha quedado modificada, como lo demuestra la ejecución de la sentencia
siguiente. Luego main() tambien modifica su valor , lo cual es demostrado por el printf()
siguiente.
Esto nos permite deducir que dicha variable es de uso público, sin que haga falta que
ninguna función la declare, para actuar sobre ella.
Las globales son a los demás tipos de variables, lo que el GOTO es a los otros tipos de
sentencias .
Puede resultar muy difícil evaluar su estado en programas algo complejos, con múltiples
llamados condicionales a funciones que las afectan, dando comunmente orígen a errores muy
engorrosos de corregir .
VARIABLES LOCALES
A diferencia de las anteriores, las variables definidas DENTRO de una función, son
denominadas VARIABLES LOCALES a la misma, a veces se las denomina también como
AUTOMATICAS, ya que son creadas y destruídas automaticamente por la llamada y el retorno
de una función, respectivamente .
Estas variables se ubican en la pila dinámica (stack) de memoria ,destinandosele un
espacio en la misma cuando se las define dentro de una función, y borrándose cuando la
misma devuelve el control del programa, a quien la haya invocado.
Este método permite que, aunque se haya definido un gran número de variables en un
programa, estas no ocupen memoria simultaneamente en el tiempo, y solo vayan incrementando
el stack cuando se las necesita, para luego, una vez usadas desaparecer, dejando al stack
en su estado original .
El identificador ó nombre que se la haya dado a una variable es sólo relevante entonces,
para la función que la haya definido, pudiendo existir entonces variables que tengan el
mismo nombre, pero definidas en funciones distintas, sin que haya peligro alguno de
confusión .
La ubicación de estas variables locales, se crea en el momento de correr el programa, por
lo que no poseen una dirección prefijada, esto impide que el compilador las pueda
inicializar previamente. Recuerdese entonces que, si no se las inicializa expresamente en
el momento de su definición, su valor será indeterminado (basura) .
VARIABLES LOCALES ESTATICAS
Las variables locales vistas hasta ahora, nacen y mueren con cada llamada y finalización
de una función, sin embargo muchas veces sería util que mantuvieran su valor, entre una
y otra llamada a la función sin por ello perder su ámbito de existencia, es decir seguir
siendo locales sólo a la función que las defina. En el siguiente ejemplo veremos que
esto se consigue definiendo a la variable con el prefacio static.
VARIABLES DE REGISTRO
Otra posibilidad de almacenamiento de las variables locales es, que en vez de ser
mantenidas en posiciones de la memoria de la computadora, se las guarde en registros
internos del Microprocesador que conforma la CPU de la misma .
De esta manera el acceso a ellas es mucho más directo y rápido, aumentando la velocidad
de ejecución del programa. Se suelen usar registros para almacenar a los contadores de
los FOR, WHILE, etc.
Lamentablemente, en este caso no se puede imponer al compilador, este tipo de variable, ya
que no tenemos control sobre los registros libres en un momento dado del programa, por lo
tanto se SUGIERE, que de ser posible, ubique la variable del modo descripto. El prefacio
en éste caso será :
register int var_reg ;
Hay que recalcar que esto es sólo válido para variables LOCALES, siendo imposible definir en un registro a una global. Por otra parte las variables de registro no son accesibles por dirección, como se verá más adelante .
VARIABLES EXTERNAS
Al DEFINIR una variable, como lo hemos estado haciendo hasta ahora, indicamos al
compilador que reserve para la misma una determinada cantidad de memoria, (sea en el
segmento de memoria de datos, si es global ó en el stack, si es local), pero debido a que
en C es normal la compilación por separado de pequeños módulos, que componen el
programa completo, puede darse el caso que una función escrita en un archivo dado, deba
usar una variable global definida en otro archivo. Bastará para poder hacerlo, que se la
DECLARE especificando que es EXTERNA a dicho módulo, lo que implica que está definida en
otro lado .
Supongamos que nuestro programa está compuesto por sólo dos módulos: mod_prin.c y
mod_sec.c los cuales se compilarán y enlazarán juntos, por medio del compilador y el
linker, por ejemplo corriendo: bcc mod_prin.c mod_sec.c si usaramos el compilador de
Borland .
Si en el primer módulo (mod_prin.c) aparece una variable global, definida como
double var1 = 5 ;
El segundo módulo, ubicado en un archivo distinto de aquel, podrá referenciarla mediante la declaración de la misma :
extern double var1 ;
Notesé que la inialización de la variable sólo puede realizarse en su DEFINICION y no en la declaración. Esta última, no reserva memoria para la variable sino que sólo hace mención que la misma ha sido definida en otro lado .
Será finalmente el Linker el que resuelva los problemas de direccionamiento de la variable al encadenar los dos módulos compilados .
7. ARGUMENTOS Y PARAMETROS DE LAS FUNCIONES
Supongamos que en un determinado programa debemos calcular repetidamente el valor medio de
dos variables, una solución razonable sería crear una función que realice dicho
cálculo, y llamarla cada vez que se necesite. Para ello será necesario, en cada llamada,
pasarle los valores de las variables para que calcule su valor medio. Esto se define en la
declaración de la funcion especificando, no solo su valor de retorno sino también el
tipo de argumentos que recibe :
double valor_medio(double x, double y) ;
de esta declaración vemos que la función valor_medio recibe dos argumentos ( x e y )
del tipo double y devuelve un resultado de ese mismo tipo .
Cuando definamos a la función en sí, deberemos incluir parámetros para que alberguen
los valores recibidos, así escribiremos:
double valor_medio(double x, double y ) { return ( (x + y) / 2.0 ) }
NOTA: No es necesario que los NOMBRES de los párametros coincidan con los
declarados previamente, es decir que hubiera sido equivalente escribir: double
valor_medio(double a, double b) etc, sin embargo es una buena costumbre mantenerlos igual.
En realidad en la declaración de la función, no es necesario incluir el nombre de los
parámetros, bastaría con poner solo el tipo, sin embargo es práctica generalizada,
explicitarlos a fin de hacer más legible al programa .
Aquí estamos utilizando la síntaxis moderna del lenguaje C, pudiendose encontrar en
versiones arcaicas, definiciones equivalentes como :
double valor_medio() ó double valor_medio(double, double) double x; double x ; double y; double y ; { { ............ ..............
Sin embargo es preferible utilizar la nomenclatura moderna, ya que esta facilita la
rápida comprensión del programa .
Veamos un ejemplo, para determinar el comportamiento de los parámetros, Supongamos desear
un programa que calcule el valor medio de dos variables incrementadas en un valor fijo, es
decir:
( ( x + incremento ) + ( y + incremento ) ) / 2.0
Lo podríamos resolver de la siguiente forma :
#include <stdio.h> /* Declaración de la función y el tipo de sus parámetros */ double valor_medio(double p_valor, double s_valor, double inc) ;
main() { double x, y, z, resultado ;
printf("Ingrese el primer valor: ") ; scanf("%lf", &x ) ;
printf("\nIngrese el segundo valor: "); scanf("%lf", &y ) ;
printf("\nIngrese el incremento : "); scanf("%lf", &z) ;
resultado = valor_medio( x, y, z ); /* llamada a la función y pasaje de argumentos */
printf("\n\nResultado de la operación: %lf", resultado) ;
printf("\n\nValor con que quedaron las variables: ") ; printf("\n Primer valor : %lf ", x ) ; printf("\n Segundo valor: %lf ", y ) ; printf("\n Incremento : %lf ", z ) ;
}
/* Definición de la función y sus parámetros */
double valor_medio( double p_valor, double s_valor, double inc ) { p_valor += inc ; s_valor += inc ;
return ( (p_valor + s_valor ) / 2.0 ) ;
}
Veamos primero cual seria la salida de pantalla de este programa :
SALIDA DEL EJEMPLO
Ingrese el primer valor: [SUPONGAMOS ESCRIBIR: 10.0] Ingrese el segundo valor: [ " " : 8.0] Ingrese el incremento : [ " " : 2.0]
Resultado de la operación: 11.000000
Valor con que quedaron las variables: Primer valor : 10.000000 Segundo valor: 8.000000 Incremento : 2.000000
Vemos que luego de obtenidos, mediante scanf(), los tres datos x, y, z, los mismos son
pasados a la función de calculo en la sentencia de asignación de la variable resultado.
La función inicializa sus parámetros ( p_valor, s_valor e inc ) con los valores de los
argumentos enviados ( x, y, z ) y luego los procesa. La unica diferencia entre un
argumento y una variable local, es que ésta no es inicializada automaticamente, mientras
que aquellos lo són, a los valores de los argumentos colocados en la expresión de
llamada.
Acá debemos remarcar un importante concepto: éste pasaje de datos a las funciones, se
realiza COPIANDO el valor de las variables en el stack y No pasandoles las variables en
sí. Esto se denomina: PASAJE POR VALOR y garantiza que dichas variables no sean afectadas
de ninguna manera por la función invocada. Una clara prueba de ello es que, en la
función valor_medio() se incrementa p_valor y s_valor, sumandoseles el contenido del
parámetro inc. Sin embargo cuando, luego de retornar al programa principal, imprimimos
las variables cuyos valores fueron enviados como parametros, vemos que conservan sus
valores iniciales. Veremos más adelante que otras estructuras de datos pueden ser pasadas
a las funciones por direcciones en vez de por valor, pudiendo aquellas modificarlas a
gusto .
Debe aclararse que, el pasaje de argumentos, es también una OPERACION, por lo que las
variables pasadas quedan afectadas por las reglas de Conversión Automática de Tipo,
vistas en el Capítulo 2. Como ejemplo, si x hubiera sido definida en la función main()
como int, al ser pasada como argumento a valor_medio() sería promovida a double. Especial
cuidado debe tenerse entonces con los errores que pueden producirse por redondeo ó
truncamiento, siendo una buena técnica de programación hacer coincidir los tipos de los
argumentos con los de los parámetros.