La modularidad es una técnica de programación que consiste en dividir un programa en partes más pequeñas y manejables. Cada parte se denomina módulo y puede ser un subprograma, una función, una acción, un procedimiento, una rutina, un método, etc. La modularidad permite dividir un problema complejo en problemas más simples y fáciles de resolver. Además, facilita la reutilización de código, la depuración y el mantenimiento de los programas.
Podemos ver cómo nuestro algoritmo, a base de solicitar a expertos que hagan el trabajo, se convierte en un algoritmo simple y fácil de entender. Lo único que hemos tenido que hacer son peticiones en las que hemos añadido valores, a los que llamamos **parámetros**, que indican exactamente cómo queremos cada plato.
Los lenguajes de programación permiten trabajar de una forma similar a como lo hemos hecho en el menú. Es decir, se pueden utilizar subalgortimos, o se pueden definir otros nuevos, que quedan aparte del algoritmo principal y que permiten dividir un algoritmo complicado en subalgoritmos más simples. Además, como veremos en los siguientes apartados, otra de las ventajas que proporcionan es el hecho de que estos subalgoritmos se pueden reutilizar evitando la reescritura de código.
Estos subalgoritmos reciben el nombre de **acciones** y **funciones** y es como si empaquetásemos un conjunto de instrucciones que resuelven un problema y no nos tenemos que preocupar de lo que hay dentro.
Supongamos que tenemos que diseñar un algoritmo que permita saber cuántas combinaciones de colores se pueden hacer si disponemos de `n` telas, cada una de un color diferente. Concretamente se quiere disponer de un algoritmo genérico que dados `n` colores calcule cuantas combinaciones se pueden hacer utilizando cada vez `m` colores, donde `m` y `n` son enteros y `m ≤ n`.
Para diseñar el algoritmo deberemos leer los valores de n y m y haberlo indicado en la fórmula. Quedaría:
```c
algorithm combinations
var
{definimos las variables, dos enteros que hay que leer y una donde almacenar el resultado}
totalNumColors, colorsToCombine: integer ;
result: integer;
end var
{leemos los dos números}
totalNumColors:= readInteger();
colorsToCombine:= readInteger();
{Hacemos un esbozo del cálculo, suponiendo que disponemos de una manera de calcular el factorial de un número }
result := totalNumColors! div (colorsToCombine! * (totalNumColors-colorsToCombine)!) ;
writeInteger(result);
end algorithm
#include <stdio.h>
int main() {
int totalNumColors;
int colorsToCombine;
int result;
result = totalNumColors! / (colorsToCombine! * (totalNumColors-colorsToCombine)!) ;
printf ("%d ", result);
return 0;
}
```
Ahora bien, para calcular el factorial de las posible combinaciones se necesita hacer una iteración para ir calculando el producto de todos los enteros, además de la necesidad de variables donde guardar los cálculos parciales::
```c
var
factorial: integer;
i: integer;
n: integer;
end var
n:= readInteger();
factorial:= 1;
i:=1;
while i ≤ n do
factorial:=factorial*i;
i:=i+1;
end while
#include <stdio.h>
int main() {
int factorial;
int i;
int n;
scanf("%d", &n);
factorial=1;
i=1;
while(i <= n) {
factorial = factorial*i;
i++;
}
return 0;
}
```
Si incorporamos este código en el algoritmo anterior, el código se complica y se hace menos legible. Deberíamos repetir el código cada vez que necesitaramos el cálculo. La solución es encapsular este código en una **acción** o **función** que se encargue de calcular el factorial de un número cada vez que se necesite con una llamada que pase el parámetro. De esta forma, el algoritmo principal quedaría más limpio y fácil de entender.
Una función es un conjunto independiente de instrucciones que resuelven una tarea concreta, que devuelve un valor y que puede ser referenciada (invocada o llamada) desde otro punto del programa.
Para definir una función necesitaremos los siguientes elementos:
- **Nombre** para poder identificarla.
- Una serie de valores sobre los que se quieran hacer los cálculos, que se denominan **parámetros formales** de la función. Es necesario indicar el nombre y el tipo de dato de cada uno de los parámetros. Actúan como variables locales a la función.
- Las instrucciones de la función, es decir, el **cuerpo de la función**.
- El valor resultante de la función al que se debe indicar el tipo de dato que se va a devolver. Este valor se devuelve mediante la instrucción `return`.
Sintaxis:
```c
function name(param1: type1 , param2: type2, ..., paramn: typen): returnType
/* returnValue es el valor que calcula la función */
return returnValue;
}
```
La **cabecera** es la parte que describe cómo debe llamarse a una función. Indica su nombre, los parámetros que espera y el tipo que retorna. La estructura de la cabecera es la siguiente:
```c
function name(param1: type1 , param2: type2, ..., paramn: typen): returntype;
A veces interesa un subalgoritmo que realice una serie de instrucciones sin devolver un valor. En estos casos, el lenguaje algorítmico generaliza el concepto de función y define la **acción**.
Una acción es un conjunto de instrucciones que resuelven una tarea concreta y que puede ser referenciada (invocada o llamada). Se diferencia de una función porque:
- No devuelve ningún valor.
- Cuando modifica un parámetro formal, puede estar también modificando el parámetro real.
- Se debe indicar delante del nombre del parámetro cómo se pasa. Utilizamos las palabras clave `in`, `out` o `inout`.
Para definir una acción necesitaremos los siguientes elementos:
- **Nombre** para poder identificarla.
- Una serie de valores sobre los que se quieran hacer los cálculos, que se denominan **parámetros formales** de la acción. Es necesario indicar el nombre y el tipo de dato de cada uno de los parámetros. Actúan como variables locales a la acción.
- Las instrucciones de la acción, es decir, el **cuerpo de la acción**.
Una vez definida la acción o función esta se puede utilizar desde el algoritmo principal o desde otra función o desde otra acción.
Para llamar a una función o acción se utiliza el nombre de la función o acción seguido de los parámetros que se le pasan entre paréntesis. Los **parámetros reales** pueden ser variables, constantes o expresiones, que deben coincidir en número, tipo y orden con los parámetros formales.
Los parámetros de una acción pueden ser de tres tipos:
- **Parámetro de entrada (in)**. El valor del parámetro solamente será consultado y utilizado dentro de la acción. Esta manera de pasar parámetros es típica de funciones matemáticas y es la única manera que puede usarse en las funciones.
- **Parámetro de salida (out)**. Se utiliza para asignar un valor al parámetro. Si el parámetro ya tiene un valor inicial, este valor no se debería utilizar dentro de la acción. Normalmente se pasan los parámetros de esta manera para que sean inicializados por la acción. Puesto que la acción dejará un valor en el parámetro, este debe ser obligatoriamente una variable.
- **Parámetro de entrada/salida (inout)**. Se utilizan para actualizar el valor del parámetro. En este caso, el valor inicial del parámetro se puede utilizar y también se puede modificar, devolviendo un nuevo valor a quien ha llamado a la acción. Puesto que la acción dejará un nuevo valor en el parámetro, este debe ser obligatoriamente una variable.
Disponemos de una acción que, dados el número de piezas compradas a un mayorista y el precio por pieza, calcula el coste total y lo muestra por el canal estándar, teniendo en cuenta que según el número de piezas, se aplicará un tipo de descuento u otro:
```c
const
MAX_DISCOUNT: real = 0.15;
MIN_DISCOUNT: real = 0.05;
LIMIT: integer = 1000;
end const
action computeCost(in num: integer, in price: real)
- Al tratarse de una acción, se llama sin asignar el retorno a una variable, ya que las acciones no retornan ningún valor.
- La acción tiene un parámetro formal de salida. Esto quiere decir que cada vez que se modifique este parámetro dentro de la acción, automáticamente se modificará con el mismo valor el parámetro actual correspondiente cost.
- Podemos ver que en lenguaje C, los parámetros de salida son en realidad **Punteros**. Más adelante se muestra un ejemplo.
**Función**: el criterio para usar una función es evaluar si esta puede actuar como una función matemática que, dados unos parámetros, puede retornar un valor concreto que se puede utilizar como un miembro de una expresión. Si es así, claramente debemos escribir una función.
**Acción**: si necesitamos actualizar uno de los parámetros o inicializar uno que no lo está, claramente debemos usar una acción. También se utiliza cuando no se retorna ningún valor o cuando se deben devolver más de uno.
Para mantener el conjunto de acciones y funciones ordenadas y que se puedan utilizar en diferentes programas de manera fácil, los lenguajes permiten crear **librerías**.
Estas librerías constan de dos partes:
- Un fichero de texto que inclute las cabeceras de las acciones y funciones que la componen y que en C tienen siempre la extensión `.h`.
- El código de las acciones y funciones, que también tendrá un extensión `.c`, pero que solamente tendrá el código de la acciones y/o funciones, sin tener un `main`.
Cuando se tendan que emplear desde algún otro programa solamente se incluirán en el programa el fichero de cabeceras.h. La sintaxis:
```c
#include "nomLlibreria.h"
```
Concretamente, la librería que incluye las cabeceras de las acciones y funciones de entrada y salida de datos se llama en C `stdio.h`:
```c
#include <stdio.h>
```
Hay muchos ejemplos de librerías, por ejemplo:
- Librerías de funciones matemáticas. `math.h`, `stdlib.h`, etc.
- Librerías de funciones y acciones de tratamiento de fechas. `time.h`, `date.h`, etc.
- Librerías de funciones de tratamiento de cadenas de caracteres. `string.h`, etc.
La posibilidad de crear las librerías facilita la reutilización del código y estandarización de la manera de trabajar.
### 12.9 Ejercicios
1. Diseñad una función que, dado un entero n, calcule la suma de los primeros n números de la serie Fibonacci.
2. Diseñad una acción que, dados un real que representa el saldo de una cuenta corriente y otro real que representa un cargo o imposición, actualice el saldo de la cuenta.
[Solución en lenguaje C]()
3. Diseñad una función que calcule n (real) elevado a la potencia m (entero).
[Solución en lenguaje C]()
4. Diseñad una acción que, dados tres números reales que representan los coeficientes de una ecuación de segundo grado, retorne sus raíces, en caso de que existan. En caso contrario, que inicialice las raíces a 0. Recordad que dada la ecuación A * x2 + B * x + C las raíces se calculan:
```
x1 = (-B + sqrt(B2 - 4 * A * C)) / (2 * A)
```
Si la expresión dentro de la raíz es negativa quiere decir que la ecuación no tiene raíces. Podéis suponer que la función raíz cuadrada está ya definida y su cabecera es:
```
function squareRoot(x: real): real;
```
[Solución en lenguaje C]()
4b. Declarad las variables necesarias para llamar a la acción del ejercicio anterior e indicad cómo se llamaría.
[Solución en lenguaje C]()
5. Para cada apartado siguiente, decidid si es mejor una acción o una función y definid su cabecera (solamente se pide la cabecera, no hay que diseñar ningún algoritmo).
- Dadas las dimensiones de una pared (altura y anchura) y el precio de pintura por metro cuadrado, determinad el coste total de pintura necesario para pintar la pared.
- Dado el consumo total de agua acumulado desde el mes de enero hasta junio (ambos incluidos), leed por el canal estándar el consumo del resto de meses (desde julio a diciembre, ambos incluidos), y retornadlo acumulado al total del consumo de los 6 primeros meses.
- Dados dos números enteros intercambiad los valores de ambas variables siempre que ambas sean diferentes de cero.
- Dado un entero retornad la suma de sus divisores.
[Solución en lenguaje C]()
5b. Declarad las variables necesarias para llamar a las acciones del ejercicio anterior e indicad cómo se llamarían.