¡Descarga Teoria capitol 8 y más Apuntes en PDF de Introducción a la Informática solo en Docsity!
Fonaments d'informàtica
8. Subprogrames
Índex del tema
Objectius
1. Conèixer les eines per estructurar el codi d’un programa.
2. Conèixer les similituds i diferències entre funcions i accions i quan utilitzar unes o altres.
3. Conèixer les diferents maneres de traspassar informació a les funcions i procediments.
4. Aprendre a utilitzar el disseny modular per a fraccionar un problema complicat en d’altres
més senzills de solucionar.
8.1. Justificació dels subprogrames.
Quan el problema que volem resoldre amb un programa d’ordinador es va fent gran, hauríem de
dividir-lo en altres de més petits que siguin més senzills de solucionar de manera que, solucionant les
parts més senzilles, acabem solucionant el problema principal.
Hi ha una dita que diu: “no es pot menjar un elefant en un sol àpat, s’ha de fer
de mossegada en mossegada”
Per posar un exemple una mica exagerat, imaginem que hem de fer un programa que gestioni totes
les operacions d’una empresa. Com a mínim haurem de dividir les operacions en els diferents
departaments de l’empresa (recursos humans, gestió proveïdors, finançament, logística,
comptabilitat, etc.). Per cadascun dels departaments, haurem de esbrinar quines operacions realitza i
segurament aquestes encara seran massa complicades per a gestionar-les amb un programa i
s’hauria de dividir en diferents parts d’operació més senzilles.
Podem veure un diagrama dels diferents mòduls que en sortirien a la figura 1.
Si ens centrem a un departament determinat, començarem a resoldre les diferents operacions, potser
a partir de programes més senzills que fan diferents parts. Imaginem una facturació on s’ha de servir
el material, complimentar la garantia, actualitzar els magatzem, omplir l’albarà, actualitzar les dades
del client, fer la factura amb possibles dates de venciment i fer el seguiment dels cobraments.
Cadascuna d’aquestes parts pot ser un programa prou complex per a ser realitzat com a una part
autònoma.
Cadascuna de les part en que es fracciona un problema és una acció o procediment entès com un
subprograma que forma part d’un programa més general. La realització dels diferents procediments
Subprogrames
en la seqüència adient, farà que es solucioni el problema més complex que les parts. Un cop
solucionades les operacions d’un departament de l’empresa, solucionaríem les dels altres
departaments per acabar solucionant l’operativa sencera de l’empresa.
Podem justificar la necessitat de partir un problema complex en altres més senzills per poder
manegar les diferents parts. Això ens permet de partir un programa amb moltes línies de codi, en
trossos més petits i cadascun d’aquests amb una funció determinada. Una de les primeres
avantatges que obtenim és una millora de la legibilitat del programa que en resulta, donat que serà
més curt i podrem seguir les seves operacions d’una manera més pràctica.
Ja hem utilitzat aquesta tècnica quan alguns algorismes els simplificàvem mitjançant el que
anomenàvem subalgorismes. Aquest símbol ens permetia de fer les funcions de resum de l’acció que
s’ha de fer, sense especificar-ne l’algorisme que es seguirà.
Altre cas on seran útils els subprogrames serà quan hi hagin accions o càlculs repetitius. No serà
necessari que cada vegada que es vulgui calcular una arrel quadrada, el programa hagi d’indicar les
instruccions a seguir. N’hi haurà prou en fer-ho bé una vegada i agrupar les instruccions en una
funció. Les funcions més habituals ja venen codificades amb el llenguatge. Són el que hem
anomenat funcions del sistema.
Una qüestió que no s’ha mencionat, i que és evident en quan s’ha de fer aquest tipus de programa,
és la compartició de dades. Les dades d’un client s’utilitzen tant per fer factures, com per enviar
correspondència amb les ofertes del més, o per fer el seguiment de la garantia dels productes
adquirits. Si hem de fer una acció que imprimeixi la garantia d’un usuari, aquesta acció haurà de
saber les dades de l’usuari i altres pròpies de la garantia com identificació de l’aparell, dates de
vigència, etc.
Cada cop que dissenyem una acció, hem de decidir quines dades necessitarà
per fer les operacions i quines en sortiran un cop fet el procés.
A part de les dades del sistema que necessitarà un procés i les dades que generarà en el sistema, hi
haurà dades pròpies que sols tenen sentit a nivell local. Pensem amb variables que fan de comptador
d’una iteració o que serveixen per a que l’usuari contesti a preguntes de l’estil “vols repetir el procés
s/n?”.
8.2. Entorn d’un programa.
Per fer el disseny modular d’un programa, fem un disseny anomenat top-down en què es parteix la
complexitat en mòduls més senzills. Aquest disseny parteix el problema en d’altres més senzills
reiteradament fins que som capaços de fer un subprograma que implementi la funció final resultant.
Aquesta implementació es fa des dels mòduls més senzills cap a d’altres més complicats que
finalment formaran el programa final. Per això es diu que la implementació es fa de baix cap a dalt
(bottom-up).
Figura 1
Subprogrames
La informació que retorna un mòdul podrà ser a través de paràmetres, com en
readln(n), o com a resultat d’una funció, com a random(8).
8.3. Funcions i accions.
Hem introduït, de forma subliminar, el concepte de funció. Aquest concepte ja s’ha vist en capítols
anteriors, de la mateixa manera que també s’ha treballat amb el que anomenem acció.
Ambdues formes, funcions i accions, són la manera d’implementar els mòduls que esmentàvem fins
el moment. La diferència entre elles, és que una funció retorna un valor i una acció no. Pensem amb
exemples de funcions com srqt(24), power(3,5) o random, que retornen un valor que haurem
d’utilitzar per a alguna cosa, ja sigui dins d’una expressió, en una assignació o per a passar a una
altra funció o procediment com a argument de la crida.
Les accions no retornen valor i, per tant, són considerades com ordres que es donen al sistema i,
quan s’acaba l’execució de l’acció es passa a la següent ordre segons el flux de l’execució del
programa. A les accions també les anomenarem procediments. Exemples d’accions són clrscr,
gotoxy(3,15) o textcolor(YELLOW), que són utilitzades com a instruccions del sistema i no retornen
cap valor.
Per què una funció o acció pugui operar, pot necessitar informació. Aquesta informació es posa entre
parèntesi quan utilitzem la funció i l’anomenem arguments de la crida o paràmetres de la funció o
acció.
Independentment de si és funció o procediment, el nombre de paràmetres pot variar segons la
quantitat d’informació que es necessiti per fer l’operació corresponent. En els exemples anteriors
hem mencionat tant funcions com procediments de cap, un i dos paràmetres.
La diferència entre funció i procediment, és que una funció retorna un valor i
un procediment no.
Tant acció com procediment són sinònims del mateix concepte.
Donat que una funció retorna un valor, hem d’especificar quin tipus de valor serà aquest. És a dir,
haurem de decidir a quin tipus de dades pertanyerà el valor retornat per una funció en el moment de
dissenyar el seu funcionament.
Fins ara hem utilitzat funcions i accions que ens proporcionava el sistema. A partir d’ara serem
capaços de realitzar les nostres pròpies funcions i accions que facin operacions que resolguin una
part del programa principal.
Normalment utilitzarem funcions per a fer càlculs i produir un resultat. En aquest procés, no és
habitual que es modifiqui l’entorn, és a dir, no es modifica la informació de la pantalla ni se’n demana
per teclat, ni cap altre funció d’entrada-sortida de perifèrics. Pensem en el comportament de sqrt(89),
on ja li donem la informació pels càlculs (89) i no esperem que ens mostri res per pantalla cada cop
que fem un càlcul.
Entenem per entorn tant l’estat dels perifèrics amb que el programa es
comunica durant la seva execució, com les dades accessibles en un punt del
programa. El fet que una acció no modifica l’entorn, també implica que no en
modifica les dades globals del sistema o d’altres accessibles a vàries funcions
En canvi, les accions solen modificar l’entorn, donat que no retornen cap valor del seu funcionament.
Per a definir una funció o procediment, es declaren els següents apartats:
Fonaments d'informàtica
o Capçalera de la funció o procediment. És una línia on s’especifica si és funció o procediment,
el nom de la funció o procediment, la llista d’arguments que se li passen i, en cas de funció, el
tipus de dades que retornarà.
La capçalera genèrica d’una funció seria:
function <nom_funció> [ ( <llista de paràmetres>) ] : ;
Un exemple seria:
function ComptaVocals ( nom:string ) : integer;
La capçalera genèrica d’un procediment o acció seria:
procedure <nom_procediment> [ ( <llista de paràmetres > ) ];
Un exemple seria:
procedure OmplePantalla ( lletra:char );
La llista de paràmetres es declara igual en ambdós casos: com un seguit de noms de
paràmetre seguit del símbol : i el tipus de dades de l’argument. Si hi ha més arguments, es
separen amb el símbol ;. A l’apartat 8.5 aprofundirem aquest aspecte.
o Declaració de variables locals amb l’apartat var. Les variables que declarem en aquest punt,
sols seran accessibles a la funció o procediment que estem implementant.
o Declaració de les instruccions a realitzar dins d’un bloc begin-end.
Apareix una instrucció nova anomenada exit , que farà finalitzar la funció o procediment i tornar el
control a qui l’hagi cridat.
Quan a les funcions, disposem d’una variable, a la qual sols s’hi pot assignar valors i mai
consultar-lo. El nom d’aquesta variable especial és el mateix que el de la funció que estem
declarant i el valor que retornarà, com a resultat de la funció, serà l’últim valor assignat a aquesta
variable.
Hem de diferenciar entre la implementació d’una funció (on es diu com ho fa) i el que anomenem
crida a la funció. La implementació és la part nova d’aquest capítol, donat que fins ara ja ens
trobàvem les funcions fetes dins el sistema i no havíem d’especificar les instruccions que realitzaven.
La crida a una funció es fa quan s’utilitza, donant valor als arguments i utilitzant el resultat de la
funció.
La crida a una funció és la utilització d’una funció disponible en el sistema,
com s’ha fet fins el moment. La declaració o implementació d’una funció és
on s’especifica les instruccions a realitza per a arribar al resultat esperat.
Quan utilitzem funcions que ja ens dona el sistema, sols fem crides i no ens
cal declarar-les.
Els conceptes introduïts de crida a una funció i declaració d’una funció, els podem estendre als
conceptes de crida d’un procediment i declaració d’un procediment.
Es pot donar el cas de declarar funcions i procediments locals a una funció o procediment que estem
declarant. En aquest cas sols es podran cridar des d’aquesta funció o procediment.
La declaració de funcions i procediments locals es fa abans d’especificar el bloc begin-end amb les
instruccions a realitzar i segueix la mateixa estructura que s’ha especificat.
8.4. Variables locals i globals. Visibilitat.
Una variable es pot utilitzar, des del punt on es declara fins a la fi del context
on s’ha declarat.
Fonaments d'informàtica
Hi ha altres casos en que la funció o procediment és capaç de canviar el valor d’una variable que és
passada com a argument de la crida. El cas més habitual és l’exemple readln(a); però altres funcions
també ho utilitzen com insert(..) , delete(..) o val(..). En aquestes accions, la variable passada com
argument, és modificada per l’acció cridada. Això és el que anomenem pas per referència.
En un pas per referència, el que es passa és la variable, mentre que en un
pas per valor, el que es passa és un valor.
Quan una funció té un paràmetre en el que s’especifica un pas per referència, no hi podem possar
una expressió com a argument de la crida. No seria vàlida la crida Readln(a+3);
Per indicar que un paràmetre és passat per referència, a la declaració del paràmetre, afegim el
modificador var davant del nom del paràmetre. Veiem les capçaleres de les funcions esmentades
com exemple.
Procedure Delete(var S:string; Index:Integer; Count:Integer);
Procedure Insert( Source:String; var S:String; Index:Integer);
Procedure Val( S:string; var V; var Code:word);
8.6. Pseudocodi.
Durant aquest capítol, hem estat veient la declaració de subprogrames en el llenguatge Pascal. En el
llenguatge pseudocodi, tindrem les mateixes prestacions, encara que a vegades seran expressades
diferent.
Comencem amb la implementació de les funcions, amb el seu format genèric:
FUNCIO <nom_funcio> ( <llista_paràmetres> ) RETORNA <tipus_dades>
[VAR
<llista_variables_locals>
FVAR]
INICI
RETORNAR <expressió>
FFUNCIO
En aquest cas, la llista de paràmetres es declaren com és <tipus_dades> amb la possibilitat
d’indicar el modificador var al davant per indicar el pas per referència. A la capçalera s’indica el tipus
de dades retornat amb la paraula clau RETORNA.
A l’apartat de variables locals, es declaren de la mateixa manera que es fa amb les variables d’una
algorisme. Tenim l’apartat VAR / FVAR i diferents línies per les diferents variables declarades.
Veiem que, a les instruccions, tenim una ordre nova, que s’anomena RETORNAR, i va lligada a una
expressió que serà, un cop avaluada, el valor retornat per la funció.
Finalment veiem el format genèric de la declaració d’una acció:
ACCIO <nom_funcio> ( <llista_paràmetres> )
[VAR
<llista_variables_locals>
FVAR]
INICI
FACCIO
Subprogrames
8.7. Exemples pràctics
8.7.1 Programa Mastermind
Seguidament es mostra les regles d’un programa anomenat Mastermind i la seva implementació en
un programa que està dividit en diferents subprogrames.
Programa "Mastermind"
En aquest joc la màquina s’ha de pensar un número natural de 4 xifres, cadascuna de les quals pot
ser des del 1 fins a 6. (per exemple 4543). Aquest número serà aleatori i canviarà en cada execució
del programa.
L’usuari té 10 intents per endevinar la combinació que ha pensat la màquina.
Cada cop que l’usuari entra una combinació la màquina li respon quantes xifres ha encertat i estan
ben col·locades (encerts plens) i quantes ha encertat la xifra però no el lloc correcte en que es troba
(encerts parcials).
Amb aquesta informació l’usuari pot pensar la seva propera jugada.
L’usuari té que disposar de totes les jugades prèvies i els seus resultats a la vista permanentment,
per poder triar millor la seva propera jugada en funció de les anteriors.
Si en els 10 encerts no endevina la combinació ha perdut i guanya si l’encerta abans.
En cas de perdre, el programa ho indicarà mostrant la combinació vàlida, que es podrà verificar amb
les jugades efectuades i avaluades per la màquina.
Exemple: Combinació de la màquina: 4322
Jugades de l’usuari:
Jugada Combinació usuario Encerts plens Encerts parcials
5 4322 Has guanyat……
Program mm; uses crt; const xifres=4; mxval=6; type // definició de la combinació Tcomb= Array[1..xifres] of integer;
// generació de combinació aleatòria function GeneraComb:TComb; var r:Tcomb; i:integer; begin for i:=1 to xifres do r[i]:=random(6)+1; GeneraComb:=r; end;
// mostra combinació per pantalla
Subprogrames
pcials:=CalcB(cmb1,cmb2); end;
// variables del sistema principal var cm,cu:Tcomb; pas:integer; blancs,negres:integer; guanyat:boolean; begin randomize; guanyat:=false; cm:=GeneraComb; VeureComb(cm); for pas:=1 to 10 do // intruccions a cada pas: demanar comb i veure resultats begin gotoxy(20,1); write('Pas: ',pas); cu:=EntrarComb; ComparaComb(cu,cm,negres,blancs); gotoxy(1,pas+2); VeureComb(cu); write(' negres:',negres); write(' blancs:',blancs); if negres=xifres then begin guanyat:=true; break; end; end; writeln; // Fi programa: missatge amb resultat del joc. if guanyat then write('has guanyat...') else begin write('has perdut. La combinacio era:'); veurecomb(cm); end; readln; end.
8.7.2 Exemple tractament d’una estructura TAlumnat.
Seguidament es mostra un programa on es tracta amb una estructura d’alumnes, amb les
assignatures a les que estan matriculats els diferents alumnes, i se’n fan diferents operacions. Per a
veure el programa principal, hem de comença pel final del llistat i veure’n els diferents subprogrames
que estaran declarats abans de la seva crida.
En el programa s’hi troben comentaris que n’expliquen l’operativa de cada apartat.
Program Alumat;
uses crt, sysutils;
// definim les constants del sistema const NombreAssig=10; // Max assignatures per alumne. NombreAlums=100; // Max alumnes en el sistema. ESC=#27; // constant amb valor tecla ESC SALT=#13#10; // constant que fa salt de línia en escriure-la a pantalla...
// definim tipus de dades pròpies del sistema type TNota=-1..10; // subrang amb valors vàlids d'avaluació TSexe= (Home, Dona); // Enumeració amb els valors vàlids TAssig= record // Tupla d'una assignatura Nom : string; // nom Nota : TNota; // nota end;
TLlistaAssig= record // llista assignatures que tindrà un alumnes Assig : array [1..NombreAssig] of TAssig; // array d'assignatures quantes: integer; // comptador associat
Fonaments d'informàtica
end;
TAlumne= record // Tupla Talumne NExpedient: integer; // número d'expedient Nom : string; // Nom (amb cognoms si cal) Sexe: TSexe; // sexe (enumeració definda a dalt) Assigs: TLlistaAssig; // assignatures de l'alumne end;
TAlumnat= record // Tupla principal del sistema Alumne : array [1..NombreAlums] of TAlumne; // array d'alumnes quants: integer; // comptador associat. end;
// Definició de procediments (accions) i funcions del sistema
// Funció que busca un alumne // Entra: Tupla TAlumnat i // integer amb el número d'expedient a buscar // Retorna: integer amb posició de l'array on s'ha trobat o 0 si no es troba
function TrobaAlumne(al:TAlumnat; n:integer): integer; var i:integer; begin TrobaAlumne:=0; // valor que retornarem si no el trobem for i:=1 to al.quants do if al.alumne[i].nexpedient=n then begin TrobaAlumne:=i; // valor que hem de retornar exit; // sortim de la funció..... end; end;
// Funció que busca un número d'expedient no existent // Entra: Tupla TAlumnat // Retorna: cert/fals si l'hem aconseguit o no i // Número d'expedient nou (en var per referència)
Function TriarNou(alum:TAlumnat; VAR NumeroExp:integer): boolean; var trobat:boolean; begin repeat writeln; write('entra el numero d''expedient (0 per avortar): '); readln(NumeroExp); // mirem si existeix...
trobat:=TrobaAlumne(alum,NumeroExp)<>0; if trobat then write ('Expedient ja existent.Posar 0 per avortar alta.'); until not trobat or (numeroexp=0);
TriarNou:= not trobat; end;
// Acció que omple un array d'assignatures segons el comptador associat // Entra: Llista d'assignatures a omplir // Retorna: La mateixa llista modificada (var per referència)
Procedure OmpleAssigs (var assigs:TLlistaAssig); // Entrem mentre hi hagi lloc i l'usuari ens doni noms // d'assignatures begin while (assigs.quantes)<NombreAssig do begin write('Nom assignatura (en blanc per finalitzar) : '); readln(assigs.assig[assigs.quantes+1].nom); assigs.assig[assigs.quantes+1].nota:=-1; if trimleft(trimright(assigs.assig[assigs.quantes+1].nom))<>'' then assigs.quantes:=assigs.quantes+ else break; end; end;
// Acció que omple les dades d'una tupla TAlumne // Entra: res // Retorna: Dades de TAlumne (var per referència)
Procedure OmpleAlumne(var alumne:TAlumne);
Fonaments d'informàtica
while (j>0) and (al.alumne[j].Nexpedient>copia.Nexpedient) do begin al.alumne[j+1]:=al.alumne[j]; j:=j-1; end; al.alumne[j+1]:=copia; end; // demanar opció de llistar desprès d'haver ordenat. Llistat(al); end;
// Acció que intercanvia els valors de 2 tuples TAlumne // Entra: tuples TAlumne a intercanviar // Retorna: tuples TAlumne canviades (vars per referència)
procedure intercanvi (var a,b:TAlumne); var copia:TAlumne; begin copia:=a; a:=b; b:=copia; end;
// Ordenació per mètode de selecció i llistat posterior. // Ordre seguit: el nom de l'alumne ordenat de menor a major. // Entra: TAlumnat amb alumnes a llistar // Retorna: res
// A cada volta selecionem l'element menor desde 'i' fins al // final i els intercanviem. procedure LlistarNoms(al:TAlumnat); var i,j,minim: integer;
begin for i:=1 to al.quants-1 do begin minim:=i; for j:=i+1 to al.quants do if al.alumne[minim].Nom>al.alumne[j].nom then minim:=j; if minim <> i then intercanvi(al.alumne[minim],al.alumne[i]); end; // demanar opció de llistar desprès d'haver ordenat.... (fer funció) Llistat(al); end;
// Acció mostraAlumne // Entrada: alumne a mostrar // Sortida: res
Procedure mostraAlumne (alumne : TAlumne); var j:integer; begin write('NExp. ',alumne.nexpedient); if wherex<13 then gotoxy(13,wherey); write(' Nom: ', alumne.nom); if wherex<30 then gotoxy(30,wherey); write(' Sexe: '); if alumne.sexe=home then writeln ('Home') else writeln ('Dona'); writeln(SALT,'Llista d''assignatures',SALT); with alumne.assigs do begin for j:=1 to quantes do begin write('Nom : ', assig[j].nom); gotoxy(20,wherey); write(' Nota : '); if assig[j].nota=-1 then writeln('Sense qualificacio.') else writeln(assig[j].nota); end; end; writeln; end;
// Acció per a consultar les dades d'un alumne // Entra: dades dels alumnes (TAlumnat) // Retorna: res
procedure ConsAlumne (alm:TAlumnat);
Subprogrames
var NumBuscat,index:integer; begin clrscr; writeln(SALT,'Consulta dades alumne. ',SALT); repeat write(SALT,'entra el numero d''expedient (0 per abortar): '); readln(NumBuscat); // mirem si existeix... index:=trobaAlumne(alm,NumBuscat); if index=0 then write ('Expedient no existent. Posar 0 per abortar consulta.'); until (index<>0) or (numBuscat=0); if NumBuscat=0 then exit; // avortament de l'operació.. // si continuem, mostrem les dades de l'alumne... clrscr; writeln(SALT,' Posicio : ',index,SALT); mostraAlumne( alm.alumne[index] ); write('Polsar tecla per continuar.'); readkey; end;
// funció que mostra menú i tria opció desitjada per l'usuari // Entra: res // Retorna: Opció triada
function menu : char; begin // escribim opcions del programa... clrscr; gotoxy(5,3); write('1.- Inicialitzacio sistema'); gotoxy(5,5); write('2.- Entrada d''un alumne'); gotoxy(5,7); write('3.- Llista alumnes'); gotoxy(5,9); write('4.- Llistat ordenat segons num. d''expedient'); gotoxy(5,11); write('5.- Llistat ordenat segons nom d''alumne'); gotoxy(5,13); write('6.- Consulta de totes les dades d''un alumne'); gotoxy(5,16); write(' per finalitzar el programa.'); Repeat menu:=upcase(readkey); until ((menu>='1') and (menu<='6')) or (menu=ESC); end;
/////////////////////////////////////////////////////////////// // Programa principal a partir de les funcions anteriors
Var alumnat:TAlumnat; // dades de l'alumnat.
begin // Inicialització de l'estructura
alumnat.quants:=0;
Repeat case menu of '1': // subprograma que torna a inicialitzar tot el sistema // posar a 0 el comptador d'alumnes alumnat.quants:=0;
'2': // subprograma que fa l'entrada d'un alumne. alumnat:=AfegirAlumne(alumnat);
'3': // subprograma que mostra llista d'alumnes Llistat (alumnat);
'4': // Ordenació per mètode d'inserció i llistat posterior. // Ordre seguit: el número d'expedient ordenat de menor a major. LlistarExpedients(alumnat);
'5': // Ordenació per mètode de selecció i llistat posterior. // Ordre seguit: el nom de l'alumne ordenat de menor a major. LlistarNoms(alumnat);
'6': // subprograma que mostra les dades d'un alumnes a partir del // del número d'expedient, mostrant també les assignatures ConsAlumne(alumnat); #27: break;
end; // fi del case
until false; // menu=ESC per sortir amb break;
end.