Informática
Llenguatje Pascal
- Annex de Llenguatge Pascal -
Índex
Índex
Llenguatge Pascal
Pascal és un llenguatge de programació imperatiu, d'alt nivell. En lloc de repassar tota la sintaxi del Pascal, començarem per l'estructura general d'un programa i després, de mica en mica, anirem veient l'equivalència en Pascal de les diferents estructures i eines de programació tractades en els altres mòduls.
Intentarem seguir el mateix ordre que en els mòduls anteriors, perquè aquest mòdul els pugui anar complementant, de manera que en acabar cadascun dels altres mòduls puguem estudiar una part d'aquest i traduir els algoritmes que haguem après a programes en Pascal, per a comprovar que funcionen adequadament.
Veurem que l'estructura dels programes en Pascal no difereix gaire de l'estructura dels algoritmes que s'expliquen en els altres mòduls de l'assignatura. Per tant, la traducció dels algoritmes a programes escrits en Pascal no ha de ser gaire complicada.
Objectius
Aquest annex té la finalitat de permetre implementar els algoritmes que s'aniran veient en els cinc mòduls teòrics que formen l'assignatura, mitjançant un llenguatge d'alt nivell. La implementació d'aquests algoritmes donarà lloc a programes que es podran executar amb l'ordinador. Això permetrà veure com els algoritmes i les tècniques de disseny de programes apresos en els altres mòduls possibiliten arribar a escriure programes que l'ordinador entén, executa i a partir dels quals dóna uns resultats.
En els materials didàctics d'aquest mòdul podreu trobar les eines necessàries per a assolir els objectius següents:
Conèixer la sintaxi del Pascal per a escriure programes en aquest llenguatge.
Conèixer els tipus de dades del Pascal per a fer-los servir de manera adequada en cada tipus específic de problema.
Conèixer els components del Pascal que cal utilitzar en la traducció dels components que hagueu vist en els altres mòduls de l'assignatura.
Saber traduir qualsevol algoritme a un programa en Pascal i convertir-lo en un fitxer executable.
Estructura general d'un programa Pascal
L'estructura d'un programa en Pascal consta de les mateixes tres parts que un algoritme:
Capçalera del programa, que identifica el programa.
Després de la capçalera del programa, escriurem la declaració dels objectes, les variables i les constants que utilitza el programa. Tot objecte emprat pel programa s'ha d'haver definit en la part de declaracions.
El cos del programa comença amb la paraula reservada begin, a continuació vénen totes les instruccions del programa i al final la paraula reservada end i un punt. Tot això ho anomenarem bloc.
program nom_programa;
{ declaracions }
begin
{ especificació d'instruccions }
end.
Una paraula reservada és una paraula a la qual el llenguatge Pascal dóna un significat especial, i només la podrem usar amb aquest significat. No podrem utilitzar una paraula reservada, per exemple, per a donar nom a un objecte. Quan fem servir l'editor de codi de RHIDE, en el moment en què escrivim una paraula reservada, aquesta surt en color blanc automàticament. Això facilita la comprensió del programa, i a més, ens permet comprovar si l'escrivim correctament.
Un bloc és un conjunt d'instruccions del llenguatge Pascal, emmarcades entre les paraules reservades begin i end. Per exemple:
begin
compt := 0;
compt_m := 0;
a := 5;
readln(x);
while x > 0 do
begin
compt := compt + 1;
if (x mod a) = 0 then
compt_m := compt_m + 1;
end;
end.
Capçalera d'un programa
La capçalera d'un programa està formada per la línia següent:
La paraula reservada program i el nom del programa, seguit d'un punt i coma, que serveix per a identificar-lo. És convenient donar al programa el mateix nom que li hem posat en el projecte (aplicació), però el podem canviar sense cap mena d'inconvenient.
Comentaris
Una eina important per a fer més clars els programes són els comentaris. Un comentari és una explicació d'una instrucció o conjunt d'instruccions, o el perquè d'un determinat objecte.
Quan escrivim un programa sabem perfectament per què escrivim cada instrucció o per què declarem cada objecte, però el que per a nosaltres és tan evident pot no ser-ho per a algú altre que s'hagi de llegir el que hem escrit, o fins i tot per a nosaltres mateixos si ens llegim el programa molt temps després d'haver-lo escrit. Si volem donar més claredat al programa i fer-lo més fàcil d'entendre, hi hem de posar explicacions de tot el que anem fent.
Podem escriure comentaris en Pascal de dues maneres:
entre els símbols { ... }
entre els símbols (* ... *)
Tot el que hi hagi entre els símbols indicats, el compilador ho ignorarà. En tots dos casos passarà exactament el mateix, i el comentari pot ocupar tantes línies com vulguem.
Ho podeu veure en l'exemple següent:
. . .
compt := 0; { Inicialitzem el comptador a 0}
{ Ara comencem a llegir caràcters d'entrada i els anem comptant.
Anirem llegint fins que arribi un punt }
read(c); { llegim el primer caràcter}
while c <> '.' do { mentre el caràcter que acabem de llegir sigui diferent de punt}
begin
compt := compt + 1; { incrementem el comptador de caràcters }
read(c); { llegim el caràcter següent }
end;
. . .
Fixem-nos que quan posem un comentari per a aclarir què fa una instrucció, normalment l'escrivim en la mateixa línia. La línia que conté la instrucció s'escriu exactament igual que si no hi hagués el comentari, que és ignorat totalment pel compilador.
Declaracions
En la part de declaracions és on definirem els objectes que fa servir el programa. Hi ha dos tipus d'objectes que podem utilitzar:
Un objecte constant. És un objecte que conté sempre el mateix valor, durant tot el temps que dura l'execució del programa.
Un objecte variable. Pot contenir diversos valors, i dins del programa podem escriure instruccions que modifiquin el valor que conté.
Tot objecte, variable o constant, que utilitzem en un programa ha d'haver estat declarat abans d'utilitzar-lo.
Tot objecte ha de tenir un nom que permeti identificar-lo. Aquest nom o identificador pot ser qualsevol, sempre que comenci per una lletra o bé pel símbol "_", i només pot contenir lletres, dígits i l'esmentat símbol.
Ni en la declaració de constants ni en la de variables importa l'ordre amb el qual declarem els objectes; l'únic que importa és que hi hagi tots els que farem servir.
Declaració de constants
Les constants es declaren escrivint la paraula reservada const, i a continuació tota la llista de noms de constants, el tipus de dades i el valor que ha de tenir cadascuna. Cada línia ha d'anar seguida de punt i coma:
const
nom_objecte_1 : tipus_1 = valor1;
nom_objecte_2 : tipus_2 = valor2;
. . .
Moltes vegades es pot prescindir d'especificar el tipus de dades de les constants, però és millor indicar-los ja que el codi serà més eficient.
Exemple de declaració de constants
Podríem escriure una declaració amb les tres constants següents:
const
nombre : integer = 157;
lletra :char = 'A';
sou : real = 121345;
Això defineix les tres constants nombre, lletra i sou per tal que les puguem utilitzar en les instruccions que hi hagi en el programa. En una expressió aritmètica, per exemple, serà exactament el mateix escriure 157 que escriure nombre.
Declaració de variables
Les variables es declaren de manera semblant a les constants. Caldrà escriure la paraula reservada var, i a continuació la llista d'objectes variables, i assenyalar per a cadascuna el tipus de valors que podrà contenir. Cada línia ha d'anar seguida de punt i coma:
var
nom_objecte_1 : tipus1;
nom_objecte_2 : tipus2;
Exemple de declaració de variables
Per exemple, podem declarar quatre variables: una que contindrà caràcters, dues que contindran enters i una que contindrà valors lògics. En el subapartat següent veurem quins són els tipus de variables que podem declarar.
var
lletra : char;
edat : integer;
nombre : integer;
trobat : boolean;
Quan declarem més d'una variable del mateix tipus podem fer-ho en la mateixa línia, separant les diferents variables amb coma. Per tant, seria exactament el mateix si haguéssim escrit la declaració de variables de la manera següent:
var
lletra : char;
edat, nombre : integer;
trobat : boolean;
Tipus de dades de les variables
El tipus de dades d'una variable determina els valors que aquesta variable podrà prendre durant l'execució del programa i les operacions que s'hi podran fer. Quan declarem una variable, n'hem d'especificar el tipus, i només podrem assignar-li valors d'aquell tipus, ja que si no es produiria un error.
Els valors més típics que ens interessarà guardar en les variables dels nostres programes són nombres (enters i reals), caràcters (lletres i símbols en general) i lògics (els valors "cert" i "fals"). Alguns d'aquests tipus de dades formen part del que s'anomena tipus ordinal, que permet fer subagrupacions dels valors, com veurem més endavant.
Enters
Una variable de tipus enter pot contenir únicament nombres enters. Els enters poden ser positius i negatius. El tipus general d'enter és:
integer: enters amb signe.
El rang dels valors permesos depèn de la UCP (unitat central de procés) i del sistema operatiu que fem servir. El compilador GPC permet modificar el tipus enter per obtenir rangs diferents de sencers, com els tipus de sencers definits en els compilador de Borland. Però el més recomanable de cara elaborar els exercicis de l'assignatura és utilitzar el tipus sencer, integer, que té el rang [-2147483648, 2147483647].
Reals
Una variable de tipus real pot contenir nombres reals. El rang de valors permesos va de [5.0 x 10-324 a 1.7 x 10308] amb números positius i negatius. El tipus general de real és:
real: número real amb signe.
Caràcters
Les variables de tipus caràcter poden contenir qualsevol dels símbols inclosos en la taula ASCII de caràcters, que és la taula de caràcters que fan servir actualment la majoria d'ordinadors:
char: qualsevol caràcter de la taula ASCII
Una variable de tipus caràcter pot contenir un únic caràcter, cosa que en molts casos no ens bastarà. Per exemple, per a guardar un nom necessitem més d'un caràcter. El Pascal disposa d'un tipus que permet guardar més d'un caràcter en una sola variable, és a dir, ens permet guardar una cadena de caràcters que pot tenir una longitud, que no superi els 255 caràcters:
WrkString: cadena de caràcters que pot contenir-ne fins a 255
o bé:
string[nombre_màxim]: cadena de caràcters de com a màxim el nombre de caràcters indicat.
Es recomana la utilització de la segona versió, ja que el tipus WrkString ha d'estar definit en un mòdul que hem de recordar-nos de carregar en l'aplicació.
Lògics
Les variables de tipus lògic només poden contenir dos valors, el valor True (cert) i el valor False (fals). Aquests valors són molt interessants en programació, ja que permeten seleccionar condicions.
boolean: un dels dos valors True o False
Tipus ordinals
Un tipus ordinal és un tipus en el qual està ben definit quin és el primer valor del tipus, quin és l'últim i quin és el successor i el predecessor de cadascun dels valors del tipus (excepte el predecessor del primer i el successor de l'últim).
Tots els tipus que hem vist, llevat dels reals, són ordinals. Els nombres reals no són ordinals, ja que hi ha un rang de valors pràcticament infinit.
En conseqüència, un tipus ordinal sempre té definit un ordre entre els seus valors:
-
En el cas dels enters l'ordre és evident.
-
En el cas dels caràcters l'ordre és el d'aparició en la taula ASCII.
-
En el cas dels booleans l'ordre és False, True.
Instruccions elementals
Quines són les instruccions elementals
Tal com hem vist en el mòdul "Introducció a l'algorísmica", hi ha tres instruccions bàsiques que haurem de poder utilitzar en els nostres programes:
-
Assignar valors a les variables.
-
Llegir valors provinents d'algun dispositiu d'entrada de l'ordinador.
-
Escriure valors en algun dispositiu de sortida.
Aquests valors que manipularem, moltes vegades no seran simples, sinó que seran el resultat d'avaluar una expressió. Per tant, hauríem de començar veient com es formen les expressions.
Expressions
Una expressió és una combinació de valors i operadors. Segons el tipus d'operadors, els valors que s'han de combinar han de ser d'un tipus concret, igual que el resultat de l'expressió. Tenim bàsicament tres tipus d'operadors:
-
operadors aritmètics,
-
operadors lògics i
-
operadors relacionals.
Veurem que els operadors del Pascal són gairebé els mateixos que hem tractat en el llenguatge algorísmic.
Operadors aritmètics
Combinen dos valors numèrics que donen com a resultat un altre valor numèric.
Taula dels operadors aritmètics
Operador | Operació | Tipus dels operands | Tipus del resultat |
+ | addició | enter o real | enter o real |
- | Subtracció | enter o real | enter o real |
* | Multiplicació | enter o real | enter o real |
/ | Divisió | enter o real | real o real |
div | Divisió entera | enter | Enter |
mod | Residu | enter | enter |
Exemples d'expressions aritmètiques
Suposant que tenim dues variables A i B que contenen els valors 17 i 5 respectivament:
(A * 2 + 6) / 4 + B avalua 15.
A mod B avalua 2 (el residu de dividir 17 entre 5).
A div B avalua 3 (la part entera de la divisió de 17 entre 5).
A / B avalua 3,4.
Operadors lògics
Combinen un o dos valors lògics per donar un nou valor lògic.
Taula dels operadors lògics
not | Dóna True quan el valor que hi ha a continuació és False, i False quan el valor és True. |
and | Dóna True si els dos valors combinats són True i False en cas contrari. |
or | Dóna True si algun dels dos valors combinats és True, i False si els dos són False. |
xor | Dóna True si un dels dos valors és True i l'altre és False, i False en cas contrari. |
Exemple d'utilització d'operadors lògics
Si tenim les variables lògiques A, B i C amb valors True, True i False, respectivament, podrem escriure les expressions següents:
A and not C dóna True.
A or C or not B dóna True.
A xor B dóna False.
(A or C) and (B or C) dóna True
Operadors relacionals
Comparen dos valors del mateix tipus, i donen com a resultat un valor lògic.
Taula dels operadors relacionals
= | dóna True quan els dos valors comparats són iguals |
<> | dóna True si els dos valors comparats són diferents |
< | dóna True si el primer valor és més petit que el segon |
> | dóna True si el primer valor és més gran que el segon |
<= | dóna True si el primer valor és més petit o igual que el segon |
>= | dóna True si el primer valor és més gran o igual que el segon |
Exemple d'utilització d'operadors relacionals
Si tenim les variables A, B, C i D amb valors 7, 2, True i False, respectivament, podrem escriure les expressions següents:
A > B dóna True.
not ( B <= A ) dóna False.
(D > C) and (A * B = 14) dóna False.
Operadors d'ordre
Els operadors d'ordre els podem utilitzar amb els tipus ordinals. Ens donen informació relativa a l'ordre que ocupa una variable en la relació d'ordre definida pel tipus de la variable.
Taula dels operadors d'ordre
ord(variable) | Ens indica l'ordre que ocupa el valor que conté la variable entre tots els possibles valors que aquesta pot prendre. |
pred(variable) | Ens dóna el valor anterior al que conté la variable. |
succ(variable) | Ens dóna el valor següent al que conté la variable. |
Exemple d'utilització d'operadors d'ordre
A continuació tenim uns exemples d'utilització dels operadors d'ordre:
var
a: char;
fvar
begin
a := ord('A'); { a val 65 }
pred('A'); { retorna `@' }
succ(65); { retrona 66 }
pred(ord('a')); { retorna 64 }
end
Ordre de precedència dels operadors
Si dins d'una expressió hi ha més d'un operador, s'avaluarà segons un ordre de precedència preestablert. L'ordre de prioritat d'avaluació dels diferents operadors entre si mostra a continuació.
-->Més prioritat[Author ID1: at Sun Feb 7 02:28:00 1999]
not
*, /, div, mod, and
+, -, or, xor
=, <>, <, >, <=, >=
-->Menys prioritat[Author ID1: at Sun Feb 7 02:28:00 1999]
Els operadors que es troben en la mateixa línia tenen la mateixa prioritat. Si dins d'una expressió trobem diversos operadors, primer s'avaluaran els de més prioritat i després els de menys. Si trobem dos operadors que tenen la mateixa prioritat, s'avaluaran en l'ordre en què apareixen en l'expressió (d'esquerra a dreta).
Podem canviar l'ordre d'avaluació dels operadors per mitjà dels parèntesis. Sempre s'avalua primer el que hi ha entre parèntesis. En cas de dubte, sempre és millor deixar clara la precedència amb l'ús dels parèntesis.
Assignació de valors a variables
Assignar un valor a una variable consisteix a guardar un valor dins de la variable, el qual hi romandrà fins que se n'hi assigni un altre. Després de l'assignació, quan utilitzem la variable estarem emprant el valor que hi hem guardat.
Tota variable a la qual vulguem assignar algun valor s'ha d'haver declarat en l'apartat de declaracions, i el valor que hi assignem ha de ser del tipus que haguem especificat per a la variable.
Per a assignar un valor a una variable, escriurem el següent:
variable := expressió;
On tenim el següent:
variable és el nom o etiqueta de la variable a la qual volem assignar el valor.
expressió és una expressió que, un cop avaluada, dóna com a resultat un valor del tipus de la variable.
Aquesta instrucció farà que s'avaluï l'expressió i el seu resultat sigui guardat dins de la variable. L'expressió pot ser:
-
Directament un valor del tipus de la variable.
-
Una combinació de valors i operadors com les que hem vist en l'apartat anterior.
Cal destacar que el compilador no reconeix el salt de línia. En Pascal podem escriure instruccions que ocupin més d'una línia, o bé escriure diverses instruccions en la mateixa línia, cosa que d'altra banda no és gaire aconsellable ja que resta claredat als programes.
Això vol dir que el compilador no reconeix el salt de línia com el final d'una instrucció i, per tant, cal emprar alguna altra marca pel que fa al cas. La marca que determina el final d'una instrucció en el llenguatge Pascal és el punt i coma. Així, doncs, tota instrucció d'un programa ha d'acabar amb aquesta marca, excepte algun cas molt concret que ja veurem més endavant.
Exemples d'assignació de valors a variables
Podríem fer les assignacions següents, declarant prèviament les variables:
Program Assignacions;
Var
lletra : char;
nom, cognom : string(30);
edat, nombre : integer;
trobat : boolean;
begin
lletra := 'A';
nom := 'Joan';
cognom := 'Pi';
edat := 18;
nombre := edat * 2;
trobat := (nombre > 50) or (lletra < 'C') or ((nombre div 2) = edat);
end.
Fixem-nos en els detalls següents, que ens seran de gran utilitat a l'hora de fer els nostres programes:
-
Quan utilitzem un valor de tipus caràcter o cadena de caràcters en una expressió, cal escriure'ls entre cometes simples, com hem fet en les assignacions lletra := 'A'; nom := 'Joan'.
-
En les expressions podem fer servir també variables i constants, com en el cas de nombre := edat * 2;.
-
Quan escrivim el nom d'un objecte, en realitat estem utilitzant el contingut de l'objecte. En el cas de edat el contingut és 18. Per tant, l'assignació feta a nombre, nombre := edat * 2; guarda dins de la variable nombre el valor 36.
Llegir valors dels dispositius d'entrada
Normalment ens interessarà que els nostres programes puguin obtenir dades que hi ha guardades en algun lloc o que algú els proporciona. Per tal que un programa pugui llegir dades, disposem d'una operació de lectura que ens llegeix un valor provinent d'un dispositiu d'entrada i ens el guarda dins d'una variable. El seu format és el següent:
read(variable);
Aquesta instrucció ens llegeix un valor d'un dispositiu d'entrada i el guarda dins de la variable indicada. Cal que el valor sigui del mateix tipus que la variable. Normalment, la lectura serà de valors del teclat, i el programa, quan arribi a aquesta instrucció, es quedarà parat fins que algú no teclegi algun valor.
També ens podem servir de la instrucció següent:
readln(variable);
Fa el mateix que l'anterior però salta una línia després de llegir. Més endavant podem veure uns exemples d'utilització d'aquesta instrucció.
Escriure valors als dispositius de sortida
Si volem escriure alguna cosa en algun dispositiu de sortida de l'ordinador, per tal de guardar informació o simplement presentar-la per la pantalla de l'ordinador, tenim també una operació d'escriptura que ens permet escriure en un dispositiu de sortida. El format d'aquesta operació és el següent:
write(expressió1, expressió2, ...);
Aquesta operació ens escriu el resultat d'avaluar la primera expressió en un dispositiu de sortida, a continuació el resultat d'avaluar la segona expressió, i així per a totes les expressions que posem entre parèntesis. Pel cap baix, hem d'especificar una expressió. També ens podem servir de la instrucció següent:
writeln(expressió1, expressió2, ...);
Fa el mateix que l'anterior, però salta una línia després d'escriure.
Si en les expressions que s'escriguin volem posar text perquè surti tal qual, cal posar-lo entre cometes. Per exemple:
write('Hola, com anem!');
També podem utilitzar variables i constants en les expressions que volem escriure, de manera que apareguin els continguts d'aquestes variables i constants. A continuació podem veure uns exemples d'utilització d'aquestes instruccions.
Exemple 1 de lectura i escriptura
program Edat;
{ Demana el nom i l'edat a l'usuari i calcula la que tindra d'aquí a deu anys }
Var
edat, edat_10 :integer;
nom : string(30);
Begin
{ Llegeix les dades d'entrada }
write('Com et dius ? ');
readln(nom);
write('Quina edat tens ? ');
readln(edat);
{ Calcula l'edat de l'usuari, d'aquí a deu anys }
edat_10 := edat + 10;
{Sortida de dades }
writeln('Hola ', nom);
writeln('Actualment tens: ',edat,' anys, i d''aqui a deu anys en tindras:',
edat_10, ' anys');
end.
Quan el programa arriba a la instrucció readln(nom); queda parat fins que algú no introdueixi un valor en el teclat. Quan s'acaba de teclejar un valor, cal prémer <Return> perquè el programa entengui que ja s'ha introduït tot el valor. En aquest cas el valor ha de ser de tipus caràcter. En la instrucció readln(edat);, el valor introduït ha de ser numèric, enter, ja que si no ho és, el compilador donarà un error perquè assignarà aquest valor a la variable edat, que és de tipus enter.
Observem també com una instrucció massa llarga la podem dividir en més d'una línia. És el cas de la darrera instrucció writeln(...). És important notar, però, que no podem partir la instrucció en qualsevol lloc. Sempre hem de respectar les paraules senceres, o les cadenes de caràcters.
Una altre observació important a destacar és la utilització de l'apòstrof dins d'una cadena de caràcters. Això pot ser problemàtic, ja que el compilador pot interpretar l'apòstrof com el final de la cadena. Per això, n'hem de col·locar dos, un darrera l'altre i sense cap espai entre ells. Tampoc no l'hem de confondre amb el caràcter `”' (cometes dobles). Observeu la cadena ' anys, i d''aqui a deu anys en tindras:' de l'exemple.
Exemple 2 de lectura i escriptura
També podíem haver escrit el programa de l'exemple anterior, estalviar-nos la variable edat_10 i posar l'expressió edat + 10 directament en l'escriptura:
program Edat2;
{ Demana el nom i l'edat a l'usuari i calcula la que tindra d'aquí a deu anys }
Var
edat :integer;
nom : string(30);
Begin
{ Llegeix les dades d'entrada }
write('Com et dius ? ');
readln(nom);
write('Quina edat tens ? ');
readln(edat);
{Sortida de dades }
writeln('Hola ', nom);
writeln('Actualment tens: ',edat,' anys, i d''aqui a deu anys en tindras:',
edat + 10, ' anys');
end.
Això ens permet estalviar-nos variables, instruccions, i en alguns casos queda més clar què volem fer. Quan l'expressió és molt complexa, però, és millor calcular-la primer, guardar-ne el resultat en alguna variable, i després escriure el contingut de la variable, com hem fet en el primer cas.
Exemple 3 de lectura i escriptura
Tot seguit veurem un programa amb una mica més de càlculs. Es tracta del programa que converteix una quantitat positiva que representa un nombre de segons en dies, hores, minuts i segons.
program Conversio_horaria;
Var
nDies, nHores, nMinuts, nSegons : integer;
segons, nMinutsTotals, nHoresTotals : integer;
begin
{ Lectura de dades }
write(`Entra el numero total de segons: ');
read(segons);
{ Calcul dels dies, hores, minuts i segons }
nSegons := segons mod 60;
nMinutsTotals := segons div 60;
nMinuts := nMinutsTotals mod 60;
nHoresTotals := nMinutsTotals div 60;
nHores := nHoresTotals mod 24;
nDies := nHoresTotals div 24;
{ Sortida de Resultats }
writeln(segons, ` segons es descomposen en:');
writeln(nDies, ' Dies');
writeln(nHores, ' Hores');
writeln(nMinuts, ' Minuts');
writeln(nSegons, ' Segons');
end.
Estructures de control
Tipus d'estructures de control
Els petits programes d'exemple que hem vist fins ara executaven les instruccions de manera seqüencial, és a dir, una darrere l'altra segons l'ordre en què es trobaven dins del programa. Es tractava d'una estructura seqüencial, un seguit d'instruccions en línies consecutives que acabaven en punt i coma, i executades per l'ordinador una darrere l'altra en l'ordre en què apareixen. Habitualment, una estructura seqüencial comença amb begin i acaba amb end (l'anomenem bloc).
Com hem vist en el mòdul "Introducció a l'algorísmica", també podrem utilitzar en els nostres programes altres estructures de control que ens permetin actuar sobre l'execució d'algunes instruccions i la repetició d'altres.
L'estructura condicional permet indicar a l'ordinador que un conjunt d'instruccions només s'han d'executar si es compleix una determinada condició. El Pascal ens ofereix dues estructures condicionals: if i case.
Les estructures repetitives ens permeten indicar a l'ordinador que executi un seguit d'instruccions més d'una vegada. En Pascal hi ha les mateixes tres estructures que havíem vist en el llenguatge algorísmic: while, repeat i for.
Estructura condicional
Estructura if
La sintaxi de l'estructura if és la següent:
if expressió then
bloc
on tenim que:
expressió dóna com a resultat un valor lògic
bloc és un conjunt d'instruccions (en les quals hi pot haver altres estructures de control)
En aquesta estructura s'executen les instruccions que formen part del bloc només si l'expressió de l'if dóna True, i el programa continua després a partir de les instruccions que hi ha després del bloc. Si el bloc conté més d'una instrucció, s'ha d'escriure begin al principi i end al final.
Una altra possibilitat de l'estructura condicional és la següent:
if expressió then
bloc1
else
bloc2
En aquest cas s'executen les instruccions del primer bloc quan l'expressió de l'if dóna True, i les del segon bloc en el cas contrari. Els dos blocs poden contenir més d'una instrucció entre begin i end. Hem de tenir en compte que la instrucció anterior a else no pot portar punt i coma al final.
Exemple d'utilització de l'estructura if
En l'exemple següent recorrem a una estructura condicional per comprovar si un nombre és positiu o negatiu per calcular-ne el valor absolut:
program Valor_absolut;
{ Calcula el valor absolut d'un nombre donat }
Var
n : integer;
Begin
Writeln('Programa Valor Absolut');
Write('Digues un nombre: ');
Readln(n); { llegim el nombre de teclat}
if n > 0 then { comprovem si el nombre es positiu}
writeln('El nombre es positiu')
else { el nombre no es més gran que zero}
if n < 0 then { comprovem si el nombre es negatiu}
begin
writeln('El nombre es negatiu');
n := -n; { canviem el signe per fer-lo positiu}
end
else { no es ni mes gran ni mes petit que 0}
writeln('El nombre es 0');
{ Sortida de dades }
writeln('El valor absolut es: ',n); { escrivim el resultat}
end.
Fixem-nos en els detalls següents:
-
Hem escrit comentaris per aclarir el funcionament del programa. Cal poder entendre fàcilment el que fa cada instrucció quan ens llegim un programa.
-
Hem fet servir una estructura condicional dins d'una altra. No hi ha cap problema per a usar estructures de control dins d'altres, sempre que quedi perfectament delimitat l'abast de cada una. Tot else se suposa associat a l'if més proper. Podem utilitzar begin i end per a indicar explícitament l'abast d'una estructura de control.
-
La primera estructura condicional no porta begin i end, ja que només hi ha una instrucció a l'if i només una estructura de control a l'else.
-
La segona estructura condicional necessita begin i end, ja que conté dues instruccions.
-
Abans d'else mai no escrivim punt i coma.
-
Hem escrit les diferents instruccions deixant espais al principi de línia per tal de fer més clara la lectura del programa, i així fem més evident l'abast de cada estructura de control. Això és només una qüestió de forma, ja que el compilador ignora aquest fet, és a dir, no les agruparà correctament simplement perquè estiguin alineades. En cas de dubte, sempre és millor indicar clarament els blocs amb begin i end.
Estructura case
La sintaxi de l'estructura case és la següent:
case expressió of
valor1 : bloc1;
valor2 : bloc2;
. . .
else
blocn;
end;
on tenim el següent:
Expressió ha de donar un valor de tipus enter, caràcter o booleà.
Valor Els diferents valors que s'especifiquin hauran de ser del mateix tipus.
En aquesta estructura s'executen les instruccions que formen part del bloc que correspon al valor que és igual al resultat de l'expressió. En el cas que no n'hi hagi cap, s'executen les instruccions que hi ha en el bloc que acompanya l'else. La part else és opcional; podem escriure una estructura case que no tingui else, de manera que si no hi ha cap valor igual al resultat de l'expressió, no s'executa cap instrucció.
Si algun bloc conté més d'una instrucció cal escriure begin al principi i end al final del mateix.
En el cas que per a valors diferents s'hagin d'executar les mateixes instruccions, podem posar els valors en la mateixa línia separats per comes. És a dir, podríem tenir una estructura com la següent:
case expressió of
valor1 : bloc1;
valor2, valor3, valor4 : bloc2;
valor5, valor6 : bloc3;
. . .
end;
En qualsevol cas, el que no pot passar mai és que un mateix valor aparegui més d'una vegada en llocs diferents, perquè llavors no queda ben determinat el bloc que s'ha d'executar.
Exemple d'utilització de l'estructura case
L'exemple següent pot aclarir la utilització d'aquesta estructura:
program Control;
{ Donat un numero, controla diverses possibilitats }
Var
I : integer;
begin
{ Entrada de dades }
Write('Tecleja un numero: ');
read(i);
{ Comprovacio de casos amb case }
case i of
0, 2, 4, 6, 8: writeln('Numero parell.');
1, 3, 5, 7, 9: writeln('Numero imparell.');
10..100: writeln('Numero entre 0 i 100.');
else
writeln('Numero negatiu o més gran que 100.');
end;
end.
Estructures repetitives
Estructura while
La sintaxi de l'estructura while és la següent:
while expressió do
bloc
on tenim que:
expressió dóna com a resultat un valor lògic
bloc és un conjunt d'instruccions (en les quals hi pot haver altres estructures de control)
Quan l'ordinador executa una estructura com aquesta, després d'avaluar l'expressió fa el següent:
1. Si dóna "cert" executa les instruccions del bloc i torna a repetir el procés.
2. Si dóna "fals" segueix amb les instruccions que hi hagi a continuació.
Per tant, aconseguirem que s'executin les instruccions del bloc repetidament mentre l'expressió avaluï cert. Evidentment, si la primera vegada l'expressió no dóna "cert" no s'executa el bloc cap vegada. També hem de tenir la precaució d'assegurar-nos que l'expressió arribi a ser falsa en algun moment, degut a les instruccions de l'interior del bloc, ja que si no el programa es quedarà executant el bloc indefinidament.
Exemple d'utilització de l'estructura while
Ara veurem com seria el programa de la divisió entera vist en el mòdul "Introducció a l'algorísmica". Caldrà llegir dos nombres i calcular el quocient i el residu enter resultants de dividir-los; així doncs:
Program divisio_entera;
{ Divisio entera per restes successives }
Var
divisor, dividend, quocient, resta : integer;
Begin
{ Entrada de dades }
write(`Entra el dividend: `);
readln(dividend);
write(`Entra el divisor: `);
read(divisor);
{ Calcul de la divisio entera }
resta := dividend; { Inicialitzem resta i quocient }
quocient := 0;
while resta >= divisor do { repetim fins que la resta es mes petita que el divisor }
begin
quocient := quocient + 1; { Incrementem el quocient }
resta := resta - divisor; { Restem el divisor del numero actual }
end;
{ Sortida de resultats }
writeln('Quocient: ', quocient);
writeln('Resta: ', resta);
end.
Estructura repeat
La sintaxi de l'estructura repeat és la següent:
repeat
bloc
until expressió
Quan l'ordinador executa una estructura com aquesta, fa el següent:
1. Executa les instruccions del bloc.
2. Avalua l'expressió, llavors:
a) Si dóna "fals" torna al pas 1.
b) Si dóna "cert" continua amb les instruccions que hi hagi després.
En aquest cas, el bloc s'executa sempre almenys una vegada. Per tant aconseguirem que s'executin les instruccions del bloc repetidament fins que l'expressió avaluï cert. Aquesta estructura és molt semblant a l'anterior, i de fet són totalment intercanviables a l'hora de programar. Tot el que podem fer amb una ho podem fer també amb l'altra.
Exemple d'utilització de l'estructura repeat
Vegem com seria l'exemple de la divisió entera amb aquesta segona estructura:
Program divisio_entera;
{ Divisio entera per restes successives }
Var
divisor, dividend, quocient, resta : integer;
Begin
{ Entrada de dades }
write(`Entra el dividend: `);
readln(dividend);
write(`Entra el divisor: `);
read(divisor);
{ Calcul de la divisio entera }
resta := dividend; { Inicialitzem resta i quocient }
quocient := 0;
if resta>=divisor then { Cal veure que com a minim hem de fer una resta }
repeat { repetim fins que la resta es mes petita que el divisor }
quocient := quocient + 1; { Incrementem el quocient }
resta := resta - divisor; { Restem el divisor del numero actual }
until resta < divisor;
{ Sortida de resultats }
writeln('Quocient: ', quocient);
writeln('Resta: ', resta);
end.
És de destacar que, amb l'ús de l'estructura while, ens cal utilitzar una condició if per descartar els casos on inicialment el dividend és menor que el divisor. Això no passava amb l'estructura while, ja que aquesta darrera pot no executar-se cap vegada. Però el repeat s'executa com a mínim un cop. Proveu a veure què passa si elimineu la condició if i introduïu valors com ara 5 pel dividend i 12 pel divisor. Proveu els mateixos valors en la versió amb while.
Estructura for
En les dues estructures anteriors, el nombre de vegades que s'executaran les instruccions del bloc depèn d'una condició. En l'estructura for, en canvi, el nombre d'iteracions es determina per mitjà d'un rang de valors compresos entre un mínim i un màxim donats. La sintaxi d'aquesta estructura és la següent:
for variable_control := valor inicial to valor final do
bloc
on tenim que:
variable_control ha de ser una variable de tipus ordinal
valor inicial, valor final han de ser del mateix tipus
Quan l'ordinador executa una estructura com aquesta, fa el següent:
1. Guarda dins la variable de control el valor inicial.
2. Compara el valor de la variable de control amb el valor final, llavors:
a) Si la variable de control conté un valor més petit o igual que el final:
Executa el bloc d'instruccions.
Incrementa el valor de la variable de control en 1.
Torna al pas 2.
b) Si la variable de control conté un valor més gran que el valor final, surt de l'estructura i continua amb les instruccions que hi hagi a continuació.
S'executaran les instruccions del bloc tantes vegades com valors hi ha entre el valor inicial i el valor final, inclusivament, i la variable de control anirà prenent aquests valors un darrere l'altre en cada iteració.
Cal anar amb compte de no modificar la variable de control dins del bloc d'instruccions continguts en l'estructura for, ja que si ho féssim manipularíem el funcionament de l'estructura i es podrien produir resultats totalment imprevistos.
Hi ha una altra possibilitat en l'estructura for: enlloc d'incrementar la variable de control en cada iteració, decrementar-la. Per tant, el valor inicial serà un valor més gran que el valor final, i la variable de control anirà prenent tots els valors intermedis. Per a indicar que el for ha de decrementar es fa servir la paraula reservada downto en lloc de to. Així doncs, la sintaxi és la següent:
for variable_control := valor inicial downto valor final do
bloc
Exemple d'utilització d'estructura for
L'exemple següent escriu els nombres del 5 al 25:
program escriure_5_a_25;
{ Programa que escriu els numeros del 5 al 25, inclosos }
Var
i : integer;
begin
Writeln(`Programa que escriu els numeros del 5 al 25, inclosos');
for i:=5 to 25 do { Iteració d'escriptura }
writeln (i); { La variable i va prenent els valors entre el 5 i el 25 }
end.
Exemples d'utilització de les tres estructures repetitives
Aquestes tres estructures repetitives s'assemblen bastant; de fet, si només disposéssim de l'estructura while podríem fer el mateix que podem fer amb les altres dues. En alguns casos, però, és més interessant emprar una estructura que una altra, bé per a fer més clar el programa, bé perquè una estructura s'adapta millor a la visió que tenim de la solució del problema.
Ara veurem uns exemples d'utilització de les tres estructures en tres programes que fan el mateix: escriure els nombres de l'1 al 20 per pantalla. Aquest programa s'adequa més a l'estructura for, ja que el nombre d'iteracions està determinat pels dos valors 1 i 20, però les altres estructures també poden ser vàlides:
Estructura while
program Ex_while;
{ Escriu els numeros de l'1 al 20 usant while }
Var
N : integer;
begin
Writeln('Nombres de 1 a 20, usant una estructura while');
n := 1; { Inicialitzacio de la variable de control }
while n <= 20 do { Iteracions, fins al numero 20, inclos }
begin
writeln(n); { Escriptura del numero }
n := n + 1; { Increment de la variable de control }
end;
writeln('S''ha acabat');
end.
Estructura repeat
program Ex_repeat;
{ Escriu els numeros de l'1 al 20 usant repeat }
Var
N : integer;
begin
Writeln('Nombres de 1 a 20, usant una estructura repeat');
n := 1; { Inicialitzacio de la variable de control }
repeat { Iteracions }
writeln(n); { Escriptura del numero }
n := n + 1; { Increment de la variable de control }
until n>20; { fins que la variable de control supera el limit }
writeln('S''ha acabat');
end.
Estructura for
program Ex_for;
{ Escriu els numeros de l'1 al 20 usant for }
Var
N : integer;
begin
Writeln('Nombres de 1 a 20, usant una estructura for');
for n := 1 to 20 do { Iteracions de 1 fins a 20}
writeln(n); { Escriptura del numero }
writeln('S''ha acabat');
end.
Fixeu-vos que aquest tercer programa resulta més simple i és més entenedor que els altres dos.
Fixeu-vos en la penúltima línia del programa: write ('S'ha acabat');. Normalment utilitzem la cometa simple per a indicar on comença i on acaba el text que s'ha d'escriure. Quan dins del text volem escriure un apòstrof cal posar dues cometes simples, ja que si no el compilador interpreta que allà s'acaba el text.
Tipus de dades definits per l'usuari
Creació de dades
En alguns casos ens pot interessar declarar dades d'algun tipus especial per al nostre programa; aleshores haurem de tenir la possibilitat de crear-les. Podem declarar els nostres propis tipus de dades, just abans de la declaració de constants i variables, escrivint la paraula reservada type i a continuació la llista de tipus que vulguem crear:
type
nom_tipus_1 = forma_tipus_1;
nom_tipus_2 = forma_tipus_2;
. . .
on tenim que:
nom_tipus són els noms dels tipus que creem
forma_tipus indica el tipus predefinit a partir del qual declarem el nostre.
La creació de tipus és sobretot interessant quan en creem d'enumerats i subrangs, com veurem a continuació, o tipus estructurats, com veurem més endavant.
Tipus enumerats
Un tipus enumerat és un conjunt ordenat de valors coneguts tots a priori, i denotats mitjançant identificadors. La declaració d'un tipus enumerat té la sintaxi següent:
Tipus = (identificador_1, identificador_2, ... , identificador_n)
Cada un d'aquests identificadors denota un valor que correspon a la posició que ocupa l'identificador dins de la llista. Una variable declarada d'aquest tipus només podrà prendre un valor entre 1 i n, i la manera d'assignar aquest valor serà mitjançant un dels identificadors.
Exemple de creació d'un nou tipus de dades
Podem crear un tipus de dades enumerat per representar els dies de la setmana. Com que sabem que de dies de la setmana només n'hi ha set, i els coneixem a priori, podem crear el tipus tDia i després declarar una variable per guardar-hi valors d'aquests tipus:
type
tDia = (dilluns, dimarts, dimecres, dijous, divendres, dissabte, diumenge);
Var
dia, dia2 : tDia;
En aquest cas la variable podrà prendre únicament els set valors especificats a la llista. Llavors tenim:
Assignacions correctes | Assignacions incorrectes |
dia := dimarts; | dia := 2; |
dia2 := dijous; | dia2 := 4; |
Internament aquestes assignacions són equivalents, ja que l'ordinador per a una variable de tipus enumerat emmagatzema el nombre corresponent a la posició que ocupa l'identificador dins de la llista. Però en el nostre programa només podrem assignar els identificadors, no el seu equivalent numèric.
Com que els identificadors del tipus enumerat estan ordenats i se'n coneix el primer i l'últim, formen part d'allò que hem definit com tipus ordinals. És possible, per tant, fer comparacions d'ordre; per exemple, si escrivim l'expressió condicional (dia < dia2) donarà "cert", ja que dimarts apareix abans que dijous a la llista.
Exemple de tipus enumerat en una estructura for
Com que hi ha un ordre entre tots els elements d'un tipus enumerat podem servir-nos dels identificadors en una estructura de control for, per exemple:
Program Valoracio_dies;
{ Fa una valoracio dels dies de la setmana }
Type
tDies = (dilluns, dimarts, dimecres, dijous, divendres, dissabte, diumenge);
Var
dia : tDies;
begin
for dia := dilluns to diumenge do
case dia of
dilluns: writeln(`Buf! Dilluns, a treballar');
dimarts, dimecres, dijous: writeln(`A treballar');
divendres: writeln(`S''acosta el cap de setmana !');
dissabte, diumenge: writeln(`Visca el cap de setmana !');
end;
end.
Les variables de tipus enumerat no es poden escriure en pantalla ni llegir del teclat. Això passa perquè, en un tipus enumerat, els identificadors que representen cadascun dels diferents valors no són cadenes de caràcters, sinó simples etiquetes que indiquen al compilador l'ordre dels valors. És a dir, l'etiqueta dilluns, vista en els exemples, no és el mateix que la cadena de caràcters 'dilluns'. Així doncs, les instruccions següents donarien error si estiguessin dintre d'un programa:
write(dia); { no es pot escriure en pantalla el contingut d'una variable de tipus enumerat}
read(dia2); { no es pot llegir una variable de tipus enumerat }
El Pascal disposa d'uns operadors molt interessants per a treballar amb objectes de tipus enumerat. De fet, aquests operadors són vàlids per a qualsevol tipus ordinal i s'anomenen operadors d'ordre.
Tipus subrang
Un tipus subrang és un subconjunt d'un altre tipus de dades. El tipus de dades sobre el qual definim el subrang ha de ser ordinal (enter, caràcter, booleà o enumerat). Per a declarar un tipus subrang cal indicar el valor inicial i el valor final separats per dos punts. Aquests dos valors assenyalaran el tipus sobre el qual definim el subrang. Un subrang també és de tipus ordinal.
Exemple de declaració d'un tipus subrang
Podrem fer les declaracions següents:
type
tDia = (dilluns, dimarts, dimecres, dijous, divendres, dissabte, diumenge);
tDia_laborable = dilluns..divendres; { subrang de tDia }
tNombre_petit = 1 .. 20;
tLletra_alfabet = 'a'..'z';
Var
dia : tDia;
dia2 : tDia_laborable;
dia3 : dilluns .. dimecres;
lletres : tLletra_alfabet;
nombre : tNombre_petit;
Una variable de tipus subrang només pot prendre valors compresos en el rang especificat. No seran vàlides, per tant, les assignacions següents:
dia2 := diumenge; { dia2 només pot prendre valors
compresos en el rang dilluns..divendres }
lletres := '.'; { lletres només pot prendre valors compresos en el rang 'a'..'z' (lletres de l'alfabet) }
nombre := 25; { nombre només pot prendre valors entre 1 i 20 }
Quan definim un subrang és important que hi hagi un ordre entre els valors del tipus sobre el qual el definim, a fi que en quedin ben determinats els valors que hi haurà inclosos. En l'exemple de declaració d'un tipus subrang, quan definim el subrang 1..20 és evident quins són els valors que hi ha dins, ja que estem molt acostumats a treballar amb enters i en veiem molt clar l'ordre. Aquest fet no és tan clar quan definim un subrang sobre un tipus enumerat. D'entrada, cal que el tipus enumerat s'hagi definit abans de definir el tipus subrang.
En l'exemple que acabem de veure, primer hem especificat el tipus tDia, i sobre aquest tipus hem definit després dos subrangs. Els valors que contindrà cada un d'aquests subrangs i l'ordre d'aquests valors queden totalment determinats per la manera com s'han escrit els identificadors en el tipus enumerat. Per tant, si escrivim dilluns..divendres queda clar que el subrang comprèn tots els valors que hi ha des del valor dilluns fins al valor divendres.
Seqüències i fitxers seqüencials
El llenguatge Pascal no disposa del tipus de dades seqüència que hem vist en el mòdul "Tractament de seqüències". De fet, una seqüència és una col·lecció formada per un nombre indeterminat de dades del mateix tipus, amb unes característiques d'accés ben definides a la teoria.
El concepte de seqüència és una abstracció mol útil, que ens permet aplicar esquemes algorísmics de manera sistemàtica sobre un conjunt de dades. Recordeu com a mostra l'exemple de l'algorisme per comprovar si un nombre és primer, descrit en l'apèndix de la Guia d'Aprenentatge del Mòdul 2.
Sovint, però ens anirà bé tenir un conjunt de dades emmagatzemades en un fitxer per tal de poder-les entrar en el nostre programa sense necessitat d'haver-les de picar pel teclat cada vegada. En Pascal disposem d'un tipus de dades fitxer, que permet un accés de tipus seqüencial i que fàcilment podrà ser modelat com una seqüència. Per declarar una variable de tipus fitxer, ho farem de la següent manera:
fvartipus : file of tipus;
on tenim que:
fvartipus és la variable que referencia el fitxer.
tipus indica el tipus de dades que conté el fitxer (char, integer, real, ...).
Si volem poder passar els fitxers com a paràmetre en una funció, els haurem de declarar com a un nou tipus, tal com es veu a l'exemple:
tipus
t_fvartipus = file of tipus;
ftipus;
var
la_meva_seq : t_fvartipus;
fvar;
begin
crida_funcio( la_meva_seq );
end;
on tenim que:
t_fvartipus és el tipus de la variable que referencia el fitxer.
tipus indica el tipus de dades que conté el fitxer (char, integer, real, ...).
la_meva_seq variable de tipus fitxer
A diferència d'altres variables, que es guarden a la memòria de l'ordinador, per poder treballar amb variables de tipus fitxer, hem d'associar aquestes variables amb un fitxer extern que es guardarà en el disc amb un nom determinat. El fitxer extern contindrà justament una seqüència d'elements, tots del mateix tipus, que es correspondrà amb el tipus que indiquem en la declaració de la variable. El fet de guardar la seqüència en el disc ens permetrà poder-la utilitzar en posteriors execucions del nostre programa.
Els fitxers, com les seqüències, poden ser accedits per lectura o per escriptura. Anem a veure com es farien aquestes operacions.
Lectura d'un fitxer
Així com les seqüències disposen d'un capçal que va avançant posició a posició pels diferents elements que conté, fins arribar al final, els fitxers disposen també d'un mecanisme similar. Les funcions bàsiques d'accés a una seqüència tenen un equivalent en la seva implementació com a fitxers. Les veurem tot seguit, i a continuació les provarem amb un exemple.
Preparar
Preparar un fitxer per lectura, equival a fer la seva associació amb un fitxer extern, emmagatzemat en el disc, que ha d'existir prèviament. En primer lloc, hem d'indicar al compilador que voldrem accedir a un fitxer extern. Per això, afegirem la variable que associarem al fitxer, en la capçalera del programa:
Program lectura(input, output, infile);
On tenim que:
infile és la variable que declararem de tipus fitxer.
A la capçalera del programa podrem afegir tantes variables com fitxers necessiti el nostre programa. A continuació, caldrà associar la variable que referencia el fitxer amb el fitxer extern que estarà emmagatzemat en el disc amb un nom determinat. Això es farà amb la funció reset:
reset( infile, nom);
On tenim que:
infile és la variable que declararem de tipus fitxer.
nom és una cadena o variable que conté el nom del fitxer extern.
Si al fer el reset el fitxer no existís, el programa donaria un error en temps d'execució.
Consultar
Podem consultar l'element del fitxer on tenim el capçal amb:
Element := Infile^
On tenim que:
infile és la variable que declararem de tipus fitxer.
element és una variable del tipus d'elements que conté el fitxer.
L'element que es troba en el punt d'interès del fitxer es llegeix i es guarda en la variable element. De fet, no és necessari fer l'assignació a una variable; també podem fer la consulta enmig d'una expressió, etc...
Avançar
Per avançar el capçal del fitxer utilitzarem:
get(infile);
On tenim que:
infile és la variable que declararem de tipus fitxer.
El punt d'interès es desplaça una posició.
Fi
Sovint ens interessarà preguntar si hem arribat al final del fitxer, per tal de no intentar llegir elements que no existeixen. La funció que ens permet saber si estem al final del fitxer és:
eof( infile )
On tenim que:
infile és la variable que declararem de tipus fitxer.
Tancar
Finalment, quan hem acabat de treballar amb el fitxer, l'hem de tancar. Això ens pot evitar danys o pèrdua d'informació en el fitxer en cas d'una fallada del subministrament elèctric, per exemple. Per tancar el fitxer utilitzarem:
close(infile);
On tenim que:
infile és la variable que declararem de tipus fitxer.
Exemple: Lectura d'un fitxer de caràcters
A continuació anem a veure un exemple senzill de lectura de fitxers. En aquest cas, accedirem a un fitxer que conté una seqüència de caràcters i el mostrarem per pantalla, fent un recorregut pels elements del fitxer.
Program lectura(input, output, infile);
{ Programa que llegeix un fitxer de caracters i el mostra per pantalla. }
VAR
infile : file of char;
nom : String(80);
Begin
Write('Entra el nom del fitxer a llegir:');
readln( nom );
{ 'nom' conte el nom d'un fitxer extern que ha d'existir en el disc }
reset( infile, nom);
{ 'infile' esta situat en el primer element del fitxer extern }
while not eof( infile ) do
begin
{ 'infile' indica la posicio actual dins del fitxer extern.
La pantalla mostra els elements previs a la posicio actual.
Queda encara algun element per llegir en el fitxer.
}
write( infile^ ); { llegeix l'element actual i l'escriu }
get(infile); { avança en el fitxer }
end;
{ 'infile' esta al final del fitxer extern.
El contingut del fitxer esta a la pantalla
}
close(infile); { Tanquem el fitxer per no perdre dades }
end.
Podeu provar el funcionament del programa amb el GPC. Quan us pregunti pel nom del fitxer, heu d'entrar el nom d'un fitxer que existeixi en el disc (per exemple, el nom del vostre propi programa).
Escriptura en un fitxer
L'escriptura en un fitxer és molt similar a l'escriptura en una seqüència. Les funcions bàsiques d'accés a una seqüència tenen un equivalent en la seva implementació com a fitxers. Les veurem tot seguit, i a continuació les provarem amb un exemple.
Crear
Crear un fitxer per escriure-hi, equival a fer la seva associació amb un fitxer extern, emmagatzemat en el disc, que pot o no existir prèviament. En primer lloc, hem d'indicar al compilador que voldrem accedir a un fitxer extern. Per això, afegirem la variable que associarem al fitxer, en la capçalera del programa:
Program escriptura(input, output, outfile);
On tenim que:
outfile és la variable que declararem de tipus fitxer.
A la capçalera del programa podrem afegir tantes variables com fitxers necessiti el nostre programa. A continuació, caldrà associar la variable que referencia el fitxer. amb el fitxer extern, que s'haurà d'emmagatzemar en el disc amb un nom determinat. Això es farà amb la funció rewrite:
rewrite(outfile, nom);
On tenim que:
outfile és la variable que declararem de tipus fitxer.
nom és una cadena o variable que conté el nom del fitxer extern.
Si al fer el rewrite no existeix cap fitxer extern amb el nom especificat, el programa el crea com una seqüència buida. Si el fitxer ja existia prèviament, el rewrite eliminarà tots els seus elements. Per tant, després de crear el fitxer, sempre tindrem una seqüència buida.
Afegir
Afegir un element a un fitxer seqüencial és tant senzill com fer-ho en una seqüència. L'element s'afegeix sempre al final, a continuació dels elements prèviament entrats. Per fer-ho utilitzarem:
outfile^ := element; put(outfile);
On tenim que:
outfile és la variable que declararem de tipus fitxer.
element és una variable del tipus d'elements que conté el fitxer.
Naturalment, l'element que afegim al fitxer ha de ser del mateix tipus que el fitxer.
Tancar
Finalment, quan hem acabat de treballar amb el fitxer, l'hem de tancar. Això ens pot evitar danys o pèrdua d'informació en el fitxer en cas d'una fallada del subministrament elèctric, per exemple, i serà del tot necessari si el volem obrir a continuació per fer-hi operacions de lectura. Per tancar el fitxer utilitzarem:
close(outfile);
Exemple: Creació d'una seqüència de sencers.
A continuació anem a veure un exemple senzill d'escriptura de fitxers. En aquest cas, crearem un fitxer que conté una seqüència de sencers.
Program escriptura(input,output,outfile );{ Programa que crea una sequencia de enters en un fitxer extern. }
VAR
outfile : file of integer;
actual : integer;
nom : string[80];
Begin
Write('Entra el nom de la sequencia que contindra els sencers: ');
readln(nom);
Rewrite( outfile, nom );
{ 'nom' conte el nom de la sequencia creada com un fitxer extern.
La sequencia definida per 'outfile' esta buida.
La sequencia definida per 'outfile' esta preparada per escriure-hi.
}
Writeln('Entra els sencers que formaran la sequencia, (0 per acabar)');
read( actual );
While actual <> 0 do
Begin
{ La sequencia conte els sencers entrats fins ara, menys l'actual.
La sequencia esta preparada per escriure al seu final.
'actual' no es zero. }
outfile^ := actual; put(outfile); { Afegim l'element al final de la seq. }
read( actual );
End;
close( outfile );
{ 'nom' conte el nom de la sequencia creada com un fitxer extern.
La sequencia definida conte els sencers entrats, excepte el 0.
}
End.
Podeu provar el funcionament del programa amb el GPC. Quan us pregunti pel nom del fitxer, entreu el nom d'un fitxer que no existeixi (useu per exemple l'extensió *.sqi per indicar una seqüència de sencers; *.sqc podria ser de caràcters i sqr de reals).
És important destacar que, a diferència dels fitxers de text, que contenen caràcters, els quals podeu visualitzar/crear amb un editor de text ASCII (com el propi RHIDE), la resta de fitxers (sencers, reals, etc...) no els podeu crear si no és amb l'ajuda d'un programa. Per comprovar-ho, proveu de carregar amb RHIDE la seqüència de sencers que acabeu de crear. Què hi veieu? Segons els números que hagueu entrat, veureu tot de símbols “estranys”, o fins i tot res de res. En tot cas, res a veure amb els bonics sencers que havíeu entrat. Bé, no cal preocupar-se. L'únic que succeeix és que el Pascal guarda els sencers (i els reals) en el seu format binari i, en canvi, RHIDE i la resta d'editors de text, mostren els fitxers en base als caràcters ASCII que els composen. És simplement una altre manera de mostrar el mateix.
Recordeu, per tant, que els fitxers de sencers i reals els heu de crear amb un programa, i no els podeu entrar usant un editor de text.
Taules
En el mòdul "Tractament de taules" hem vist un tipus de dades estructurat que va molt bé per a certs tipus de tractaments. Aquest tipus de dades, que hem anomenat taula, també existeix en el llenguatge Pascal i té una definició i un tractament molt semblants als que hem vist en el llenguatge algorísmic.
Agrupació d'un conjunt de variables del mateix tipus
Les taules ens permeten agrupar un conjunt de variables del mateix tipus, donant un sol nom a tot el conjunt i referint-nos a cada variable concreta amb un nombre o índex que indica la posició dins del conjunt de la variable a la qual ens volem referir. Per tant, treballar amb els elements d'una taula serà el mateix que treballar amb qualsevol altre variable, però amb una manera especial de referenciar-la. La manera d'al·ludir a un element d'una taula és la mateixa que ja hem vist en el llenguatge algorísmic:
nom_taula[ expressió ]
L'expressió ha de ser tal que quan l'avaluem doni com a resultat un valor del mateix tipus que l'índex (o índexs) que hem declarat per a la taula. Aquest valor indicarà la posició on hi ha l'element que volem fer servir. En el cas que no fos un valor comprès entre els valors extrems indicats en l'índex (o índexs) de la taula, es produiria un error.
Podríem considerar que hi ha tres tipus de taules:
-
Taules simples. Només tenim un índex per referenciar els elements de la taula.
-
Taules n-dimensionals. Els elements de la taula són al seu torn noves taules.
-
Taules constants. Els elements de la taula no canvien de valor.
A continuació anem a estudiar cadascun d'aquest tipus.
Taules simples
Declaració de taules simples
Per a indicar que una variable que emprarem en un programa és de tipus taula, escriurem una declaració com la següent:
var
nom_variable : array [tipus_índex] of tipus_base;
on tenim que:
Nom_variable: serà el nom que utilitzarem en el programa per a referir-nos a la taula. Juntament amb un índex, ens permetrà també referenciar cadascun dels elements de la taula.
Tipus_índex: especifica el nombre d'elements de la taula, i també la manera d'indexar-los. Ha de ser de tipus ordinal. Pot ser el nom d'un tipus ordinal, que indicarà que la taula tindrà tants elements com valors tingui el tipus. O bé pot ser un subrang que indicarà els índexs extrems de la taula.
Tipus_base: indica de quin tipus seran els elements que hi haurà en cada posició de la taula. Cal tenir en compte que tots els elements de la taula seran d'aquest mateix tipus.
Exemple de declaració de taules simples
Considerem les declaracions de taules del següent programa, i la seva utilització:
Program Taules;
Type
colors = (blau, groc, verd, vermell, blanc, negre);
VAR
caracters : array[char] of char; { una taula amb 256 caràcters }
c1 : array [colors] of integer; { una taula amb 6 nombres enters } c2 : array [groc..vermell] of integer; { una taula amb 3 enters }
v1 : array [1..10] of integer; { una taula amb deu enters}
v2 : array [-5..5] of real; { una taula amb onze nombres reals }
lletres : array ['a'..'z'] of integer; { una taula amb 26 enters }
j : integer;
Begin
{ Amb les taules que hem declarat podem procedir de la manera següent
v2[-3] := 5.4; { assignem un valor al tercer element de la taula v2 } v1[7] := 190; { assignem un valor al setè element de la taula v1 }
j := -1;
v2[j] := v2[j + 2]; { assignem al j-éssim element de la taula v2 el mateix valor
que hi ha en l'element dues posicions mes endavant }
lletres['g'] := lletres['g'] + 1; { incrementem el setè element de la taula lletres
en una unitat }
read(lletres['k']); { llegim un valor de teclat i el guardem en l'onzè element de la
taula lletres, a la posició identificada per `k' }
writeln(v1[7]); { escrivim en pantalla el contingut del setè element de la taula
v1 }
end.
Fixem-nos que l'expressió que emprem per a determinar la posició de l'element de la taula al qual ens volem referir (per llegir el seu valor, o per modificar-lo) és sempre del mateix tipus que el de l'índex que hem declarat en la taula.
Exemple d'utilització de taules
Tot seguit veurem un petit programa que utilitza una taula per a comptar l'aparició de cadascun dels enters entre 1 i 20 en una seqüència de valors que llegim pel teclat i que acaba en zero:
Program comptar_enters;
{ Llegeix una sequencia marcada de sencers pel teclat (acabada en zero) Compta el numero d'aparicions dels sencers entre 1 i 20 a la sequencia
Mostra els resultats per pantalla
}
Const
MIN = 1; { Minim sencer a comptar }
MAX = 20; { Maxim sencer a comptar }
Var
x : integer;
comptador : array [MIN..MAX] of integer;
i : MIN..MAX; { Rang de sencers a comptar }
Begin
writeln('Programa Compta_sencers entre ', MIN, ' i ', MAX);
write('Entra una sequencia de sencers acabada en zero: ');
{ inicialitzem tots els elements de la taula a zero}
for i := MIN to MAX do
comptador[i] := 0;
{ Lectura d'una sequencia de sencers marcada, acabada en zero }
read(x);
while x <> 0 do { anem tractant elements mentre no llegim el 0}
begin
if (x >= MIN) and (x <= MAX) then
comptador[x] := comptador[x] + 1; { incrementem l'element de
la posicio llegida} read(x); { corresponent al nombre llegit}
end;
for i := MIN to MAX do
writeln('Nombre d''aparicions del numero', i, ' : ',comptador[i]);
end.
Taules n-dimensionals
Ja hem dit que una taula és una agrupació de variables del mateix tipus. Hem vist alguns exemples on teníem taules d'enters, taules de reals i taules de caràcters. De fet, podem declarar taules amb elements de qualsevol dels tipus existents en Pascal, i fins i tot dels nous tipus que haguem definit nosaltres en el programa. En concret, com que una taula és un tipus més del llenguatge, podem tenir taules on cada element a la vegada és una altra taula. Això és el que es coneix amb el nom de taules de taules o taules multi-dimensionals.
Exemple de taula bidimensional
Un exemple de taula on cada posició és a la vegada una altra taula és el següent:
temp_mínima_diaria : array [1..12] of array [1..31] of real; { temperatures mínimes de
cada dia dels dotze mesos de l'any }
També es pot fer la declaració, i és exactament el mateix, de la següent forma:
temp_mínima_diaria : array [1..12,1..31] of real; { temperatures mínimes de
cada dia dels dotze mesos de l'any }
En qualsevol de les dues declaracions, tenim una taula que conté dotze elements que corresponen als dotze mesos de l'any, on cadascun dels quals és a la vegada una taula que conté 31 elements de tipus real, que corresponen al número màxim de dies que pot tenir cada mes de l'any. En cada element de la taula hi podrem guardar un nombre real, ja que aquest és el tipus que hem especificat pels elements de la taula.
Aquestes dues declaracions són equivalents, i signifiquen exactament el mateix pel que fa al compilador i, per tant, el programa treballarà exactament de la mateixa manera tant si declarem les taules d'una manera com si ho fem de l'altra.
De manera similar, també podrem escriure les referències a cadascun dels elements de la taula de dues maneres diferents:
temp_mínima_diaria[mes][dia]
o bé:
temp_mínima_diaria[mes,dia]
on tenim que:
mes serà un valor entre 1 i 12 que indiqui a quina de les dotze taules dels dotze mesos ens referim.
dia serà un valor entre 1 i 31 que indiqui, dins de la taula del mes, a quin dia ens referim.
Aquestes dues maneres de referenciar els elements de les taules també són totalment equivalents i, a més, les podem utilitzar independentment de quina de les dues declaracions de la taula haguem fet.
De fet, el que hem vist per a taules de dues dimensions (o taules de taules) ho podem extrapolar a tantes dimensions com vulguem. Així, podem declarar una taula de n dimensions amb el procediment següent:
nom_taula : array [tipus_índex1] of array [tipus_índex2] ... of array [tipus_índexn] of tipus base;
o bé:
nom_taula : array [tipus_índex1, tipus_índex2 ... tipus_índexn] of tipus base;
Podem obtenir els diferents elements de la taula de les dues maneres següents:
nom_taula[expressió1, expressió2 ... expressión]
o bé:
nom_taula[expressió1][expressió2] ... [expressión]
Exemple d'utilització d'una taula n-dimensional
A continuació tenim un exemple d'utilització d'una taula de tres dimensions, que utilitzarem per a guardar el valors de la cotització en borsa d'unes determinades accions, durant tots els dies de l'any. Per cada dia, volem conèixer:
-
El valor d'obertura.
-
El valor de tancament.
-
El valor mitjà.
El programa es limita a fer la declaració de la taula i a inicialitzar totes les posicions a 0. Evidentment, tot això hauria de formar part d'un programa més llarg, que posteriorment fes servir aquesta taula per actualitzar la informació, però a tall d'exemple ja ens serveix. Anem a veure'l:
Program Taula_3;
{ Exemple de declaracio i utilitzacio d'una taula de 3 dimensions.
La taula conte les cotitzacions d'obertura, tancament i mitja d'un paquet d'accions.
En aquest exemple, nomes s'inicialitza la taula. Caldria completar el tractament.
}
Type
valors = (obertura, tancament, mig);
Var
cotitzacions : array [1..12, 1..31, valors] of real; { 3 dimensions }
i, j: integer;
k : valors;
Begin
{ Inicialitzacio de tots els elements de la taula a 0 }
for i := 1 to 12 do
for j := 1 to 31 do
for k := obertura to mig do
cotitzacions[i, j, k] := 0;
{ A partir d'aqui caldria continuar el tractament de la taula,
per exemple, llegint les cotitzacions d'una sequencia }
End.
Observem a l'exemple com podem utilitzar un tipus ordinal (en aquest cas l'enumerat valors) per especificar els valors que pot prendre l'índex de la taula (correspondrà als diferents elements del tipus).
Taules constants
Quan volem utilitzar una taula en un programa que contingui valors constants, és a dir, que no han de variar durant tota l'execució, podem declarar la taula com a constant en lloc de variable. Les taules constants es declararan com qualsevol altra constant, excepte en la manera d'assignar valors als seus elements. Les constants que havíem vist fins ara tenien un sol valor i l'assignació era senzilla. Una taula, com que pot tenir més d'un valor, fa que l'assignació sigui una mica diferent.
S'assignen valors a una constant de tipus taula posant aquests valors entre parèntesis i separats per comes. Cal especificar tants valors com elements es declarin a la taula, i han de ser del tipus base de la taula:
nom_taula : array [tipus_índex] of tipus_base = ( valor_1, valor_2, . . ., valor_n);
Exemple d'utilització de taules constants
Ara considerarem un exemple per a escriure en pantalla el contingut d'una variable de tipus enumerat. Les variables de tipus enumerat no es poden escriure directament, ja que contenen un identificador i no un valor, com ja hem comentat abans. Llavors:
Program Tla_enum;
Type
tdia = (dilluns, dimarts, dimecres, dijous, divendres, dissabte, diumenge);
Const
{ Declarem una constant de tipus taula de cadenes de caracters,
amb tantes posicions com elements te el tipus enumerat tdia,
i a cada posicio de la taula hi posem la cadena de caracters
corresponent al nom del dia }
nom_dia : array [tdia] of string[10] = ( 'Dilluns ', 'Dimarts ' ,'Dimecres '
,'Dijous ' ,'Divendres ' ,'Dissabte '
,'Diumenge ');
Var
dia : tdia;
Begin
dia := dimecres;
writeln(nom_dia[dia]); { no escrivim el contingut de la variable dia, sino el
contingut de la taula nom_dia de la posicio indicada
per la variable dia.
La taula conte cadenes de caracters i, per tant,
es poden escriure }
End.
Cal destacar la importància que té el facilitar la comprensió dels resultats del programa a l'usuari. Això és justament el que hem fet en l'exemple anterior.
Finalment, el següent exemple il·lustra com podem inicialitzar taules constants de més d'una dimensió:
Program tconst;
Const
taula : array[1..2, 1..3] of integer = ((1,2,3),(4,5,6));
Var
i,j : integer;
Begin
for i=1 to 2 do
for j=1 to 3 do writeln('tconst[',i,' ,', j,'= ',tconst[i,j]);
End.
Tal com es pot observar en l'exemple, només hem de tenir present de conservar la jerarquia de taules a l'hora d'inicialitzar-les.
Tuples
Les tuples són un tipus de dades que ens permeten agrupar unes quantes variables sota un nom únic. La diferència amb les taules és que en les tuples les variables que agrupem poden ser de tipus diferents. Les distintes variables que s'agrupen dins d'una tupla s'anomenen camps.
La sintaxi per a definir el tipus tupla és la següent:
nom_variable: record
nom_camp_1 : tipus_1;
nom_camp_2: tipus_2;
. . .
end;
Els camps es declaren com si fossin variables normals, però els camps d'una tupla no són identificadors per si mateixos. Cal indicar el nom de la tupla i el camp, separats per un punt, és a dir:
nom_tupla.nom_camp
Exemple de declaració d'una tupla
Suposem que volem definir una variable on voldrem guardar dates. Una data està formada per tres parts: dia, mes i any. Per tant, podem definir la variable com una tupla amb tres components (o camps).
data: record
dia: 1..31;
mes: 1..12;
any: 1900..2100;
end;
Evidentment, també podíem haver declarat les tres variables dia, mes i any com a variables separades, i el programa que les utilitzés hauria pogut fer exactament el mateix.
Quin és, doncs, l'avantatge de les tuples? Bàsicament, la claredat que donen al programa. El fet de tenir els tres camps agrupats en una sola variable s'acosta més a la idea que tenim de data. L'expressió d'una data consta de tres parts, que juntes formen aquesta data. Un nombre de dia per si sol no ens dóna gaire informació, però sí que ens la dóna si l'ajuntem amb el mes i l'any.
Exemple d'assignació de valors als camps d'una tupla
Per exemple, podríem veure com es poden assignar valors als camps de la tupla que hem creat abans:
data.dia := 15;
data.mes := 11;
data.any := 1997;
Exemple d'utilització de tuples
Ara veurem un exemple més complet d'utilització de tuples. En l'exemple, utilitzarem les tuples per a crear una agenda on guardarem dades sobre una colla d'amics. Fixem-nos com podem fer servir tuples contingudes en altres tuples, i aquestes com a elements d'una taula. Això és perquè la tupla és un tipus de dades.
Program agenda;
{ Agenda: Exemple d'utilitzacio de tuples }
TYPE
tdata = record { Tipus data }
dia: 1..31;
mes: 1..12;
any: 1900..2100;
end;
tpersona = record { Tipus d'informacio sobre una persona }
nom: string(40);
telefon: string(9);
aniversari: tdata;
end;
VAR
agenda: array [1..25] of tpersona; { agenda contindra informacio sobre 25
persones }
-->Begin[Author ID1: at Sun Feb 7 02:28:00 1999]
{ Omplim les dades del primer element de la taula }
agenda[1].nom:= 'Pere Delacu Llera';
agenda[1].telefon:= '4312590';
agenda[1].aniversari.dia:= 3;
agenda[1].aniversari.mes:= 6;
agenda[1].aniversari.any:=1970;
{ . . . }
End.
Podem emprar aquesta agenda i, donat el dia d'avui, comprovar si cal felicitar algun dels nostres amics. Suposem que hem declarat les variables:
dia_avui : 1..31;
mes_avui : 1..12;
El codi del programa quedaria com:
. . .
writeln('Quin dia és avui?');
read(dia_avui);
writeln('De quin mes?);
read(mes_avui);
for i:=1 to 25 do
if (agenda[i].aniversari.dia = dia_avui) and
(agenda[i].aniversari.mes = mes_avui) then
writeln(' Cal felicitar en/la ', agenda[i].nom);
Procediments i funcions
Procediments i funcions
A mesura que els programes comencen a ser més complexos, sorgeix la necessitat d'anar-los dividint en parts més reduïdes i d'estructurar-los de manera que els grups d'instruccions que resolen petites parts del problema es puguin escriure formant una acció més gran, que puguem fer servir dins del programa.
Els procediments i les funcions ens permeten associar un identificador a tot un bloc d'instruccions, de manera que quan usem aquell identificador s'executen totes les instruccions que formen el bloc.
Definició d'un procediment
Quan definim un procediment, hem d'indicar l'identificador que triarem per referir-nos-hi i hem d'indicar el bloc d'instruccions que s'hauran d'executar quan invoquem el procediment dins del programa.
La sintaxi és la següent:
Procedure nom_procediment ;
declaracions opcionals
begin
bloc d'instruccions
end;
on
nom_procediment: qualsevol identificador vàlid en Pascal; ha de respectar, per tant, la
sintaxi dels noms de variables i constants. Només pot contenir
lletres, nombres i el símbol "_".
Observem que després de la capçalera del procediment hi ha una part, opcional, de declaracions on podem indicar les variables i constants que són específiques del procediment.
El bloc d'instruccions pot contenir qualsevol grup d'instruccions i estructures de control, tal com les podíem escriure en els programes que hem vist fins ara.
La definició de procediments (i funcions) es porta a terme sempre després de la zona de declaracions del programa, i abans que en comenci el cos principal (abans de la paraula reservada begin).
Per a executar un procediment (les seves instruccions), n'hi ha prou d'escriure el nom del procediment, com si fos una instrucció més del llenguatge.
Exemple d'utilització d'un procediment
Veiem un exemple en què utilitzem un procediment que escriu dos salts de línia a la pantalla:
Program Ex_proc;
{ Exemple d'us d'un procediment }
VAR
nom : string(40);
Procedure salt_linies; { Procediment que salta dues linies }
{ Aqui hi podria haver declaracions de variables }
Begin
writeln; { escriu dos salts de linia }
writeln;
End;
Begin { comenca el programa principal }
salt_linies; { executem el procediment salt_linies }
write('Hola, Com et dius? ');
readln(nom);
salt_linies; { executem el procediment salt_linies}
writeln('Adeu ' , nom, ' !');
End.
Fixem-nos com hem definit el procediment salt_linies, que escriu dos salts de línia a la pantalla, just després de la zona de declaracions del programa i abans del begin del cos del programa. En aquesta zona és on es defineixen tots els procediments i les funcions que utilitza el programa.
En el cas que ens ocupa, es tracta d'un procediment molt simple, però fixem-nos que ja ens estalvia d'escriure instruccions, atès que només posant salt_linies n'executem dues. A més a més, podem executar el procediment tantes vegades com vulguem dins del programa. Això vol dir que escrivim una sola vegada el cos del procediment i l'executem tantes vegades com vulguem.
Definició d'una funció
Les funcions són molt semblants als procediments, tant en la definició com en l'execució. La diferència entre una funció i un procediment és que la funció retorna un resultat després de ser executada i el procediment no. Això vol dir que les funcions s'usaran dins d'expressions que facin servir el valor que retornen.
Una funció es declara igual que un procediment, però usant la paraula reservada function (enlloc de procedure). A continuació, anirà el nom de la funció i després el tipus de dades que retorna la funció, que serà algun dels tipus de dades permesos en el llenguatge. La sintaxi és:
Function nom_funció : tipus;
declaracions opcionals
begin
bloc d'instruccions
end;
Com que la funció ha de retornar un valor, hi ha d'haver alguna instrucció dins del cos de la funció que indiqui quin és aquest valor. Per a aconseguir-ho, assignarem el valor que s'ha de retornar al nom de la funció. Així doncs, tindrem:
nom_funció := valor_a_retornar;
L'assignació fa que el valor que s'assigna sigui retornat per la funció allà on l'han cridada.
Exemple d'utilització d'una funció
Vegem un exemple d'una funció que quan s'executa retorna el valor 10:
Program Ex_func;
{ Exemple d'utilitzacio de funcions }
Var
x : integer;
Function deu : integer; { funcio que retorna un enter }
begin
deu := 10; { retornem el valor 10}
end;
{ comenca el programa principal }
Begin
x := 15;
writeln( x + deu); { escriu per pantalla 25 (15 + 10) }
End.
Aquesta funció quan s'executa sempre retorna el valor 10. Evidentment, és un exemple força inútil però que permet, com a mínim, il·lustrar el funcionament d'una funció.
Així, doncs, les funcions, a diferència dels procediments, retornen un valor i, per tant, només té sentit executar-les dins d'una expressió. Si l'executem com una instrucció, fora d'una expressió (com un procediment), el valor que retorna serà ignorat. Normalment, el compilador ens avisarà d'aquest fet.
En l'exemple anterior, cridem la funció dins d'una expressió que en suma el resultat amb el valor contingut en x i que escriu el resultat final per pantalla.
Variables locals i globals. Àmbit
Totes les variables i constants que s'hagin declarat en el programa principal es poden usar en qualsevol funció o procediment. Aquestes variables s'anomenen globals, ja que són visibles des de qualsevol punt del programa.
Les variables que declarem dins de les funcions i procediments, en canvi, s'anomenen locals perquè només són visibles dins del procediment on es declaren.
S'anomena àmbit d'una variable a la part del programa des de la qual la variable és visible. En la resta del programa, la variable no és visible i no pot ser usada.
Així:
-
L'àmbit d'una variable global és tot el programa, inclosos els procediments i les funcions.
-
L'àmbit d'una variable local és només la funció o procediment on la variable està declarada.
Anem a veure un exemple.
Exemple d'utilització de variables locals i de variables globals
Podem veure el concepte d'àmbit amb un exemple:
Program Ex_ambit;
{ Exemple d'ambit de variables }
Var
n, m: integer;
Procedure accio_1;
var
n : char;
begin
n := 'a'; { estem modificant el contingut de la variable local,
que es de tipus caracter. }
m := 5; { estem modificant el contingut de la variable global,
que es de tipus integer.}
writeln(' Variable n: ', n);
writeln(' Variable m: ', m);
end;
{ comenca el programa principal }
Begin
n := 1;
m := 2;
writeln(' Variable n: ', n); { escriu el contingut de la variable global n que es 1}
writeln(' Variable m: ', m); { escriu el contingut de la variable global m que es 2}
accio_1; { el procediment modifica el contingut de la variable global m }
writeln(' Variable n: ', n); { escriu el contingut de la variable global n, que
continua valent 1 }
writeln(' Variable m: ', m); { escriu el contingut de la variable global m que ara es
5, ja que ha estat modificat per l'accio_1 }
End.
Així com declarem funcions i procediments globals dins del programa principal, també podem fer-ho dins d'altres funcions o procediments. Una funció que es declara dins d'una altra, o d'un procediment, només es pot utilitzar dins d'aquesta, ja que no és visible des d'enlloc més. L'àmbit de la funció està restringit a on ha estat declarada.
Exemple de declaració de procediments i funcions locals
Així doncs, la sintaxi que utilitzem per a declarar procediments i funcions locals és la següent:
Program nom_programa;
Var
llista de variables globals { aquestes variables es poden utilitzar en
qualsevol punt del programa i de les funcions i
procediments de què consta aquest }
Procedure A;
var
llista de variables locals del procediment A
Function C : char;
var
llista de variables locals a la funció C
begin
. . . { podem utilitzar les variables locals de C i de A i tambe les globals }
end;
begin
. . . { bloc d'accions del procediment A. Es poden utilitzar les variables globals,
les locals de A i també la funció C, que no és utilitzable des de cap altre
lloc del programa }
end;
Function B : integer;
var
llista de variables locals de la funció B
begin
. . . { bloc d'accions de la funció B. Es poden utilitzar les variables globals i les
locals de B, però no es poden utilitzar les de A, ni tampoc la funció C }
end;
{ comença el programa principal }
Begin
. . . { bloc d'accions del programa principal, podem utilitzar les funcions A i B,
però no C}
End.
Què passa si dins d'un procediment o una funció declarem una variable que té el mateix nom que una variable global? En aquest cas, quan ens referim a la variable dins del procediment ens referirem a la local. Observem que serà impossible accedir a la variable global des del procediment, ja que la local ens l'amaga.
Cal destacar que el mateix succeeix amb una variable local declarada dins d'un àmbit local, per exemple, una variable local declarada a la funció C pot amagar una variable amb el mateix nom declarada al procediment A.
Paràmetres
Una de les funcionalitats més importants dels procediments i les funcions és el fet que poden manipular unes dades que els podem passar en el moment de fer la crida i que, per tant, poden canviar d'una crida a una altre. Aquestes dades s'anomenen paràmetres.
La manera de declarar una funció o un procediment que rep paràmetres d'entrada és especificant aquests paràmetres i els seus tipus corresponents després del nom, entre parèntesis. La sintaxi seria:
Procedure nom_procediment (nom_paràmetre1 : tipus1; nom_paràmetre2 : tipus2; ...);
o bé:
Function nom_funció (nom_param1 : tipus1; nom_param2 : tipus2; ...) : tipus_retorn;
Dins del procediment o la funció, els paràmetres es poden utilitzar com si fossin variables declarades localment. De fet, no hi pot haver cap variable local amb el mateix nom que un paràmetre. Si es donés aquest cas es produiria un error de compilació. De manera similar, un paràmetre pot "amagar" una variable global, o una de local visible en un àmbit superior.
Cal notar que si hi ha diversos paràmetres que apareixen consecutivament i són del mateix tipus, no cal anar indicant el tipus de cadascun: ho podem fer per tots ells de cop. Podem escriure, per exemple, el següent:
procedure prova(a, b : integer; c : char);
En aquest cas el compilador entén que tant a com b són paràmetres de tipus enter.
Finalment, cal destacar que la crida a la funció o procediment amb paràmetres serà molt semblant al que ja hem vist anteriorment.
Crida a una funció/procediment amb paràmetres
En la crida a una funció o procediment on hem declarat paràmetres en la seva capçalera, hi haurem d'especificats els valors actuals que han de rebre aquests paràmetres. La manera de fer-ho és indicar la llista de valors actuals, també entre parèntesis i separats per comes. Ara no cal indicar-ne els tipus, que hauran de coincidir en el mateix ordre que l'especificat en la declaració de la funció/procediment. També és important que hi hagi tants valors com paràmetres s'han especificat en la declaració. La correspondència entre valors i paràmetres és un a un, és a dir, el primer valor passa al primer paràmetre, el segon valor al segon paràmetre, etc... D'aquí que hagin de coincidir tant en número com en tipus.
De fet, els valors que passem com a paràmetre poden ser el resultat d'avaluar una expressió; així doncs, podem tenir perfectament el següent:
nom_procediment_o_funció(expressió_1, expressió_2, ...)
Exemple d'utilització de paràmetres
Tot seguit, veurem un programa que utilitza una funció que, donats dos nombres, en calcula la exponenciació:
program potencia;
{ Programa de demostracio de pas de parametres }
VAR
a, b: integer;
function potencia(base: integer; exponent: integer): integer;
{ Retorna la base elevada a l'exponent }
Var
i, result: integer;
begin
result := 1;
{ base elevat a exponent vol dir multiplicar la base per ella mateixa
tantes vegades com indiqui l'exponent }
for i:=1 to exponent do
result := result * base;
potencia := result; { Retornem el resultat }
end;
{ comenca el programa principal }
Begin
a := 2;
b := 5;
writeln(potencia(a, b)); { calculem 2 elevat a 5 }
writeln(potencia(3, 2)); { calculem 3 al quadrat }
End.
Fixem-nos com els paràmetres ens donen la possibilitat de fer servir les funcions i els procediments amb dades diferents en cada crida. Aquest és un dels aspectes importants de la descomposició del programa en grups d'instruccions, ja que ens permet poder utilitzar un mateix grup (que hem escrit una sola vegada) tantes vegades com vulguem i, a més a més, cada vegada sobre unes dades diferents. En aquest cas, fem servir dues vegades la funció potència, una per a calcular 2 elevat a 5 i l'altra per a calcular 3 al quadrat.
Fixem-nos també que en executar la funció podem indicar com a valors actuals dels paràmetres valors o variables o, fins i tot, expressions del mateix tipus que el paràmetre. En principi, quan a la funció li passem variables, aquesta rep els valors que contenen les dites variables. En l'apartat següent, veurem alguns casos particulars en aquest sentit.
Paràmetres per valor i per referència
Com hem dit anteriorment, dins d'una funció o procediment els paràmetres es poden utilitzar com si fossin variables. Això vol dir, per exemple, que en podem modificar el seu contingut.
Però, què passa quan es modifica el contingut d'un paràmetre que correspon a una variable en la crida? Anem-ho a veure.
Modificar els paràmetres
Imaginem que tenim el tros de programa següent:
. . .
procedure prova(a:integer) : integer;
begin
a := 5;
end;
Begin
. . .
prova(c);
. . .
End.
Què passa quan es modifica el contingut d'un paràmetre que correspon a una variable en la crida? En el cas concret de l'exemple, la pregunta és si la variable c es veu afectada per la modificació del paràmetre a dins de la funció. En aquest cas, la resposta és que a la variable c no li passa absolutament res, no es veu afectada per la modificació del paràmetre a, tot i que dins de la funció el paràmetre a sí que canvia de efectivament de valor.
Quan modifiquem un paràmetre, aquesta modificació només té efecte dins de la funció o procediment on té lloc, però el paràmetre original no varia. La raó és que, en realitat, dins de la funció fem servir una còpia de la variable, que té un àmbit local a la funció/procediment i que s'inicialitza amb el valor especificat. Aquest tipus de paràmetres s'anomenen paràmetres per valor.
De totes maneres, si volem, també podem aconseguir el funcionament contrari. És a dir, un paràmetre tal que les modificacions que hi fem s'apliquin també a la variable corresponent. És el que s'anomena paràmetre per referència. En aquest cas, el paràmetre i la variable són la mateixa cosa. No se'n fa una còpia, sinó que es treballa directament amb l'original.
Paràmetre per valor i per referència
Un paràmetre per valor és una variable local que no té cap relació amb el paràmetre original, a part de contenir inicialment el mateix valor. Dins de la funció o el procediment tenim una còpia del valor original.
Un paràmetre per referència, en canvi, és la mateixa variable que s'ha especificat en la crida i, si el modifiquem, aquesta variable també es modifica. Per a aconseguir-ho, ho hem d'indicar en la capçalera de la funció o el procediment, escrivint davant del nom del paràmetre, la paraula reservada var.
Modificar els paràmetres (2)
Suposem que tenim el tros de programa següent:
. . .
procedure prova( Var a:integer) : integer;
begin
a := 5;
end;
{ comença el programa principal }
begin
. . .
c := 1;
prova(c);
. . .
End.
En aquest cas el paràmetre a és per referència. Per tant, en modificar-lo modifiquem la variable utilitzada en la crida, c. Després de la crida i l'execució del procediment prova, la variable c valdrà 5 enlloc de 1. Utilitzar el paràmetre a és, en realitat, com utilitzar directament la variable c.
Evidentment, si hem declarat un paràmetre per referència, en la crida hem d'utilitzar una variable. No podem fer servir ni un valor ni una expressió, ja que no hi hauria cap variable per poder utilitzar en la funció/procediment. Per tant, les crides següents donarien un error de compilació:
prova(3); { no podem passar un valor ja que la funció espera una variable }
prova(c + 3); { no podem passar el resultat d'una expressió }
Exemple 1: utilització d'un paràmetre per referència
En l'exemple següent podem veure com, dins d'un procediment que rep un paràmetre per referència, utilitzem tant aquest paràmetre com la variable global que li correspon. Quin serà el resultat?
Program param1;
{ Exemple de pas de parametres per referencia }
Var
c:integer;
procedure prova(var a:integer);
begin
a := a + 1; {estem incrementant la variable que ens passen en 1 }
c := c + 1; {incrementem la variable global c en 1 }
end;
{ comenca el programa principal }
Begin
c:=1;
prova(c); { com que la variable que passem es justament la c, la incrementem
dues vegades }
writeln(c); { la c val 3 }
End.
Fixem-nos que executem la funció prova passant-li justament com a paràmetre la variable c. Dins de la funció prova, el paràmetre a és exactament el mateix que la variable global c. Per tant, el resultat és que incrementem dues vegades la variable c.
Fixem-nos que els paràmetres per referència ens donen, doncs, una altra possibilitat de retornar resultats. Amb les funcions ja hem vist que es pot retornar un resultat, però només un; i en els procediments, ni tan sols això. Però, donat que amb els paràmetres per referència tenim la possibilitat de modificar el contingut de les variables emprades en la crida, els podem utilitzar per retornar diversos valors en les funcions i els procediments.
Exemple 2: utilització d'un paràmetre per referència
A continuació veurem un programa amb un procediment que calcula el residu i el quocient de la divisió entera de dos nombres enters (suposem que el llenguatge Pascal no disposés dels operadors mod i div):
Program divisio;
{ Exemple de retorn de valors amb parametres per referencia }
-->Var[Author ID1: at Sun Feb 7 02:28:00 1999]
a, b: integer;
procedure div_entera(x, y: integer; Var r,q: integer);
begin
r := x;
q := 0;
{ Calcul per restes succesives }
while r >= y do
begin
q := q + 1;
r := r - y;
end;
end;
{ programa principal }
Begin
div_entera(14, 5, a, b);
writeln('Dividim 14 entre 5. Quocient: ', b, ' Residu: ', a);
div_entera(21, 2, a, b);
writeln('Dividim 21 entre 2. Quocient: ', b, ' Residu: ', a);
End.
Quan executem el procediment div_entera les variables a i b no contenen encara cap valor. En canvi, una vegada executat div_entera, contenen els dos resultats que volíem.
En cada crida a div_entera, les dues variables passades en tercer i quart lloc s'actualitzaran amb el residu i el quocient de la divisió entera de les dues primeres.
Les variables per referència, doncs, ens seran molt útils. De totes maneres, cal anar amb compte a l'hora d'utilitzar-les, ja que poden ser font d'anomalies en el funcionament dels programes si no tenim en compte que modifiquem la variable original.
Paràmetres constants
La modificació d'un paràmetre passat per valor dins del procediment no afecta el paràmetre o la variable original. De fet, quan un paràmetre no es modifica dins d'un procediment, podem avisar el compilador posant la paraula reservada const al davant del paràmetre, en la capçalera:
nom_procediment_o_funció( const nom_paràmetre : tipus; ...)
Això fa que el compilador generi codi més eficient i, a més a més, que comprovi que realment no es produeix cap modificació del paràmetre dins del procediment. Si ho provéssim, es produiria un error de compilació que ens avisaria que estem intentant modificar el paràmetre, quan se suposa que no ho hem de fer.
Recursivitat
Els algoritmes recursius segueixen un tipus d'esquema que s'adapta molt bé a certs tipus de problemes, com ja veurem en més profunditat a l'assignatura de Fonaments de Programació II. Un algoritme recursiu és aquell en el qual una funció o un procediment tenen una crida a ells mateixos dins del seu cos, o bé tenen una crida a un altre procediment o funció, que pot incloure altres crides, però que al final s'acaba fent una crida a la primera funció o procediment. És a dir, que ens trobem amb un cicle tancat de crides.
Hi ha llenguatges d'alt nivell que no permeten la recursivitat, ja que cal un tipus d'implementació especial del llenguatge per a fer-la possible. El Pascal sí que permet crides recursives i, no es necessita cap mena de declaració especial per a fer-les, si no és que es tracta de crides que passen per més d'una funció. Aquestes darreres les veurem en un proper apartat. Ara anem a veure un exemple simple de recursivitat.
Exemple d'utilització de funció recursiva
Un exemple típic de funció recursiva és la funció per a calcular el factorial d'un nombre. De fet, el factorial d'un nombre n es defineix com el producte de tots els nombres inclosos entre 1 i n. Però també ho podem pensar en termes de la recursivitat i dir que el factorial de n és igual a n multiplicat pel factorial de n-1:
factorial(n) = n * factorial(n - 1)
Això vol dir que si escrivim la funció en Pascal que resolgui el factorial de manera recursiva, la funció constarà d'una crida a ella mateixa:
Program facto;
{ Exemple de funcions recursives }
Var
i : integer;
function factorial(n : integer) : integer;
{ Pre: n>= 0 }
begin
if (n = 0) or (n = 1) then
factorial := 1
else
factorial := n * factorial(n - 1);
end;
{ Programa Principal }
Begin
for i := 0 to 10 do
writeln('El factorial de ', i, ' es ', factorial(i));
End.
Fixem-nos que dins de la funció factorial hem afegit un control que assegura que en algun moment, les successives crides finalitzen, ja que el paràmetre ha de ser inicialment positiu o zero i en cada crida es va decrementant en 1. Per tant, en algun moment arriba a valer 1. És important tenir-ho en compte sempre que es programi un procediment recursiu, ja que sinó correm el risc que el programa no surti mai del procediment recursiu.
Referències creuades entre funcions i procediments
Tot procediment o funció s'ha d'haver declarat abans de l'execució. Per això podem tenir problemes quan des d'una funció o un procediment en volem executar un altre que està declarat més endavant. En principi, podríem canviar d'ordre les declaracions. Però que passa si el primer crida al segon i el segon al primer, en un algoritme basat en la recursivitat?
Referència creuada errònia
Imaginem que tenim, per exemple, dos procediments, el procediment A i el procediment B, declarats en aquest ordre, i que des de A volem executar B:
. . .
Procedure A;
begin
. . .
B; { no es pot executar el procediment B perquè encara no ha estat definit.}
end;
Procedure B;
begin
. . .
end;
. . .
Això donarà un error a l'hora de compilar, que ens dirà que no podem executar el procediment B ja que encara no s'ha definit. Evidentment, el problema es pot resoldre definint el procediment B abans que l'A, de manera que dins d'A podrem executar B. Però què passa si tenim un cicle recursiu (des d'A volem executar B, i des de B volem executar A)? En aquest cas, sigui quin sigui el que definim primer, sempre ens donarà un error ja que l'altre no estarà definit.
Sortosament, el Pascal ens dóna la possibilitat d'avisar el compilador que una funció o procediment es definirà més endavant. Això es fa afegint la paraula reservada forward al final de la capçalera de la funció o el procediment, sense especificar quin és el cos de la funció o del procediment. La sintaxi és:
Procedure nom_procediment(llista_paràmetres); forward;
o bé, per una funció:
Function nom_funció(llista_paràmetres) : tipus_resultat; forward;
Així doncs, podríem resoldre la referència creuada de l'exemple anterior sense problemes.
Referència creuada correcta
L'exemple, suposant que des de B cridem també a A, quedaria com segueix:
. . .
Procedure B; forward; { avisem el compilador de l'existencia del procediment B }
Procedure A; { Definim el procediment A }
begin
. . .
B; { el procediment B no està definit però el compilador sap ara que existeix,
ja que s'ha especificat la seva capçalera. Per tant, es pot executar. }
end;
Procedure B; { Definim el procediment B }
begin
A; { el procediment A ja està definit i el podem executar sense problemes }
. . .
end;
Bibliografia
-
“Programación en Pascal”
Leestma, Stanford (et al.], 4ª. Ed.
Ed. Prentice Hall, Madrid 1999 ISBN 84-8322-031-8
-
“Algoritmos + Estructuras de Datos = Programas”
Niklaus Wirth
Ed. Castillo S.A., Madrid
(Referència del creador del llenguatge)
Descargar
Enviado por: | Txc |
Idioma: | catalán |
País: | España |