Guión 3: Funciones


Definición: Unidad de código diseñada para llevar a cabo una tarea determinada.

Una función consta de dos partes:
- Encabezamiento: Es la interfaz pública de la función y, por tanto, constituye la parte visible y accesible de ésta. Consiste en su tipo y su nombre o identificador, además del tipo y el identificador de cada uno de sus parámetros, si existen.
- Cuerpo: Es el código de la función y está encerrado entre llaves. Tanto las variables declaradas dentro del cuerpo, denominadas variables locales, como las sentencias ejecutables pertenecientes a dicho cuerpo, son privadas y desconocidas para el resto de las funciones.
El tipo de una función coincide con el valor devuelto por ésta. El tipo por omisión es entero (int).

En el lenguaje C todas las funciones deben poseer un valor de retorno, excepto aquéllas cuyo tipo es void.

Una función puede considerarse como una caja negra, definida exclusivamente por su nombre (identificador), la información suministrada (parámetros de entrada) y el producto recibido (resultado de salida).
Ejemplo: printf()
Ejemplo 3.1. Comprobar que la función printf() devuelve como resultado el número de caracteres impresos en la pantalla.
#include <stdio.h>

int main() {

  int cont = printf("... y le dio un soponcio a Pilatos.\n\n");

  printf("Caracteres impresos: %d.\n\n", cont);

  system("pause");
  return 0;
}
Parámetros formales: Variables que se colocan en la declaración de la función, entre los paréntesis, separados por comas y precedidos por su tipo, como cualquier variable.

Parámetros reales o efectivos: Valores particulares que se asignan a los parámetros formales durante la llamada a una función concreta. Un parámetro real puede ser una constante, una variable o una expresión válida en C.

Devolución de valores: Una función devuelve un resultado (valor de retorno) a la función que la invocó mediante el uso de la sentencia return. El tipo del valor de retorno es el tipo de la función, y se antepone a su nombre o identificador.

Ejemplo 3.2. Realizar un programa que muestre por pantalla el máximo de dos enteros.

#include <stdio.h>

int max(int, int); /* encabezamiento o prototipo */

int main() {

  int x = 4, y = 10;
  int z;

  z = max(x, y); /* parámetros reales: "x" e "y" */

  printf("El mayor de %d y %d es %d.\n", x, y, z);

  system("pause");
  return 0;
}

int max(int a, int b) { /* parámetros formales: "a" y "b" */

  int x; /* variable local */

  if (a >= b)
     x = a;
  else
     x = b;

  return x; /* resultado de la función: valor de "x" */
}

Ejercicio: Realizar un programa que solicite un entero por teclado y muestre por pantalla su valor absoluto. Una función auxiliar, denominada abs(), aceptará como parámetro un entero y devolverá como resultado el valor absoluto del mismo.
Nota: El valor absoluto de un entero positivo coincide con el valor de dicho entero, y el valor absoluto de un entero negativo es el valor del entero cambiado de signo.

La sentencia return provoca que el flujo de ejecución abandone la función en el lugar donde se encuentra dicha sentencia y, por tanto, el código restante de la función es ignorado y no tiene efectos de cara a la ejecución del programa. En este caso, el compilador puede mostrar un mensaje de aviso de código inaccesible.

Ejemplo 3.3. Comprobar cómo en el siguiente ejemplo la aparición de la sentencia return provoca que no se ejecuten las restantes sentencias de la función auxiliar.

#include <stdio.h>

int max(int, int); /* encabezamiento o prototipo */

int main() {

  int x = 4, y = 10;
  int z;

  z = max(x, y); /* parámetros reales: "x" e "y" */

  printf("El mayor de %d y %d es %d.\n", x, y, z); 

  system("pause");
  return 0;
}

int max(int a, int b) { /* parámetros formales: "a" y "b" */

  int x; /* variable local */

  if (a >= b)
     x = a;
  else
     x = b;

  return x; /* resultado de la función: valor de "x" */

  puts("Esta sentencia NUNCA se ejecuta.");
  puts("Y ésta tampoco.");
}


Paso de parámetros.

Paso de parámetros por valor: Cuando se realiza la llamada a la función, los valores de los parámetros reales se copian en los parámetros formales (parámetros de entrada).

Paso de parámetros por referencia: Cuando se realiza la llamada a la función, los valores de los parámetros reales se modifican dentro de la función y permancen alterados al abandonarla (parámetros de salida y de entrada/salida). En este caso, los parámetros reales deben ser exclusivamente variables.

En el lenguaje C el paso de parámetros por referencia se realiza de manera indirecta: La función llamadora transfiere a la función llamada las direcciones de los parámetros reales. La función llamada accede a dichas direcciones y, mediante un acceso indirecto, modifica los valores de los parámetros.
- Operador de dirección (&): Indica la dirección en que se ha almacenado la variable afectada por dicho operador.
- Operador de indirección (*): Accede al valor que se encuentra en una dirección.
Una variable de tipo puntero almacena direcciones de otras variables.
tipo *variable;
Ejemplo 3.4. Escribir un programa que, a través de un puntero, acceda de manera indirecta al valor de una variable entera y muestre por pantalla dicho valor.

#include <stdio.h>

int main() {

  int entero = 4;
  int *puntent; /* puntero a entero */
 
  puntent = &entero; /* "puntent" apunta a "entero" */
 
  printf("El entero apuntado por \"puntent\" es %d.\n", *puntent);

  /* "*puntent": acceso indirecto al valor de "entero" */

  system("pause");

  return 0;

}

Ejemplo 3.5. Ejecutar el siguiente programa y comprobar que, tras la llamada a la función intercambia(), los valores de los parámetros reales (las variables x e y) no han sido modificados.

#include <stdio.h>

void intercambia(int, int); /* encabezamiento o prototipo */

int main() {

  int x = 5, y = 10;

  printf("Antes: x = %d e y = %d.\n\n", x, y);
  intercambia(x, y); /* paso por valor */
 
printf("Ahora: x = %d e y = %d.\n\n", x, y);

  system("pause");
  return 0;
}

void intercambia(int a, int b) {

  
int temp = a;
  a = b;
  b = temp;
}

Ejemplo 3.6. Ejecutar el siguiente programa y comprobar que, tras la llamada a la función intercambia(), los valores de los parámetros reales (las variables x e y) ahora sí resultan modificados por dicha función. En este caso, la función intercambia() no se limita a recibir los valores de x e y, sino que ahora recibe las direcciones de estas variables. Por tanto, la función auxiliar conoce en qué posición de memoria se encuentra cada una y, a través de sus direcciones, puede acceder a dichas variables indirectamente para consultar sus valores y modificarlos.

#include <stdio.h>

void intercambia(int *, int *); /* encabezamiento o prototipo */

int main() {

  int x = 5, y = 10;

  printf("Antes: x = %d e y = %d.\n\n", x, y);
  intercambia(&x, &y); /* paso por referencia */
 
printf("Ahora: x = %d e y = %d.\n\n", x, y);

  system("pause");
  return 0;
}

void intercambia(int *a, int *b) {

  int temp = *a;
  *a = *b;
  *b = temp;
}
Ejercicio: Modificar el Ejemplo 3.2 para que la función max() acepte como parámetros de entrada los dos enteros cuyo máximo debe determinar y además, acepte un parámetro de salida. De este modo, una vez que la función auxiliar haya finalizado su ejecución, la función principal tendrá el resultado en la variable que ha funcionado como tercer parámetro real. En este caso, la función max() debe declararse de tipo void, puesto que ahora carece de valor de retorno.


Uso de prototipos.

 En el lenguaje C las funciones se suponen de tipo int por omisión. Si una función no es de tipo int, además de indicarse en su encabezamiento, debe realizarse una declaración por adelantado (forward) de la misma.

Las declaraciones por adelantado, prototipos o encabezamientos de las funciones deben colocarse al principio del programa.

Ejemplo 3.7. Comprobar que el siguiente programa no funciona si se suprime la declaración por adelantado. En tal caso, la primera referencia a la función max() aparecería en la función principal: z = max(x, y); Entonces, el compilador registrará en sus estructuras de datos internas el nombre de la función y el tipo de los parámetros, pero como en ese momento no dispone de más información, asociará a dicha función el tipo por omisión (int). Posteriormente, cuando el compilador analice el encabezamiento de max() se producirá un conflicto de tipos, puesto que el compilador suponía que el tipo de la función era int y, sin embargo, en su encabezamiento se ha declarado como double. Este error puede corregirse de dos maneras: intercambiando el orden de las funciones para que el código de max() aparezca primero, o incluyendo al principio del programa el prototipo de max(), como muestra el programa de ejemplo.

#include <stdio.h>

double max(double, double); /* prototipo */

int main() {

  double x = 4, y = 10;
  double z;

  z = max(x, y); /* parámetros reales: "x" e "y" */

  printf("El mayor de %lf y %lf es %lf.\n", x, y, z);

  system("pause");
  return 0;
}

double max(double a, double b) { /* parámetros formales: "a" y "b" */

  double x; /* variable local */

  if (a >= b)
     x = a;
  else
     x = b;

  return x; /* resultado de la función: valor de "x" */

}

Ejemplo 3.8. Escribir un programa que solicite por teclado las dos coordenadas de un punto del plano como valores enteros y utilice una función auxiliar para determinar en qué cuadrante se encuentra dicho punto. Un mensaje en la pantalla indicará al usuario el número del cuadrante. Los ejes de coordenadas dividen al plano en cuatro cuadrantes, los cuales se numeran en sentido contrario a las agujas del reloj. Los semiejes positivos de abcisas y de ordenadas limitan al primer cuadrante. Si el punto pertenece a uno de los ejes o coincide con el origen de coordenadas la función deberá devolver cero.

#include <stdio.h>

int cuadrante(int, int); /* encabezamiento o prototipo */

int main() {

  int x, y, c;

  printf("Coordenadas \"x\" e \"y\": ");
  scanf("%d %d", &x, &y);

  c = cuadrante(x, y); /* parámetros reales: "x" e "y" */

  printf("Punto: (%d, %d). Cuadrante: %d.\n\n", x, y, c);

  system("pause");
  return 0;
}

int cuadrante(int x, int y) { /* parámetros formales: "x" e "y" */

  if (x == 0 || y == 0) return 0;

  if (x > 0) {
     if (y > 0) return 1;
     return 4;
     }
 
  if (y > 0) return 2;

  return 3;
}
Ejercicio: En el ejemplo anterior, la función cuadrante() no se ha codificado según las reglas de la programación estructurada, puesto que dicha función tiene un punto de entrada y cinco puntos de salida. Modificar la función para que posea un punto de entrada y un punto de salida. Para ello se sugiere el uso de una variable local auxiliar, cuyo valor final será el resultado que deberá devolver la función.
Nota: Según las reglas de la programación estructurada, todo módulo de un programa debe tener un único punto de entrada a su código, situado al principio de éste, y un único punto de salida al final del mismo.

Ejemplo 3.9. Desarrollar un programa que muestre por pantalla la suma de los cien números siguientes al máximo común divisor de dos enteros positivos pedidos por teclado.
- Para obtener el máximo común divisor de dos enteros positivos se utilizará el algoritmo de Euclides (ver capítulo de ejercicios del guión anterior).
- La suma de los cien enteros siguientes a uno dado (x) puede calcularse como la suma iterada de los enteros comprendidos entre x + 1 y x + 100, ambos inclusive.

#include <stdio.h>

unsigned mcd(unsigned, unsigned);
unsigned suma_cien(unsigned);

int main() {

  unsigned a, b, c;

  printf("Introducir \"a\" y \"b\": ");
  scanf("%u %u", &a, &b);

  c = mcd(a, b);
  printf("Suma: %u.\n\n", suma_cien(c));

  system("pause");
  return 0;
}

unsigned mcd(unsigned x, unsigned y) {

  while (x != y)
        if (x > y)
           x -= y;
        else
           y -= x;

  return x;
}


unsigned suma_cien(unsigned x) {

  unsigned sum = 0, tope = x + 100;

  x++;            /* x = x + 1; */

  while (x <= tope) {
        sum += x; /* sum = sum + x; */
        x++;      /* x = x + 1; */
        }

  return sum;
}


El tipo void.

Cuando una función carece de valor de retorno, se suele declarar con el tipo void (vacío). En versiones más antiguas de C se omitía el tipo de la función, de modo que ésta adquiría el tipo int por defecto.

Ejemplo 3.10. El siguiente programa utiliza una función auxiliar para imprimir un mensaje en la pantalla. Como no devuelve ningún resultado a la función principal, la auxiliar se ha declarado como void.
#include <stdio.h>

void mensaje(int); /* encabezamiento o prototipo */

int main() {

  mensaje(5 + 5); /* cualquier expresión puede funcionar
                     como parámetro real */

  system("pause");
  return 0;
}

void mensaje(int arg) {

  puts("Subprograma demostrativo.");
  printf("Valor del argumento: %d.\n", arg);
}

Una función de tipo void, al igual que las funciones que sí tienen valor de retorno, admite cualquier combinación de parámetros de entrada, de salida y de entrada/salida, y en cualquier número (ejemplo 3.6).


Alcance de una variable.

Variable local (auto): Su alcance o ámbito se limita a la función donde ha sido declarada y fuera de ésta la variable es privada y desconocida para el resto de las funciones del programa. Pueden existir dos variables locales, cada una declarada en una función distinta, con el mismo nombre o identificador. Dentro de una función, una variable local se crea en la sentencia donde se declara, y se destruye una vez que el flujo de ejecución abandona dicha función.

Variable global (extern): Se define fuera de cualquier función y es conocida por todas las funciones del programa situadas a partir de la sentencia donde se ha declarado. En principio, no se recomienda utilizar una variable global debido a los posibles efectos laterales: Si son varias las funciones que incluyen código para modificar una misma variable global, puede resultar difícil controlar los valores que ésta vaya tomando durante la ejecución del programa.

No obstante, en el caso en que varias funciones necesiten leer el valor de una misma variable, en lugar de declararla como local en la función principal y pasarla como parámetro a las funciones que necesitan acceder a ésta, es preferible declararla como global para no tener que sobrecargar los encabezamientos de dichas funciones.

Ejemplo 3.11. En el siguiente programa se declara una variable global cuyo valor es modificado por la función principal y presentado en pantalla por una función auxiliar.
#include <stdio.h>

void prueba(void); /* encabezamiento o prototipo */

int a = 5; /* variable global y externa a las funciones */

int main() {


  a = 1;
  prueba();


  system("pause");
  return 0;
}

void prueba() {

  printf("La variable \"a\" vale: %d.\n", a);
}

Si en una función se ha declarado una variable local con el mismo identificador que una variable global, dentro de dicha función sólo se tendrá acceso a la variable local, puesto que su ámbito es el más inmediato.

Ejercicio: Declarar una variable local de tipo entero en la función auxiliar del ejemplo anterior, antes de la llamada a printf() y con el mismo nombre que la variable global. En la propia sentencia declarativa, inicializar la variable local con el valor -10, ejecutar el programa y analizar el resultado.


Ejercicios de afianzamiento.

Escribir un programa que solicite dos enteros a través del teclado e imprima por pantalla el mayor y el menor de éstos. Para ello, también deberán desarrollarse las funciones auxiliares max() y min(), las cuales aceptarán como parámetros dos enteros y devolverán como resultado el máximo y el mínimo de éstos respectivamente.

Desarrollar un programa que resuelva la ecuación de segundo grado: a x2 + b x + c. Para ello, el programa pedirá en primer lugar los tres coeficientes reales. A continuación, según el signo del discriminante (b2 - 4 a c), presentará un mensaje para indicar al usuario si la ecuación posee dos soluciones reales, una solución doble o no tiene solución en el conjunto de los números reales. En los dos primeros casos el programa mostrará la solución: (-b ± SQRT(b2 - 4 a c)) / 2 a. La función discrimina() aceparará como parámetros los tres coeficientes y devolverá 1 si el discriminante es positivo, -1 si es negativo y 0 si es cero. La función resul() también aceptará como parámetros los tres coeficientes y, en su caso, devolverá el resultado de la ecuación al programa principal para que este último lo muestre en pantalla.

Modificar el ejemplo 3.8 para que la función auxiliar cuadrante() suministre el resultado al programa principal no como valor de retorno, sino a través de un tercer parámetro, que será de salida. Al carecer la nueva versión de la función de valor de retorno, deberá declararse como void.

Escribir un programa que solicite dos enteros positivos por teclado y utilice la función auxiliar mcd(), desarrollada en el ejemplo 3.9, para calcular el máximo común divisor de dichos enteros. En este caso, la función auxiliar suministrará el resultado al programa principal no como valor de retorno, sino a través de un tercer parámetro, que será de salida. Al carecer la nueva versión de la función de valor de retorno, deberá declararse como void.

Desarrollar un programa que muestre en pantalla la suma de los n primeros términos de la sucesión An = 1/2n. El primer término de la misma se corresponde con n igual a uno. El valor de n debe suministrarse a través del teclado. Una función auxiliar aceptará la variable n como parámetro, calculará el valor de la suma y lo devolverá como resultado a la función principal.

Completar el siguiente programa, el cual muestra en pantalla el término n-simo de la sucesión de Fibonacci. Ésta es una sucesión recurrente donde, excepto los dos primeros, cada nuevo término se calcula como la suma de los dos anteriores:
Fibo(0) = 0
Fibo(1) = 1
Fibo(n) = Fibo(n - 1) + Fibo(n - 2)

La función fibo() calcula el término n-simo de forma iterativa: A partir de los dos primeros términos se van obteniendo sucesivamente los demás hasta llegar al término solicitado, cuyo número de orden se ha suministrado como parámetro a dicha función.
#include <stdio.h>

unsigned fibo(unsigned); /* encabezamiento o prototipo */

int main() {


  unsigned x;

  printf("Introducir entero: ");
  ...
  printf("\nFibonacci(%u) = %u.\n\n", x, fibo(x));


  ...
  return 0;
}

unsigned fibo(...) {

  ...
  unsigned i = 2;

  if (n == 0 || n == 1) return n;
  /* casos base: fibo (0) == 0 y fibo(1) == 1 */

  s1 = 0;
  s2 = 1;

  ...   (i <= n) { /* caso general: fibo(n) */
        s3 = s1 + s2;
        s1 = s2; /* preparar siguiente iteración */
        s2 = s3;
        ...
        }

  ...
}

Ejemplo: Cálculo del término undécimo de la sucesión de Fibonacci.
Fibo(0) = 0;
Fibo(1) = 1;
Fibo(2) = Fibo(1) + Fibo(0) = 0 + 1 = 1;
Fibo(3) = Fibo(2) + Fibo(1) = 1 + 1 = 2;
Fibo(4) = Fibo(3) + Fibo(2) = 2 + 1 = 3;
Fibo(5) = Fibo(4) + Fibo(3) = 3 + 2 = 5;
Fibo(6) = Fibo(5) + Fibo(4) = 5 + 3 = 8;
Fibo(7) = Fibo(6) + Fibo(5) = 8 + 5 = 13;
Fibo(8) = Fibo(7) + Fibo(6) = 13 + 8 = 21;
Fibo(9) = Fibo(8) + Fibo(7) = 21 + 13 = 34;
Fibo(10) = Fibo(9) + Fibo(8) = 34 + 21 = 55;