Chunche95

La forma más razonable de encarar el desarrollo de un programa compilado 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 dubdivide estas en otras varias tareas concurrentes, por ejemplo:
Leer un teclado, procesar datos, mostrar los resultados.
Luego a estas se les 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' o 'imprimir un carácter'. Luego sólo resta abocarse a resolver cada una de ellas por separado.
De esta forma el programador sólo se tendra que ver con ciminutas 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 son las funciones. Todo compilador comercial trae una gran cantidad de librerias de toda índole, matemáticas, de entrada-salida, de manejo de textos, de manejo de gráficos, etc. que solucionan ma layor parte de los problemas básicos de programación.
Sin emabrgo, será inevitable que en algúm moemnto tenga que crear mis propias funciones, las reglas para ello son las que desarrollamos en este capítulo.
Convencionalmente en C los nombres de las funciones se escriben en minúsculas 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 o 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 varaibles 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 dirección en memoria de dicha variable. Las funciones pueden o 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 afectan cálsulos, 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.

Declaración 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 retorna al programa invocante.
Estas informaciones están contenidas en la declaración de prototipo de la función. Formalmente dicha declaración queda dada por: tipo del valor de retorno nombre_de_la_funcion(lista de tipos de parámetros)
Ejemplo:

										
float miFuncion(int i, double j);
double otraFuncion(void);
otraFuncionMas(long p);
void ultimaFuncion(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 archivos *.h que tienen los prototipos de las funciones de Librería.
Si una o 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.

Definición 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 prototiopo, y no pede estar dentro de la definición de otra función (include main()). Es decir, que a diferencia de Pacal, en C las definiciones ni pueden anidarse.
Nota. No debe confundirse 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 miFuncion(int i, double j); /* Declaración, termianda en ';' */
main(){
	float k;
	int p;
	double z;
	... 
	k= miFuncion(p,z); /* Llamada a la funcion */
	... 
} /* Fin de la funcion main() */

float miFuncion(int i, double j) /* Definición, sin ';' */
{
	float n;
	... 
	printf("%d",i); 
	... 
	return(2*n);
}
											
										
Pasamos ahora a describir más profundamente las distintas modalidades que adoptan las funciones.

Funciones que no retornan valor ni reciben parámetros.

Veamos como ejemplo la implementación de una función "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 al salir del while: %d", contador);
	return 0;
}

void pausa(void){
	char c;
	printf("\n Pulse enter para continuar. \n");
	while((c=getchar()!='\n'));
}
										
									
En cada llamada, el programa transfiere el comando a la función, ejecutandose, hasta que esta 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 practica de programación nombrarlas con términos que represente su operación. Se puede salir prematuramente de una función void mediante el uso de return, sin que este sea seguido de ningun parámetro o valor.

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 leer_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 \n Fin del programa. \n");
	return 0;
}

int finalizar(void){
	int c;
	printf("¿Otro número?(S/N)");
	do{
		c=leer_char();
	}while((c!='N') && (c!='S'));
	return (c=='N');
}

int leer_char(void){
	int j;
	if((j=getch()) >>='A' && j<<='Z'){
		return(j+('a'-'A'));
	}else{
		return j;
	}
}
										
									
Nota:Preste atención a que en la función finalizar() se ha usado un do-while. En la función leer_char se ha 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 distindo al int. Debemos presentar antes, otra dunción muy común de entrada de datos: scanf(), que nos permitirá leer datos completos enviados desde el teclado, su expresión formal es algo similar a la del printf().
											
scanf("secuencia de control", direccion de la variable);
											
										
Donde la secuencia de control se indicará que tipo de variable se espera leer, por ejemplo:
  • %d - Leer entero decimal (int).
  • %o - Leer octal.
  • %x - Leer hexadecimal.
  • %c - Leer caracter.
  • %f - Leer float
  • %ld - Leer long int.
  • %lf - Leer long float.
  • %Lf - Leer un long double.
Encaremos ahora un programa que nos presente primero, un menú para seleccionar la conversión de ºC a ºF o 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 constantes que use, en un archivo de texto, por ejemplo convers.h. Este podrá ser guardado en el subdirectorio donde están todos los demás include o 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.
											
#include <stdio.h>
#include <conio.h>

#define FALSO 0
#define CIERTO 1
#define CENT_POR_INCH 25.4

void pausa(void);
void mostrarMenu(void);
int seleccion(void);
void cmApulgadas(void);
void centAfahr(void);
double leerValor(void);
											
										
Vemos que un header puede incluir llamadas a otros (en este caso conio.h). Hemos puesto también 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 conversion.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){
		mostrarMenu();
		switch(seleccion()){
			case 1:
				cmApulgadas();
				break;
			case 2:
				centAfahr();
				break;
			case 3: 
				fin=CIERTO;
				break;
			default:
				printf("\n Error, la opción seleccionada no es válida. \a\a\n");
				pausa();
		}
	}
	return 0;
}

/* Funciones */

void pausa(void){
	char c=0;
	printf("\n\n Pulse ENTER para continuar. \n");
	while((c=getch() != '\r'));
}

void mostrarMenu(void){
	clrscr();
	printf("\n Menú.  \n");
	printf(" 1. Centímetros a pulgadas. \n");
	printf(" 2. Celsius a Farhenheit. \n");
	printf(" 3. Salir. \n");
}

int seleccion(void){
	printf("\n Seleccione una de las opciones del menú: ");
	return(getch()-'0');
}

void cmApulgadas(void){
	double centimetros; /* Guarda el valor pasado por leerValor() */
	double pulgadas;    /* Guarda el valor calculado */
	printf("\n Escriba los centímetros a convertir: ");
	centimetros=leerValor();
	pulgadas=centimetros*CENT_POR_INCH;
	printf("%3f Centimetros= %3f pulgadas. \n",centimetros,pulgadas);
	pausa();
}

void centAfahr(void){
	double centigrados; /* Guardará el valor pasado por leerValor() */
	double Farhenheit;  /* Guardará el valor calculado. */

	printf("\n Escriba los grados Centigrados a convertir: ");
	centigrados=leerValor();
	Farhenheit=(((centigrados*9.0)/5.0)+32);
	printf("%3f grados centigrados = $3f Farhenheit", centigrados, Farhenheit);
	pausa();
}

double leerValor(void){
	double valor; /* Variable para guardar lo leido del teclado */
	scanf("%1f", &valor);
	return valor;
}
										
									

Ambito de las variables (Scope).

Variables globales.

Hasta ahora hemos diferenciado a las variables según 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 automáticamente iniciazadas a cero cuando el programa comienza a ejecutarse.
Son accesibles a todas las funciones que estan 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) ; 
} 
											
										

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 les llama también como automaticas, ya que son creadas y destruídas automáticamente por la llamada y el retorno de una función, respectivamente.
Estas variables se ubican en la pila dinámica (stack) de memoria, destinandose 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 quein 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 les necesita, para luego, una vex usadas desaparecer, dejando al stack en su estado original.
El identificador o nombre que se le haya dado a una variable es solo 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 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 les inicializa expresamente en el momento de su definición, su valor será indeterminado.

Variables locales estáticas.

Las variables locales vistas hasta ahora, ancen y mueren con cada llamada y finalización de una función, sin embargo, muchas veces sería útil 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 guarden 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 variables, ya que no tenemos control sobre los registros libres en un momento dado del programa, por lo tanto se sugiere, que de ser posiblem ubique la variable del modo descrito. El prefacio en éste caso será: register int var_reg;
Hay que racalcar que esto es solo 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.

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 gloabal o 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, piede 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 ldeclare especificado que es externa a dicho moçódulo, lo que implica que está definida en otro lado. Supongamos que nuestro programa está compuesto por sólo dos módulos: mod_principal.c y mod_secundario.c los cuales se compilarán y enlazarán juntos, por medio del compilador y el linker.

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 función especificando, no solo su valor de retorno sino también el tipo de argumentos que recibe:
souble 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 parámetros coincidan don los declarados previamente, es decir, que hubiera sido equivalente escribir double valor_medio(double a, double b)etc, sin embargo, es una buena práctica mantenerlos igual. En realidad en la declaración de la función, no es necesario incluir el nombre de los parámetros a fin de hacer más legible el programa.
Aquí estamos utilizando la sintaxis moderna del lenguaje C, pudiendose encontrar en versiones arcaicas, definiciones equivalentes como:

double valor_medio()  o  double valor_medio(double, double) 
double x;   double x; 
double y;   double y; 
{           {
...         ... 	
}           }