7 PAC5

7.1 Quan utilitzar & dins de funcions/accions

De vegades quan fem crides a accions dins d’accions, veiem que si passem un paràmetre amb & els resultats no són correctes, però en canvi passant el paràmetre sense l’& tot funciona. I a l’inrevés.

Per tal d’aclarir aquests dubtes utilitzarem el següent exemple: és un programa senzill que treballa amb tipus tPac. Un element tPac conté dos atributs: un nom (cadena de caràcters) i una nota (decimal). El programa realitza la lectura de valors des de teclat amb l’acció pacRead(...), la modificació del nom de la PAC amb nomToUpperCase(...), i la seva posterior impressió per pantalla mitjançant pacWrite(...).

Amb l’objectiu de deixar-ho tot el més clar possible s’han afegit comentaris extensos dins del programa, explicant en cada situació què es fa i per quin motiu.

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

/* Definició de constants */
#define MAX_NOM 5+1

/* Definició de la tupla tPac */
typedef struct {
    char nom[MAX_NOM];
    float nota;
} tPac;

/* Predeclaració de funcions i accions */

/* Acció que llegeix per teclat els atributs d'un 
 * tipus tPac i li assigna els valors. En aquest
 * cas el paràmetre pac és de classe 'out', donat que 
 * el seu valor abans i després d'executar l'acció
 * haurà canviat. A més, com que el valor inicial
 * de pac no ens interessa per res (recordem que
 * l'objectiu d'aquesta acció és llegir de teclat
 * i donar valor a pac, per tant sobreescriurem 
 * qualsevol valor previ que tingui), podem 
 * descartar que sigui de classe 'inout'.
 * Quan un paràmetre és de classe out/inout, el 
 * passem per referència o, el que és el mateix,
 * passem un punter a un tipus d'element; com 
 * es pot veure, aquí pac és un punter a un 
 * element de tipus tPac, ja que va precedit per *.
 * Utilitzem un punter perquè és l'única manera
 * que tenim de modificar un element definit
 * fora de l'àmbit de l'acció, des de dins de 
 * la pròpia acció.
 */
void pacRead(tPac *pac);

/* Acció que, donat un tipus tPac, mostra el seu 
 * contingut (nom i nota) per pantalla. Aquí el 
 * paràmetre pac és de classe 'in': el seu valor
 * abans i després d'executar l'acció no variarà.
 * Un paràmetre de classe 'in' el passem per valor: 
 * en aquest cas és un element de tipus tPac. 
 * Com es pot veure, no ha d'anar precedit per *.
 */
void pacWrite(tPac pac);

/* Acció que, donat un tipus tPac, agafa el seu
 * nom i el passa a majúscules. Per exemple, si
 * el nom és "pAc01" el modificarà a "PAC01".
 * El paràmetre és de classe 'inout': el seu valor
 * abans i després d'executar l'acció haurà canviat
 * i a més a més, el valor inicial que té és
 * important, ja que el necessitem per calcular el
 * valor final ("pAc01" --> "PAC01"). En ser un
 * paràmetre de classe 'inout', passarem el seu
 * valor per referència: necessitem que pugui ser
 * modificat des de dins de l'acció, amb el que
 * és necessari treballar amb un punter a l'element
 * pac, d'aquí que vagi precedit amb *.
 */
void nomToUpperCase(tPac *pac);

/* Programa principal */
int main(int argc, char **argv) {
    /* Definim les variables */
    tPac pac1, pac2, pac3;

    /* Donem valor als atributs de cadascuna
     * de les 3 PAC. Fixeu-vos que estem passant
     * punters a elements de tipus tPac: el &
     * previ indica que agafem la direcció de 
     * memòria on resideix l'element de tipus 
     * tPac. Com que passem punters a memòria, 
     * des de dins de l'acció podrem modificar
     * el contingut de pac1, pac2 i pac3, tot
     * i que aquestes tres variables han estat
     * definides fora de l'àmbit de l'acció.
     */
    pacRead(&pac1);
    pacRead(&pac2);
    pacRead(&pac3);
    
    /* Mostrem per pantalla els atributs de
     * cadascuna de les 3 PAC. En aquest cas
     * el pas de paràmetres es fa per valor:
     * passem directament els elements de tipus
     * tPac, ja que aquests no seran modificats
     * des de dins de l'acció (són de classe 'in').
     */
    pacWrite(pac1);
    pacWrite(pac2);
    pacWrite(pac3);
    return 0;
}

/* Implementació de funcions i accions */

void pacRead(tPac *pac) {
    /* Llegim des de teclat el valor
     * corresponent al nom de la PAC
     */
    printf("Introdueix nom : ");
    scanf("%s", pac->nom);

    /* Llegim des de teclat el valor
     * corresponent a la nota de la PAC
     */
    printf("Introdueix nota: ");
    scanf("%f", &pac->nota);
    
    /* Ara arribem en un punt on, dins d'una
     * acció, cridem a un altra acció. Hem de 
     * passar com a atribut pac? o bé &pac?. 
     * Davant d'aquest dubte, ens hem de 
     * preguntar quin tipus de valor conté ara
     * mateix (abans d'executar la següent acció)
     * el paràmetre pac. Si recordem com està 
     * definida l'acció pacRead(tPac *pac), pac
     * és un punter a memòria, ja que va precedit
     * per *. Això significa que en aquest precís
     * moment pac continua sent un punter.
     * D'altra banda, si ens fixem amb la definició
     * de l'acció nomToUpperCase(tPac *pac), veiem
     * que també espera rebre un punter. Així com 
     * que pac és un punter i nomToUpperCase(...)
     * espera rebre un punter, simplement li passem
     * pac com a paràmetre (i no pas &pac!)
     */
    nomToUpperCase(pac);
    printf("----------------------\n");
}

void pacWrite(tPac pac) {
    /* Mostrem per pantalla els valors
     * dels atributs de la PAC
     */
    printf(">> %s amb nota %.1f \n", pac.nom, pac.nota);
}

void nomToUpperCase(tPac *pac) {
    /* Hi ha llibreries que ja implementen els canvis
     * a majúscules o minúscules. En aquest cas però
     * hem optat per no utilitzar-ne cap, i implementar-la
     * nosaltres mateixos, tractant l'string nom de pac
     * com a un recorregut caràcter a caràcter.
     */
    int i;
    for (i = 0; pac->nom[i] != '\0'; i++) {
        if (pac->nom[i] >= 'a' && pac->nom[i] <= 'z') {
            pac->nom[i] = pac->nom[i] + ('A' - 'a');
        }
    }
}

7.2 Multiple definition of NOM_CONSTANT

Quan utilitzem constants definides amb const en un programa modulat es pot obtenir l’error multiple definition of NOM_CONSTANT.

Per exemple: tenim el següent programa modulat en tres arxius, dni.h dni.c main.c, que calcula la lletra que li correspon a un DNI.

El contingut de dni.h és:

const char CODI[] = "TRWAGMYFPDXBNJZSQVHLCKE";
const int DENOMINADOR = 23;

/* Predeclaració de les funcions/accions */
int llegeix();
char calculaLletra(int numero);
void mostrar(int numero, char lletra);

El contingut de dni.c és:

#include <stdio.h>
#include "dni.h"

/* Implementació de les funcions/accions */
int llegir() {
    int dni;
    printf("Tecleja un DNI (sense lletra): ");
    scanf("%d", &dni);
    return dni;
}

char calcularLletra(int numero) {
    int resultat;
    resultat = numero % DENOMINADOR;
    return CODI[resultat];
}

void mostrar(int numero, char lletra) {
    printf("El DNI amb lletra és: %d-%c \n", numero, lletra);
}

El contingut de main.c és:

/* Exemple CA0702 */
#include "dni.h"

/* Codi principal */
int main(int argc, char **argv) {
    int dni;
    char lletra;
    dni = llegir();
    lletra = calcularLletra(dni);
    mostrar(dni, lletra);
    return 0;
}

Si executem el programa, retornarà els següents errors:

#include "dni.h"
... dni.h:3: multiple definition of `CODI'
... dni.h:3: first definition here 
... dni.h:4: multiple definition of `DENOMINADOR'
... dni.h:4: first definition here 

L’error està causat perquè es fa l’include de dni.h a dos punts diferents del programa:

  • A l’arxiu dni.c, per poder utilitar les constants de dni.h.
  • A l’arxiu main.c, per poder utilitzar les accions i funcions predeclarades a dni.h.

Aquest fet intenta definir en memòria dues vegades la constant CODI i dues més la constant DENOMINADOR, una situació que no és possible: no es permet tenir múltiples constants definides amb el mateix nom en un mateix àmbit.

Per solucionar l’error tenim dues opcions:

Opció 1:

Utilitzar #define en comptes de const. Canviant d’aquesta forma la definició de les constants a l’arxiu dni.h se soluciona l’error:

/* Exemple CA0703 */
#define CODI "TRWAGMYFPDXBNJZSQVHLCKE"
#define DENOMINADOR 23

/* Predeclaració de les funcions/accions */
int llegeix();
char calculaLletra(int numero);
void mostrar(int numero, char lletra);

Opció 2:

L’opció static permet modificar l’àmbit de la constant, passant de ser una constant de classe a una constant d’arxiu, impedint d’aquesta manera que hi hagin duplicats en l’àmbit global del programa. Per tant, podem solucionar l’error afegir static a la definició de les constants amb const dins de l’arxiu dni.h.

/* Exemple CA0704 */
static const char CODI[] = "TRWAGMYFPDXBNJZSQVHLCKE";
static const int DENOMINADOR = 23;

/* Predeclaració de les funcions/accions */
int llegeix();
char calculaLletra(int numero);
void mostrar(int numero, char lletra);

7.3 Tipus de paràmetres en accions i funcions

En les accions els paràmetres poden ser de classe in, out o inout. Cal especificar-ho en el moment de definir l’acció al nostre algorisme.

Exemple: tres formes d’implementar una suma de dos enters amb diferents accions segons els classes in, out o inout dels paràmetres:

action suma1(in num1: integer, in num2: integer)
    var
        resultat: integer;
    end var

    resultat := num1 + num2;
    writeString("Resultat de la suma = ");
    writeInteger(resultat);
end action

action suma2(in num1: integer, in num2: integer, out resultat: integer)
    resultat := num1 + num2;
end action

action suma3(inout num1: integer, in num2: integer)
    num1 := num1 + num2;
end action

A les funcions, en canvi, tots els paràmetres són d’entrada i, per tant, no cal indicar l’in.

Exemple:

function suma4(num1: integer, num2: integer): integer
    var
        resultat: integer;
    end var

    resultat := num1 + num2;
    return resultat;
end function

7.4 Pas per valor vs pas per referència

El clàssic pas per valor correspon als paràmetres de tipus in, en els quals passem la variable/valor.

D’altra banda, el pas per referència consisteix a passar com a paràmetre de tipus out o inout la direcció de memòria de la variable (punter).

7.5 Exemple: capgirarParaula

Imaginem que tenim una tupla tParaula que té dos camps:

  • cadena : conté l’string amb el valor de la tParaula.
  • numeroCaractersCadena : conté el número de caràcters de la cadena.

Implementem l’acció capgirar(), que fa dues operacions:

  • Capgira el camp cadena de la tParaula; per exemple, si entrem “Fonaments” el resultat serà “stnemanoF”.
  • Calcula el valor de numeroCaractersCadena; si tenim com a cadena “Fonaments”, el valor serà 9.

Volem que per teclat es demani el valor pel camp cadena de dues tParaula, i en totes dues volem aplicar l’acció capgirar(). Una possible forma d’implementar-ho tot plegat seria la següent:

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

#define MAX_CHAR 20+1
#define MAX_PARAULES 2

typedef struct {
    char cadena[MAX_CHAR];
    int numeroCaractersCadena;
} tParaula;

/* Predeclaració de l'acció.
 * Aquesta acció reb un paràmetre inout de tipus tParaula,
 * el qual capgira el camp cadena, i calcula el valor
 * corresponent pel camp numeroCaractersCadena 
 */
void capgirar(tParaula *mot);

int main(int argc, char **argv) {
    int i;
    /* Introduim per teclat un total de MAX_PARAULES */
    for (i = 0; i < MAX_PARAULES; i++) {
        tParaula paraula;
        printf("Introdueix una paraula : ");
        scanf("%s", paraula.cadena);
        capgirar(&paraula);
        printf("La paraula capgirada és : %s, de %d lletres.\n", 
            paraula.cadena, paraula.numeroCaractersCadena);
    }
}

/* Implementació de l'acció */
void capgirar(tParaula *mot) {
    tParaula motCapgirat;
    int midaMot;
    int i;
 
    midaMot = strlen(mot->cadena);
    for (i=0; i<midaMot; i++ ) {
        motCapgirat.cadena[(midaMot-1)-i] = mot->cadena[i];
    }
    
    /* Indiquem el finalitzador de l'string */
    motCapgirat.cadena[midaMot] ='\0';
    
    strcpy(mot->cadena, motCapgirat.cadena);
    mot->numeroCaractersCadena = midaMot;
}

L’acció només rep un paràmetre tParaula, ja que únicament s’executa sobre una tParaula. Com que la volem executar per a cadascuna de les dues tParaula, repetim la crida dues vegades dins del bucle, una per cadatParaula.

7.6 Exemple: isParell

Exemple de funció que retorna un booleà:

/* Exemple CA0706 */
#include <stdio.h>
#include <stdbool.h>

/* Predeclaració de la funció isParell, la qual retorna un
 * booleà que indica si el número passat per paràmetre és
 * parell (true) o no (false). 
 */
bool isParell(int numero);

int main(int argc, char **argv) {
    int numero;
    
    printf("Tecleja un número : ");
    scanf("%d", &numero);

    if (!isParell(numero)) {
        printf("El número %d és senar.\n", numero);
    } else {
        printf("El número %d és parell.\n", numero);
    }
}

/* Implementació de la funció */
bool isParell(int numero) {
    bool resultat;
    if (numero % 2 == 0) {
        resultat = true;
    } else {
        resultat = false;
    }
    return resultat;
    
    /* L'estructura condicional anterior es pot
     * substituir per la següent expressió equivalent:
     * return (numero % 2 == 0);
     */
}

7.7 Exemple: pivotDefensiuTirsLliures

S’afegeix a l’exemple previ pivotDefensiva un nou factor de comparació: en cas d’empat de les comparacions anteriors, escollirem la que tingui un millor percentatge de tirs lliures.

/* Exemple CA0707 */
#include <stdio.h>
#include <string.h>
#include <stdbool.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. En cas d'empat ens interessarà
 * escollir la que tingui millor percentatge
 * de tirs lliures.
 * Caldrà implementar 3 accions i 2 funcions:
 * - acció llegirJugadora(j): llegeix de teclat i
 *   guarda tots els atributs de la jugadora a la
 *   tupla j.
 * - acció mostrarJugadora(j): mostra per pantalla
 *   el valor dels atributs de la tupla j.
 * - acció copiarJugadores(j1, j2): copia el valor
 *   de tots els atributs 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.
 * - funció percentatgeTirsLliures(intentats, encertats):
 *   retorna el percentatge d'encert en tirs lliures 
 *   en funció dels valors passats per paràmetre.
 */
 
#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;
    float tirsLliures; /* en percentatge */
} tJugadora;

/* Predeclaracions */
void llegirJugadora(tJugadora *j);
void mostrarJugadora(tJugadora j);
void copiarJugadora(tJugadora *desti, tJugadora origen);
int compararJugadores(tJugadora j1, tJugadora j2);
float percentatgeTirsLliures(int intentats, int encertats);

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

    /* 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;
    millorPivot.tirsLliures = 0.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 tipus 
     * out, es realitzarà un pas per 
     * referència (= passarem un punter)
     */
    for (i=0; i<MAX_JUGADORES; i++) {
        llegirJugadora(&vJugadores[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, vJugadores[i]);
        if (resultat != -1) {
            copiarJugadora(&millorPivot, vJugadores[i]);
        }
    }
    
    printf("\nMillor opció com a pivot defensiu : ");
    mostrarJugadora(millorPivot);
    return 0;
}

/* Implementació de les accions */
void llegirJugadora(tJugadora *j) {
    int intentats;
    int encertats;
    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);
    printf("\tTirs lliures intentats: ");
    scanf("%d", &intentats);
    printf("\tTirs lliures encertats: ");
    scanf("%d", &encertats);
    j->tirsLliures = percentatgeTirsLliures(intentats, encertats);
}

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

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

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
     * 3. En cas d'empat, la que tingui millor
     *    percentatge de tirs lliures
     */
    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
             * atribut segons la prioritat definida
             * del perfil pivot defensiu
             */
            if (j1.taps > j2.taps) {
                resultat = -1;
            } else {
                if (j1.taps < j2.taps) {
                    resultat = 1;
                } else {
                    /* Afegim la variant de valorar
                     * el percentatge de tirs lliures
                     */
                    if (j1.tirsLliures >= j2.tirsLliures) {
                        resultat = -1;
                    } else {
                        resultat = 1;
                    }
                }
            }
        }
    }
    return resultat;
}

float percentatgeTirsLliures(int intentats, int encertats) {
    return ((float)encertats/intentats)*100.0;
}

7.8 Exemple: IMC

Imaginem que volem fer un programa que ens calculi l’IMC (índex de massa corporal) d’una persona. El programa tindrà dues accions i una funció:

  • llegirDades(): acció que rebrà 2 paràmetres de classe out, el pes i l’alçada de la persona.
  • calcularIMC(): funció que rebrà 2 paràmetres de classe in, el pes i l’alçada, i retornarà un valor decimal.
  • mostrarIMC(): acció que rebrà 1 paràmetre de classe in, el valor de l’IMC, i el mostrarà per pantalla.

En primer lloc, dissenyem l’algorisme:

action llegirDades(out pes: real, out alcada: real)
    writeString("Introdueix el pes (kg): ");
    pes := readReal();
    writeString("Introdueix l'alçada (m): ");
    alcada := readReal();
end action

function calcularIMC(pes: real, alcada: real): real
    var
        imc: real;
    end var
    imc := pes / (alcada * alcada);
    return imc;
end function

action mostrarIMC(in imc: real)
    writeString("IMC = ");
    writeReal(imc);
end action

algorithm IMC
    var
        pes: real;
        alcada: real;
        resultat: real;
    end var

    llegirDades(pes, alcada);
    resultat := calcularIMC(pes, alcada);
    mostrarIMC(resultat);
end algorithm

A continuació fem la conversió de l’algorisme a llenguatge C. S’han afegit explicacions com a comentaris perquè es vegi clarament el tipus de paràmetres passats (punter o valor) segons la seva classe:

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

/* Predeclaració de funcions i accions */

/* Els paràmetres pes i alcada de l'acció
 * llegirDades són de classe out, amb el que
 * els precedim amb * (ens indica que són
 * punters).
 */
void llegirDades(float *pes, float *alcada);

/* Els paràmetres pes i alcada de la funció
 * calcularIMC són de classe in, com tots
 * els paràmetres de les funcions.
 */
float calcularIMC(float pes, float alcada);

/* El paràmetre imc de l'acció mostrarIMC
 * és de classe in.
 */
void mostrarIMC(float imc);

int main(int argc, char **argv) {
    /* Definició de les variables */
    float pes;
    float alcada;
    float resultat;

    /* A l'acció llegirDades() li passem els punters
     * de les variables pes i alcada,
     * per tal que el seu valor pugui ser modificat
     * des de dins de l'acció. Són paràmetres de
     * classe out.
     */
    llegirDades(&pes, &alcada);

    /* En el cas de la funció calcularIMC() i l'acció
     * mostrarIMC(), en ser tots els paràmetres de
     * classe in, els passem per valor.
     */
    resultat = calcularIMC(pes, alcada);
    mostrarIMC(resultat);
    return 0;
}

/* Implementació de funcions i accions */

/* L'acció llegirDades s'encarrega de llegir
 * des de teclat el pes i l'alçada d'una
 * persona, i assigna el valor als paràmetres
 * pes i alcada. Fixeu-vos que tant pes com
 * alcada són punters (van precedits amb *),
 * amb el que quan fem l'scanf ja no cal
 * afegir el prefix &.
 */
void llegirDades(float *pes, float *alcada) {
    printf("Introdueix el pes (kg): ");
    scanf("%f", pes);
    printf("Introdueix l'alçada (m): ");
    scanf("%f", alcada);
}

/* Funció que realitza el càlcul de l'IMC */
float calcularIMC(float pes, float alcada) {
    float imc;
    imc = pes / (alcada*alcada);
    return imc;
}

/* Acció que mostra per pantalla el valor
 * que conté el paràmetre imc, de classe in.
 */
void mostrarIMC(float imc) {
    printf("IMC = %.1f \n", imc);
}

A continuació es mostra un exemple d’execució del programa:

Introdueix el pes (kg): 56
Introdueix l'alçada (m): 1.69
IMC = 19.6

7.9 Exemple: mitjanaNotes

Volem un programa que ens calculi la nota mitjana de 3 PAC. La nota de cada PAC l’anem entrant per teclat i ha de ser un valor entre [0..10]. En cas que alguna de les notes no pertanyi a aquest rang, volem que el programa mostri un missatge d’error i finalitzi.

En aquest cas no volem que el bucle es limiti a fer les n-iteracions corresponents: volem que, donada una situació concreta, puguem sortir del bucle. Ho podem aconseguir afegint una variable booleana a la condició d’entrada del bucle (en el cas d’aquest exemple, isValorCorrecte).

Una possible implementació del programa en llenguatge C és la següent:

/* Exemple CA0709 */
#include <stdio.h>
#include <stdbool.h>

#define NUM_PACS 3

int main(int argc, char **argv) {
    float nota;
    float sumaNotes;
    bool isValorCorrecte;
    int i;

    isValorCorrecte = true;
    sumaNotes = 0.0;
    i = 0;

    /* S'utilitza un bucle while que té dues
    * condicions de finalització:
    * - que hagi fet totes les iteracions
    * possibles (valor indicar per NUM_PACS).
    * - que detecti que la nota teclejada
    * no pertanyi al rang [0..10].
    */
    while (i<NUM_PACS && isValorCorrecte) {
        printf("Nota PAC%d: ", i + 1);
        scanf("%f", &nota);

        /* Si la nota introduïda està fora del
        * rang [0..10], modifiquem el valor
        * de la variable booleana isValorCorrecte
        * a false. Això provocarà que la condició
        * d'entrada al bucle sigui false i, per
        * tant, sortirem d'ell.
        */
        if (nota < 0.0 || nota > 10.0) {
            isValorCorrecte = false;

        /* Si el valor de la nota és correcte,
        * continuem fent les operacions corresponents.
        */
        } else {
            sumaNotes = sumaNotes + nota;
            i = i + 1;
        }
    }

    /* Si no s'ha detectat cap error en les notes
    * significarà que el valor de isValorCorrecte continua
    * sent true, amb el que mostrarem el resultat de la
    * mitjana per pantalla.
    */
    if (isValorCorrecte) {
        printf("Mitjana = %.2f \n", sumaNotes / (float)NUM_PACS);

    /* En cas contrari, mostrem el missatge d'error. */
    } else {
        printf("Error: el valor de la nota és incorrecte! \n");
    }
    return 0;
}

Un exemple d’execució sense errors:

Nota PAC1: 9.0
Nota PAC2: 10.0
Nota PAC3: 8.4
Mitjana = 9.13

Un exemple d’execució amb error:

Nota PAC1: 9.3
Nota PAC2: 12
Error: el valor de la nota és incorrecte!