CAPITULO 5: FUNCIONES

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
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
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 .
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
#include
#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
#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
#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
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
/* 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.


© Derechos Reservados, Copyright, DATA-2013, 1998-2020.
|