3 PAC3

3.1 Com declarar un vector en llenguatge algorísmic

Quan es declara un vector en llenguatge algorísmic no cal declarar una variable per cadascuna de les posicions d’aquest vector.

Per exemple, si volem un algorisme que sumi tres enters continguts dins d’un vector, podem fer el següent:

const
    NUM_ENTERS: integer = 3;
end const

algorithm sumaEnters

    var
        enters: vector[NUM_ENTERS] of integer;
        suma: integer;
    end var

    enters[1] := 13;
    enters[2] := 24;
    enters[3] := 2;

    { Com es pot veure, accedim directament a les posicions }
    { del vector en comptes de definir una variable }
    { per cada posició. }

    suma := enters[1] + enters[2] + enters[3];

    { Cal recordar també que en llenguatge algorísmic, }
    { en un vector de mida n la primera posició del vector } 
    { és la 1 i la darrera la n; en canvi en llenguatge C }
    { la primera sempre és la 0 i l'última la n-1. }

    writeString("La suma dels 3 enters del vector és: ");
    writeInteger(suma);

end algorithm

3.2 Significat dels arguments del main

La principal diferència entre la definició main(int argc, char **argv) i main() és que la primera opció està preparada per rebre arguments quan s’executa el programa i la segona no.

Per exemple, si tenim el següent programa compilat en C i li passem una sèrie d’arguments des de la línia de comandes:

$> programa a1 a2 a3

Amb el main definit com a main(int argc, char **argv) podem accedir des de dins del programa a tots els arguments passats; així haurem de:

argc = 4

argv[0] = "programa"
argv[1] = "a1"
argv[2] = "a2"
argv[3] = "a3"

El mateix sistema operatiu s’ocupa de donar-li el valor a l’argument int argc (número total d’arguments inclòs el nom del programa), amb el que únicament ens hem de preocupar de passar els arguments. D’altra banda, argv és un array de punters on cadascun d’ells apunta a un argument format per una cadena de caràcters; així argv contindrà a cadascuna de les seves posicions els arguments passats des de línia de comandes, i en la posició 0 el nom del programa.

Si, en canvi, tenim definit el programa com a main(), simplement no hi ha forma d’accedir als arguments que se li puguin arribar a passar. Moltes vegades podem tenir les dades ja definides dins del mateix programa o pot ser que les anem a consultar a una font externa, per la qual cosa, no tenir la capacitat de processar arguments no suposa cap impediment a l’hora d’executar el nostre programa.

3.3 Assignar valors a un vector

La lectura i assignació de valors a un vector es realitza de la següent forma en llenguatge algorísmic:

const
    MAX_TEMP: integer = 2;
end const

algorithm lecturaTemperatures

    var
        temperatures: vector[MAX_TEMP] of float;
    end var

    writeString("Introdueix la lectura 1 : ");
    temperatures[1] := readReal();

    writeString("Introdueix la lectura 2 : ");
    temperatures[2] := readReal();
    
    writeString("Els valors introduïts han estat : ");
    
    writeString("> Valor de la posició ");
    writeInteger(1);
    writeString(" : ");
    writeReal(temperatures[1]);
    
    writeString("> Valor de la posició ");
    writeInteger(2);
    writeString(" : ");
    writeReal(temperatures[2]);

end algorithm

I en llenguatge C:

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

#define MAX_TEMP 2

int main(int argc, char **argv) {
    float temperatures[MAX_TEMP];

    printf("Introdueix la lectura 1 : ");
    scanf("%f", &temperatures[0]);

    printf("Introdueix la lectura 2 : ");
    scanf("%f", &temperatures[1]);
    
    printf("> Valor de la posició %d : %.1f \n", 0, temperatures[0]);
    printf("> Valor de la posició %d : %.1f \n", 1, temperatures[1]);
    return 0;
}

Cal remarcar una diferència important:

  • En llenguatge algorísmic les posicions del vector van des de la 1 fins a la N.
  • En llenguatge C, van des de la 0 fins a la N-1.

En tots dos casos N fa referència al nombre total d’elements del vector.

3.4 Stack smashing detected

L’error stack smashing detected (code dumped) es produeix quan s’intenta accedir o operar amb una posició d’un vector que no hem definit prèviament. Es pot donar per diferents situacions que acaben generant el mateix problema.

  • Cas 1: es defineix un vector de N-posicions, però en comptes de començar per la posició 0 ho fem per la 1. Això és incorrecte: recordem que en C la posició inicial d’un vector sempre és la 0, i la final sempre és N-1. Exemple, per un vector de 3 posicions tindrem:

int vector1[3];
vector1[1] = 13;  /* Posició del vector1 vàlida */
vector1[2] = 24;  /* Posició del vector1 vàlida */
vector1[3] = 48;  /* Posició del vector1 no vàlida! */
/* En canvi la posició 0 del vector, 
 * que tenim disponible no l'hem utilitzat! 
 */
  • Cas 2: es defineix un vector amb menys posicions de les que necessitem. Per exemple, si tenim:

int vector2[2];
Significa que les posicions reservades en memòria per aquest vector són:

vector2[0] = 10;  /* Posició del vector2 vàlida */
vector2[1] = 13;  /* Posició del vector2 vàlida */
vector2[2] = 24;  /* Posició del vector2 no vàlida! */
Per tant, qualsevol operació amb vector2[2] ens generarà l’error indicat. Si volem que el vector contingui 3 elements només cal definir-ne correctament la mida:

int vector2[3];

3.5 Concatenació en llenguatge algorísmic

A diferència del llenguatge C, en notació algorísmica no es preveu l’ús d’especificadors que permetin fer concatenacions entre cadenes de caràcters, enters, decimals, etc.

Per tant, en llenguatge algorísmic cal trencar els strings amb fragments més petits i que facin referència únicament a un tipus de dades. Per exemple, si es vol mostrar per pantalla el missatge “L’empleat que cobra més és Marta, i la seva nòmina és de 4675.30 €.”, ho farem de la següent forma:

writeString("L'empleat que cobra més és ");
writeString(nomEmpleat);
writeString(", i la seva nòmina és ");
writeReal(nomina);
writeString(" €.");

En llenguatge C equivaldria a:

printf("L'empleat que cobra més és %s, i la seva nòmina és de %.2f €.", 
    nomEmpleat, nomina);

3.6 Importància dels tipus utilitzats en llenguatge C

El resultat de les operacions en llenguatge C depèn del tipus de variable definit i dels tipus de valors utilitzats. A continuació s’exposen tres casos que, segons el tipus que s’hagi definit en les variables utilitzades, donarà un resultat o un altre:

Cas 1:

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

int main(int argc, char **argv) {
    int numero1;
    int numero2;
    int numero3;
    int mitjana;

    printf("Tecleja el primer número: ");
    scanf("%d", &numero1);
    printf("Tecleja el segon número: ");
    scanf("%d", &numero2);
    printf("Tecleja el tercer número: ");
    scanf("%d", &numero3);

    mitjana = (numero1 + numero2 + numero3) / 3;

    printf("La mitjana és %d", mitjana);
    return 0;
}

En aquest cas, si donem els valors number1 = 1, number2 = 3, number3 = 4, el resultat és average = 2. El resultat de la divisió serà un enter, ja que tant numerador com denominador estan formats per enters. El resultat enter es desa en una variable entera, i per pantalla obtindrem: 2.

Cas 2:

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

int main(int argc, char **argv) {
    int numero1;
    int numero2;
    int numero3;
    float mitjana;

    printf("Tecleja el primer número: ");
    scanf("%d", &numero1);
    printf("Tecleja el segon número: ");
    scanf("%d", &numero2);
    printf("Tecleja el tercer número: ");
    scanf("%d", &numero3);

    mitjana = (numero1 + numero2 + numero3) / 3;

    printf("La mitjana és %f", mitjana);
    return 0;
}

Igual que en el cas anterior, el numerador i el denominador de la divisió estan formats per enters, amb la qual cosa el resultat serà un enter. En aquest cas, el resultat enter el desem en una variable de tipus float, i per això C mostrarà el resultat amb decimals: 2.000000

Cas 3:

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

int main(int argc, char **argv) {
    int numero1;
    int numero2;
    int numero3;
    float mitjana;

    printf("Tecleja el primer número: ");
    scanf("%d", &numero1);
    printf("Tecleja el segon número: ");
    scanf("%d", &numero2);
    printf("Tecleja el tercer número: ");
    scanf("%d", &numero3);

    mitjana = (numero1 + numero2 + numero3) / 3.0;

    printf("La mitjana és %f", mitjana);
    return 0;
}

En aquest cas, el resultat de la divisió serà un decimal, ja que el denominador conté un decimal (en aquest cas 3.0). El resultat amb decimals es guarda en una variable de tipus float, i per pantalla es mostrarà : 2.666667.

3.7 Exemple: notaFinal

El següent exemple calcula la nota final d’una assignatura en funció d’una sèrie de condicionals i d’operacions:

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

/* Volem un programa que calculi la nota final
 * d'una assignatura. La nota final es calcula a partir
 * de l'AC (Avaluació continua) i la nota de la Pràctica:
 *
 * Nota final = 30% AC + 70% Pràctica
 *
 * Una vegada entrades totes les notes, si se'n
 * detecta alguna que sigui incorrecta (fora del
 * rang [0.0 a 10.0]), es mostrarà un missatge
 * informatiu per pantalla i no es realitzarà cap 
 * més operació.
 *
 * L'AC està formada per 3 PAC: PAC1, PAC2, PAC3. 
 * La nota de l'AC es calcula mitjançant la 
 * mitjana de les 3 PAC.
 *
 * Si la nota de l'AC és inferior a 4, no cal 
 * realitzar cap càlcul: l'assignatura queda suspesa.
 *
 * Si la nota de l'AC és superior o igual a 4, es 
 * calcula la nota final juntament amb la nota de 
 * la Pràctica.
 * 
 * La nota final es mostrarà en format "grade letters", 
 * segons la següent relació:
 *
 * MH: 10
 * A: de 9.0 a 9.9
 * B: de 7.0 a 8.9
 * C+: de 5.0 a 6.9 
 * C-: de 3.0 a 4.9 
 * D: de 0.0 a 2.9
 *
 * Els punts que tracta aquest exemple:
 * - definició de vectors
 * - utilització del condicional if-else
 * - condicionals if-else niats
 * - assignació d'un string a una variable amb strcpy
 * - reserva espai pel finalitzador '\0'
 */

#define PAC1 0
#define PAC2 1
#define PAC3 2
#define PRA 3
#define MAX_ACTIVITATS 4
#define MAX_CHARS 2+1    /* el +1 correspon al finalitzador '\0' */
#define PES_AC 0.3       /* AC 30% pes de la nota final */
#define PES_PRA 0.7      /* PRA 70% pes de la nota final */

int main(int argc, char **argv) {
    /* Vector que conté les notes de 
     * totes les activitats del curs 
     */
    float notes[MAX_ACTIVITATS]; 

    /* Nota d'avaluació continua: equival
     * a la mitjana de les 3 PAC 
     */
    float notaAC;
    float notaFinalNumerica;

    /* String que conté la nota final 
     * de l'assignatura (MH, A, B...) 
     */
    char notaFinal[MAX_CHARS];

    /* Es demana des de teclat les notes
     * de les 3 PAC i de la PRA 
     */
    printf("Nota PAC1: ");

    /* L'assignació d'un valor es pot
     * fer directament sobre una posició
     * del vector
     */
    scanf("%f", &notes[PAC1]);

    /* Idem per la resta d'activiats */
    printf("Nota PAC2: ");
    scanf("%f", &notes[PAC2]);
    printf("Nota PAC3: ");
    scanf("%f", &notes[PAC3]);
    printf("Nota PRA: ");
    scanf("%f", &notes[PRA]);

    /* Primer de tot, comprovem que
     * totes les notes del vector estiguin
     * dins del rang [0.0 .. 10.0]
     */
    if (notes[PAC1] > 10.0 || notes[PAC2] > 10.0 ||
        notes[PAC3] > 10.0 || notes[PRA] > 10.0 ||
        notes[PAC1] < 0.0 || notes[PAC2] < 0.0 ||
        notes[PAC3] < 0.0 || notes[PRA] < 0.0) {

        printf("\n>> Error detectat en una o més notes:");
        printf("\n>> S'atura el càlcul de la nota final.\n");

    } else {

        /* En aquest punt sabem que totes les notes
         * estan dins del rang [0.0 .. 10.0]
         */

        /* Comprovem ara que la mitjana de les 3 PAC 
         * no sigui inferior a 4
         */
        notaAC = (notes[PAC1] + notes[PAC2] + notes[PAC3]) / 3.0;

        if (notaAC < 4) {

           /* Per donar millor visibilitat, mostrem només
            * el primer decimal de les notes numèriques
            */
           printf("\n>> Nota mínima d'AC insuficient: %.1f", notaAC);
           printf("\n>> S'atura el càlcul de la nota final.\n");

        } else {

            /* En aquest punt totes les notes són correctes,
             * per tant es pot començar amb el càlcul de la 
             * nota final
             */
            notaFinalNumerica = notaAC * PES_AC + notes[PRA] * PES_PRA;

            /* Ara falta saber quina "grade letter" correspon
             * a la notaFinalNumerica calculada; ho solucionem
             * amb nous if-else niats
             */
            if (notaFinalNumerica <= 2.9) {

                /* Per assignar un string a una variable
                 * de tipus string, utilitzem la comanda
                 * strcpy, no pas '='
                 */
                strcpy(notaFinal, "D");

            } else {
                if (notaFinalNumerica <= 4.9) {
                    strcpy(notaFinal, "C-");

                } else {
                    if (notaFinalNumerica <= 6.9) {
                        strcpy(notaFinal, "C+");

                    } else {
                        if (notaFinalNumerica <= 8.9) {
                            strcpy(notaFinal, "B");

                        } else {
                            if (notaFinalNumerica <= 9.9) {
                                strcpy(notaFinal, "A");

                            } else {
                                strcpy(notaFinal, "MH");
                            }
                        }
                    }
                }
            }

            /* Per finalitar, mostrem tots els resultats
             * calculats per pantalla
             */
            printf("\n>> Nota AC: %.1f", notaAC);
            printf("\n>> Nota PRA: %.1f", notes[PRA]);
            printf("\n>> Nota final: %s (%.1f)\n", notaFinal, notaFinalNumerica);
        }
    }

    return 0;
}

3.8 Exemple: lloguerFurgoneta

Imaginem que tenim una empresa de lloguer de furgonetes i volem oferir als nostres possibles clients un comparador de preus. Aquest comparador tindrà en compte el número de kilòmetres estimat que farà el client amb la nostra furgoneta, i el número de dies que la tindrà en lloguer.

Per calcular l’import del lloguer utilitzarem dos valors: d’una banda el consum per kilòmetre de la furgoneta, i d’altra banda el preu de lloguer per dia. Aquests valors variaran en funció de la furgoneta que vulguin llogar, amb el que cadascun d’ells el desarem en un vector diferent. La posició (índex) del vector ens indicarà quina furgoneta estem tractant.

El programa demanarà des del canal estàndard d’entrada el número de dies que es vol llogar i els kilòmetres estimats. A continuació calcularà el cost total de lloguer per cadascuna de les furgonetes i desarà el resultat en un altre vector (l’índex continuarà sent l’identificador de la furgoneta).

Degut a la gran quantitat de demanda de furgonetes, només ens en queden tres per llogar. El programa s’ha de resoldre únicament amb vectors i blocs condicionals if-else (no es poden utilitzar bucles). El programa ha de retornar com a resultat quina és la furgoneta més econòmica de les tres en funció de les dades entrades, desglossant tots els elements que s’han tingut en compte per realitzar el càlcul.

Primer de tot, ens plantejarem mentalment què ha de fer el nostre algorisme:

  1. Definir les constants.
  2. Definir les variables.
  3. Llegir els valors requerits des del canal d’entrada estàndard.
  4. Calcular el cost total i desar-lo al vector de resultats.
  5. Mirar quina de les furgonetes és la més econòmica.
  6. Mostrar per pantalla el desglòs detallat de la més econòmica.

Una possible forma de codificar l’algorisme pot ser la següent:

const
    NUM_FURGONETES: integer = 3;
    PREU_LITRE_COMBUSTIBLE: real = 1.912;
end const

algorithm lloguerFurgoneta
    var
       consumsPerKm: vector[NUM_FURGONETES] of real;
       preusLloguerPerDia: vector[NUM_FURGONETES] of real;
       preusFinals: vector[NUM_FURGONETES] of real;

       { Valors d'entrada }
       numDiesLloguer: integer;
       previsioKm: integer;

       { Utilitzarem la següent variable per saber quina d'elles
       és la més econòmica de totes, i així poder mostrar el desglòs
       final. }
       furgonetaMesEconomica: integer;
    end var

    { Inicialitzem els vectors amb les dades de cada furgoneta.
    Els següents valors formen part de la nostra aplicació
    de lloguer, no es demanen pel canal d'entrada. }
    consumsPerKm[1] := 8.25/100.0;
    consumsPerKm[2] := 12.0/100.0;
    consumsPerKm[3] := 9.5/100.0;

    preusLloguerPerDia[1] := 80.5;
    preusLloguerPerDia[2] := 69.9;
    preusLloguerPerDia[3] := 72.8;

    { Lectura de valors des del canal d’entrada. }
    writeString("Quants dies vols llogar la furgoneta? ");
    numDiesLloguer := readInteger();
    writeString("Quants kilòmetres faràs? ");
    previsioKm := readInteger();

    { Càlcul de costos totals per furgoneta. }
    preusFinals[1] := preusLloguerPerDia[1] * integerToReal(numDiesLloguer) +
    consumsPerKm[1] * integerToReal(previsioKm) * PREU_LITRE_COMBUSTIBLE;

    preusFinals[2] := preusLloguerPerDia[2] * integerToReal(numDiesLloguer) +
    consumsPerKm[2] * integerToReal(previsioKm) * PREU_LITRE_COMBUSTIBLE;

    preusFinals[3] := preusLloguerPerDia[3] * integerToReal(numDiesLloguer) +
    consumsPerKm[3] * integerToReal(previsioKm) * PREU_LITRE_COMBUSTIBLE;

    { Mirem quina de les tres és la més econòmica, comparant els corresponents
    costos totals de les furgonetes. Fixeu-vos que s'utilitza una estructura de
    condicionals niats (un dins de l'altre). Guardem l'identificador de la opció
    més econòmica dins de la variable furgonetaMesEconomica. }

    if preusFinals[1] ≤ preusFinals[2] and 
        preusFinals[1] ≤ preusFinals[3] then
        furgonetaMesEconomica := 1;
    else
        if preusFinals[2] ≤ preusFinals[1] and 
            preusFinals[2] ≤ preusFinals[3] then
            furgonetaMesEconomica := 2;
        else
            furgonetaMesEconomica := 3;
        end if
    end if

    { Es mostra el detall de valors de la furgoneta més econòmica
    de totes, així com el seu import final. }
    
    writeString("La furgoneta més econòmica és la número: ");
    writeInteger(furgonetaMesEconomica);
    writeString(">> Consum litres per kilòmetre: ");
    writeReal(consumsPerKm[furgonetaMesEconomica]);
    writeString(">> Preu per litre de combustible: ");
    writeReal(PREU_LITRE_COMBUSTIBLE);
    writeString("€");
    writeString(">> Previsió kilòmetres: ");
    writeInteger(previsioKm);
    writeString(">> Preu de lloguer diari: ");
    writeReal(preusLloguerPerDia[furgonetaMesEconomica]);
    writeString("€");
    writeString(">> Dies de lloguer: ");
    writeInteger(numDiesLloguer);
    writeString(">> Import total: ");
    writeReal(preusFinals[furgonetaMesEconomica]);
    writeString(" €");

end algorithm

Una vegada tenim l’algorisme definit, només queda que traduïr-lo al llenguatge C:

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

#define NUM_FURGONETES 3
#define PREU_LITRE_COMBUSTIBLE 1.912

int main(int argc, char **argv) {

    float consumsPerKm[NUM_FURGONETES];
    float preusLloguerPerDia[NUM_FURGONETES];

    /* El cost total de cada furgoneta es desarà en el vector
    * preusFinals.
    */
    float preusFinals[NUM_FURGONETES];

    /* Valors d'entrada per teclat */
    int numDiesLloguer;
    int previsioKm;

    /* Utilitzarem la següent variable per saber quina d'elles
    * és la més econòmica de totes, i així poder mostrar el desglòs
    * final.
    */
    int furgonetaMesEconomica;

    /* Inicialitzem els vectors amb les dades de cada furgoneta.
    * Els següents valors formen part de la nostra aplicació
    * de lloguer, no es demanen per teclat.
    */
    consumsPerKm[0] = 8.25/100.0;
    consumsPerKm[1] = 12.0/100.0;
    consumsPerKm[2] = 9.5/100.0;

    preusLloguerPerDia[0] = 80.5;
    preusLloguerPerDia[1] = 69.9;
    preusLloguerPerDia[2] = 72.8;

    /* Lectura de valors des de teclat. */
    printf("Quants dies vols llogar la furgoneta? ");
    scanf("%d", &numDiesLloguer);
    printf("Quants kilòmetres faràs? ");
    scanf("%d", &previsioKm);

    /* Càlcul de costos totals per furgoneta */
    preusFinals[0] = preusLloguerPerDia[0] * (float)numDiesLloguer +
    consumsPerKm[0] * (float)previsioKm * PREU_LITRE_COMBUSTIBLE;

    preusFinals[1] = preusLloguerPerDia[1] * (float)numDiesLloguer +
    consumsPerKm[1] * (float)previsioKm * PREU_LITRE_COMBUSTIBLE;

    preusFinals[2] = preusLloguerPerDia[2] * (float)numDiesLloguer +
    consumsPerKm[2] * (float)previsioKm * PREU_LITRE_COMBUSTIBLE;

    /* Mirem quina de les tres és la més econòmica, 
    * comparant els corresponents costos totals 
    * de les furgonetes. Fixeu-vos que s'utilitza 
    * una estructura de condicionals niats (un dins de l'altre). 
    * Guardem l'identificador de la opció més econòmica 
    * dins de la variable furgonetaMesEconomica.
    */
    if (preusFinals[0] <= preusFinals[1] && preusFinals[0] <= preusFinals[2]) {
        furgonetaMesEconomica = 0;
    } else {
        if (preusFinals[1] <= preusFinals[0] && preusFinals[1] <= preusFinals[2]) {
            furgonetaMesEconomica = 1;
        } else {
            furgonetaMesEconomica = 2;
        }
    }

    /* Es mostra el detall de valors de la furgoneta més econòmica
    * de totes, així com el seu import final.
    */
    printf("\nLa furgoneta més econòmica és la número %d: \n", 
        furgonetaMesEconomica);
    printf(">> Consum litres per kilòmetre: %.2f \n", 
        consumsPerKm[furgonetaMesEconomica]);
    printf(">> Preu per litre de combustible: %.2f € \n", 
        PREU_LITRE_COMBUSTIBLE);
    printf(">> Previsió kilòmetres: %d \n", 
        previsioKm);
    printf(">> Preu de lloguer diari: %.2f €\n", 
        preusLloguerPerDia[furgonetaMesEconomica]);
    printf(">> Dies de lloguer: %d \n", 
        numDiesLloguer);
    printf(">> Import total: %.2f € \n", 
        preusFinals[furgonetaMesEconomica]);

    return 0;
}

Quan mirem quina de les furgonetes és la més econòmica, utilitzem condicionals niats (un dins de l’altre). Això ens permet fer més òptim el nostre programa: en cas que detectem que es compleix la primera de les condicions (la furgoneta més econòmica és la 0), ja no fem cap més comprovació de les que quedarien pendents (la més econòmica és la 1? la més econòmica és la 2?). És molt important tenir sempre present aquest plantejament, per tal que els vostres programes no realitzin més comprovacions de les necessàries.

A més, hem utilitzat la variable furgonetaMesEconomica per saber quina d’elles és la més econòmica de les 3. El valor que conté aquesta variable és realment un índex, de forma que quan es mostrin les dades de la més econòmica per pantalla, simplement l’utilitzarem sobre els vectors i anirem recuperant els valors de la posició indicada per l’índex. Així ens estalviem d’anar desant tots els valors dels vectors en variables auxiliars de tipus consumPerKmFurgonetaEconomica, preuLloguerPerDiaFurgonetaEconomica, costTotalFurgonetaEconomica. Utilitzant una única variable, furgonetaMesEconomica, obviem la creació de múltiples variables innecessàries.

3.9 Exemple: aprovarAssignatura

A continuació es planteja un exemple que ens permetrà saber com podem superar una assignatura. L’aproximació inicial, poc òptima, avalua totes les condicions possibles utilitzant condicionals niats:

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

/* Volem un programa que ens indiqui si hem superat l'assignatura
 * a partir dels resultats obtinguts en les 4 àrees d'avaluació:
 * l'avaluació contínua (AC), les pràctiques (PR), la prova de
 * síntesi (PS) i l'examen final (EX).
 * A partir del pla docent, veiem que l'assignatura es pot superar
 * en els següents casos
 * 1. Si se supera l'AC, la PR i la PS.
 * 2. Si se supera l'AC, la PR i l'EX.
 * 3. Si se supera la PR i l'EX.
 * Una possible solució (GENS ÒPTIMA I MOLT MILLORABLE!!) seria...
 */

int main(int argc, char **argv) {

    bool hasAC;
    bool hasPR;
    bool hasPS;
    bool hasEX;
    bool assignSuperada;
    int intToBool;

    printf("Has superat l'avaluació contínua? (0-false, 1-true) :");
    scanf("%d", &intToBool);
    hasAC = intToBool;

    printf("Has superat les pràctiques? (0-false, 1-true) :");
    scanf("%d", &intToBool);
    hasPR = intToBool;

    printf("Has superat la prova de síntesi? (0-false, 1-true) :");
    scanf("%d", &intToBool);
    hasPS = intToBool;

    printf("Has superat l'examen? (0-false, 1-true) :");
    scanf("%d", &intToBool);
    hasEX = intToBool;

    if (hasAC) {
        if (hasPR) {
            if (hasPS) {
                if (hasEX) {
                    assignSuperada = true;
                } else {
                    assignSuperada = true;
                }
            } else {
                if (hasEX) {
                    assignSuperada = true;
                } else {
                    assignSuperada = false;
                }
            }
        } else {
            if (hasPS) {
                if (hasEX) {
                    assignSuperada = false;
                } else {
                    assignSuperada = false;
                }
            } else {
                if (hasEX) {
                    assignSuperada = false;
                } else {
                    assignSuperada = false;
                }
            }
        }
    } else {
        if (hasPR) {
            if (hasPS) {
                if (hasEX) {
                    assignSuperada = true;
                } else {
                    assignSuperada = false;
                }
            } else {
                if (hasEX) {
                    assignSuperada = true;
                } else {
                    assignSuperada = false;
                }
            }
        } else {
            if (hasPS) {
                if (hasEX) {
                    assignSuperada = false;
                } else {
                    assignSuperada = false;
                }
            } else {
                if (hasEX) {
                    assignSuperada = false;
                } else {
                    assignSuperada = false;
                }
            }
        }
    }

    if (assignSuperada) {
        printf(">> Assignatura superada. Enhorabona! \n");
    } else {
        printf(">> Assignatura no superada. \n");
    }

    return 0;
}

Aquesta gran estructura de condicionals niats es pot simplificar. Si mostrem tots els possibles valors que poden prendre les variables booleanes en una taula, tenim que:

hasAC hasPR hasPS hasEX assignSuperada
false false false false false
false false false true false
false false true false false
false false true true false
false true false false false
false true false true true
false true true false false
false true true true true
true false false false false
true false false true false
true false true false false
true false true true false
true true false false false
true true false true true
true true true false true
true true true true true

Com es pot veure, únicament s’aprovarà l’assignatura en dos casos:

  • Si s’ha superat la pràctica (hasPR), l’avaluació contínua (hasAC) i la prova de síntesi (hasPS).
  • Si s’ha superat la pràctica (hasPR) i l’examen (hasEX).

Per tant, podem substituir tots els condicionals niats per una única expressió:

assignSuperada = (hasPR && hasAC && hasPS) || (hasPR && hasEX)

Que també equival a:

assignSuperada = hasPR && ((hasAC && hasPS) || hasEX)

Així, una solució molt més òptima i correcta és la següent:

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

/* Codificació alternativa del mateix exercici: més eficient i òptima. */

int main(int argc, char **argv) {

    bool hasAC;
    bool hasPR;
    bool hasPS;
    bool hasEX;
    bool assignSuperada;
    int aux;

    printf("Has superat l'avaluació contínua? (0-false, 1-true) :");
    scanf("%d", &aux);
    hasAC = aux;

    printf("Has superat les pràctiques? (0-false, 1-true) :");
    scanf("%d", &aux);
    hasPR = aux;

    printf("Has superat la prova de síntesi? (0-false, 1-true) :");
    scanf("%d", &aux);
    hasPS = aux;

    printf("Has superat l'examen? (0-false, 1-true) :");
    scanf("%d", &aux);
    hasEX = aux;

    /* Expressió equivalent */
    assignSuperada = hasPR && ((hasAC && hasPS) || hasEX);

    if (assignSuperada) {
        printf(">> Assignatura superada. Enhorabona! \n");
    } else {
        printf(">> Assignatura no superada. \n");
    }

    return 0;
}

3.10 Errors més freqüents

3.10.1 Strings: declaració com a vectors de caràcters en llenguatge algorísmic

Pseudocodi incorrecte:

var
    name: vector[MAX_CHAR] of char;
end var

En l’algorisme anterior, s’intenta emular el llenguatge C a l’hora de declarar una variable de tipus string, declarant-la com un vector de caràcters. Això és incorrecte i absolutament innecessari, perquè en llenguatge algorísmic que existeix el tipus string i, per tant, la seva declaració és molt més senzilla.

Pseudocodi correcte:

var
    name: string;
end var

3.10.2 Sintaxi pròpia de C: funcions complexes

Pseudocodi incorrecte:

function hotelCmp(hotel1: tHotel, hotel2: tHotel): boolean;
    if strcmp(hotel1.brand, hotel2.brand) = 0 then
        {...}
    end if
end function

La intenció de l’algorisme és comparar dos camps de tipus string de les variables hotel1 i hotel2. No obstant això, es fa ús de la funció strcmp(), que és pròpia de C i no existeix en llenguatge algorísmic.

En llenguatge algorísmic, la comparació de dues cadenes de caràcters és molt més senzilla: es fa directament amb =.

Pseudocodi correcte:

function hotelCmp(hotel1: tHotel, hotel2: tHotel): boolean;
    if (hotel1.brand = hotel2.brand) then
        { ... }
    end if

3.10.3 Estructura alternativa: condicionals consecutius

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

Pseudocodi incorrecte:

if discountHotel ≥ 0 and discountHotel ≤ 10 then
    writeString("Invalid data");
end if

if discountHotel > 10 and discountHotel ≤ 20 then
    writeString("Not bad");
end if

if discountHotel > 20 and discountHotel ≤ 50 then
    writeString("Good!");
end if

L’algorisme anterior vol mostrar un missatge en pantalla en funció del valor de la variable discountData. Per aquest propòsit, res millor que una estructura alternativa, però no de la manera com està dissenyada.

Fixeu-vos que s’han construit tres blocs ifend if independents i consecutius. Això vol dir que durant l’execució, tots els blocs seran avaluats de forma consecutiva per decidir si cal executar el codi interior o no. Això no és necessari ni desitjable, ja que augmenta el temps d’execució.

Sempre que puguem, cal construir estructures alternatives niades i excloents, de forma que només s’avaluïn les condicions imprescindibles.

Pseudocodi correcte:

if discountHotel ≥ 0 and discountHotel ≤ 10 then
    writeString("Invalid data");
else 
    if discountHotel > 10 and discountHotel ≤ 20 then
        writeString("Not bad");
    else 
        if discountHotel > 20 and discountHotel ≤ 50 then
            writeString("Good!");
        end if    
    end if
end if

3.10.4 Estructura alternativa: condicionals buits

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

Pseudocodi incorrecte:

if discountHotel ≥ 0 then
else
    writeString("Invalid data");
end if

L’algorisme anterior vol mostrar un missatge d’error en pantalla si el valor de la variable discountHotel és negatiu, però l’estructura per fer-ho no és apropiada. En cas que es compleixi la condició discountHotel ≥ 0, l’estructura alternativa no executa res. Recordem que és possible una estructura if sense else, i de fet sembla que en aquest cas s’hagi afegit únicament perquè es creia el contrari.

Pseudocodi correcte:

if discountHotel < 0 then
    writeString("Invalid data");
end if

3.10.5 Estructura alternativa: interrompre un algorisme (I)

Pseudocodi incorrecte:

algorithm nameAlgorithm
    if discountHotel < 0 then
        writeString("Invalid data");
        end algorithm;
    else
        writeString("Continue...");
        { ... }
    end if

Sovint ens demanen d’interrompre l’execució d’un algorisme en cas que es doni alguna situació, per exemple, un error en l’entrada de dades. Pel que fa al disseny algorísmic, no hi ha cap funció ni sentència pensada per efectuar aquesta acció de forma explícita. En l’agorisme de l’exemple, s’intenta fer-ho utiltzant la sentència end algorithm, però això no és correcte.

Senzillament cal muntar l’estructura alternativa de forma que quan es produeixi l’error, no s’executi cap més sentència, per exemple, posant tot el codi a executar dins el bloc de l’else. Ens hem d’assegurar, això sí, que un cop sortim del bloc ifelse, l’algorisme no té res pendent per executar.

Un disseny molt més elegant seria amb les condicions inverses, deixant sempre els errors i la sortida de l’algoritme dins el bloc else:

Pseudocodi correcte:

algorithm nameAlgorithm
    if discountHotel ≥ 0 then
        writeString("Continue...");
        { Do something }
    else
        writeString("Invalid data");
        { Nothing else to do }
    end if
end algorithm;

3.10.6 Estructura alternativa: interrompre un algorisme (II)

Pseudocodi incorrecte:

algorithm nameAlgorithm
    if discountHotel < 0 then
        writeString("Invalid data");
        exit();
    else
        writeString("Continue...");
        { ... }
    end if
end algorithm

Hi ha vegades en les que es vol emular en llenguatge algorísmic algunes funcions de C que interrompen l’execució del codi, com per exemple exit(). Això és incorrecte ja que aquesta funció no existeix en llenguatge algorísmic; de fet, en llenguatge C, encara que existeixi, també cal evitar-la, ja que el seu ús no és una bona pràctica de programació.

Pseudocodi correcte:

algorithm nameAlgorithm
    if discountHotel ≥ 0 then
        writeString("Continue...");
        { Do something }
    else
        writeString("Invalid data");
        { Nothing else to do }
    end if
end algorithm;

3.10.7 Constants i nombres: Valors numèrics en el codi (hardcode)

Pseudocodi incorrecte:

const 
    NUM_SEATS1: integer = 34;
    NUM_RIDES: integer = 3;
    A1: integer = 1;
    A2: integer = 2;
    A3: integer = 3;
end const

var
    emptySeats: vector[3] of integer;
end var

{ input values }
writeString("EMPTY SEATS  ");
writeString(NAME_RIDE1);
writeString("  >> ");
emptySeats[1] := readInteger();

En l’algoritme anterior, es declara el vector d’enters emptySeats, amb una longitud igual a 3 posicions. Posteriorment, es llegeix un enter i es guarda a la primera posició del vector. A l’enunciat de l’exercici es donaven unes constants ja declarades, i es demanava explícitament utilitzar-les per operar amb els vectors.

El motiu no és altre que introduir el (bon) costum de fer servir constants i evitar al màxim els valors numèrics directes al codi (tècnica coneguda com hardcode). D’aquesta manera, és molt més fàcil mantenir el codi posteriorment i fer-hi modificacions.

En cas que volguéssim modificar la longitud del vector, o guardar els valors en posicions diferents, només hauríem de modificar la constant al principi del codi, en comptes d’haver de modificar totes les línies on apareixen aquests valors numèrics.

Pseudocodi correcte:

const 
    NUM_SEATS1: integer = 34;
    NUM_RIDES: integer = 3;
    A1: integer = 1;
    A2: integer = 2;
    A3: integer = 3;
end const

var
    emptySeats: vector[NUM_RIDES] of integer;
end var

{input values}
writeString("EMPTY SEATS  ");
writeString(NAME_RIDE1);
writeString("  >> ");
emptySeats[A1] := readInteger();

3.10.8 Constants: declaració de constants mal ubicada

Codi incorrecte:

#include <stdio.h>

int main(int argc, char **argv) {
    #define MAX_LEN 15
    char brand[MAX_LEN];
    return 0;
}

Les constants s’han de declarar al principi del bloc de codi, i fora de qualsevol funció (sigui el main o una altra), ja que igual que els tipus de dades, les constants es defineixen de forma global per a tot el programa. No és correcte obrir un bloc de declaració de constants dins un bloc central de codi, i encara menys obrir diversos blocs de constants a mesura que les necessitem.

Codi correcte:

#include <stdio.h>
#define MAX_LEN 15

int main(int argc, char **argv) {
    char brand[MAX_LEN];
    /* ... */
    return 0;
}

3.10.9 Strings: comparació directa

Codi incorrecte:

#include <stdio.h>
#define MAX_LEN 15

int main(int argc, char **argv) {
    char name1[MAX_LEN];
    char name2[MAX_LEN];
    if (name1 == name2) {
        /* ... */
    }
    return 0;
}

En C les cadenes de caràcters (així com la resta de vectors) no es poden comparar directament amb l’operador ==. En el seu lloc tenim dues opcions:

  • En el cas dels vectors, es poden comparar element a element, de forma individual.
  • En el cas dels strings, el C disposa de funcions específiques, com ara strcmp().

Codi correcte:

#include <stdio.h>
#define MAX_LEN 15

int main(int argc, char **argv) {
    char name1[MAX_LEN];
    char name2[MAX_LEN];
    if (strcmp(name1, name2) == 0) {
        /* ... */
    }
    return 0;
}