6 PR2

6.1 Diferències entre funcions i accions

A continuació s’expliquen les diferències entre una funció i una acció, quines classes de paràmetres utilitzen, com s’implementen en llenguatge C, i finalment què passa quan un paràmetre és una tupla. És important que es vagin consolidant tots aquests conceptes.

Les principals diferències són:

Funcions Accions
Retornen un valor? no
Classes de paràmetres entrada (in) entrada (in), sortida (out), entrada/sortida (inout)

El retorn de valor de les funcions permet que aquest es pugui assignar a una variable, cosa que no es pot fer amb les accions.

Per exemple, imaginem que volem implementar en llenguatge C la funció suma(); una possible implementació podria ser:

/* Exemple CA0601 */
#include <stdio.h>

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

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

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

L’execució generarà la següent sortida:

Valor de num1 = 3
Valor de num2 = 2
Valor de resultat = 0
>> Inici execució funció
Suma = 5
>> Fi execució funció
Valor de resultat = 5

Com es pot veure, el valor de retorn de la funció suma() l’assignem a la variable resultat.

En una funció, els paràmetres sempre seran d’entrada (in): això significa que dins de la funció únicament seran valors de consulta, no els modificarem per res.

Per entendre bé com s’implementa una acció, relacionarem exemples similars amb les diferents classes de paràmetres que pot tenir una acció: entrada (in), sortida (out) i entrada/sortida (inout).

6.1.1 Paràmetres d’entrada (in)

Són aquells paràmetres que es passen a una acció i dels quals només utilitzarem el seu contingut. Això significa que hi treballarem en mode lectura: obtindrem els seus valors per tal de realitzar càlculs, però mai modificarem el seu contingut. Exemple:

/* Exemple CA0602 */
#include <stdio.h>

/* Predeclaració de l'acció */
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(">> Inici execució acció\n");
    suma(num1, num2);
    printf(">> Fi execució acció\n");
    printf("Valor de num1 = %d\n", num1);
    printf("Valor de num2 = %d\n", num2);
    return 0;
}   

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

L’execució generarà la següent sortida:

Valor de num1 = 3
Valor de num2 = 2
>> Inici execució acció
Suma = 5
>> Fi execució acció
Valor de num1 = 3
Valor de num2 = 2

Com es pot observar, ni num1 ni num2 han modificat el seu valor després de l’execució de l’acció: son paràmetres d’entrada.

Aquesta classe de paràmetre també es coneix com a paràmetre per valor, o pas per valor, ja que el que estem passant és un valor, no pas un punter a un valor.

6.1.2 Paràmetres de sortida (out)

A diferència dels paràmetres d’entrada, els de sortida s’utilitzen únicament per guardar valors. Poden contenir qualsevol valor inicial, que no serà utilitzat dins de l’acció. Una vegada realitzats tots els càlculs de l’acció, el resultat final es guardarà en el paràmetre de sortida.

Exemple:

/* Exemple CA0603 */
#include <stdio.h>

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

int main(int argc, char **argv) {
    int num1;
    int num2;
    int resultat;
    int *pResultat;
    
    num1 = 3;
    num2 = 2;
    resultat = 0;
    pResultat = &resultat;
    
    printf("Valor de num1 = %d\n", num1);
    printf("Valor de num2 = %d\n", num2);
    printf("Valor de resultat = %d\n", resultat);
    printf(">> Inici execució acció\n"); 
    suma(num1, num2, pResultat);
    printf("Suma = %d\n", resultat);
    printf(">> Fi execució acció\n");
    printf("Valor de resultat = %d\n", resultat);
    return 0;
}

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

L’execució generarà la següent sortida:

Valor de num1 = 3
Valor de num2 = 2
Valor de resultat = 0
>> Inici execució acció
Suma = 5
>> Fi execució acció
Valor de resultat = 5

De moment ignorarem que es tracta d’un punter: independientement del valor que tingui res en passar per paràmetre, quan finalitzi l’acció contindrà la suma dels altres dos paràmetres d’entrada num1 i num2.

El valor resultat ha canviat, de 0 a 5. Es considera un paràmetre de sortida perquè, independentement del valor inicial que tingui aquest, no s’utilitza per res i en finalitzar l’acció contindrà el resultat de l’operació suma().

Ara sí que comentem el fet d’utilitzar el punter: la manera que tenim a C per modificar una variable definida fora d’una acció des de dins de la mateixa és treballant precisament amb la seva direcció de memòria.

Aquesta classe de paràmetre es coneix com a paràmetre per referència, o pas per referència, ja que passem un punter.

6.1.3 Paràmetres d’entrada/sortida (inout)

Aquesta classe de paràmetre és una suma dels dos comportaments anteriors: d’una banda el seu valor importa a l’hora de realitzar els càlculs de l’acció, i a la vegada en finalitzar l’execució de l’acció tindrà un valor diferent, també significatiu per tractar-se del resultat final del càlcul.

Exemple:

/* Exemple CA0604 */
#include <stdio.h>

/* Predeclaració de l'acció */
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(">> Inici execució acció\n"); 
    suma(pNum1, num2);
    printf("Suma = %d\n", num1);
    printf(">> Fi execució acció\n");
    printf("Valor de num1 = %d\n", num1);
    return 0;
}

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

L’execució generarà la següent sortida:

Valor de num1 = 3
Valor de num2 = 2
>> Inici execució acció
Suma = 5
>> Fi execució acció
Valor de num1 = 5

En aquest cas, s’ha realitzat la suma com a num1 = num1 + num2 : el resultat de la suma de les dues variables es guarda a la primera d’elles. Així doncs, el valor de la variable num1 importa tant a l’entrada com a la sortida de l’acció, per la qual cosa es tracta d’un paràmetre d’entrada/sortida.

Igual que en el cas anterior, estem davant d’un paràmetre per referència, o pas per referència, ja que passem un punter.

6.2 Tuples com a paràmetres

Quan un paràmetre d’una acció/funció és una tupla, la forma com accedirem als seus camps dins de l’acció/funció variarà en funció del llenguatge i de la classe del paràmetre:

  • Llenguatge algorísmic: sempre accedirem als camps d’una tupla amb ., independentment de la classe del paràmetre.

  • Llenguatge C:
    • Paràmetres d’entrada: accedirem als camps amb . (un punt). Per tant dins de la funció/acció, accedirem als camps de la tupla passada per paràmetre de la següent forma: nomTupla.nomCamp
    • Paràmetres de sortida i d’entrada/sortida: en aquest cas, accedirem als camps de la tupla amb ->. Així, dins de l’acció ens referirem als camps de la tupla passada per paràmetre de la forma: nomTupla->nomCamp

A continuació se sintetitza tota aquesta informació a la següent taula:

Classe de paràmetre (tupla) Llenguatge algorísmic Llenguatge C
in nomTupla.nomCamp nomTupla.nomCamp
out nomTupla.nomCamp nomTupla->nomCamp
inout nomTupla.nomCamp nomTupla->nomCamp

6.3 Estructura d’un algorisme

A continuació es mostra l’estructura d’un algorisme amb tots els elements tractats fins ara, de manera ordenada:

const
    { Definició de constants. }
end const

type
    { Definició de tipus i tuples. }
end type

function nomFunció(nomParàmetre1: tipusValor): tipusValor
    var
        { Definició de les variables locals de la funció. }
    end var
    { Codificació de la funció. }
    return tipusValor;
end function

action nomAcció(classeParàmetre nomParàmetre1: tipusValor)
    var
        { Definició de les variables locals de l’acció. }
    end var
    { Codificació de l'acció. }
end action

algorithm nomAlgorisme
    var
        { Definició de les variables de l'algorisme. }
        nomVariable1: tipusValor;
        nomVariable2: tipusValor;
        nomVariable3: tipusValor;
    end var
    { Codificació de l’algorisme. }
    { - crida a una funció : }
    nomVariable1 := nomFunció(nomVariable2);
    { - crida a una acció : }
    nomAcció(nomVariable3);
end algorithm

6.4 Exemple: ús d’accions

A continuació s’adjunta un exemple inventat de com es poden definir accions que permetin modificar els camps d’una tupla passada com a punter. Dins del codi hi ha comentaris addicionals per tal que quedi el més clar possible:

/* Exemple CA0605 */
#include <stdio.h>
#include <string.h>

#define MAX_CHAR 10+1

typedef struct {
    char nom[MAX_CHAR];
    float nomina;
} tEmpleat;

/* Predeclaració de les accions */

void printEmpleat(tEmpleat empleat);
void setNominaEmpleat(tEmpleat *empleat, float nominaEmpleat);
void setNomEmpleat(tEmpleat *empleat, char nomEmpleat[MAX_CHAR]);

int main(int argc, char **argv) {
    /* Declarem nouEmpleat de tipus tEmpleat,
     * però no li donarem cap valor directament
     * als seus dos camps (nom i nomina): ho 
     * farem mitjançant dues accions 
     */
    tEmpleat nouEmpleat;
   
    /* Els valors dels camps nom i nomina els
     * llegirem des de teclat i els desarem inicialment
     * en les següents dues variables 
     */
    char nomEmpleat[MAX_CHAR];
    float nominaEmpleat;
   
    printf("\nNom de l'empleat : ");
    scanf("%s", nomEmpleat);
      
    printf("Nòmina (€) de l'empleat : ");
    scanf("%f", &nominaEmpleat);
   
    /* Assignem el nom i la nòmina al tEmpleat nouEmpleat
     * utilitzant les accions definides. Fixeu-vos
     * que el paràmetre nouEmpleat el passem com a punter
     * (passem la seva adreça en memòria, ja que va 
     * precedit per &) 
     */
    setNomEmpleat(&nouEmpleat, nomEmpleat);
    setNominaEmpleat(&nouEmpleat, nominaEmpleat);
   
    /* Mostrem les dades de la tupla tEmpleat nouEmpleat
     * per pantalla 
     */
    printEmpleat(nouEmpleat);
    return 0;
}

/* Implementació de les accions */

void printEmpleat(tEmpleat empleat) {
    /* El paràmetre empleat és d'entrada, amb el
     * que l'accés als seus camps ho farem
     * amb un punt : empleat.nom, empleat.nomina 
     */
    printf("\nDades de l'empleat: \n");
    printf("\tNom: %s\n", empleat.nom);
    printf("\tNòmina: %.2f €\n", empleat.nomina);
}

void setNominaEmpleat(tEmpleat *empleat, float nominaEmpleat) {
    /* El paràmetre empleat (de classe inout) és un punter,
     * per tal que des de dins de l'acció sigui possible
     * modificar el valor (d'un camp) de l'empleat
     * definit al main del nostre programa, fora de l'àmbit
     * de l'acció.
     * L'accés a un camp d'un element referenciat amb
     * un punter es fa amb '->' : empleat->nominaEmpleat. 
     */
    empleat->nomina = nominaEmpleat;
}

void setNomEmpleat(tEmpleat *empleat, char nomEmpleat[MAX_CHAR]) {
    /* Idem que en l'acció setNominaEmpleat. En aquest cas
     * a més a més cal recordar que l'assignació d'strings
     * la fem amb la funció strcpy de C, en comptes 
     * d'utilitzar l'assignació habitual '=' dels tipus 
     * primitius (char, int, float, ...) 
     */
    strcpy(empleat->nom, nomEmpleat);
}

Un exemple d’execució seria:

Nom de l'empleat : Laura
Nòmina (€) de l'empleat : 1850.32

Dades de l'empleat: 
    Nom: Laura
    Nòmina: 1850.32

6.5 Exemple: ús de funcions

Si necessitem un mètode que com a resultat retorni un string, en comptes d’utilitzar una funció farem servir una acció amb un paràmetre de sortida o d’entrada/sortida. Farem servir les funcions per retornar valors de tipus primitiu (enter, decimal, caràcter), i deixarem els tipus compostos (vectors, tuples, etc.) per les accions.

A continuació s’exposa un exemple perquè quedi més clar:

/* Exemple CA0606 */
#include <stdio.h>
#include <string.h>

/* Programa que, donada una hora, indica
 * a quina part del dia correspon.
 * Per "part del dia" s'entèn: matinada,
 * matí, migdia, tarda, vespre i nit.
 * L'hora s'aconsegueix mitjançant una
 * funció, la qual retorna un valor
 * enter en format HHMM (on HH=hora i
 * MM=minut).
 * D'altra banda, el càlcul de la part
 * del dia es realitza amb una acció,
 * la qual rep dos paràmetres: un d'entrada
 * corresponent a l'hora en format HHMM, 
 * i un altre de sortida que contindrà
 * la part del dia (string) que correspon
 * a l'hora.
 */

#define MAX_CHARS 8+1

/* Predeclaració de funcions i accions */
void calcularPartDelDia(int hora, char *part);
int demanarHora();

/* Programa principal */
int main(int argc, char **argv) {
    int hora;
    char partDelDia[MAX_CHARS];
    hora = demanarHora();
    calcularPartDelDia(hora, partDelDia);
    printf("L'hora %d correspon a: %s \n", hora, partDelDia);
    return 0;
}

/* Implementació de funcions i accions */

/* Funció que retorna un enter, corresponent
 * a l'hora introduïda per teclat, en format
 * HHMM
 */
int demanarHora() {
    int hora;
    printf("Tecleja hora (format HHMM) : ");
    scanf("%d", &hora);
    return hora;
}

/* Acció que, donada una hora (paràmetre
 * d'entrada), calcula quina part del dia
 * correspon (paràmetre de sortida). La
 * part del dia és de tipus string. El
 * paràmetre de sortida ha de ser un 
 * punter, per tal que des de dins de
 * l'acció es pugui modificar el valor
 * de la variable definida fora de 
 * l'acció, dins del main.
 */
void calcularPartDelDia(int hora, char *part) {

    /* Particionat horari extret de
     * https://www.parlament.cat/document/intrade/6698
     */

    /* 0 correspon a 0000 */
    /* 14 correspon a 0014 */
    if (hora >= 0 && hora <= 14) {
        strcpy(part, "nit");
    } else {
        /* 414 correspon a 0414 */
        if (hora > 14 && hora <= 414) {
            strcpy(part, "matinada");
        } else {
            /* 0500 correspon a 500 */
            if (hora > 414 && hora <= 1114) {
                strcpy(part, "matí");
            } else {
                if (hora > 1114 && hora <= 1414) {
                    strcpy(part, "migdia");
                } else {
                    if (hora > 1414 && hora <= 1814) {
                        strcpy(part, "tarda");
                    } else {
                        if (hora > 1814 && hora <= 2214) {
                            strcpy(part, "vespre");
                        } else {
                            if (hora > 2214 && hora <= 2359) {
                                strcpy(part, "nit");
                            } else {
                                strcpy(part, "unknown!");
                            }
                        }
                    }
                }
            }
        }
    }
}

Un exemple d’execució:

Tecleja hora (format HHMM) : 1906
L'hora 1906 correspon a: vespre

6.6 Exemple: nòmines

Imaginem que treballem amb els empleats d’una empresa. Posem per cas que després d’introduir n-empleats al nostre sistema, volem una funció que ens indiqui l’empleat que té la nòmina més petita.

Obtindrem un valor de retorn de tipus primitiu (un enter que ens indicarà la posició de l’empleat), amb el que estem davant d’una funció. A la funció li passarem un vector d’empleats amb tots els empleats que prèviament hem introduït a la nostra empresa.

Sense entrar en la codificació de la funció, el main del nostre programa pot ser similar al següent:

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

#define MAX_EMPLEATS 2
#define MAX_CARACTERS 20+1

typedef struct {
    char nom[MAX_CARACTERS];
    char cognom[MAX_CARACTERS];
    float nomina;
} tEmpleat;

int main(int argc, char **argv) {
    tEmpleat empleats[MAX_EMPLEATS];
    int posicioNomina;
    int i;

    for (i = 0; i < MAX_EMPLEATS; i++) {
        printf("\nNom empleat %d : ", i);
        scanf("%s", empleats[i].nom);
        printf("Cognom empleat %d : ", i);
        scanf("%s", empleats[i].cognom);
        printf("Nòmina empleat %d : ", i);
        scanf("%f", &empleats[i].nomina);
    }
    posicioNomina = cercaEmpleatNominaMinima(empleats);
    printf("\nL'empleat amb la nòmina més baixa és %s, %s (%.2f €)", 
        empleats[posicioNomina].cognom, 
        empleats[posicioNomina].nom, 
        empleats[posicioNomina].nomina);
    return 0;
}

Fins aquest punt no hi ha res nou: utilitzem un vector de tEmpleat per tal d’anar introduint els empleats per teclat, i per cada empleat demanem el nom, el cognom i la seva nòmina.

El que cal fer ara és implementar la funció cercaEmpleatNominaMinima, que rep com a paràmetre el vector d’empleats de l’empresa.

De moment oblidem-nos que estem davant d’una funció, simplement centrem-nos en quines són les operacions que volem fer. En aquest cas, volem fer un programa que recorri un a un tots els elements d’un vector, i trobi la posició de l’empleat que cobra menys.

Una possible codificació seria la següent:

int posicio;
int i;

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

Aquest fragment de codi simplement recorre un a un tots els tEmpleat d’un vector, comparant la nòmina més baixa trobada fins el moment amb la de l’empleat que està tractant en qüestió: si aquesta segona és més baixa, ens quedem amb la seva posició dins del vector com a empleat amb la nòmina més baixa.

Ara convertim aquest fragment de codi en una funció:

int cercaEmpleatNominaMinima(tEmpleat vector[MAX_EMPLEATS]) {
    int posicio;
    int i; 

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

Com es pot veure, l’única diferència és que ara el codi té la capçalera de la funció, la que ens diu que rep com a paràmetre un element de tipus vector de tEmpleat, i que retornarà un valor enter.

D’aquesta forma el programa complet queda de la següent manera:

/* Exemple CA0607 */
#include <stdio.h>
#include <string.h>

#define MAX_EMPLEATS 2
#define MAX_CARACTERS 20+1

typedef struct {
    char nom[MAX_CARACTERS];
    char cognom[MAX_CARACTERS];
    float nomina;
} tEmpleat;

/* Predeclaració de funcions/accions */
int cercaEmpleatNominaMinima(tEmpleat vector[MAX_EMPLEATS]);

int main(int argc, char **argv) {
    tEmpleat empleats[MAX_EMPLEATS];
    int posicioNomina;
    int i;

    for (i = 0; i < MAX_EMPLEATS; i++) {
        printf("\nNom empleat %d : ", i);
        scanf("%s", empleats[i].nom);
        printf("Cognom empleat %d : ", i);
        scanf("%s", empleats[i].cognom);
        printf("Nòmina empleat %d : ", i);
        scanf("%f", &empleats[i].nomina);
    }
    posicioNomina = cercaEmpleatNominaMinima(empleats);
    printf("\nL'empleat amb la nòmina més baixa és %s, %s (%.2f €)", 
        empleats[posicioNomina].cognom, 
        empleats[posicioNomina].nom, 
        empleats[posicioNomina].nomina);
    return 0;
}

/* Implementació de funcions/accions */
int cercaEmpleatNominaMinima(tEmpleat vector[MAX_EMPLEATS]) {
    int posicio;
    int i; 
    
    posicio = 0;
    for (i = 0; i < MAX_EMPLEATS; i++) {
        if (vector[i].nomina < vector[posicio].nomina) {
            posicio = i;
        }
    }
    return posicio;
}

Aquest exemple pot semblar senzill perquè el tipus de comparació que estem fent és numèrica: comparem dos camps de tipus float (les nòmines de dos empleats).

Ampliem ara la funcionalitat del nostre programa: volem que pugui fer una cerca per cognom entre els nostres empleats. Per fer aquesta cerca, caldrà comparar una cadena de caràcters amb el cognom de cada empleat.

Per no fer la funció més complexa del necessari, imaginarem que cap cognom es pot repetir i que sempre trobarem un cognom com el que busquem (l’objectiu és veure com funciona strcmp()). Així doncs, la nostra funció rebrà un vector d’empleats i un cognom a cercar, i retornarà la posició de l’empleat amb aquell cognom.

A continuacio es mostra el codi complet, comentant la part de l’strcmp() detalladament:

/* Exemple CA0608 */
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

#define MAX_EMPLEATS 2
#define MAX_CARACTERS 20+1

typedef struct {
    char nom[MAX_CARACTERS];
    char cognom[MAX_CARACTERS];
    float nomina;
} tEmpleat;

/* Predeclaració de funcions/accions */
int cercaEmpleatNominaMinima(tEmpleat vector[MAX_EMPLEATS]);
int cercaEmpleatPerCognom(tEmpleat vector[MAX_EMPLEATS], 
    char cognom[MAX_CARACTERS]);

int main(int argc, char **argv) {
    tEmpleat empleats[MAX_EMPLEATS];
    char cognom[MAX_CARACTERS];
    int posicioNomina;
    int posicioCognom;
    int i;

    for (i = 0; i < MAX_EMPLEATS; i++) {
        printf("\nNom empleat %d : ", i);
        scanf("%s", empleats[i].nom);
        printf("Cognom empleat %d : ", i);
        scanf("%s", empleats[i].cognom);
        printf("Nòmina empleat %d : ", i);
        scanf("%f", &empleats[i].nomina);
    }
    posicioNomina = cercaEmpleatNominaMinima(empleats);
    printf("\nL'empleat amb la nòmina més baixa és %s, %s (%.2f €)", 
        empleats[posicioNomina].cognom, 
        empleats[posicioNomina].nom, 
        empleats[posicioNomina].nomina);

    printf("\nCognom de l'empleat a cercar : ");
    scanf("%s", cognom);

    posicioCognom = cercaEmpleatPerCognom(empleats, cognom);
    printf("\nDades de l'empleat: %s, %s (%.2f €)", 
        empleats[posicioCognom].cognom, 
        empleats[posicioCognom].nom, 
        empleats[posicioCognom].nomina);
    return 0;
}

/* Implementació de funcions/accions */
int cercaEmpleatNominaMinima(tEmpleat vector[MAX_EMPLEATS]) {
    int posicio;
    int i; 
    
    posicio = 0;
    for (i = 0; i < MAX_EMPLEATS; i++) {
        if (vector[i].nomina < vector[posicio].nomina) {
            posicio = i;
        }
    }
    return posicio;
}

int cercaEmpleatPerCognom(tEmpleat vector[MAX_EMPLEATS], 
    char cognom[MAX_CARACTERS]) {
    int posicio;
    bool isTrobat;
    
    posicio = 0;
    isTrobat = false;
    while (!isTrobat) {
        /* Per comparar strings utilitzarem la funció
         * strcmp() de C. Aquesta funció compara dues
         * cadenes de caràcters, i retorna un valor
         * com a resultat de la comparació:
         * - si el valor == 0: les dues cadenes són iguals
         * - si el valor < 0: la primera cadena < segona cadena
         * - si el valor > 0: la primera cadena > segona cadena
         * En el nostre cas ens interessa detectar que
         * els cognoms siguin iguals, amb el que 
         * volem controlar que el valor que retorna
         * la funció strcmp() sigui 0. 
         */
        if (strcmp(vector[posicio].cognom, cognom) == 0) {
            isTrobat = true;
        } else {
            posicio++;
        }
    }
    return posicio;
}

6.7 Exemple: pivotDefensiva

/* Exemple CA0609 */
#include <stdio.h>
#include <string.h>

/* Rebem una petició d'un equip femení de bàsquet,
 * en el qual ens demanen un programa que els permeti
 * seleccionar la millor pivot defensiu d'entre una 
 * sèrie de candidates.
 * La millor pivot defensiu és aquella que captura
 * més rebots; en cas d'empat, s'escollirà la que
 * faci més taps.
 * Caldrà implementar 3 accions i 1 funció:
 * - acció llegirJugadora(j): llegeix de teclat i
 *   guarda tots els camps de la jugadora a la
 *   tupla j.
 * - acció mostrarJugadora(j): mostra per pantalla
 *   el valor dels camps de la tupla j.
 * - acció copiarJugadores(j1, j2): copia el valor
 *   de tots els camps de j2 cap a j1.
 * - funció compararJugadores(j1, j2): retorna -1 en 
 *   cas que la millor pivot sigui j1, i 1 en cas 
 *   que la millor sigui j2.
 */
#define MAX_NOM 20+1
#define MAX_COGNOM 20+1
#define MAX_JUGADORES 3

typedef struct {
    char nom[MAX_NOM];
    char cognom[MAX_COGNOM];
    float rebots;
    float taps;
} tJugadora;

/* Predeclaracions de funcions/accions */
void llegirJugadora(tJugadora *j);
void mostrarJugadora(tJugadora j);
void copiarJugadora(tJugadora *desti, tJugadora origen);
int compararJugadores(tJugadora j1, tJugadora j2);

/* Programa principal */
int main(int argc, char **argv) {
    tJugadora jugadores[MAX_JUGADORES];
    int resultat;
    int i;

    /* Es crea la tJugadora fictícia 
     * millorPivot que ens ajudarà a trobar 
     * la millor opció d'entre totes les 
     * candidates
     */
    tJugadora millorPivot; 
    millorPivot.rebots = 0;
    millorPivot.taps = 0;

    /* Llegim totes les jugadores amb 
     * l'acció llegirJugadora(). Aquesta
     * acció rep un paràmetre de sortida
     * (out), el qual contindrà la 
     * jugadora llegit per teclat. Com que
     * es tracta d'un paràmetre de classe 
     * out, es realitzarà un pas per 
     * referència (= passarem un punter)
     */
    for (i=0; i<MAX_JUGADORES; i++) {
        llegirJugadora(&jugadores[i]);
    }

    /* Mostrem per pantalla quina
     * és la millor jugadora amb perfil
     * pivot defensiu. La idea és anar
     * recorrent una a una les jugadores
     * del vector i comparar-les amb millorPivot:
     * 1. Si la jugadora del vector és millor
     *    que millorPivot, copiarem les dades
     *    de la jugadora cap a millorPivot.
     * 2. Si millorPivot és millor que la
     *    jugadora del vector, no farem res.
     * En finalitzar el recorregut de totes
     * les jugadores del vector, tindrem que
     * millorPivot contindrà la jugadora
     * que estem buscant.
     */
    for (i=0; i<MAX_JUGADORES; i++) {
        resultat = compararJugadores(millorPivot, jugadores[i]);
        if (resultat == 1) {
            copiarJugadora(&millorPivot, jugadores[i]);
        }
    }

    printf("\nMillor opció com a pivot defensiu : ");
    mostrarJugadora(millorPivot);
    return 0;
}

/* Implementació de les funcions/accions */
void llegirJugadora(tJugadora *j) {
    printf("Introdueix les dades de la nova jugadora: \n");
    printf("\tNom: ");
    scanf("%s", j->nom);
    printf("\tCognom: ");
    scanf("%s", j->cognom);
    printf("\t>> Promigs per partit:\n");
    printf("\tRebots: ");
    scanf("%f", &j->rebots);
    printf("\tTaps: ");
    scanf("%f", &j->taps);
}

void mostrarJugadora(tJugadora j) {
    printf("\n%s, %s: %.1f rebots, %.1f taps \n", 
        j.cognom, j.nom, j.rebots, j.taps);
}

void copiarJugadora(tJugadora *desti, tJugadora origen) {
    /* Recordem: 
     * - si el paràmetre és un punter, accedirem 
     *   als camps amb '->'
     * - si el paràmetre és un valor, accedirem
     *   als camps amb '.'
     */
    strcpy(desti->nom, origen.nom);
    strcpy(desti->cognom, origen.cognom);
    desti->rebots = origen.rebots;
    desti->taps = origen.taps;
}

int compararJugadores(tJugadora j1, tJugadora j2) {
    /* Estem buscant una jugadora que 
     * tingui un perfil de pivot defensiu, 
     * amb el que agafarem:
     * 1. Aquella que tingui més rebots per partit
     * 2. En cas d'empat de rebots, aquella que
     *    faci més taps per partit
     */ 
    int resultat;
    resultat = 0;
    if (j1.rebots > j2.rebots) {
        resultat = -1;
    } else {
        if (j1.rebots < j2.rebots) {
            resultat = 1;
        } else {
            /* En aquest punt tenim que 
             * j1.rebots == j2.rebots,
             * amb el que anem a comparar el següent
             * camp segons la prioritat definida
             * del perfil pivot defensiu
             */
            if (j1.taps > j2.taps) {
                resultat = -1;
            } else {
                if (j1.taps < j2.taps) {
                    resultat = 1;
                } else {
                    resultat = 0;
                }
            }
        }
    }
    return resultat;
}

Exemple d’execució:

Introdueix les dades de la nova jugadora: 
    Nom: Júlia
    Cognom: Sanz
    >> Promigs per partit:
    Rebots: 7.9
    Taps: 0.9
Introdueix les dades de la nova jugadora: 
    Nom: Núria
    Cognom: Gutiérrez
    >> Promigs per partit:
    Rebots: 7.9
    Taps: 1.4
Introdueix les dades de la nova jugadora: 
    Nom: Mireia
    Cognom: Mateu
    >> Promigs per partit:
    Rebots: 6.8
    Taps: 2.1

Millor opció com a pivot defensiu : 
Gutiérrez, Núria: 7.9 rebots, 1.4 taps 

6.7.1 Explicació sobre l’acció copiarJugadora()

L’acció copiarJugadora(tJugadora *desti, tJugadora origen) de l’exemple rep dos paràmetres:

  • El primer d’ells és desti, un punter a una tupla de tipus tJugadora; una altra forma de dir-ho és que el valor de desti el passem per referència. Aquest paràmetre és de classe out, ja que no utilitzem per res el valor inicial que té i només ens interessa el valor final que tindrà en executar l’acció.
  • El segon és origen, una tupla que passem per valor. Per tant, en aquest cas no estem passant el punter a una tJugadora, sinó directament una tJugadora.

Quan tenim un punter a una tupla, accedim a l’element mitjançant l’operador ->. En canvi, si estem tractant una tupla accedirem a un camp seu amb l’operador . (punt).

La codificació de l’acció és:

void copiarJugadora(tJugadora *desti, tJugadora origen) {
    /* Recordem: 
     * - si el paràmetre és un punter, accedirem 
     *   als camps amb '->'
     * - si el paràmetre és un valor, accedirem
     *   als camps amb '.'
     */
    strcpy(desti->nom, origen.nom);
    strcpy(desti->cognom, origen.cognom);
    desti->rebots = origen.rebots;
    desti->taps = origen.taps;
}

No utilitzarem el prefix & davant de la tJugadora origen, de la mateixa forma que no utilitzem l’& quan anem a imprimir per pantalla amb printf() el valor d’una variable: com que estem tractant amb un valor, simplement hi accedim i l’utilitzem. Per tant, aquestes accions serien incorrectes:

    strcpy(desti->nom, &origen.nom);
    strcpy(desti->cognom, &origen.cognom);
    desti->rebots = &origen.rebots;
    desti->taps = &origen.taps;

Sí que tenim una alternativa possible a l’operador ->, segons s’indica a la xWiki:

    strcpy((*desti).nom, origen.nom);
    strcpy((*desti).cognom, origen.cognom);
    (*desti).rebots = origen.rebots;
    (*desti).taps = origen.taps;

Per tant, aquest darrer bloc de codi es pot substituir a l’exemple i tot continua funcionant correctament, ja que són equivalents. Es pot utilitzar una forma o l’altra, tot i que l’operador -> sembla més fàcil d’entendre visualment.

6.8 Errors més freqüents

6.8.1 Definició d’accions/funcions: noms de paràmetres

Pseudocodi incorrecte:

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

En l’exemple mostrat, sembla que la intenció és definir dos paràmetres, però falta indicar l’identificador (nom) per a cada un d’ells. Recordem que en la declaració de la capçalera d’una acció/funció cal indicar per a cada paràmetre:

  • La classe de paràmetre que rep l’acció o funció, (in, out o inout).
  • El tipus de dades del paràmetre (tHotel en l’exemple).
  • L’identificador associat al paràmetre (no definit en l’exemple).

Pseudocodi correcte:

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

6.8.2 Definició d’accions i funcions: classes de paràmetres

Pseudocodi incorrecte:

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

En la declaració de la capçalera d’una acció cal indicar la classe dels paràmetres que rep: in, out o inout. Els paràmetres de classe in no es poden modificar a l’interior de l’acció, mentre sí que podem fer-ho en el cas dels paràmetres de classe out o inout. En l’exemple, s’està modificant el paràmetre hotel1 actualitzant el valor d’un dels seus camps. O bé es tracta d’una confusió entre els dos paràmetres, o bé hotel1 ha de ser de classe out.

Pseudocodis correctes:

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

Fixeu-vos que si el paràmetre hotel1 és de classe out i hem de llegir el paràmetre hotel2, aquest segon paràmetre ha de ser forçosament de classe in o inout, per la qual cosa n’hem modificat la classe a la capçalera de la funció.

6.8.3 Definició de funcions: tipus de dades de retorn

Pseudocodi incorrecte:

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

En l’exemple mostrat, hotelCmp està definida com a funció, amb els paràmetres d’entrada h1 i h2, però les funcions sempre retornen un valor, i cal indicar el seu tipus a la mateixa capçalera.

Pseudocodi correcte:

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

Observeu que, en aquest cas, no cal indicar les classes dels paràmetres (in, out o inout), perquè en les funcions els paràmetres sempre són d’entrada i no es poden modificar.

6.8.4 Definició d’accions: retorn de valor

Pseudocodi incorrecte:

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

Aquí s’ha definit una acció anomenada hotelTableInit, que rep un paràmetre d’entrada/sortida (inout) de tipus tHotelTable, amb l’identificador tabHotels. Les accions mai retornen un valor, però erròniament s’ha interpretat que cal indicar aquest fet afegint :void al final de la capçalera de l’acció.

Aquest cas és doblement incorrecte degut al fet que :void és un identificador propi de C que no existeix en llenguatge algorísmic.

Pseudocodi correcte:

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

Observeu que en aquest cas, en tractar-se d’una acció cal indicar les classes dels seus paràmetres: in, out o inout.

6.8.5 Definició d’accions/funcions: accions obertes

Pseudocodi incorrecte:

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

Les funcions i accions constitueixen blocs de codi amb unes funcionalitats i característiques especials, i com a tal han d’estar ben delimitades, mitjançant les paraules reservades functionend function i actionend action. Deixar una acció sense tancar pot semblar un tema menor, però en C això comportarà amb tota seguretat un error de compilació (sovint difícil de detectar).

Pseudocodi correcte:

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 Crida a accions/funcions: classes de paràmetres

Pseudocodi incorrecte:

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

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

Les classes dels paràmetre que rep una acció/funció, així com el tipus de dades i l’identificador del paràmetre, cal indicar-los únicament en la definició de la capçalera de l’acció. En el cas de funcions, no indicarem les classes dels paràmetres, ja que sempre són in.

En la crida a una acció/funció únicament cal indicar els paràmetres.

Pseudocodi correcte:

hotelRead(h1);
hotelRead(h2);

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

6.8.7 Crida a accions/funcions: paraules reservades

Pseudocodi incorrecte:

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

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

La paraula reservada action cal indicar-la únicament en la definició de la capçalera de l’acció.

En la crida a una acció únicament cal indicar el nom de l’acció i els paràmetres; es incorrecte afegir novament la paraula reservada action.

Pseudocodi correcte:

hotelRead(h1);
hotelRead(h2);

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

6.8.8 Crida a funcions: crida buida

Pseudocodi incorrecte:

bestHotel: integer;

compareHotels(h1,h2);

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

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

Com passa en totes les funcions, hotelRead() retorna un valor: en aquest cas, la variable bestHotel. No obstant, la crida que es fa de la funció no és correcta, perquè el retorn de la funció no es guarda enlloc, i la variable bestHotel del programa principal segueix sense haver-se actualitzat.

En el cas de C aquest codi no provocaria un error, però si no fem res més des del programa principal no tindrem accés al resultat que retorna la funció (que és el que se suposa que volíem fer).

Pseudocodi correcte:

bestHotel: integer;

bestHotel:= compareHotels(h1,h2);

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

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

El retorn d’una funció també es pot utilitzar en una expressió, en aquest cas sense assignar-lo a cap variable, ja que en la mateixa expressió, el compilador calcularà el retorn de la funció i el substituirà el valor corresponent dins la funció:

Pseudocodi correcte:

bestHotel: integer;

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

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

6.8.9 Crida a funcions: absència de paràmetres

Pseudocodi incorrecte:

var
    myReal: real;
    myInteger: integer;
end var

myInteger:= realToInteger();

La funció realToInteger retorna un valor i el guardem a la variable myInteger. El problema és que la funció necessita que li passem un paràmetre quan la cridem: en aquest cas, el nombre real a convertir a enter.

En cas de dubte, cal que consultem sempre la capçalera de declaració de la funció, ja que ens indicarà en cada cas el nombre i el tipus dels paràmetres que espera.

Pseudocodi correcte:

var
    myReal: real;
    myInteger: integer;
end var

myInteger:= realToInteger(myReal);

6.8.10 Crida a funcions: errors de sintaxi múltiples

Pseudocodi incorrecte:

var
    hotel1: tHotel;
end var

hotelRead(in: &myHotel);

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

El pseudocodi anterior mostra una barreja d’errors de sintaxi diversos en la crida a la funció (n’és només un exemple): - S’indica la classe de paràmetre in. - S’inclou l’operador :. - S’afegeix sintaxi pròpia de C amb l’operador &, per passar el paràmetre myHotel per referència. En llenguatge algorísmic el pas d’un paràmetre per referència es determina mitjançant la capçalera de la funció.

Pseudocodi correcte:

var
    hotel1: tHotel;
end var

hotelRead(myHotel);

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

6.8.11 Definició de funcions: funcions dins el main

Codi incorrecte:

#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;
}

Les funcions i accions cal definir-les de forma individual i separada. No és correcte definir funcions niades o incloses dins d’altres (com per exemple el main), entre altres coses perquè aquestes funcions no estaran disponibles des de l’exterior de la funció on són creades.

Codi correcte:

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

#define MAX_LEN 15

/* Predeclaració de la funció */
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 Trencament del flux d’execució: funció amb múltiples return

Aquest no és un error sintàctic o semàntic, sinó una mala pràctica de disseny i programació.

Pseudocodi incorrecte:

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

Les dues funcions tenen com a objectiu trobar si el paràmetre que reben conté algun número parell: la primera d’elles ho comprova sobre un únic enter, mentre que la segona ho revisa en un vector d’enters. La mala pràctica està en el trencament del flux d’execució: en una funció no hi pot haver més d’un return.

Per solucionar-ho, utilitzarem una variable auxiliar que anirem actualitzant amb el resultat a calcular i, al final de tot, executarem el return de la variable.

Pseudocodi correcte:

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