6 PR2

6.1 Diferencias entre funciones y acciones

A continuación se explican las diferencias entre una función y una acción, qué clases de parámetros utilizan, cómo se implementan en lenguaje C, y finalmente qué pasa cuando un parámetro es una tupla. Es importante que se vayan consolidando todos estos conceptos.

Las principales diferencias son:

funciones acciones
¿Devuelven un valor? no
Clases de parámetros entrada (in) entrada (in), salida (out), entrada / salida (inout)

El retorno de valor de las funciones permite que este se pueda asignar a una variable, hecho que no permiten las acciones.

Por ejemplo, imaginemos que queremos implementar en lenguaje C la función suma(); una posible implementación podría ser:

/* Ejemplo ES0601 */
#include <stdio.h>

/* Predeclaración de la función */
int suma(int n1, int n2);

int main(int argc, char **argv) {
    int num1;
    int num2;
    int resultado;
    
    num1 = 3;
    num2 = 2;
    resultado = 0;
    
    printf("Valor de num1 = %d\n", num1);
    printf("Valor de num2 = %d\n", num2);
    printf("Valor de resultado = %d\n", resultado);
    printf(">> Inicio ejecución función\n");
    resultado = suma(num1, num2);
    printf("Suma = %d\n", resultado);
    printf(">> Fin ejecución función\n");
    printf("Valor de resultado = %d\n", resultado);
    return 0;
}

/* Implementación de la función */
int suma(int n1, int n2) {
    return (n1+n2);
}

La ejecución generará la siguiente salida:

Valor de num1 = 3
Valor de num2 = 2
Valor de resultado = 0
>> Inicio ejecución función
Suma = 5
>> Fin ejecución función
Valor de resultado = 5

Como se puede ver, el valor de retorno de la función suma() lo asignamos a la variable resultado.

En una función, los parámetros siempre serán de entrada (in): esto significa que dentro de la función únicamente serán valores de consulta, no los modificaremos para nada.

Para entender bien cómo se implementa una acción, relacionaremos ejemplos similares con las diferentes clases de parámetros que puede tener una acción: entrada (in), salida (out) y entrada/salida (inout).

6.1.1 Parámetros de entrada (in)

Son aquellos parámetros que se pasan a una acción y de los que únicamente utilizaremos su contenido. Esto significa que vamos a trabajar con ellos en modo lectura: obtendremos sus valores para realizar cálculos, pero nunca modificaremos su contenido.

Ejemplo:

/* Ejemplo ES0602 */
#include <stdio.h>

/* Predeclaración de la acción */
void suma(int n1, int n2);

int main(int argc, char **argv) {
    int num1;
    int num2;
    
    num1 = 3;
    num2 = 2;
    
    printf("Valor de num1 = %d\n", num1);
    printf("Valor de num2 = %d\n", num2);
    printf(">> Inicio ejecución acción\n");
    suma(num1, num2);
    printf(">> Fin ejecución acción\n");
    printf("Valor de num1 = %d\n", num1);
    printf("Valor de num2 = %d\n", num2);
    return 0;
}   

/* Implementación de la acción */
void suma(int n1, int n2) {
    int resultado;
    resultado = n1 + n2;
    printf("Suma = %d\n", resultado);
}

La ejecución generará la siguiente salida:

Valor de num1 = 3
Valor de num2 = 2
>> Inicio ejecución acción
Suma = 5
>> Fin ejecución acción
Valor de num1 = 3
Valor de num2 = 2

Como se puede observar, ni num1 ni num2 han modificado su valor después de la ejecución de la acción: son parámetros de entrada.

Esta clase de parámetros también se conoce como parámetro por valor, o paso por valor, ya que lo que estamos pasando es un valor, no un puntero a un valor.

6.1.2 Parámetros de salida (out)

A diferencia de los parámetros de entrada, los de salida se utilizan únicamente para guardar valores. Pueden contener cualquier valor inicial, que no será utilizado dentro de la acción. Una vez realizados todos los cálculos de la acción, el resultado final se guardará en el parámetro de salida.

Ejemplo:

/* Ejemplo ES0603 */
#include <stdio.h>

/* Predeclaración de la acción */
void suma(int n1, int n2, int *res);

int main(int argc, char **argv) {
    int num1;
    int num2;
    int resultado;
    int *pResultado;
    
    num1 = 3;
    num2 = 2;
    resultado = 0;
    pResultado = &resultado;
    
    printf("Valor de num1 = %d\n", num1);
    printf("Valor de num2 = %d\n", num2);
    printf("Valor de resultado = %d\n", resultado);
    printf(">> Inicio ejecución acción\n"); 
    suma(num1, num2, pResultado);
    printf("Suma = %d\n", resultado);
    printf(">> Fin ejecución acción\n");
    printf("Valor de resultado = %d\n", resultado);
    return 0;
}

/* Implementación de la acción */
void suma(int n1, int n2, int *res) {
    *res = n1 + n2;
}

La ejecución generará la siguiente salida:

Valor de num1 = 3
Valor de num2 = 2
Valor de resultado = 0
>> Inicio ejecución acción
Suma = 5
>> Fin ejecución acción
Valor de resultado = 5

De momento ignoraremos que se trata de un puntero: independientemente del valor que tenga res al pasar por parámetro, cuando finalice la acción contendrá la suma de los otros dos parámetros de entrada num1 y num2.

El valor resultado ha cambiado, de 0 a 5. Se considera un parámetro de salida porque, independientemente del valor inicial que tenga este, no se utiliza para nada y al finalizar la acción contendrá el resultado de la operación suma().

Ahora sí comentamos el hecho de utilizar el puntero: el modo que tenemos en C para modificar una variable definida fuera de una acción desde dentro de esta es trabajando precisamente con su dirección de memoria.

Igual que en el caso anterior, estamos delante de un parámetro por referencia, o paso por referencia, ya que pasamos un puntero a un valor, no el valor en sí mismo.

6.1.3 Parámetros de entrada/salida (inout)

Esta clase de parámetro es una suma de los dos comportamientos anteriores: por un lado su valor importa a la hora de realizar los cálculos de la acción, y a la vez al finalizar la ejecución de la acción tendrá un valor diferente, también significativo por tratarse del resultado final del cálculo.

Ejemplo:

/* Ejemplo ES0604 */
#include <stdio.h>

/* Predeclaración de la acción */
void suma(int *pN1, int n2);

int main(int argc, char **argv) {
    int num1;
    int num2;
    int *pNum1;
    
    num1 = 3;
    num2 = 2;
    pNum1 = &num1;
    
    printf("Valor de num1 = %d\n", num1);
    printf("Valor de num2 = %d\n", num2);
    printf(">> Inicio ejecución acción\n"); 
    suma(pNum1, num2);
    printf("Suma = %d\n", num1);
    printf(">> Fin ejecución acción\n");
    printf("Valor de num1 = %d\n", num1);
    return 0;
}

/* Implementación de la acción */
void suma(int *pN1, int n2) {
    *pN1 = *pN1 + n2;
}

La ejecución generará la siguiente salida:

Valor de num1 = 3
Valor de num2 = 2
>> Inicio ejecución acción
Suma = 5
>> Fin ejecución acción
Valor de num1 = 5

En este caso, se ha realizado la suma como num1 = num1 + num2: el resultado de la suma de las dos variables se guarda en la primera de ellas. Así pues, el valor de la variable num1 importa tanto a la entrada como a la salida de la acción, con lo que se trata de un parámetro de entrada/salida.

Esta clase de parámetro se conoce como parámetro por referencia, o paso por referencia, ya que pasamos un puntero.

6.2 Tuplas como parámetros

Cuando un parámetro de una acción/función es una tupla, la forma como accederemos a sus campos dentro de la acción/función variará en función del lenguaje y de la clase del parámetro:

  • Lenguaje algorítmico: siempre accederemos a los campos de una tupla con ., independientmente de la clase del parámetro.

  • Lenguaje C:
    • Parámetros de entrada: accederemos a los campos con . (un punto). Por lo tanto dentro de la función/acción, accederemos a los campos de la tupla pasada por parámetro de la siguiente forma: nombreTupla.nombreCampo
    • Parámetros de salida y de entrada/salida: en este caso, accederemos a los campos de la tupla con ->. Así, dentro de la acción podremos hacer referencia a los campos de la tupla pasada por parámetro de la forma: nombreTupla->nombreCampo

A continuación se sintetiza toda esta información en la siguiente tabla:

Clase de parámetro (tupla) Lenguaje algorítmico Lenguaje C
in nombreTupla.nombreCampo nombreTupla.nombreCampo
out nombreTupla.nombreCampo nombreTupla->nombreCampo
inout nombreTupla.nombreCampo nombreTupla->nombreCampo

6.3 Estructura de un algoritmo

A continuación se muestra la estructura de un algoritmo con todos los elementos tratados hasta ahora, de forma ordenada:

const
    { Definición de constantes. }
end const

type
    { Definición de tipos y tuplas. }
end type

function nombreFunción(nombreParámetro1: tipoValor): tipoValor
    var
        { Definición de les variables locales de la funció. }
    end var
    { Codificación de la función. }
    return tipoValor;
end function

action nombreAcción(claseParámetro nombreParámetro1: tipoValor)
    var
        { Definición de las variables locales de la acción. }
    end var
    { Codificación de la acción. }
end action

algorithm nombreAlgoritmo
    var
        { Definición de las variables del algoritmo. }
        nombreVariable1: tipoValor;
        nombreVariable2: tipoValor;
        nomVbreariable3: tipoValor;
    end var
    { Codificación del algoritmo. }
    { - llamada a una función : }
    nombreVariable1 := nombreFunción(nombreVariable2);
    { - llamada a una acción : }
    nombreAcción(nombreVariable3);
end algorithm

6.4 Ejemplo: uso de acciones

A continuación se adjunta un ejemplo inventado de cómo se pueden definir acciones que permitan modificar los campos de una tupla pasada como puntero. Dentro del código hay comentarios adicionales para que quede lo más claro posible:

/* Ejemplo ES0605 */
#include <stdio.h>
#include <string.h>

#define MAX_CHAR 10+1

typedef struct {
    char nombre[MAX_CHAR];
    float nomina;
} tEmpleado;

/* Predeclaración de las acciones */

void printEmpleado(tEmpleado empleado);
void setNominaEmpleado(tEmpleado *empleado, float nominaEmpleado);
void setNombreEmpleado(tEmpleado *empleado, char nombreEmpleado[MAX_CHAR]);

int main(int argc, char **argv) {
    /* Declaramos nuevoEmpleado de tipo tEmpleado,
     * pero no le daremos ningún valor directamente
     * a sus dos campos (número y nómina): lo
     * haremos mediante dos acciones
     */
    tEmpleado nuevoEmpleado;
   
    /* Los valores de los campos número y nómina los
     * leeremos desde teclado y los guardaremos inicialmente
     * en las siguientes dos variables
     */
    char nombreEmpleado[MAX_CHAR];
    float nominaEmpleado;
   
    printf("\nNombre del empleado : ");
    scanf("%s", nombreEmpleado);
      
    printf("Nómina (€) del empleado : ");
    scanf("%f", &nominaEmpleado);
   
    /* Asignamos el número y la nómina al tEmpleado nuevoEmpleado
     * utilizando las acciones definidas. Fijaos en
     * que el parámetro nuevoEmpleado lo pasamos como puntero
     * (pasamos su dirección en memoria, ya que va
     * precedido de &)
     */
    setNombreEmpleado(&nuevoEmpleado, nombreEmpleado);
    setNominaEmpleado(&nuevoEmpleado, nominaEmpleado);
   
    /* Mostramos los datos de la tupla tEmpleado nuevoEmpleado
     * por pantalla
     */
    printEmpleado(nuevoEmpleado);
    return 0;
}

/* Implementación de las acciones */

void printEmpleado(tEmpleado empleado) {
    /* El parámetro empleado es de entrada, por lo
     * que el acceso a sus campos lo haremos
     * con un punto: empleado.nombre, empleado.nomina
     */
    printf("\nDatos del empleado: \n");
    printf("\tNombre: %s\n", empleado.nombre);
    printf("\tNómina: %.2f €\n", empleado.nomina);
}

void setNominaEmpleado(tEmpleado *empleado, float nominaEmpleado) {
    /* El parámetro empleado (de clase inout) es un puntero,
     * para que desde dentro de la acción sea posible
     * modificar el valor (de un campo) del empleado
     * definido al main de nuestro programa, fuera el ámbito
     * de la acción. 
     * El acceso a un campo de un elemento referenciado con
     * un puntero se hace con '->': empleado->nominaEmpleado.
     */
    empleado->nomina = nominaEmpleado;
}

void setNombreEmpleado(tEmpleado *empleado, char nombreEmpleado[MAX_CHAR]) {
    /* Idem que en la acción setNominaEmpleado. En este caso
     * además hay que recordar que la asignación de strings
     * la hacemos con la función strcpy de C, en vez
     * de utilizar la asignación habitual '=' de los tipos
     * primitivos (char, int, float, ...)
     */
    strcpy(empleado->nombre, nombreEmpleado);
}

Un ejemplo de ejecución sería:

Nombre del empleado : Laura
Nómina (€) del empleado : 1850.32

Datos del empleado: 
    Nombre: Laura
    Nómina: 1850.32

6.5 Ejemplo: uso de funciones

Si necesitamos un método que como resultado devuelva un string, en vez de utilizar una función utilizaremos una acción con un parámetro de salida o de entrada/salida. Usaremos las funciones para devolver valores de tipo primitivo (entero, decimal, carácter), dejando los tipos compuestos (vectores, tuplas, etc.) para las acciones.

A continuación se expone un ejemplo para que quede más claro:

/* Ejemplo ES0606 */
#include <stdio.h>
#include <string.h>

/* Programa que, dada una hora, indica
 * a qué parte del día corresponde.
 * Por "parte del día" se entiende: madrugada,
 * mañana, mediodía, tarde, atardecer.
 * La hora se consigue mediante una
 * función, que devuelve un valor
 * entero en formato HHMM (donde HH = hora y
 * MM = minuto).
 * Por otra parte, el cálculo de la parte
 * del día se realiza con una acción,
 * la que recibe dos parámetros: uno de entrada
 * correspondiente a la hora en formato HHMM,
 * y otro de salida que contendrá
 * la parte del día (string) que corresponde
 * a la hora.
 */

#define MAX_CHARS 9+1

/* Predeclaración de funciones y acciones */
void calcularParteDelDia(int hora, char *parte);
int pedirHora();

/* Programa principal */
int main(int argc, char **argv) {
    int hora;
    char parteDelDia[MAX_CHARS];
    hora = pedirHora();
    calcularParteDelDia(hora, parteDelDia);
    printf("La hora %d corresponde a: %s \n", hora, parteDelDia);
    return 0;
}

/* Implementación de funciones y acciones */

/* Función que devuelve un entero, correspondiente
 * a la hora introducida por teclado, en formato
 * HHMM
 */
int pedirHora() {
    int hora;
    printf("Teclea hora (formato HHMM) : ");
    scanf("%d", &hora);
    return hora;
}

/* Acción que, dada una hora (parámetro
 * de entrada), calcula qué parte del día
 * corresponde (parámetro de salida). La
 * parte del día es de tipo string. El
 * parámetro de salida debe ser un
 * puntero, para que desde dentro de
 * la acción se pueda modificar el valor
 * de la variable definida fuera del
 * ámbito de la acción, dentro del main.
 */
void calcularParteDelDia(int hora, char *parte) {

    /* Particionado horario extraído de
     * http://www.wikilengua.org/index.php/Hora
     */

    /* 0 corresponde a 0000 */
    /* 600 corresponde a 0600 */
    if (hora >= 0 && hora <= 600) {
        strcpy(parte, "madrugada");
    } else {
        if (hora > 600 && hora <= 1200) {
            strcpy(parte, "mañana");
        } else {
            if (hora > 1200 && hora <= 1800) {
                strcpy(parte, "tarde");
            } else {
                if (hora > 1800 && hora <= 2359) {
                    strcpy(parte, "noche");
                } else {
                    strcpy(parte, "¡unknown!");
                }
            }
        }
    }
}

Un ejemplo de ejecución:

Teclea hora (formato HHMM) : 1906
La hora 1906 corresponde a: noche 

6.6 Ejemplo: nóminas

Imaginemos que trabajamos con los empleados de una empresa. Supongamos que después de introducir n-empleados en nuestro sistema, queremos una función que nos indique qué empleado tiene la menor nómina.

Obtendremos un valor de retorno de tipo primitivo (un entero que indicará la posición del empleado), por lo que estamos ante una función. A la función le pasaremos un vector de empleados con todos los empleados que previamente hemos introducido en nuestra empresa.

Sin entrar en la codificación de la función, el main de nuestro programa puede ser similar al siguiente:

#include <stdio.h>
#include <string.h>

#define MAX_EMPLEADOS 2
#define MAX_CARACTERES 20+1

typedef struct {
    char nombre[MAX_CARACTERES];
    char apellido[MAX_CARACTERES];
    float nomina;
} tEmpleado;

int main(int argc, char **argv) {
    tEmpleado empleados[MAX_EMPLEADOS];
    int posicionNomina;
    int i;

    for (i = 0; i < MAX_EMPLEADOS; i++) {
        printf("\nNombre empleado %d : ", i);
        scanf("%s", empleados[i].nombre);
        printf("Apellido empleado %d : ", i);
        scanf("%s", empleados[i].apellido);
        printf("Nómina empleado %d : ", i);
        scanf("%f", &empleados[i].nomina);
    }
    posicionNomina = buscarEmpleadoNominaMenor(empleados);
    printf("\nEl empleado con menor nómina es %s, %s (%.2f €)", 
        empleados[posicionNomina].apellido, 
        empleados[posicionNomina].nombre, 
        empleados[posicionNomina].nomina);
    return 0;
}

Hasta este punto no hay nada nuevo: utilizamos un vector de tEmpleado para ir introduciendo los empleados por teclado, y por cada empleado pedimos el nombre, el apellido y su nómina.

Ahora toca implementar la función buscarEmpleadoNominaMenor(), que recibe como parámetro el vector de empleados de la empresa.

De momento olvidemos que estamos ante una función, simplemente centrémonos en cuáles son las operaciones que queremos hacer. En este caso, queremos hacer un programa que recorra uno a uno todos los elementos de un vector, y encuentre la posición del empleado que cobra menos.

Una posible codificación sería la siguiente:

int posicion;
int i;

posicion = 0;
for (i = 0; i < MAX_EMPLEATS; i++) {
    if (vector[i].nomina < vector[posicion].nomina) {
        posicion = i;
    }
}

Este fragmento de código simplemente recorre uno a uno todos los tEmpleado de un vector, comparando la nómina menor encontrada hasta el momento con la del empleado que está tratando en cuestión: si esta segunda es inferior, nos quedamos con su posición dentro del vector como empleado con la nómina menor.

Ahora convertimos este fragmento de código en una función:

int buscarEmpleadoNominaMenor(tEmpleado vector[MAX_EMPLEADOS]) {
    int posicion;
    int i; 

    posicion = 0;
    for (i = 0; i < MAX_EMPLEADOS; i++) {
        if (vector[i].nomina < vector[posicion].nomina) {
            posicion = i;
        }
    }
    return posicion;
}

Como se puede ver, la única diferencia es que ahora el código tiene la cabecera de la función, la cual nos dice que recibe como parámetro un elemento de tipo vector de tEmpleado, y que devolverá un valor entero.

De esta forma el programa completo queda de la siguiente manera:

/* Ejemplo ES0607 */
#include <stdio.h>
#include <string.h>

#define MAX_EMPLEADOS 2
#define MAX_CARACTERES 20+1

typedef struct {
    char nombre[MAX_CARACTERES];
    char apellido[MAX_CARACTERES];
    float nomina;
} tEmpleado;

/* Predeclaración de funciones/acciones */
int buscarEmpleadoNominaMenor(tEmpleado vector[MAX_EMPLEADOS]);

int main(int argc, char **argv) {
    tEmpleado empleados[MAX_EMPLEADOS];
    int posicionNomina;
    int i;

    for (i = 0; i < MAX_EMPLEADOS; i++) {
        printf("\nNombre empleado %d : ", i);
        scanf("%s", empleados[i].nombre);
        printf("Apellido empleado %d : ", i);
        scanf("%s", empleados[i].apellido);
        printf("Nómina empleado %d : ", i);
        scanf("%f", &empleados[i].nomina);
    }
    posicionNomina = buscarEmpleadoNominaMenor(empleados);
    printf("\nEl empleado con menor nómina es %s, %s (%.2f €)", 
        empleados[posicionNomina].apellido, 
        empleados[posicionNomina].nombre, 
        empleados[posicionNomina].nomina);
    return 0;
}

/* Implementación de funciones/acciones */
int buscarEmpleadoNominaMenor(tEmpleado vector[MAX_EMPLEADOS]) {
    int posicion;
    int i; 
    
    posicion = 0;
    for (i = 0; i < MAX_EMPLEADOS; i++) {
        if (vector[i].nomina < vector[posicion].nomina) {
            posicion = i;
        }
    }
    return posicion;
}

Este ejemplo puede parecer sencillo porque el tipo de comparación que estamos haciendo es numérica: comparamos dos campos de tipo float (las nóminas de dos empleados).

Ampliamos ahora la funcionalidad de nuestro programa: queremos que pueda hacer una búsqueda por apellido entre nuestros empleados. Para hacer esta búsqueda, habrá que comparar una cadena de caracteres con el apellido de cada empleado.

Para no hacer la función más compleja de lo necesario, imaginamos que ningún apellido se puede repetir y que siempre encontraremos un apellido como el que buscamos (el objetivo es ver cómo funciona strcmp()). Así pues, nuestra función recibirá un vector de empleados y un apellido que buscar, y devolverá la posición del empleado con ese apellido.

A continuación se muestra código completo, comentando al detalle la parte del strcmp():

/* Ejemplo ES0608 */
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

#define MAX_EMPLEADOS 2
#define MAX_CARACTERES 20+1

typedef struct {
    char nombre[MAX_CARACTERES];
    char apellido[MAX_CARACTERES];
    float nomina;
} tEmpleado;

/* Predeclaración de funciones/acciones */
int buscarEmpleadoNominaMenor(tEmpleado vector[MAX_EMPLEADOS]);
int buscarEmpleadoPorApellido(tEmpleado vector[MAX_EMPLEADOS], 
    char apellido[MAX_CARACTERES]);

int main(int argc, char **argv) {
    tEmpleado empleados[MAX_EMPLEADOS];
    char apellido[MAX_CARACTERES];
    int posicionNomina;
    int posicionApellido;
    int i = 0;

    for (i=0; i<MAX_EMPLEADOS; i++) {
        printf("\nNombre empleado %d : ", i);
        scanf("%s", empleados[i].nombre);
        printf("Apellido empleado %d : ", i);
        scanf("%s", empleados[i].apellido);
        printf("Nómina empleado %d : ", i);
        scanf("%f", &empleados[i].nomina);
    }
    posicionNomina = buscarEmpleadoNominaMenor(empleados);
    printf("\nEl empleado con menor nómina es %s, %s (%.2f €)", 
        empleados[posicionNomina].apellido, 
        empleados[posicionNomina].nombre, 
        empleados[posicionNomina].nomina);

    printf("\nApellido del empleado a buscar : ");
    scanf("%s", apellido);

    posicionApellido = buscarEmpleadoPorApellido(empleados, apellido);
    printf("\nDatos del empleado: %s, %s (%.2f €)", 
        empleados[posicionApellido].apellido, 
        empleados[posicionApellido].nombre, 
        empleados[posicionApellido].nomina);
    return 0;
}

/* Implementación de funciones/acciones */
int buscarEmpleadoNominaMenor(tEmpleado vector[MAX_EMPLEADOS]) {
    int posicion;
    int i;
    
    posicion = 0;
    for (i = 0; i < MAX_EMPLEADOS; i++) {
        if (vector[i].nomina < vector[posicion].nomina) {
            posicion = i;
        }
    }
    return posicion;
}

int buscarEmpleadoPorApellido(tEmpleado vector[MAX_EMPLEADOS], 
    char apellido[MAX_CARACTERES]) {
    int posicion;
    bool isEncontrado; 
    
    posicion = 0;
    isEncontrado = false;
    while (!isEncontrado) {
        /* Para comparar strings utilizaremos la función
         * strcmp() de C. Esta función compara dos
         * cadenas de caracteres, y devuelve un valor
         * como resultado de la comparación:
         * - si el valor == 0: las dos cadenas son iguales
         * - si el valor < 0: la primera cadena < segunda cadena
         * - si el valor > 0: la primera cadena > segunda cadena
         * En nuestro caso nos interesa detectar que los
         * apellidos sean iguales, con lo que 
         * tenemos que controlar que el valor devuelto por
         * la función strcmp() sea 0.
         */
        if (strcmp(vector[posicion].apellido, apellido) == 0) {
            isEncontrado = true;
        } else {
            posicion++;
        }
    }
    return posicion;
}

6.7 Ejemplo: pivoteDefensiva

/* Ejemplo ES0609 */
#include <stdio.h>
#include <string.h>

/* Recibimos una petición de un equipo femenino de baloncesto,
 * en la que nos piden un programa que les permita
 * seleccionar la mejor pivote defensiva entre una
 * serie de candidatas.
 * La mejor pivote defensiva es aquella que captura
 * más rebotes; en caso de empate, se elegirá la que
 * haga más tapones.
 * Habrá que implementar 3 acciones y 1 función:
 * - acción leerJugadora(j): lee desde teclado y
 *   guarda todos los campos de la jugadora
 *   en la tupla j.
 * - acción mostrarJugadora(j): muestra por pantalla
 *   el valor de los campos de la tupla j.
 * - acción copiarJugadoras(j1, j2): copia el valor
 *   de todos los campos de j2 hacia j1.
 * - función compararJugadoras(j1, j2): devuelve -1 en
 *   caso de que la mejor pivote sea j1, y 1 en caso
 *   de que la mejor sea j2.
 */
#define MAX_NOMBRE 20+1
#define MAX_APELLIDO 20+1
#define MAX_JUGADORAS 3

typedef struct {
    char nombre[MAX_NOMBRE];
    char apellido[MAX_APELLIDO];
    float rebotes;
    float tapones;
} tJugadora;

/* Predeclaraciones de funciones/acciones */
void leerJugadora(tJugadora *j);
void mostrarJugadora(tJugadora j);
void copiarJugadora(tJugadora *destino, tJugadora origen);
int compararJugadoras(tJugadora j1, tJugadora j2);

/* Programa principal */
int main(int argc, char **argv) {
    tJugadora jugadoras[MAX_JUGADORAS];
    int resultado;
    int i;

    /* Se crea la tJugadora ficticia
     * mejorPivote que nos ayudará a encontrar
     * la mejor opción de entre todas las
     * candidatas
     */
    tJugadora mejorPivote; 
    mejorPivote.rebotes = 0;
    mejorPivote.tapones = 0;

    /* Leemos todas las jugadoras con
     * la acción leerJugadora(). Esta
     * acción recibe un parámetro de salida
     * (out), el cual contendrá la
     * jugadora leída por teclado. Como
     * se trata de un parámetro de tipo
     * out, se realizará un paso por
     * referencia (= pasaremos un puntero)
     */
    for (i=0; i<MAX_JUGADORAS; i++) {
        leerJugadora(&jugadoras[i]);
    }

    /* Mostramos por pantalla cuál
     * es la mejor jugadora de perfil
     * pivote defensivo. La idea es ir
     * recorriendo una a una las jugadoras
     * del vector y compararlas con mejorPivote:
     * 1. Si la jugadora del vector es mejor
     *    que mejorPivote, copiaremos los datos
     *    de la jugadora hacia mejorPivote.
     * 2. Si mejorPivote es mejor que la
     *    jugadora del vector, no haremos nada.
     * Al finalizar el recorrido de todas
     * las jugadoras del vector, tendremos que
     * mejorPivote contendrá la jugadora
     * que estamos buscando.
     */
    for (i=0; i<MAX_JUGADORAS; i++) {
        resultado = compararJugadoras(mejorPivote, jugadoras[i]);
        if (resultado == 1) {
            copiarJugadora(&mejorPivote, jugadoras[i]);
        }
    }
    printf("\nMejor opción como pivote defensiva : ");
    mostrarJugadora(mejorPivote);
    return 0;
}

/* Implementación de les funciones/acciones */
void leerJugadora(tJugadora *j) {
    printf("Introduce los datos de la nueva jugadora: \n");
    printf("\tNombre: ");
    scanf("%s", j->nombre);
    printf("\tApellido: ");
    scanf("%s", j->apellido);
    printf("\t>> Promedios por partido:\n");
    printf("\tRebotes: ");
    scanf("%f", &j->rebotes);
    printf("\tTapones: ");
    scanf("%f", &j->tapones);
}

void mostrarJugadora(tJugadora j) {
    printf("\n%s, %s: %.1f rebotes, %.1f tapones \n", 
        j.apellido, j.nombre, j.rebotes, j.tapones);
}

void copiarJugadora(tJugadora *destino, tJugadora origen) {
    /* Recordemos: 
     * - si el parámetro es un puntero, accederemos
     *   a los campos con '->'
     * - si el parámetro es un valor, accederemos
     *   a los campos con '.'
     */
    strcpy(destino->nombre, origen.nombre);
    strcpy(destino->apellido, origen.apellido);
    destino->rebotes = origen.rebotes;
    destino->tapones = origen.tapones;
}

int compararJugadoras(tJugadora j1, tJugadora j2) {
    /* Estamos buscando una jugadora que
     * tenga un perfil de pivote defensivo,
     * Con lo que seleccionaremos:
     * 1. Aquella que tenga más rebotes por partido
     * 2. En caso de empate en rebotes, aquella que
     *    haga más tapones por partido
     */ 
    int resultado;
    resultado = 0;
    if (j1.rebotes > j2.rebotes) {
        resultado = -1;
    } else {
        if (j1.rebotes < j2.rebotes) {
            resultado = 1;
        } else {
            /* En este punto tenemos que
             * j1.rebotes == j2.rebotes,
             * con lo que vamos a comparar el siguiente
             * campo según la prioridad definida
             * para la posición de pivote defensiva
             */
            if (j1.tapones > j2.tapones) {
                resultado = -1;
            } else {
                if (j1.tapones < j2.tapones) {
                    resultado = 1;
                } else {
                    resultado = 0;
                }
            }
        }
    }
    return resultado;
}

Ejemplo de ejecución:

Introduce los datos de la nueva jugadora: 
    Nombre: Julia
    Apellido: Sanz
    >> Promedios por partido:
    Rebotes: 7.9
    Tapones: 0.9
Introduce los datos de la nueva jugadora: 
    Nombre: Nuria
    Apellido: Gutiérrez
    >> Promedios por partido:
    Rebotes: 7.9
    Tapones: 1.4
Introduce los datos de la nueva jugadora: 
    Nombre: Mireia
    Apellido: Mateu
    >> Promedios por partido:
    Rebotes: 6.8
    Tapones: 2.1

Mejor opción como pivote defensiva : 
Gutiérrez, Nuria: 7.9 rebotes, 1.4 tapones 

6.7.1 Explicación sobre la acción copiarJugadora()

La acción copiarJugadora(tJugadora *destino, tJugadora origen) del ejemplo recibe dos parámetros:

  • El primero de ellos es destino, un puntero a una tupla de tipo tJugadora; otra forma de decirlo es que el valor destino lo pasamos por referencia. Este parámetro es de tipo out, ya que no utilizamos para nada el valor inicial que tiene y solo nos interesa el valor final que tendrá al ejecutar la acción.
  • El segundo es origen, una tupla que pasamos por valor. Por lo tanto en este caso no estamos pasando el puntero a una tJugadora, sino directamente una tJugadora.

Cuando tenemos un puntero a una tupla accedemos a sus campos mediante el operador ->. En cambio, si estamos tratando una tupla accederemos a ellos con el operador . (punto).

La codificación de la acción es:

void copiarJugadora(tJugadora *destino, tJugadora origen) {
    /* Recordemos: 
     * - si el parámetro es un puntero, accederemos
     * a los campos con '->'
     * - si el parámetro es un valor, accederemos
     * a los campos con '.'
     */
    strcpy(destino->nombre, origen.nombre);
    strcpy(destino->apellido, origen.apellido);
    destino->rebotes = origen.rebotes;
    destino->tapones = origen.tapones;
}

No utilizaremos el prefijo & delante de la tJugadora origen, de la misma forma que no utilizamos &cuando vamos a imprimir por pantalla con printf() el valor de una variable: como que estamos tratando con un valor, simplemente accedemos a él y lo utilizamos. Por lo tanto, estas acciones serían incorrectas:

    strcpy(destino->nombre, &origen.nombre);
    strcpy(destino->apellido, &origen.apellido);
    destino->rebotes = &origen.rebotes;
    destino->tapones = &origen.tapones;

Sí que tenemos una alternativa posible al operador ->, según se indica en la xWiki:

    strcpy((*destino).nombre, origen.nombre);
    strcpy((*destino).apellido, origen.apellido);
    (*destino).rebotes = origen.rebotes;
    (*destino).tapones = origen.tapones;

Por lo tanto, este último bloque de código se puede sustituir en el ejemplo anterior y todo seguirá funcionando correctamente, ya que son equivalentes. Se puede utilizar una forma u otra, aunque el operador -> parece más fácil de entender visualmente.

6.8 Errores más frecuentes

6.8.1 Definición de acciones/funciones: nombres de parámetros

Pseudocódigo incorrecto:

action hotelCmp(in tHotel, in tHotel)
    { ... }
end action

En el ejemplo mostrado parece que la intención es definir dos parámetros, pero falta indicar el identificador (nombre) para cada uno de ellos. Recordemos que en la declaración de la cabecera de una acción/función, hay que indicar, para cada parámetro:

  • La clase de parámetro que recibe la acción o función (in, out o inout).
  • El tipo de datos del parámetro (tHotel en el ejemplo).
  • El identificador asociado al parámetro (no definido en el ejemplo).

Pseudocódigo correcto:

action hotelCmp(in hotel1: tHotel, in hotel2: tHotel)
    { ... }
end action

6.8.2 Definición de acciones y funciones: tipos de parámetros

Pseudocódigo incorrecto:

action hotelCmp(in hotel1: tHotel, out hotel2: tHotel)
    hotel1.id = hotel2.id;
end action

En la declaración de la cabecera de una acción hay que indicar la clase de los parámetros que recibe: in,out o inout. Los parámetros de clase in no se pueden modificar en el interior de la acción, mientras sí podemos hacerlo en el caso de los parámetros de clase out o inout. En el ejemplo, se está modificando el parámetro hotel1 actualizando el valor de uno de sus campos. O bien se trata de una confusión entre ambos parámetros, o bien hotel1 debe ser de clase out.

Pseudocódigos correctos:

action hotelCmp(in hotel1: tHotel, out hotel2: tHotel)
    hotel2.id = hotel1.id;
end action

action hotelCmp(out hotel1: tHotel, in hotel2: tHotel)
    hotel1.id = hotel2.id;
end action

action hotelCmp(out hotel1: tHotel, inout hotel2: tHotel)
    hotel1.id = hotel2.id;
end action

Fijaos en que si el parámetro hotel1 es de clase out y tenemos que leer el parámetro hotel2, este segundo parámetro debe ser forzosamente de clase in o inout, por lo que hemos modificado la clase en la cabecera de la función.

6.8.3 Definición de funciones: tipos de datos de retorno

Pseudocódigo incorrecto:

function hotelCmp(h1: tHotel, h2: tHotel)
    { ... }
end function

En el ejemplo mostrado, hotelCmp está definida como función, con h1 y h2 como parámetros de entrada, pero las funciones siempre devuelven un valor, y hay que indicar siempre el tipo de dato en la misma cabecera.

Pseudocódigo correcto:

function hotelCmp(h1: tHotel, h2: tHotel): integer
    { ... }
end function

Observad que, en este caso, no es necesario indicar la clase de los parámetros (in, out o inout), porque en las funciones los parámetros siempre son de entrada y no se pueden modificar.

6.8.4 Definición de acciones: retorno de valor

Pseudocódigo incorrecto:

action hotelTableInit(inout tabHotels: tHotelTable): void
    { ... }
end action

Se ha definido una acción llamada hotelTableInit, que recibe un parámetro de entrada/salida (inout) de tipo tHotelTable, con el identificador tabHotels. Las acciones nunca devuelven un valor, pero erróneamente se ha interpretado que hay que indicar este hecho añadiendo : void al final de la cabecera de la acción.

Este caso es doblemente incorrecto debido a que : void es un identificador propio de C que no existe en lenguaje algorítmico.

Pseudocódigo correcto:

action hotelTableInit(inout tabHotels: tHotelTable)
    { ... }
end action

Observad que en este caso, al tratarse de una acción, es necesario indicar las clases de sus parámetros: in, out o inout.

6.8.5 Definición de acciones/funciones: acciones abiertas

Pseudocódigo incorrecto:

action hotelCmp(in hotel1: tHotel, out hotel2: tHotel)
    hotel1.id = hotel2.id;
    { ... }
action readHotel(out hotel1: tHotel)
    hotel1.id = readInteger();
end action

Las funciones y acciones constituyen bloques de código con unas funcionalidades y características especiales, y como tal deben estar bien delimitadas con las palabras reservadas functionend function y actionend action. Dejar una acción sin cerrar puede parecer un tema menor, pero en C esto comportará con toda seguridad un error de compilación (a menudo difícil de detectar).

Pseudocódigo correcto:

action hotelCmp(in hotel1: tHotel, out hotel2: tHotel)
    hotel1.id = hotel2.id;
    { ... }
end action
action readHotel(out hotel1: tHotel)
    hotel1.id = readInteger();
end action

6.8.6 Llamada a acciones/funciones: clases de parámetros

Pseudocódigo incorrecto:

hotelRead(out h1: tHotel);
hotelRead(out h2: tHotel);

action hotelRead(out h: tHotel)
    { ... }
end action

Las clases de los parámetro que recibe una acción/función, así como el tipo de datos y el identificador del parámetro, hay que indicarlos únicamente en la definición de la cabecera de la acción. En el caso de las funciones, no indicaremos las clases de los parámetros, ya que siempre son in.

En la llamada a una acción/función únicamente se tienen que indicar los parámetros.

Pseudocódigo correcto:

hotelRead(h1);
hotelRead(h2);

action hotelRead(out h: tHotel)
    { ... }
end action

6.8.7 Llamada a acciones/funciones: palabras reservadas

Pseudocódigo incorrecto:

action hotelRead(h1);
action hotelRead(h2);

action hotelRead(out h: tHotel)
    { ... }
end action

La palabra reservada action se usará únicamente para la definición de la cabecera de la acción.

En la llamada a una acción únicamente hay que indicar el nombre de la acción y los parámetros; es incorrecto añadir nuevamente la palabra reservada action.

Pseudocódigo correcto:

hotelRead(h1);
hotelRead(h2);

action hotelRead(out h: tHotel)
    { ... }
end action

6.8.8 Llamada a funciones: llamada vacía

Pseudocódigo incorrecto:

bestHotel: integer;

compareHotels(h1,h2);

if (bestHotel = 1) then
    { ... }
end if  

function hotelRead(h1: tHotel, h2: tHotel): integer
    { ... }
    return bestHotel;
end function

Como ocurre en todas las funciones, la función hotelRead() devuelve un valor: en este caso la variable bestHotel. Sin embargo, la llamada que se hace de la función no es correcta, porque el retorno de la función no se guarda en ninguna parte y la variable bestHotel del programa principal sigue sin actualizarse.

En el caso de C este código no provocaría un error, pero si no hacemos nada más desde el programa principal no tendremos acceso al resultado que devuelve la función (que es lo que se supone que queríamos hacer).

Pseudocódigo correcto:

bestHotel: integer;

bestHotel:= compareHotels(h1,h2);

if (bestHotel = 1) then
    {...}
end if  

function hotelRead(h1: tHotel, h2: tHotel): integer
    {...}
    return bestHotel;
end function

El retorno de una función también se puede utilizar en una expresión, en este caso sin asignarlo a ninguna variable, ya que en la misma expresión el compilador calculará el retorno de la función y lo sustituirá por el valor correspondiente a la función:

Pseudocódigo correcto:

bestHotel: integer;

if (compareHotels(h1,h2) = 1) then
    { ... }
end if  

function hotelRead(h1: tHotel, h2: tHotel): integer
    { ... }
    return bestHotel;
end function

6.8.9 Llamada a funciones: ausencia de parámetros

Pseudocódigo incorrecto:

var
    myReal: real;
    myInteger: integer;
end var

myInteger:= realToInteger();

La función realToInteger devuelve un valor y lo guardamos en la variable myInteger. El problema es que la función necesita que le pasemos un parámetro cuando la llamamos: en este caso, el número real que hay que convertir en entero.

En caso de duda hay que consultar siempre la cabecera de declaración de la función, ya que nos indicará en cada caso el número y el tipo de los parámetros que espera.

Pseudocódigo correcto:

var
    myReal: real;
    myInteger: integer;
end var

myInteger:= realToInteger(myReal);

6.8.10 Llamada a funciones: errores de sintaxis múltiples

Pseudocódigo incorrecto:

var
    hotel1: tHotel;
end var

hotelRead(in: &myHotel);

action hotelRead(in myHotel: tHotel)
    { ... }
end action

El pseudocódigo anterior contiene varios errores de sintaxis en la llamada a la función:

  • Se indica la clase de parámetro in.
  • Se incluye el operador :.
  • Se añade sintaxis propia de C con el operador &, para pasar el parámetro myHotel por referencia. En lenguaje algorítmico el paso de un parámetro por referencia se determina mediante la cabecera de la función.

Pseudocódigo correcto:

var
    hotel1: tHotel;
end var

hotelRead(myHotel);

action hotelRead(in myHotel: tHotel)
    { ... }
end action

6.8.11 Definición de funciones: funciones dentro del main

Código incorrecto:

#include <stdio.h>
#include <stdbool.h>

#define MAX_LEN 15

int main(int argc, char **argv) {
    int num1;
    int num2;
    bool myBool;

    bool compareNumbers(int n1, int n2){
       return (n1 > n2); 
    }
    
    myBool = compareNumbers(num1, num2);
    return 0;
}

Las funciones y acciones se tienen que definir de forma individual y separada. No es correcto definir funciones anidadas o incluidas dentro de otras (como por ejemplo, en el main), entre otras cosas porque estas funciones no estarán disponibles desde el exterior de la función donde son creadas.

Código correcto:

#include <stdio.h>
#include <stdbool.h>

#define MAX_LEN 15

/* Predeclaración de la función */
bool compareNumbers(int n1, int n2);

int main(int argc, char **argv) {
    int num1;
    int num2;
    bool myBool;
    myBool = compareNumbers(num1, num2);
    return 0;
}

bool compareNumbers(int n1, int n2){
    return (n1 > n2); 
}

6.8.12 Rotura del flujo de ejecución: función con múltiples return

Este no es un error sintáctico o semántico, sino una mala práctica de diseño y programación.

Pseudocódigo incorrecto:

const
    MAX_NUMBERS: integer = 10;
end const

function isEven(number: integer): boolean
    if number mod 2 = 0 then
        return true;
    end if 
    return false;
end function

function isEvenInNumbers(numbers: vector[MAX_NUMBERS] of integer): boolean
    for i:=1 to MAX_NUMBERS do
        if numbers[i] mod 2 = 0 then
            return true;
        end if 
    end for
    return false;
end function

Ambas funciones tienen como objetivo encontrar si el parámetro recibido contiene algún número par: la primera de ellas lo comprueba sobre un único entero, mientras que la segunda lo revisa en un vector de enteros. La mala práctica está en la rotura del flujo de ejecución: en una función no puede haber más de un return.

Para solucionarlo, utilizaremos una variable auxiliar que iremos actualizando con el resultado calculado y, al final de todo, ejecutaremos el return de la variable.

Pseudocódigo correcto:

const
    MAX_NUMBERS: integer = 10;
end const

function isEvenNumber(number: integer): boolean
    var
        isEven: boolean;
    end var
    
    if number mod 2 = 0 then
        isEven := true;
    else
        isEven := false;
    end if
    return isEven;
end function

function isEvenInNumbers(numbers: vector[MAX_NUMBERS] of integer): boolean
    var
        isEvenFound: boolean;
        i: integer;
    end var
    
    isEvenFound := false;
    i := 1;
    while not isEvenFound and i ≤ MAX_NUMBERS do
        if numbers[i] mod 2 = 0 then
            isEvenFound := true;
        else
            i := i+1;
        end if
    end for
    return isEvenFound;
end function