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.
CAPITULO ANTERIOR DEL CURSO | |
PROXIMO CAPITULO DEL CURSO |