1 PAC1

1.1 Llenguatge algorísmic

El llenguatge algorísmic l’hem d’entendre com una aproximació al món real, que utilitza unes normes definides per nosaltres mateixos. En aquest punt encara no parlem de programes escrits en C, en Java, en Python o en PHP, per dir alguns llenguatges de programació.

Per exemple, en el llenguatge algorísmic que utilitzem a l’assignatura definim un bloc de variables de la següent forma:

var
    edat: integer;
    pes: real;
end var

Que es tracti d’un llenguatge més proper al món real no significa que no s’hagin de complir unes determinades regles. Com es pot veure en aquest exemple, una d’aquestes regles és que quan definim variables ho precedim amb var i ho finalitzem amb end var.

Hem decidit utilitzar aquesta forma de llenguatge algorísmic, tot i que també ho podríem haver plantejat de la següent forma :

variable
    enter edat
    decimal pes
fvariable

Remarcar que aquest segon exemple és incorrecte, no segueix la nomenclatura del llenguatge algorísmic definit a l’assignatura. El correcte és el primer exemple.

El llenguatge algorísmic és com fer una aproximació formal a la realitat, no és un llenguatge de programació en si com és C, Java o Python. Per tant, no és un llenguatge que es pugui compilar i executar amb l’Integrated Development Environment (IDE) utilitzat a l’assignatura, que està preparat únicament per interpretar i executar codi programat en llenguatge C.

Ara ve la gran pregunta: i per què és necessari primer dissenyar l’algorisme, si puc directament programar-ho en C?

Un algorisme ens permet dissenyar un programa sense tenir presents les particularitats de cada llenguatge de programació. Aquesta aproximació formal a la realitat dels algorismes ens facilita poder fer posteriorment una traducció ràpida a qualsevol llenguatge de programació simplement coneixent les equivalències corresponents. Per exemple, el primer cas, si el programem en C equival a:

int edat;
float pes;

El codi en C no el podem canviar, ja que si en comptes de posar int utilitzem enter, el compilador de C no comprèn el mot i ens donarà un error de codi.

Si mai hem programat és normal que aquest plantejament sobti al principi, però és important que a poc a poc es vagin veient les diferències entre llenguatge algorísmic i llenguatge C.

1.2 Llenguatge algorísmic vs llenguatge C

En general:

  • Llenguatge algorísmic: proper al llenguatge natural, es tracta d’una convenció que adoptem nosaltres mateixos per definir el d’un programa formalment. Els algorismes tenen una sèrie de normes i sentències que nosaltres definim (Nomenclàtor), però que no són de cap forma interpretables per un ordinador. Per tant, un algorisme no pot ser compilat ni executat.
  • Llenguatge C: es tracta d’un llenguatge de programació que sí comprèn un ordinador. Això significa que únicament podem utilitzar les seves comandes i les seves normes per tal que el codi pugui ser compilat i executat sense problemes.

El llenguatge algorísmic és un pseudocodi que ens ajuda a definir com funciona un programa. No està lligat a cap llenguatge de programació, amb la qual cosa, les accions que realitzarà, la forma de definir variables, etc., és genèrica. Funcions com writeString(), readInteger() o writeChar() formen part del llenguatge algorísmic: indiquen una acció genèrica a realitzar, com és escriure una cadena de caràcters, llegir un enter o escriure un caràcter. Quan es vulgui codificar aquest algorisme en un llenguatge de programació concret com és C, només caldrà saber les comandes pròpies de C que ens permeten implementar l’algorisme.

La programació en C funciona exclusivament amb la sintaxi definida per aquest llenguatge de programació. Instruccions com scanf() i printf() són pròpies de C.

A mode d’exemple:

Algorisme: volem introduir la lectura de la llum de casa nostra; una possible implementació és:

algorithm lecturaLlum
    var
        lecturaMensual: integer;
    end var

    writeString("Introdueix la lectura mensual de la llum (kWh): ");
    lecturaMensual := readInteger();
end algorithm

Llenguatge C: en aquest llenguatge no existeixen les funcions algorísmiques writeString() ni readInteger(), però en canvi sí que tenim diverses funcions pròpies de C que ens permeten llegir un valor per teclat i assignar-lo a una variable d’entorn. Per tant, les accions algorísmiques anteriors correspondran a la següent codificació en C:

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

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

    printf("Introdueix la lectura mensual de la llum (kWh): ");
    scanf("%d", &lecturaMensual);
    return 0;
}

És molt important que es vegi clarament què és un algorisme i què és un programa en C.

1.3 Equivalències entre llenguatge algorísmic i llenguatge C

A continuació s’indiquen algunes de les equivalències existents entre llenguatge algorísmic i el llenguatge de programació C:

Llenguatge algorísmic Llenguatge C
Segueix unes normes?
Es pot compilar? no
Es pot executar? no
Assignació de valors a variables := =
Tipus booleà boolean bool
Tipus enter integer int
Tipus decimal real float
Tipus caràcter char char
Operador igual = ==
Operador diferent !=
Operador major > >
Operador major o igual >=
Operador menor < <
Operador menor o igual <=
Operador lògic de conjunció and &&
Operador lògic de disjunció or ||
Operador lògic de negació not !

1.4 Impressió de valors incorrecta

Quan es mostra per pantalla el contingut d’alguna variable amb printf() és important eliminar el prefix & de la variable. Per exemple, si no ho fem tenim que:

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

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

    printf("Introdueix l'identificador d'avió : ");
    scanf("%d", &idAvio);
    
    printf(">> Has escollit l'avió amb id %d \n", &idAvio);
    return 0;
}

El resultat de l’execució és:

Introdueix l'identificador d'avió : 9
>> Has escollit l'avió amb id -1078693464

Per quin motiu obtenim el valor estrany en l’identificador d’avió? Quan fem referència a &idAvio estem obtenint realment la posició de memòria on resideix la variable idAvio, no pas el valor de la variable. Per obtenir el seu valor cal eliminar de dins printf() el prefix & de la variable idAvio:

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

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

    printf("Introdueix l'identificador d'avió : ");
    scanf("%d", &idAvio);
    
    printf(">> Has escollit l'avió amb id %d \n", idAvio);
    return 0;
}

La sortida generada ara sí que és correcta:

Introdueix l'identificador d'avió : 9
>> Has escollit l'avió amb id 9 

1.5 Els enumeratius en codificació algorísmica

La definició d’un tipus enumeratiu en llenguatge algorísmic es fa de la següent forma:

type
    typeName = {ELEMENT1, ELEMENT2, ELEMENT3, ... , ELEMENTn}
end type

Els elements ELEMENT1, ELEMENT2, etc. els hem d’entendre com a constants, que podrem utilitzar per avaluar expressions i/o fer comparacions.

1.6 Els enumeratius en llenguatge C

Una enumeració és una assignació d’un valor enter a la sèrie d’elements que s’hi ha definit, començant pel 0 i incrementant-se en 1 en cada element. En llenguatge C aquests elements es comportaran com a constants: tenen un valor associat en el moment de la definició de l’enumeratiu, i no és possible modificar-lo posteriorment.

Per exemple, podem tenir la següent definició:

typedef enum {MALE, FEMALE} tGender;

Això significa que MALE=0 i FEMALE=1. Si l’ordre de la definició s’hagués fet al revés, {FEMALE, MALE}, tindríem que FEMALE=0 i MALE=1.

Una possible forma d’utilitzar els enumeratius és llegir un enter i comparar-lo amb l’element corresponent definit dins de l’enum, per tal de realitzar una acció o una altra. Una possible implementació en llenguatge C seria:

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

typedef enum {MALE, FEMALE} tGender;

int main(int argc, char **argv) {
    tGender gender;

    printf("Type patient gender: 0 for MALE, 1 for FEMALE\n");
    scanf("%u", &gender);

    if (gender == MALE) {
        printf("Patient gender MALE\n");
    } else {
        if (gender == FEMALE) {
            printf("Patient gender FEMALE\n");
        } else {
            printf("Incorrect option\n");
        }
    }
    return 0;
}

Es possible modificar l’assignació de valors que per defecte es fa als elements d’un enumeratiu.

typedef enum {MALE, FEMALE} tGender;
/* Valors associats a l'enumeratiu tGender:
 * MALE = 0
 * FEMALE = 1
 * És el comportament per defecte d'un enumeratiu.
 */

typedef enum {DL=1, DM, DC, DJ, DV, DS, DG} tDies;
/* Valors associats a l'enumeratiu tDies:
 * DL = 1, valor 1 assignat manualment; la resta 
 *         d'elements incrementaran en +1
 *         el seu valor respecte l'element anterior:
 * DM = 2
 * DC = 3
 * DJ = 4
 * DV = 5
 * DS = 6
 * DG = 7
 */
 
typedef enum {MOTO = 2, COTXE = 4, FURGONETA, CAMIO} tCategoria;
/* Valors associats a l'enumeratiu tCategoria:
 * MOTO = 2, valor 2 assignat manualment.
 * COTXE = 4, valor 4 assignat manualment. Els posteriors elements
 *             de l'enumeratiu aniran prenent els valors 5, 6, etc.:
 * FURGONETA = 5
 * CAMIO = 6
 */

1.7 Especificador d’un enumeratiu

Els enumeratius en llenguatge C, enum, utilitzen l’especificador %u.

Exemple:

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

typedef enum {PRIVAT, PUBLIC} tTransport;

int main(int argc, char **argv) {
    tTransport tipusTransport;

    printf("Amb quin tipus de transport vas a la feina (0=privat, 1=públic)?: ");
    scanf("%u", &tipusTransport);
    printf("Vas a la feina amb transport (0=privat, 1=públic): ");
    printf("%u\n", tipusTransport);
    return 0;
}

1.8 Lectura de caràcters en C

En el llenguatge C la lectura d’un char pot comportar-se de forma inadequada si prèviament el buffer d’entrada conté algun caràcter previ.

Imaginem que volem crear un programa molt senzill que donat un número de DNI i la seva lletra, ens concateni els dos valors i ho mostri per pantalla. Una possible forma d’implementar aquest programa en C seria:

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

int main(int argc, char **argv) {
    int dniNum;    /* número del DNI */
    char dniChar;  /* lletra del DNI */

    printf("Introdueix el número del DNI: ");
    scanf("%d", &dniNum);
    printf("Introdueix la lletra del DNI: ");
    scanf("%c", &dniChar);

    printf("El DNI introduït és: %d-%c\n", dniNum, dniChar);
    return 0;
}

Què passa si executem aquest codi? Que veiem que es comporta de forma incorrecta, ja que no ens arriba a demanar la lletra del DNI, i mostra directament el resultat:

Introdueix el número del DNI: 12345678
Introdueix la lletra del DNI:
El DNI introduït és: 12345678-

Quan teclegem el primer enter el que fem realment és introduir un número + un intro al final de tot. El número queda assignat a la variable dniNum, i l’intro és llegit com un caràcter i s’assigna a la variable dniChar. Per aquest motiu C interpreta que les dues variables ja tenen valor i finalitza el programa.

Com podem solucionar aquest comportament? Buidant l’intro del buffer d’entrada abans de llegir el caràcter, i una possible forma per fer-ho és amb la comanda getchar(). Aquesta comanda llegeix un caràcter del buffer d’entrada i el buida del buffer.

Per tant es pot corregir el programa anterior de la següent forma:

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

int main(int argc, char **argv) {
    int dniNum;    /* número del DNI */
    char dniChar;  /* lletra del DNI */

    printf("Introdueix el número del DNI: ");
    scanf("%d", &dniNum);
    getchar();
    printf("Introdueix la lletra del DNI: ");
    scanf("%c", &dniChar);

    printf("El DNI introduït és: %d-%c\n", dniNum, dniChar);
    return 0;
}

Si ara executem ja funcionarà com desitgem:

Introdueix el número del DNI: 12345678
Introdueix la lletra del DNI: B
El DNI introduït és: 12345678-B

En cas de necessitat, amb getChar() es pot guardar el caràcter del buffer en una variable, per tal de tractar-lo posteriorment:

char nomVariable;
nomVariable = getChar();

1.9 Lectura de float en C

El separador de valors decimals (tipus float) en C és el punt, no la coma. Per aquest motiu quan s’introdueix un valor decimal des de teclat sempre ho farem amb un punt.

Exemple:

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

int main(int argc, char **argv) {
    /* Variable que contindrà el pes d'una persona */
    float pes;

    /* Lectura de la dada per teclat (el separador de decimals és un . ) */
    printf("Introdueix el pes (kg) d'una persona : ");
    scanf("%f", &pes);

    /* Es mostra el valor decimal per pantalla */
    printf("Has introduït el pes = %.1f kg.\n", pes);
    return 0;
}

L’execució serà:

Introdueix el pes (kg) d'una persona : 79.440
Has introduït el pes = 79.4 kg.

1.10 Segmentation fault

L’error Segmentation fault (core dumped) es produeix quan intentem accedir a una posició de memòria incorrecta. Habitualment suceeix perquè en la lectura d’algun valor des de teclat amb scanf() ens oblidem el caràcter & davant de la variable:

    printf("Introdueix la teva edat : ");
    scanf("%d", edat);

Per solucionar-ho, afegim el prefix &:

    printf("Introdueix la teva edat : ");
    scanf("%d", &edat);

1.11 Exemple: calcularIMC

Volem un programa que calculi l’índex de massa corporal, IMC, a partir del pes i l’alçada d’una persona. Aquests dos valors es demanaran pel canal d’entrada, i es mostrarà l’IMC pel canal de sortida.

La codificació algorísmica del programa podria ser següent:

algorithm calcularIMC
    var
        pes: real;
        alcada: real;
        imc: real;
    end var

    writeString("Introdueix el pes (kg): ");
    pes := readReal();
    writeString("Introdueix  l'alçada (m): ");
    alcada := readReal();
    
    imc := pes / (alcada*alcada);
    writeString("IMC = ");
    writeReal(imc);
end algorithm

A continuació, el traduïm a llenguatge C:

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

int main(int argc, char **argv) {
    float pes;
    float alcada;
    float imc;

    printf("Introdueix el pes (kg): ");
    scanf("%f", &pes);
    printf("Introdueix l'alçada (m): ");
    scanf("%f", &alcada);

    imc = pes / (alcada*alcada); 
    printf("IMC = %.1f \n", imc);
    return 0;
}

Un exemple d’execució pot ser el següent:

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

1.12 Errors més freqüents

1.12.1 Definició de tipus: tipus booleà

En llenguatge algorísmic, el tipus boolean és un tipus bàsic, i com a tal no cal definir-lo en un bloc type.

Pseudocodi incorrecte:

type
    boolean = {FALSE, TRUE}
end type
var 
    myNum: integer;
    myBool: boolean;
end var

Es poden declarar variables de tipus booleà directament, igual que si fos un enter, un real o un caràcter.

Pseudocodi correcte:

var 
    myNum: integer;
    myBool: boolean;
end var

1.12.2 Estil i format: absència d’estil i format en llenguatge algorísmic

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

Pseudocodi incorrecte:

algorithm preuHotel
var
preuHabitacio:real;
numDies:integer;
preuTotal:real;
end var
writeString("Preu de l'habitació: ");
preuHabitacio:=readReal();
writeString("Número de dies: ");
numDies:=readInteger();
preuTotal:=preuHabitacio*integerToReal(numDies);
writeString("Preu total = ");
writeReal(preuTotal);
end algorithm

En el cas del llenguatge algorísmic, les regles de format i d’estil són arbitràries i fixades per conveni, però cal seguir-les perquè sigui fàcil de llegir i revisar, de la mateixa forma que es fa quan es programa en C o altres llenguatges. En l’exemple anterior, no hi ha aplicada cap indentació i el pseudocodi és molt difícil de llegir. Fixeu-vos com canvia quan apliquem correctament unes mínimes regles.

Pseudocodi correcte:

algorithm preuHotel
    var
        preuHabitacio: real;
        numDies: integer;
        preuTotal: real;
    end var
    
    writeString("Preu de l'habitació: ");
    preuHabitacio := readReal();
    writeString("Número de dies: ");
    numDies := readInteger();
    
    preuTotal := preuHabitacio * integerToReal(numDies);
    writeString("Preu total = ");
    writeReal(preuTotal);
end algorithm

Cal esmentar que les indentacions són especialment importants per a la lectura dels programes, ja que permeten identificar ràpidament els blocs de codi, les funcions i accions, les estructures iteratives i alternatives, i la seva dependència jeràrquica. Per aquest motiu, l’ús d’indentacions en el pseudocodi es absolutament necessari.

1.12.3 Declaració de variables: identificadors no permesos

Pseudocodi incorrecte:

var
    1Hotel_ID: integer;
    2Hotel_ID: integer;
end var

El nom de les variables pot contenir números sempre i quan no sigui a la primera posició. Utilitzarem el model camelCase per definir el nom de les variables.

Pseudocodi correcte:

var
    hotelId1: integer;
    hotelId2: integer;
end var

1.12.4 Declaració de variables: operador de declaració

Pseudocodi incorrecte:

var
    id:= integer;
    brand:= string;
    name:= string;
end var

El següent error podria semblar un error lleu, però és important respectar el Nomenclator i utilitzar els operadors correctament. En llenguatge algorísmic, l’operador de declaració de tipus és : i no :=, que és l’operador d’assignació de valor.

Pseudocodi correcte:

var
    id: integer;
    brand: string;
    name: string;
end var

1.12.5 Declaració de variables: noms inadequats

Aquest no és un error sintàctic o semàntic, però sí un error de disseny molt freqüent.

Pseudocodi incorrecte:

const
    aux: integer = 7;
end const

var 
    aux2: integer;
    result1: integer;
end var

aux2 := 189;
result1 := aux1 * aux2;

{ ... }

Els noms de les variables i constants ha d’aportar el màxim d’informació possible sobre el seu contingut i tipus de les dades, així com de la seva funció dins l’algorisme. Cal evitar sempre l’ús de noms genèrics que no aporten cap mena d’informació i es poden confondre fàcilment amb d’altres variables amb noms similars (per exemple aux, aux2, result1, flag, number, etc.).

L’algorisme anterior calcula el nombre d’hores fetes per un treballador; el total s’obté a partir dels dies treballats durant l’any i de la seva jornada laboral de set hores diàries. Com es pot comprovar en llegir l’algorisme, resulta impossible entendre la funció de cada variable i, encara menys, el significat de les dades que contenen. Aquest problema queda resolt utilitzant noms més descriptius:

Pseudocodi correcte:

const
    dailyWorkHours: integer = 7;
end const

var 
    yearlyWorkDays: integer;
    yearlyWorkHours: integer;
end var

yearlyWorkDays := 189;
yearlyWorkHours := yearlyWorkDays * dailyWorkHours;

{ ... }