2 PAC2

2.1 Booleans en C

Alguns punts a considerar amb els booleans en C:

  • Quan utilitzem el tipus bool de C ens cal importar la llibreria <stdbool.h>, ja que el tipus bool no es va definir a les primeres versions del llenguatge C.
  • Els valors que pot prendre una variable booleana en C són false i true. El llenguatge C tracta internament aquests valors com a enters: false correspon a 0 i true a 1.
  • Quan vulguem introduïr el valor d’un booleà per teclat o bé mostrar-lo per pantalla, utilitzarem l’enter 0 per referir-nos a false i 1 per true.
  • L’especificador de tipus dels booleans és %d.
  • Per mostrar el valor d’una variable booleana en C ho podem fer de la següent forma:

bool isVocal;
printf("La lletra %c és una vocal (0=false, 1=true) ? %d\n", lletra, isVocal);
  • Per llegir un booleà des de teclat, ho farem de la següent forma:

bool variable;
int intToBool;
scanf("%d", &intToBool);
variable = (bool)intToBool;

El llenguatge C no disposa de cap especificador de tipus que treballi només amb 1 bit, amb el que utilitzarem una variable auxiliar que ajudi a fer una conversió intermitja a int, per tal de transformar posteriorment el valor a bool.

En cas de no llegir els booleans d’aquesta forma, pot derivar en dues situacions diferents, segons el compilador de C que utilitzem:

  • Provocar que la variable de tipus bool no contingui el valor esperat, fet que ocasionarà comportaments incorrectes en el nostre programa.
  • Generar un warning del següent tipus: warning: format '%d' expects argument of type 'int *', but argument 2 has type '_Bool *' [-Wformat=]. Aquest avís significa que estem utilitzant un especificador de tipus (%d) diferent del que li correspondria al tipus bool, que treballa únicament amb 1 bit.

Per tant, per assegurar el correcte funcionament del nostre programa en qualsevol cas, utilitzarem sempre la variable auxiliar per llegir un booleà des de teclat.

2.2 Booleans definits com a enumeratius

En semestres anteriors de l’assignatura de Fonaments de Programació, s’utilitzava un enumeratiu per definir el tipus booleà:

typedef enum {FALSE, TRUE} boolean;

Aquesta forma de definir el tipus booleà és obsoleta i no s’utilitza aquest semestre; tal i com s’ha comentat a l’apartat Booleans en C, els booleans els definirem mitjançant la llibreria <stdbool.h>. Tingueu-ho present quan consulteu PAC, PR i PS de semestres anteriors, en els quals s’utilitzava la nomenclatura ara obsoleta.

2.3 Constants: define vs const

La definició de constants tant es pot fer amb define com amb const. Tot i això, la forma de comportar-se d’aquestes dues opcions és completament diferent, si bé el resultat final és el mateix:

  • define: quan utilitzem aquesta opció no es desa en cap posició de memòria el valor de la constant. El que es fa realment és que en els passos previs a la mateixa compilació del programa, el preprocessador substitueix totes les referències del define pel valor indicat.

Per exemple, si tenim el següent programa amb una constant creada amb define :

/* Exemple CA0201 */
#include <stdio.h>
#define MIDA 8

char lletres[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
int vertical, horitzontal;

int main(int argc, char **argv) {
    /* font: https://en.wikipedia.org/wiki/Chess */
    for (vertical=MIDA; vertical>=1; vertical--) {
        for (horitzontal=0; horitzontal<=MIDA-1; horitzontal++) {
            printf("%c%d ", lletres[horitzontal], vertical);
        }
        printf("\n");
    }
    return 0;
}

Abans de la compilació, el preprocessador, entre altres accions, elimina comentaris i substitueix totes les referències MIDA per 8:

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

char lletres[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
int vertical, horitzontal;

int main(int argc, char **argv) {
    for (vertical=8; vertical>=1; vertical--) {
        for (horitzontal=0; horitzontal<=8-1; horitzontal++) {
            printf("%c%d ", lletres[horitzontal], vertical);
        }
        printf("\n");
    }
    return 0;
}

Per tant, la definició de constants amb define es comporta com si d’un “cercar-reemplaçar” d’un processador de textos es tractés. No es desa cap constant en memòria, però, per contra, el programa ocuparà una mica més per la substitució directa de referències que fa; la substitució la fa en tot el programa, no es pot limitar a un àmbit concret (per exemple, només dins d’una funció).

  • const: en aquest cas sí que es reserva una posició de memòria. En C es comporta igual com si fos una variable, però que únicament funciona en mode lectura: no li podem modificar el valor.

A més, const ens permet també dir quin tipus de valor tindrà la constant: si és de tipus float, int, char… amb la qual cosa, aquest fet ens dóna un punt addicional de control, ja que ens assegurem que el tipus de valor assignat serà el correcte per al programa.

Amb aquest tipus de definició de constant, l’exemple anterior quedaria de la següent forma:

/* Exemple CA0203 */
#include <stdio.h>
const int MIDA = 8;

char lletres[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
int vertical, horitzontal;

int main(int argc, char **argv) {
    /* La constant MIDA està desada en memòria */
    printf("posició en memòria de la constant MIDA : %p \n", &MIDA);
    for (vertical=MIDA; vertical>=1; vertical--) {
        for (horitzontal=0; horitzontal<=MIDA-1; horitzontal++) {
            printf("%c%d ", lletres[horitzontal], vertical);
        }
        printf("\n");
    }
    return 0;
}

Com es pot veure, és possible obtenir l’adreça en memòria on es desa la constant MIDA. En aquest cas, sí que es pot definir una constant amb const i fer que només afecti un àmbit determinat (per exemple, que la constant estigui definida únicament dins d’una funció).

Aquestes són les principals diferències entre define i const a l’hora de definir una constant. Durant el curs, serà més comú utilitzar define per sobre de const degut als problemes que const ocasiona en determinades situacions amb vectors (veure l’apartat Variable-sized object may not be initialized de les FAQ).

2.4 Com mostrar el valor d’una constant

En C podem mostrar per pantalla el valor d’una constant definida amb #define mitjançant printf(). Exemple:

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

#define WORD "world"
#define YEAR 2021
#define EXCLAMATION '!'

int main(int argc, char **argv) {
    printf("hello %s and happy %d %c\n", WORD, YEAR, EXCLAMATION);
    return 0;
}

El resultat que es mostrarà per pantalla serà:

hello world and happy 2021 !

2.5 Precisió en variables float

Hi ha alguns valors decimals determinats que no es poden representar de forma precisa en una variable de tipus float. La millor solució pels casos que tractem és arrodonir al número de decimals que realment necessitem.

Si en canvi volem sí o sí treballar amb tots els decimals, podem optar per utilitzar un tipus de dada que tingui major precisió que float: double.

Per exemple, el següent programa retorna el resultat esperat si es desa en un double, i no pas si es fa en un float:

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

int main(int argc, char **argv) { 
    float num1; 
    float num2;
    float resultat1; 
    double resultat2; 
    
    num1 = 1.3; 
    num2 = 17;
    resultat1 = num1 + num2; 
    printf("resultat amb float : %f\n", resultat1); 
    resultat2 = num1 + num2; 
    printf("resultat amb double: %f\n", resultat2); 
    return 0; 
}

La sortida que genera és:

resultat amb float : 18.299999
resultat amb double: 18.300000

2.6 Semàntica d’una expressió

Si recordem el que es comenta al punt 3.2. Semàntica d’una expressió del mòdul de la xWiki Tipus bàsics de dades, hem d’aconseguir que les expressions i comparacions realitzades als algorismes siguin semànticament correctes.

Amb un exemple es veurà més clar: tenim el següent algorisme que indica si una persona és major d’edat. Fixeu-vos que l’expressió realitza una comparació entre dos enters: la variable edat i el número 17.

var
    edat: integer;
    isMajorEdat: boolean;
end var

algorithm serMajorEdat

    writeString("Introdueix edat del conductor :");
    edat := readInteger();

    isMajorEdat := (edat > 17);

    writeString("El conductor és major d'edat? :");
    writeBoolean(isMajorEdat);

end algorithm

Aquesta expressió és semànticament correcta.

En canvi, imaginem ara que el nostre algorisme accepta decimals per l’edat; per exemple, 19.5 indicaria que l’edat és de 19 anys i 6 mesos. Així tenim el següent plantejament, on ara la variable edat és de tipus real:

var
    edat: real;
    isMajorEdat: boolean;
end var

algorithm serMajorEdat

    writeString("Introdueix edat del conductor :");
    edat := readReal();

    isMajorEdat := (edat > 17);

    writeString("El conductor és major d'edat? :");
    writeBoolean(isMajorEdat);

end algorithm

L’algorisme ara no és correcte ja que conté una expressió semànticament incorrecta, en la qual es compara edat (real) amb 17 (enter). Per solucionar-ho, podem utilitzar alguna de les funcions de conversió comentades a l’apartat 4. Funcions de conversió de tipus del mateix mòdul:

{ opció 1: fem que els dos valors siguin de tipus enter }
isMajorEdat := (realToInteger(edat) > 17);    

{ opció 2: fem que els dos valors siguin de tipus real }
isMajorEdat := (edat > integerToReal(17));

2.7 Exemples d’expressions

2.7.1 Exemple 1: esParell

Imaginem que ens demanen un algorisme que indiqui si un número és parell.

Una possible solució seria:

algorithm esParell
    var
        numero: integer;
        isParell: boolean;
    end var

    writeString("Introdueix un número : ");
    numero:= readInteger();
    isParell:= (numero mod 2 = 0);
    
    writeString("El número ");
    writeInteger(numero);
    writeString(" és parell? ");
    writeBoolean(isParell);

end algorithm

La variable isParell prendrà el valor true si el número és parell i false en cas contrari. No ha calgut utilitzar cap estructura if-else per resoldre l’algorisme.

Una possible forma de codificar-ho en llenguatge C és:

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

int main(int argc, char **argv) {
    int numero;
    bool isParell;

    printf("Introdueix un número : ");
    scanf("%d", &numero);
    isParell = (numero % 2 == 0);
    
    printf("El número %d és parell? (0=false, 1=true) : %d \n", numero, isParell);
    return 0;
}

2.7.2 Exemple 2: capDeSetmana

Imaginem que volem fer un programa molt senzill que ens digui si avui és cap de setmana o no. El seu algorisme seria el següent:

type
    dies = {DILLUNS, DIMARTS, DIMECRES, DIJOUS, DIVENDRES, DISSABTE, DIUMENGE}
end type

algorithm capDeSetmana

    var
        esCapDeSetmana: boolean;
        diaSetmana: dies;
    end var

    writeString("Quin dia de la setmana és avui ?");

    { s'utilitza la funció ad hoc readDiaSetmana() per llegir el dia }
    diaSemana:= readDiaSetmana();
    esCapDeSetmana:= (diaSetmana = DISSABTE or diaSetmana = DIUMENGE);

    writeString("Avui és cap de setmana?");
    writeBoolean(esCapDeSetmana);

end algorithm

La variable boolean esCapDeSetmana prendrà el valor de true o false en funció del resultat d’avaluar l’expressió. No cal utilitzar estructures condicionals if-else que veurem més endavant en el curs.

Una possible forma de codificar-ho en llenguatge C és:

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

typedef enum {DILLUNS, DIMARTS, DIMECRES, DIJOUS, DIVENDRES, DISSABTE, DIUMENGE} dies;

int main(int argc, char **argv) {
    bool esCapDeSetmana;
    dies diaSetmana;

    printf("\nQuin dia de la setmana és avui ?\n");
    printf("Per DILLUNS tecleja 0\n");
    printf("Per DIMARTS tecleja 1\n");
    printf("Per DIMECRES tecleja 2\n");
    printf("Per DIJOUS tecleja 3\n");
    printf("Per DIVENDRES tecleja 4\n");
    printf("Per DISSABTE tecleja 5\n");
    printf("Per DIUMENGE tecleja 6\n");

    scanf("%u", &diaSetmana);
    esCapDeSetmana = (diaSetmana == DISSABTE || diaSetmana == DIUMENGE);

    printf("Avui és cap de setmana (0=false, 1=true) ? %d\n", esCapDeSetmana);
    return 0;
}

Diversos punts a considerar:

  • Recordem que inicialment en C no existia el tipus booleà. Per poder utilitzar bool, i els valors true i false ens cal importar prèviament la llibreria <stdbool.h>.
  • L’especificador de tipus d’un bool és %d.
  • Quan definim una variable de tipus enum utilitzem l’especificador de tipus %u. Ho podríem fer com a %d, però retornarà un warning tot i que el resultat sigui correcte. El tipus %u és igual que un enter %d però sense signe: això significa que amb %d podem tractar valors negatius com -12 i amb %u això no és possible, però com que sabem que els valors que pot prendre un enum sempre seran >= 0 ens convé utilitzar %u.
  • Per les particularitats dels bool en el llenguatge de programació C que ja hem comentat anteriorment, l’entrada i sortida de valors d’un boolean serà numèrica. Per facilitar la comprensió podem mostrar per pantalla un literal que ens indiqui que 0 equival a false i 1 a true.

2.7.3 Exemple 3: esVocal

Volem fer un programa que, entrat un caràcter pel canal d’entrada, ens indiqui si es tracta o no d’una vocal.

Una possible solució per l’algorisme és la següent:

var
    lletra: char;
    isVocal: boolean;
end var

algorithm esVocal

    writeString("Tecleja una lletra: ");
    lletra := readChar();

    { En aquest exemple només tractem les vocals minúscules }
    isVocal := lletra = 'a' or lletra = 'e' or 
        lletra = 'i' or lletra = 'o' or lletra = 'u';

    writeString("La lletra ");
    writeChar(lletra);
    writeString(" és una vocal? ");
    writeBoolean(isVocal);

end algorithm

Com es pot veure, el plantejament de l’algorisme és:

  • Llegim un caràcter des del canal d’entrada.
  • Comparem el caràcter amb a, e, i, o, u.
    • Si coincideix amb alguna d’aquestes vocals, la variable isVocal := true.
    • Si no coincideix amb cap de les vocals, la variable isVocal := false.
  • Es mostra el resultat per pantalla.

Com ho podem traduir a llenguatge C? Una possible opció és:

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

int main(int argc, char **argv) {
    char lletra;
    bool isVocal;

    printf("Introdueix una lletra: ");
    scanf("%c", &lletra);

    /* en aquest exemple només tractem les vocals minúscules */
    isVocal = lletra == 'a' || lletra == 'e' || 
        lletra == 'i' || lletra == 'o' || lletra == 'u';

    printf("La lletra %c és una vocal (0=false, 1=true)? %d\n", lletra, isVocal);
    return 0;
}

2.7.4 Exemple 4: votacions

Imaginem que hem de fer un programa que validi si una persona pot anar a votar o no; la condició que ens diuen que cal complir és que la persona sigui major d’edat i a més estigui al cens electoral de la localitat on està votant.

L’algorisme podria ser el següent:

algorithm votacions
    var
        isMajorEdat: boolean;
        isCensat: boolean;
        isVotant: boolean;
    end var
    
    writeString("Ets major d'edat? ");
    isMajorEdat := readBoolean();
    writeString("Estàs al cens electoral? ");
    isCensat := readBoolean();
    
    { Expressió }
    isVotant := isMajorEdat and isCensat;

    writeString("Pots anar a votar: ");
    writeBoolean(isVotant);

end algorithm

Una possible implementació en C seria:

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

int main(int argc, char **argv) {
    bool isMajorEdat;
    bool isCensat;
    bool isVotant;
    int intToBool;

    printf("Ets major d'edat (0=false, 1=true)? ");
    scanf("%d", &intToBool);
    isMajorEdat = (bool)intToBool;

    printf("Ets al cens electoral (0=false, 1=true)? ");
    scanf("%d", &intToBool);
    isCensat = (bool)intToBool;

    /* Expressió */
    isVotant = isMajorEdat && isCensat;

    printf("Pots anar a votar (0=false, 1=true): %d\n", isVotant);
    return 0;
}

L’execució d’aquest exemple seria:

Ets major d'edat (0=false, 1=true)? 1
Ets al cens electoral (0=false, 1=true)? 0
Pots anar a votar (0=false, 1=true): 0

2.7.5 Exemple 5: ginTonicPreparation

Imaginem que volem preparar un gintònic. Sabem el volum de ginebra i de tònica que utilitzarem, i quina és la capacitat de la copa de baló que el contindrà.

Hem vist una oferta per internet i hem comprat glaçons d’acer inoxidable, però se’ns n’ha anat una mica el cap i n’hem adquirit un total de 20 unitats.

Volem fer un programa que, utilitzant únicament expressions, ens digui si podem preparar o no el gintònic en funció del número de glaçons que hi volem posar:

  • Si el nombre de glaçons caben dins de la copa, retornarà true.
  • En cas contrari, retornarà false.

Per tant, el que ha de fer el nostre programa bàsicament és validar si el volum de ginebra + tònica + glaçó * número de glaçons supera o no el volum de la copa.

L’algorisme podria ser el següent:

const
    GIN: real = 50.0;              { in ml }
    TONIC: real = 200.0;           { in ml }
    GLASS: real = 620.0;           { in ml }
    METAL_ICE_CUBE: real = 42.875; { in ml }
end const

algorithm ginTonicPreparation

    var
        numMetalIceCubes: integer;
        isPossible: boolean;
    end var

    writeString("Number of metal ice cubes ? (integer) : ");
    numMetalIceCubes:= readInteger();

    isPossible:= (GLASS ≥ 
        (GIN + TONIC + METAL_ICE_CUBE * integerToReal(numMetalIceCubes)));

    writeString("Can you make a gin & tonic? : ");
    writeBoolean(isPossible);

end algorithm

L’expressió que dona valor a isPossible s’ocupa d’avaluar el volum de la copa respecte el resultant de ginebra, tònica i glaçons.

La seva traducció a C podria ser:

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

#define GIN 50.0              /* in ml */
#define TONIC 200.0           /* in ml */
#define GLASS 620.0           /* in ml */
#define METAL_ICE_CUBE 42.875 /* in ml */ 

int main(int argc, char **argv) {
    int numMetalIceCubes;
    bool isPossible;

    printf("Number of metal ice cubes ? (integer) : ");
    scanf("%d", &numMetalIceCubes);

    isPossible = GLASS >= (GIN + TONIC + METAL_ICE_CUBE * numMetalIceCubes);

    printf("Can you make a gin & tonic? (0=false, 1=true) : %d\n", isPossible);
    return 0;
}

Per realitzar el càlcul en C també s’ha utilitzat una expressió.

2.7.6 Exemple 6: ginTonicFreeMl

Anem a evolucionar l’exemple anterior del gintònic: imaginem ara que volem que el nostre programa ens digui el volum (en mil·lilitres) que queda lliure a la copa una vegada posat un determinat nombre de glaçons.

L’algorisme quedaria de la següent forma:

const
    GIN: real = 50.0;              { in ml }
    TONIC: real = 200.0;           { in ml }
    GLASS: real = 620.0;           { in ml }
    METAL_ICE_CUBE: real = 42.875; { in ml }
end const

algorithm ginTonicFreeMl

    var
        numMetalIceCubes: integer;
        volumeFree: real;
    end var

    writeString("Number of metal ice cubes ? (integer) : ");
    numMetalIceCubes:= readInteger();

    volumeFree:= GLASS - 
        (GIN + TONIC + METAL_ICE_CUBE * integerToReal(numMetalIceCubes));

    writeString("How many free ml in the glass? : ");
    writeReal(volumeFree);

end algorithm

I la codificació en C :

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

#define GIN 50.0              /* in ml */
#define TONIC 200.0           /* in ml */
#define GLASS 620.0           /* in ml */
#define METAL_ICE_CUBE 42.875 /* in ml */

int main(int argc, char **argv) {
    int numMetalIceCubes;
    float volumeFree;

    printf("Number of metal ice cubes ? (integer) : ");
    scanf("%d", &numMetalIceCubes);

    volumeFree = GLASS - (GIN + TONIC + METAL_ICE_CUBE * numMetalIceCubes);

    printf("How many free ml in the glass ? : %.3f ml \n", volumeFree);
    return 0;
}

2.7.7 Exemple 7: scoutingBasquet

Imaginem que fem tasques de scouting per les seccions de bàsquet femení i masculí del nostre club, i ens han encarregat cobrir alguna de les tres places següents:

  • Per a l’equip femení: una pivot que com a mínim faci 195 cm d’alçada.
  • Per a l’equip femení: una base, l’alçada de la qual sigui inferior a 170 cm.
  • Per a l’equip masculí: un base que sigui més alt de 175 cm però a la vegada que no superi els 190 cm.

El nostre programa demanarà per teclat si es tracta d’una jugadora o un jugador, i quina és la seva alçada. A continuació, amb expressions, avaluarà les condicions introduïdes i si les compleix per alguna de les tres places disponibles, l’escollirà (isDrafted).

Una possible forma de codificar en C aquest programa seria:

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

typedef enum {MALE, FEMALE} tGender;

int main(int argc, char **argv) {
    bool isPointGuard; /* Point Guard = base */
    bool isCenter;     /* Center = pivot */
    bool isDrafted;
    int height;
    tGender gender;

    printf("Gender (0=MALE, 1=FEMALE) : ");
    scanf("%u", &gender);
    printf("Heigth (integer value) : ");
    scanf("%d", &height);

    /* Primer mirem si la posició de base 
       femení o masculí la podem cobrir o no */
    isPointGuard = 
        (height < 170 && (gender == FEMALE)) ||
        (height < 190 && height > 175 && (gender == MALE));

    /* A continuació comprovem si es tracta de la pívot femenina que busquem */
    isCenter = (height >= 195 && (gender == FEMALE));

    /* Només que es compleixi alguna de les dues expressions anteriors 
       (que isPointGuard sigui true o que isCenter sigui true), el jugador/a 
       serà escollit per formar part de les nostres seccions de bàsquet */
    isDrafted = isPointGuard || isCenter;

    printf("\nIs drafted (0=false, 1=true) ? : ");
    printf("%d\n", isDrafted);
    return 0;
}

Fixeu-vos que isPointGuard i isCenter són variables de tipus bool, ja que l’avaluació de les expressions també serà de tipus bool.

Hi poden haver altres codificacions igual de vàlides, aquesta no és la única solució possible.

2.8 Errors més freqüents

2.8.1 Interfície d’usuari: absència de textos informatius

El següent no és un error sintàctic o semàntic, sinó un error de disseny molt freqüent.

Pseudocodi incorrecte:

writeInteger(room);
writeInteger(totalPrice);

En l’exemple mostrat, sembla que la intenció és mostrar pel canal estàndard dues variables (room i totalPrice), i efectivament les sentències per imprimir-les són correctes. El problema és que l’usuari no té cap informació sobre el significat dels valors que se li mostren. Cal sempre complementar les dades amb missatges informatius, indicant també les unitats quan sigui necessari.

Pseudocodi correcte:

writeString("Room number: ");
writeInteger(room);
writeString("Total price [€]: ");
writeInteger(totalPrice);

Aquest error de disseny és més greu en el cas (també real), de no posar cap text informatiu a l’hora de demanar a l’usuari que introdueixi dades per teclat.

Pseudocodi incorrecte:

readInteger(room);
readReal(totalPrice);

L’usuari no té cap informació sobre els valors a introduir (tipus de dades, intervals vàlids, etc.).

Pseudocodi correcte:

writeString("Enter room number [1-100]: ");
readInteger(room);
writeString("Enter total price [€]: ");
readInteger(totalPrice);

2.8.2 Caràcters: ús de cometes simples

Pseudocodi incorrecte:

var
    fastpass: char;
    areaMap: char;
    allowsFastPass: boolean;
end var
{...}
fastPass := (allowsFastPass = y or allowsFastPass = Y) and 
    (areaMap = B or areaMap = C);
{...}

En l’algoritme anterior, sembla que es vol comparar la variable allowsFastPass amb els caràcters y, Y, i la variable areaMap amb els caràcters B, C. El problema és que falten les cometes simples per indicar que es tracta de caràcters i, per tant, el que fa l’expressió és comparar amb les variables y, Y, B i C respectivament (que a més a més, en aquest algoritme no existeixen. Aquest és un error semàntic greu, que pot no provocar cap error de compilació en llenguatge C i, en canvi, generar comportaments inesperats.

Pseudocodi correcte:

var
    fastpass: char;
    areaMap: char;
    allowsFastPass: boolean;
end var
{...}
fastPass := (allowsFastPass = 'y' or allowsFastPass = 'Y') and 
    (areaMap = 'B' or areaMap = 'C');
{...}

2.8.3 Sintaxi pròpia de C: funcions de lectura/escriptura

Pseudocodi incorrecte:

writeString("Code value: %d\n", codeValue);

S’ha intentat aplicar a la funció writeString() en llenguatge algorísmic la sintaxi pròpia de la funció printf() de C, la qual cosa evidentment no és correcta. Recordeu que el llenguatge algorísmic és un llenguatge de disseny de propòsit general, que ha de permetre posteriorment codificar en qualsevol llenguatge de programació

Pseudocodi correcte:

writeString("Code value");
writeInteger(codeValue);

2.8.4 Conversió de tipus: funcions inexistents

Pseudocodi incorrecte:

b1:= characterToCode(myChar);

Les funcions de conversió de tipus estan definides al Nomenclator de l’assignatura, i serveixen per assegurar la coherència semàntica entre els tipus de variables d’una expressió. En aquest cas, la funció characterToCode() no existeix, ja que la que hem declarat és characterToInteger(). És habitual trobar errors d’aquest tipus, amb diverses variants de noms de funció. Cal evitar-ho i usar el Nomenclator com a guia.

Pseudocodi correcte:

b1:= characterToInteger(myChar);

2.8.5 Expressions: variables intermitges

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

Pseudocodi incorrecte:

var
    bool1: boolean;
    bool2: boolean;
    bool3: boolean;
    resultBool: boolean;
end var
bool1 := hasGym or hasPool;
bool2 := closeToSubway or distanceFromCityCentre < 5;
bool3 := priceDouble ≤ 100;
resultBool := bool1 and bool2 and bool3;

En el codi anterior, el que volem obtenir al final és una variable de tipus booleà que ens indiqui si un objecte (suposem que un hotel), té piscina o gimnàs, i a més és prop del metro o a menys de 5 km del centre de la ciutat, i a més el preu és inferior a 100 (euros). Volem reunir tota aquesta informació en una variable booleana, perquè segurament això ens indica alguna qualitat de l’objecte (per exemple, que és un bon hotel). Ara bé, fixeu-vos com per arribar al resultat final, s’han declarat fins a tres variables auxiliars, que no tenen cap altra finalitat que construir l’expressió final.

Declarar variables auxiliars o temporals no és una mala pràctica sempre que es faci amb mesura, i aplicant el sentit comú (cosa que només s’adquireix amb la pràctica del disseny i la programació). Recordem que les variables ocupen espai de memòria i, per tant, cal ser curosos a l’hora de declarar-les. Una alternativa seria la que presentem a continuació.

Pseudocodi correcte:

var
    boolFinal: boolean;
end var
resultBool := hasGym or hasPool and 
              closeToSubway or distanceFromCityCentre < 5 and 
              priceDouble ≤ 100;

Recordem que el delimitador de les sentències és el punt i coma ;, i que hi ha flexibilitat a l’hora d’escriure les sentències en diverses línies (també en C). Finalment, cal recordar un cop més que no hi ha cap norma que ens indiqui quantes variables auxiliars hem d’utilitzar. En la solució alternativa hem optat per no utilitzar-ne cap i donar un format entenedor a l’expressió, però això caldrà decidir-ho en cada cas, en base a la pràctica i l’experiència.

2.8.6 Declaració de variables: declaració mal ubicada (bloc central de codi)

Pseudocodi incorrecte:

for i:= 1 to selectedHotels->nHotels do 
    if selectedHotels[i].city = city then 
        var
            scorePoints: integer;
        end var
        scorePoints := scorePoints + 1;
    end if
end for

Les variables s’han de declarar al principi del bloc de pseudocodi, ja sigui una funció, una acció o bé un algorisme directament. No és correcte obrir un bloc de declaració de variables dins un bloc central de pseudocodi (i encara menys obrir diversos blocs de declaració a mesura que necessitem variables). El mateix passa en C: les variables s’han de declarar sempre al bloc inicial de codi (ja sigui una funció, acció o algorisme).

Pseudocodi correcte:

var
    scorePoints: integer;
end var

for i:= 1 to selectedHotels->nHotels do 
    if selectedHotels[i].city = city then 
       scorePoints := scorePoints + 1;  
    end if
end for

2.8.7 Declaració de variables: declaració mal ubicada (variables globals)

Aquest no és un error sintàctic o semàntic, sinó que es tracta d’una mala pràctica de programació que cal evitar.

Codi incorrecte:

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

float maxPrice;
int bestHotel;

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

Les variables s’han de declarar sempre dins una funció o acció (ja sigui el main o qualsevola altra), i al principi del seu bloc de codi. No és correcte declarar variables fora de qualsevol funció, ja que es converteixen en variables globals, i el seu ús no es considera una bona pràctica de programació. El motiu és que les variables globals dificulten la lectura i la comprensió del codi, i poden provocar errors d’execució inesperats o actualitzacions involuntàries, en no estar protegides dins de funcions o accions.

Codi correcte:

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

int main(int argc, char **argv) {
    float maxPrice;
    int bestHotel;
    /* ... */
    return 0;
}

2.8.8 Tipus booleà: valors numèrics

Codi incorrecte:

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

int main(int argc, char **argv) {
    bool myBool1;
    bool myBool2;
    bool resultBool;
    /* ... */
    resultBool = (myBool1 == 1) && (myBool2 == 0);
    return 0;
}

Si disposem de la llibreria <stdbool.h> pel tractament de booleans en C, no té cap sentit que utilizem els seus equivalents numèrics al codi, especialment en el cas de les expressions. En el seu lloc heu d’utilizar les paraules reservades true i false.

Com a regla general, hem de procurar usar el mínim nombre possible de valors numèrics, i fer servir en el seu lloc variables i constants.

Codi correcte:

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

int main(int argc, char **argv) {
    bool myBool1;
    bool myBool2;
    bool resultBool;
    /* ... */
    resultBool = myBool1 && !myBool2;
    return 0;
}

2.8.9 Tipus booleà: cadenes de caràcters

Codi incorrecte:

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

int main(int argc, char **argv) {
    bool myBool1;
    bool myBool2;
    bool resultBool;
    /* ... */
    resultBool = (myBool1 =="true") && (myBool2 == "false");
    return 0;
}

Si disposem de la llibreria <stdbool.h> pel tractament de booleans en C, podem utilitzar les paraules reservades true i false, sense cap tipus de modificador ni caràcter. Si afegim les cometes, “true” i “false” es converteixen en cadenes de caràcters, que són una cosa molt diferent i no tenen res a veure amb els booleans (ni amb els seus possibles valors). Aquest error molt greu també es dona en llenguatge algorísmic.

Cal afegir, a més, que en C qualsevol valor que no sigui 0 és interpretat com a true, per la qual cosa es produiran errors d’execució, ja que les cadenes de caràcters seran interpretades sempre com un valor cert.

Codi correcte:

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

int main(int argc, char **argv) {
    bool myBool1;
    bool myBool2;
    bool resultBool;
    /* ... */
    resultBool = myBool1 && !myBool2;
    return 0;
}

2.8.10 Sintaxi pròpia de C: operadors

Pseudocodi incorrecte:

if (acceptable == 1) or (acceptable == 2) then
    writeString("Hotels cannot be compared");
end if

L’operador == és propi del llenguatge C, i el seu ús llenguatge algorísmic no és correcte. L’operador correcte en aquest cas és =.

Pseudocodi correcte:

if (acceptable) = 1 or (acceptable = 2) then
    writeString("Hotels cannot be compared");
end if

2.8.11 Semàntica de les expressions

Aquesta tema es comenta a l’apartat Semàntica d’una expressió. A continuació, reproduïm un dels errors habituals.

Pseudocodi incorrecte:

const
    NUM_CASH_DESKS: integer = 4;
end const

var
    cashDesksTotalIncome: real;
    cashDesksAverageIncome: real;
end var

cashDesksAverageIncome := cashDesksTotalIncome / NUM_CASH_DESKS;

La semàntica d’una expressió fa referència a la coherència entre els tipus de dades que s’usen. En aquest sentit, quan volem construir expressions ens hem d’assegurar que les operacions es facin sempre entre variables amb el mateix tipus de dades.

En cas que tinguem variables de diferents tipus, cal que utilitzem les funcions de conversió de tipus disponibles. Si no es fa així, es poden produir resultats inesperats deguts arrodoniments de dades, pèrdua de precisió o conversions implícites incorrectes.

A l’exemple anterior es vol calcular la mitjana d’ingressos d’un nombre determinat de caixes d’un supermercat, però es barregen de forma incorrecta variables i constants de tipus enter i real.

Pseudocodi correcte:

const
    NUM_CASH_DESKS: integer = 4;
end const

var
    cashDesksTotalIncome: real;
    cashDesksAverageIncome: real;
end var

cashDesksAverageIncome := cashDesksTotalIncome / integerToReal(NUM_CASH_DESKS);

2.8.12 Tipus booleà: operacions amb altres tipus

Pseudocodi incorrecte:

const
    POINTS_DELIVERY: integer = 4;
end const

var
    hasDelivery: boolean;
    pointsDelivery: integer;
end var

pointsDelivery := 0;
pointsDelivery := POINTS_DELIVERY * hasDelivery;

L’algorisme anterior intenta actualitzar la variable pointsDelivery si es compleix la condició establerta per la variable booleana hasDelivery. D’aquesta forma, la variable prendria el valor POINTS_DELIVERY en cas que hasDelivery fos true, i zero en cas contrari. Aquesta operació no és semànticament correcta, ja que s’estan barrejant variables i operacions lògiques amb aritmètiques.

La semàntica d’una expressió fa referència a la coherència entre els tipus de dades que s’utilitzen. En aquest sentit, quan volem construir expressions ens hem d’assegurar que les operacions es facin sempre entre variables amb el mateix tipus de dades. Tampoc seria possible una conversió de tipus, ja que els booleans tenen un valor lògic (true, false), que en llenguatge algorísmic no té cap equivalència numèrica. En el seu lloc, haurem de muntar una estructura alternativa.

Pseudocodi correcte:

const
    POINTS_DELIVERY: integer = 4;
end const

var
    hasDelivery: boolean;
    pointsDelivery: integer;
end var

pointsDelivery := 0;
if hasDelivery then 
    pointsDelivery := POINTS_DELIVERY;
end if

En el cas del llenguatge C, els booleans sí que tenen una equivalència numèrica, però l’algorisme original tampoc es consideraria correcte malgrat que el programa compilés i funcionés correctament. Com s’ha esmentat, cal respectar sempre la semàntica d’expressions i dades.

2.8.13 Inicialització de variables mal ubicada

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

Pseudocodi incorrecte:

const
    POINTS_DELIVERY: integer = 4;
end const

var
    hasDelivery: boolean;
    pointsDelivery: integer = POINTS_DELIVERY;
end var

{ ... }
if hasDelivery then 
    pointsDelivery := POINTS_DELIVERY + 1;
end if
{ ... }

A l’algorisme anterior, la variable entera pointsDelivery s’inicialitza en el mateix moment de la declaració, prenent el valor POINTS_DELIVERY, fet que no està permès en la codificació algorísmica i que es desaconsella en el llenguatge C, ja que dificulta el seguiment del valor de les variables al llarg del codi. Com a norma general, les variables han d’inicialitzar-se abans de ser utilitzades. D’aquesta forma, ens assegurem que tenen un valor per defecte abans de ser avaluades en qualsevol expressió, i será més senzill identificar aquest valor i els següents que pugui tenir.

Pseudocodi correcte:

const
    POINTS_DELIVERY: integer = 4;
end const

var
    hasDelivery: boolean;
    pointsDelivery: integer;
end var

{ ... }
pointsDelivery := POINTS_DELIVERY;
if hasDelivery then 
    pointsDelivery := POINTS_DELIVERY + 1;
end if
{ ... }

2.8.14 Utilització incorrecta de l’operador lògic not

Pseudocodi incorrecte:

var
    canDrive: boolean;
    isDrunk: boolean;
    isDrugged: boolean;
end var

{ ... }
canDrive := not isDrunk and isDrugged;
{ ... }

A l’algorisme anterior, l’operador not no afecta a la totalitat de l’expressio isDrunk and isDrugged, sinó que únicament s’aplica sobre la variable isDrunk. Per tant, l’expressió és incorrecta, ja que serà true en el cas que no hagi begut però sí hagi consumit altres drogues.

Per solucionar-ho, apliquem not a les dues variables booleanes.

Pseudocodi correcte:

var
    canDrive: boolean;
    isDrunk: boolean;
    isDrugged: boolean;
end var

{ ... }
canDrive := not isDrunk and not isDrugged;
{ ... }

Una expressió equivalent i igualment correcta seria la següent:

var
    canDrive: boolean;
    isDrunk: boolean;
    isDrugged: boolean;
end var

{ ... }
canDrive := not (isDrunk or isDrugged);
{ ... }

En aquest darrer algorisme els parèntesis permeten que l’operador not s’apliqui sobre el conjunt de isDrunk or isDrugged.