Pages - Menu

miércoles, 15 de mayo de 2013

Curso básico de C 7/10: Procedimientos, funciones y paso de parámetros

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.


Procedimientos y funciones
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.

¿Cómo ves de positivo el uso de procedimientos y funciones? ¿Es recomendable abusar de ellos?