Conforme seguimos desarrollando programas nos vamos dando cuenta de que el
tamaño de éstos va aumentando considerablemente, en cuanto realizamos una
aplicación que haga tres o cuatro operaciones distintas tenemos un código
fuente de un tamaño considerable. Además que a medida que vamos creando
aplicaciones tenemos que repetir muchas veces determinados trozos de código.
Foto tomada de freedigitalphotos.net |
En programación esto se gestiona mediante procedimientos y funciones,
muchos de ellos los importamos en librerías (como la stdio.h) y otros podemos
realizarlos nosotros mismos.
Procedimiento
Un procedimiento es un fragmento de código cuya función es la de realizar
una tarea específica independientemente del programa en el que se encuentre.
Con los procedimientos se pueden crear algoritmos de ordenación de arrays, de
modificación de datos, cálculos paralelos a la aplicación, activación de
servicios, etc. Prácticamente cualquier cosa puede realizarse desde procedimientos
independientes.
Función
Una función es exactamente lo mismo que un procedimiento salvo por un
detalle, una función puede devolver un valor al programa principal y un
procedimiento no. Con las funciones podemos realizar todas las tareas que se
hacen con los procedimientos pero además pudiendo devolver valores (como por
ejemplo el área de un triángulo).
Estructura y uso
Como se puede ver los conceptos de procedimiento y función son bastante
similares, incluso podríamos definir un procedimiento como una función que no
retorna ningún valor. Ambos poseen la misma estructura:
Tipo_Dato_Retorno Nombre (Parámetros){
sentencias;
retorno; (sólo en el caso
de las funciones)
}
Para un procedimiento el Tipo_Dato_Retorno es siempre void, o lo que es lo
mismo, nada. En el caso de las funciones se puede poner cualquier tipo de dato,
int, float, char, etc.
Ahora vamos a plantear un algoritmo que calcule el área de un triángulo,
veamos como se podría hacer mediante un procedimiento y una función:
Procedimiento:
#include <stdio.h>
#include <stdlib.h>
void areatriangulo (void) {
float base, altura;
printf("Introduce base:
");
scanf("%f",&base);
printf("Introduce altura:
");
scanf("%f",&altura);
printf("El área es: %2.2f
\n",(base*altura)/2);
}
int main(int argc, char *argv[])
{
areatriangulo();
system("PAUSE");
return 0;
}
Función:
#include <stdio.h>
#include <stdlib.h>
float areatriangulo (void) {
float base, altura;
printf("Introduce base:
");
scanf("%f",&base);
printf("Introduce altura:
");
scanf("%f",&altura);
return (base*altura)/2;
}
int main(int argc, char *argv[])
{
float area;
area=areatriangulo();
printf("El área es:
%2.2f \n",area);
system("PAUSE");
return 0;
}
Recordemos, un procedimiento siempre se declarará con valor de retorno void
y una función se declarará con cualquier tipo de dato como valor de retorno y
en su código se añadirá la sentencia return y el valor que se desea devolver al
programa que lo llama.
Es muy recomendable definir todos los procedimientos y funciones antes de
la función principal main, el motivo de esto es que en C todas las funciones y
procedimientos deben definirse antes de la primera línea de código en que se
usa. En caso de que incumplamos ésta norma a la hora de compilar tendremos
errores.
Para llamar a un procedimiento o función debemos hacerlo de la siguiente
forma:
En un procedimiento escribimos su nombre seguido de los parámetros entre
paréntesis:
areatriangulo();
En una función debemos declarar antes una variable que almacene el dato que
la función va a devolver, la variable debe ser del mismo tipo que el que
retorna la función. Seguidamente llamamos a la función escribiendo la variable
seguido del operador de asignación, el nombre de la función y los parámetros
entre paréntesis:
float area;
area=areatriangulo();
Implementar funciones y procedimientos en nuestros programas es una muy
buena práctica que se debe hacer siempre que se pueda, ayuda a mantener
organizado el código y nos permite reutilizar funcionalidades en otras
secciones del programa.
Parámetros.
A la hora de crear funciones y procedimientos puede que sea necesario que
trabajemos con datos que se encuentran alojados en el programa que los llama,
para eso existen los parámetros. Los parámetros son valores que se mandan a un
procedimiento o función para que éstos puedan trabajar con ellos.
Para utilizar los parámetros en un procedimiento o función debemos declarar
el tipo de dato y su identificador, exactamente igual que si declaramos una
variable.
Veamos el programa del área del triángulo pasando por parámetros los datos
de base y altura:
#include <stdio.h>
#include <stdlib.h>
void areatriangulo (float base,
float altura) {
printf("El área
es: %2.2f \n",(base*altura)/2);
}
int main(int argc, char *argv[])
{
float base, altura;
printf("Introduce base:
");
scanf("%f",&base);
printf("Introduce altura:
");
scanf("%f",&altura);
areatriangulo(base,altura);
system("PAUSE");
return 0;
}
Es muy importante que las variables se pasen
en el mismo orden que como se han definido en los parámetros. En caso de que no
sea así podemos tener resultados inesperados en la ejecución del programa.
Recuerda que los identificadores son palabras en lenguaje humano legible
mediante el cual manejamos celdas de memoria, da igual como se llamen en el
programa, en las funciones y en los procedimientos.
Paso de parámetros por valor y por
referencia.
El paso de parámetros a procedimientos y
funciones es un recurso bastante potente de cara a trabajar con datos, además
tenemos la posibilidad de pasar estos parámetros de varias formas, por valor o
por referencia, esto lo especificamos cuando definimos el procedimiento o
función.
El paso por valor es el que acabamos de ver,
mediante el paso por valor el procedimiento o función reciben los valores
indicados para poder trabajar con ellos, su declaración es la siguiente:
Procedimiento - void areatriangulo (float
base, float altura)
Función - float areatriangulo (float base,
float altura)
A veces necesitaremos modificar varios de
estos datos que hemos pasado en los parámetros, para ello es posible declarar
los parámetros de tal forma que se puedan modificar, a esto se le llama paso
por referencia.
Gracias al paso por referencia un procedimiento o función no sólo realiza
unas operaciones y devuelve (en el caso de las funciones) un valor, sino que
también puede modificar los valores que se pasan como parámetros.
Para poder realizar el paso de parámetros por referencia es necesario que
en la declaración del procedimiento o función se especifique el espacio de
memoria donde se encuentra alojado el dato, esto se realiza así:
Procedimiento - void areatriangulo (float
*base, float *altura)
Función - float areatriangulo (float *base, float *altura)
Lo que acabamos de definir como parámetros no son las variables en sí, sino
un puntero que apunta a la dirección de memoria donde se encuentra alojado el
valor, así podremos modificarlo.
Para llamar a un procedimiento o función pasando los parámetros por valor
se hace de la forma que hemos visto:
Procedimiento - areatriangulo(base, altura);
Función - area=areatriangulo(base, altura);
Para llamar a un procedimiento o función pasando los parámetros por
referencia se hace de la siguiente forma:
Procedimiento - areatriangulo(&base, &altura);
Función - area=areatriangulo(&base, &altura);
Así lo que pasamos como parámetros no es el valor de la variable, sino la
dirección de memoria donde se encuentra el dato, de ésta forma el procedimiento
o función podrá modificar su valor directamente.
Veamos el caso de un programa que intercambia los valores de dos variables,
en uno utilizaremos parámetros por valor y en el otro por referencia:
Por valor:
#include <stdio.h>
#include <stdlib.h>
void intercambio(int num1, int
num2){
int aux;
aux=num1;
num1=num2;
num2=aux;
printf("El intercambio es:
num1=%d num2=%d\n",num1,num2);
}
int main(int argc, char *argv[])
{
int num1=3, num2=5;
printf("Vamos a
intercambiar: num1=%d num2=%d\n",num1,num2);
intercambio(num1,num2);
printf("El resultado tras el
intercambio es: num1=%d num2=%d\n",num1,num2);
system("PAUSE");
return 0;
}
Por referencia:
#include <stdio.h>
#include <stdlib.h>
void intercambio(int *num1, int
*num2){
int aux;
aux=*num1;
*num1=*num2;
*num2=aux;
printf("El intercambio es:
num1=%d num2=%d\n",*num1,*num2);
}
int main(int argc, char *argv[])
{
int num1=3, num2=5;
printf("Vamos a
intercambiar: num1=%d num2=%d\n",num1,num2);
intercambio(&num1,&num2);
printf("El resultado tras el
intercambio es: num1=%d num2=%d\n",num1,num2);
system("PAUSE");
return 0;
}
Compila ambos código y observa la diferencia, es en ésta característica
donde radica el potencial del paso por valor y por referencia.
Variables locales y globales.
Gracias al concepto de procedimiento y función podemos tener dentro de un
mismo programa varios subprogramas, los cuales se irán llamando entre ellos
para poder realizar las tareas pertinentes, esto está muy bien pero es posible
que necesitemos realizar tareas con variables alojadas en otros sitios, o
necesitemos declarar nuevas, o simplemente no deseemos usar parámetros.
Para solucionar esto existe el concepto de variables locales y variables
globales.
Las variables locales son aquellas que declaramos al principio del cuerpo
de un procedimiento o función, esto incluye a las declaradas en la función
principal main.
Las variables globales son aquellas que las podemos definir en la zona de
importación de librerías #include y la
definición de constantes con #define. Su utilidad radica en poder operar con su
valor en cualquier procedimiento o función sin necesidad de pasarla como
parámetro.
Veamos el ejemplo del triángulo usando variables globales:
#include <stdio.h>
#include <stdlib.h>
float base, altura;
void areatriangulo (void) {
printf("El área
es: %2.2f \n",(base*altura)/2);
}
int main(int argc, char *argv[])
{
printf("Introduce base: ");
scanf("%f",&base);
printf("Introduce
altura: ");
scanf("%f",&altura);
areatriangulo();
system("PAUSE");
return 0;
}
Como se puede comprobar, el programa funciona exactamente igual que en su
versión con parámetros, no obstante muchos desarrolladores no recomiendan hacer
uso de ésta técnica debido a que en proyectos medios o grandes puede resultar
confuso manejar variables globales sin un determinado estándar que regule su
uso.
El modo más común de operar es mediante el uso de parámetros así que
durante el curso y posteriormente seguiremos con esa técnica, no obstante si
puede interesarte practicar los ejercicios que hemos visto hasta ahora usando
variables globales.
Recursividad.
El último punto de éste capítulo se lo voy a dedicar a la recursividad, una
técnica bastante común e importante para desarrollar determinados algoritmos.
La recursividad no es más que la técnica de desarrollar una función que se
llama a sí misma.
La recursividad normalmente la veremos en funciones que realicen cálculos
matemáticos, donde para realizar un cálculo repetitivo la función tendrá que
llamarse a sí misma y así ir desarrollando el cálculo.
Una cosa importante que hay que tener en cuenta es que para realizar
funciones recursivas debemos tener muy claro que queremos hacer, planificarlo
bien y no complicarnos si no es necesario, para ello podemos hacer uso de todos
los elementos que conocemos, parámetros por referencia, funciones, bucles,
variables que indiquen el fin del ciclo, etc.
El ejemplo más típico que se suele utilizar para explicar la recursividad
es el de un programa que calcula el factorial de un número, el factorial de un
número responde a la siguiente fórmula matemática:
n! = n * (n-1)!
Es decir, que el factorial de un número es la multiplicación de ese número
por el factorial del número inmediatamente anterior. Así si queremos saber el
factorial de 5 sería:
5! = 5 * 4!
O lo que es lo mismo:
5! = 5 * 4 * 3 * 2 * 1
Veamos esto primero mediante un programa normal usando un bucle while:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int num, num2;
printf("Introduce
número: ");
scanf("%d",&num);
num2=num;
while (num2!=1){
num2=num2-1;
num=num*num2;
}
printf("El factorial es: %d
\n", num);
system("PAUSE");
return 0;
}
Ahora veámoslo mediante el uso de una función recursiva:
#include <stdio.h>
#include <stdlib.h>
int factorial(int num){
if (num==0)
{
return 1;
}
else
{
return num * factorial(num-1);
}
}
int main(void)
{
int num;
printf("Introduce número: ");
scanf("%d",&num);
printf("El factorial es: %d
\n", factorial(num));
system("PAUSE");
return 0;
}
Como podéis observar el resultado es el mismo pero el código ha variado
bastante, la función recursiva ha cumplido perfectamente su cometido, no
obstante si ves que te resulta un concepto demasiado abstracto y prefieres
utilizar los bucles adelante, tu eres el programador y eres completamente libre
de dar la solución que desees.
Conclusiones
Con el tema de la recursividad ponemos punto y final al séptimo capítulo
del curso básico de C, un capítulo bastante interesante y que nos ofrece un
abanico de posibilidades bastante amplio de cara a comenzar con desarrollos más
grandes.
El haber visto el concepto de procedimientos y funciones nos facilitará
enormemente la tarea de organizar el código, esto apoyado en el paso de
parámetros y, opcionalmente, las variables globales, nos asegura unas
posibilidades casi ilimitadas.
En el siguiente capítulo entraré en un tema también bastante importante,
los punteros a memoria, iremos profundizando en éste concepto de cara a
aplicarlo a nuestros programas, comenzaremos a ver las listas dinámicas y así
poder almacenar información de forma masiva sin necesidad de limitarnos a un
tamaño concreto (como pasa en los arrays). Si me es posible comenzaré con las
listas y algunas de las operaciones básicas que se pueden hacer con ellas.