Argomenti trattati
In Pascal ogni canale di comunicazione con l'ambiente esterno al programma viene trattato come file. Ad esempio, la tastiera e lo schermo sono visti come file di nomi, rispettivamente, input e output, da cui si possono ricevere o su cui si possono inviare dati. Un programma può utilizzare altri dispositivi esterni (stampante, disco, ecc.). L'invio o la ricezione di dati avviene sempre trattando questi dispositivi come file. Lo studio completo dei tipi FILE in Pascal verrà affrontato piú avanti. Ci limitiamo per ora a considerare una particolare famiglia di file, i file di testo, cioè del tipo predefinito text, alla quale appartengono input e output.
Un file di testo non è altro che una sequenza di righe, ognuna delle quali è, a sua volta, costituita da una sequenza di caratteri che termina con un indicatore di fine linea. Il file, a sua volta, termina con un indicatore di fine file.
Per dichiarare una variabile di nome f, di tipo file di testo, è necessario utilizzare la dichiarazione:
VAR f: text;Il nome del file va inoltre specificato nell'intestazione del programma, in quanto esso rappresenta un canale di comunicazione con l'esterno (quando successivamente studieremo in dettaglio i tipi FILE, vedremo che è anche possibile utilizzare un file ``localmente'' a un programma, senza in questo caso indicarne il nome nell'intestazione).
Per essere utilizzato, un file deve essere aperto in lettura o in scrittura, mediante le procedure predefinite reset e rewrite. Una volta che il file è stato aperto, un puntatore indica la posizione in cui verrà effettuata la prossima operazione. I file sono sequenziali, pertanto il puntatore può muoversi solo in avanti. Per il file di output è utile immaginare il puntatore come il cursore sullo schermo che può man mano avanzare a destra di una posizione o andare all'inizio della riga successiva.
In particolare:
Nel caso in cui il file f sia aperto il scrittura, possiamo effettuale le operazioni:
Esistono due funzioni, che restituiscono valori di tipo
boolean, che permettono di verificare se è stata raggiunta
la fine di una riga o la fine del file:
Il programma sarà costituito da due fasi: la prima di apertura dei file, la seconda di copia.
L'apertura dei due file avviene utilizzando le procedure standard reset e rewrite, rispettivamente su f e su g (si ricordi che l'eventuale contenuto precedente di g verrà perso).
Una volta che i file sono stati aperti, si copieranno successivamente le righe di f su g, fino a raggiungere la fine del file. Ad alto livello si dovrà dunque eseguire il seguente ciclo:
WHILE f non e' finito DO BEGIN copia la riga di f su cui si trova il puntatore in g sposta il puntatore di f sulla riga successiva scrivi un "a capo" su g ENDLe operazioni sposta il puntatore di f sulla riga successiva e scrivi un "a capo" su g vengono effettuate semplicemente con le due chiamate readln(f) e writeln(g). Per effettuare l'operazione copia la riga di f su cui si trova il puntatore in g occorre ricordare che una riga è una sequenza di caratteri. Pertanto, si copiano uno alla volta i caratteri della riga nel file g. In altre parole, si può utilizzare il seguente ciclo:
WHILE la riga di f su cui si trova il puntatore non e' finita DO BEGIN leggi il prossimo carattere di f e scrivilo su g ENDA tale scopo si definisce una variabile c di tipo char, per effettuare la copia del carattere. L'operazione leggi il prossimo carattere di f sarà semplicemente una read(f, c), mentre l'operazione scrivilo su g sarà una write(f, c).
Ecco il listato del programma completo costruito seguendo questo schema:
PROGRAM copiafile (f, g); {copia il contenuto del file di testo f nel file di testo g} VAR f, g: text; c: char; BEGIN reset(f); rewrite(g); WHILE NOT eof(f) DO BEGIN WHILE NOT eoln(f) DO BEGIN read(f, c); write(g, c) END; {while} readln(f); writeln(g) END {while} END.
Così come è stato scritto, il programma copiafile permette di copiare un file di nome f in un file di nome g. Volendo utilizzare il programma per copiare file di nomi differenti, è necessario sostituire i nomi in tutto il testo del programma. Per evitare ciò, i vari ambienti di programmazione mettono a disposizione dei metodi per associare al nome interno del file, cioè al nome utilizzato nel programma, come f e g, un nome esterno. Negli ambienti THINK Pascal e HP-Pascal, è possibile creare questa associazione, indicando, come secondo parametro di reset o rewrite, il nome esterno del file. Ad esempio, per copiare un file di nome pippo in un file di nome pluto, potremmo sostituire le due istruzioni di apertura file del programma copiafile, con reset(f,'pippo') e rewrite(g,'pluto'). Al posto di indicare direttamente i nomi come costanti tra apici, è possibile utilizzare variabili di tipo stringa alle quali siano stati assegnati i nomi desiderati. Nel Turbo Pascal l'associazione avviene invece chiamando, prima dell'apertura del file, una procedura di nome assign, che abbia come parametri rispettivamente il nome interno e il nome esterno. Nel nostro esempio si dovrebbe dunque scrivere:
assign(f, 'pippo'); reset(f); assign(g, 'pluto'); rewrite(g);
Nell'ambiente IRIE Pascal sono disponibili entrambe le modalità di associazione tra nome esterno e nome interno sopra descritte.
Vogliamo costruire un programma che legga un testo da input e stampi il numero di occorrenze di ciascuna vocale nel testo.
Il programma sarà così strutturato:
Scriviamo questo programma utilizzando tre procedure senza parametri corrispondenti alle tre parti evidenziate sopra. Il programma avrà la seguente struttura:
PROGRAM FrequenzeVocali (input, output); {conta la frequenza di ciascuna vocale nel file di input} VAR na, ne, ni, no, nu: integer; ...dichiarazioni delle procedure inizializza, LeggiEConta, scrivi... BEGIN {FrequenzeVocali} inizializza; LeggiEConta; scrivi END. {FrequenzeVocali}
La procedura inizializza non deve far altro che azzerare le variabili utilizzate come contatori. Pertanto il suo codice è:
PROCEDURE inizializza; BEGIN {inizializza} na := 0; ne := 0; ni := 0; no := 0; nu := 0 END; {inizializza}La procedura scrivi deve invece scrivere in output i valori dei contatori:
PROCEDURE scrivi; BEGIN {scrivi} writeln('Il testo contiene ', na : 1, ' a'); writeln('Il testo contiene ', ne : 1, ' e'); writeln('Il testo contiene ', ni : 1, ' i'); writeln('Il testo contiene ', no : 1, ' o'); writeln('Il testo contiene ', nu : 1, ' u') END; {scrivi}
La procedura LeggiEConta deve occuparsi di ricevere i caratteri da input, esaminarli e incrementare opportunamente i contatori. Poniamoci sempre ad alto livello, e consideriamo prima di tutto il file input come una sequenza di righe. Per esaminare l'intero file dovremo pertanto effettuare un ciclo tipo:
WHILE il file non e' finito DO BEGIN esamina una riga spostati sulla riga successiva ENDPer spostarsi sulla riga successiva è sufficiente scrivere readln. Ricordando che una riga non è altro che una sequenza di caratteri, l'esame di ciascuna riga può essere realizzato a sua volta con un altro ciclo:
WHILE la riga non e' finita DO BEGIN leggi il prossimo carattere esaminalo ENDDefiniamo, localmente alla procedura, una variabile c di tipo char, utilizzata per memorizzare il carattere letto. Pertanto, la lettura del prossimo carattere verrà effettuata mediante read(c). L'esame del carattere avverrà secondo questo schema:
IF il carattere e' una vocale THEN incrementa il contatore corrispondenteL'incremento del contatore corrispondente alla vocale, viene effettuato all'interno di un'istruzione CASE, con la quale si effettua una selezione in base alla vocale letta. La procedura risulta essere pertanto:
PROCEDURE LeggiEConta; VAR c: char; BEGIN {LeggiEConta} WHILE NOT eof DO {esamina una riga} BEGIN WHILE NOT eoln DO {esamina un carattere} BEGIN read(c); {se il carattere e' una vocale, incrementane il contatore} IF (c = 'a') OR (c = 'A') OR (c = 'e') OR (c = 'E') OR (c = 'i') OR (c = 'I') OR (c = 'o') OR (c = 'O') OR (c = 'u') OR (c = 'U') THEN CASE c OF 'a', 'A': na := na + 1; 'e', 'E': ne := ne + 1; 'i', 'I': ni := ni + 1; 'o', 'O': no := no + 1; 'u', 'U': nu := nu + 1 END {case c} END; {while not eoln ...} {passa alla riga successiva} readln END {while not eof ...} END; {LeggiEConta}La lunga condizione (c = 'a') OR (c = 'A') OR ... OR (c = 'U') può essere sostituita dalla condizione piú compatta ed elegante c IN ['a','A','e','E','i','I','o','O','u','U'], che risulta vera quando il contenuto della variabile c di tipo char appartiene all'insieme di caratteri specificato a destra dell'operatore IN. Lo studio degli insiemi in Pascal verrà affrontato in maniera sistematica in una lezione successiva.
Ecco il listato completo del programma:
PROGRAM FrequenzeVocali (input, output); {conta la frequenza di ciascuna vocale nel file di input} VAR na, ne, ni, no, nu: integer; PROCEDURE inizializza; BEGIN {inizializza} na := 0; ne := 0; ni := 0; no := 0; nu := 0 END; {inizializza} PROCEDURE LeggiEConta; VAR c: char; BEGIN {LeggiEConta} WHILE NOT eof DO {esamina una riga} BEGIN WHILE NOT eoln DO {esamina un carattere} BEGIN read(c); {se il carattere e' una vocale, incrementane il contatore} IF c IN ['a','A','e','E','i','I','o','O','u','U'] THEN CASE c OF 'a', 'A': na := na + 1; 'e', 'E': ne := ne + 1; 'i', 'I': ni := ni + 1; 'o', 'O': no := no + 1; 'u', 'U': nu := nu + 1 END {case c} END; {while not eoln ...} {passa alla riga successiva} readln END {while not eof ...} END; {LeggiEConta} PROCEDURE scrivi; BEGIN {scrivi} writeln('Il testo contiene ', na : 1, ' a'); writeln('Il testo contiene ', ne : 1, ' e'); writeln('Il testo contiene ', ni : 1, ' i'); writeln('Il testo contiene ', no : 1, ' o'); writeln('Il testo contiene ', nu : 1, ' u') END; {scrivi} BEGIN {FrequenzeVocali} inizializza; LeggiEConta; scrivi END. {FrequenzeVocali}
Vogliamo scrivere un programma che legga un testo da input e scriva in output il numero di parole incontrate. Per semplicità considereremo come parola una sequenza di lettere, delimitata da qualsiasi altro carattere. Ad esempio la sequenza aa54bc corrisponderà a due parole.
Anche in questo caso il programma è costituito da una fase di inizializzazione, da una fase di lettura e conteggio e da una fase di scrittura del risultato. Il dato fondamentale è una variabile, nparole, di tipo integer, utilizzata per contare il numero di parole.
Schematizziamo brevemente la fase di lettura e conteggio. Come nel caso precedente, essa sarà costituita da un ciclo principale in cui si esaminano le righe:
WHILE il file non e' finito DO BEGIN esamina una riga spostati sulla riga successiva ENDL'esame di ciascuna riga verrà a sua volta realizzato mediante un altro ciclo:
WHILE la riga non e' finita DO BEGIN leggi il prossimo carattere esaminalo ENDUtilizziamo, come sempre, una variabile c di tipo char, per memorizzare il carattere letto. L'esame di un singolo carattere è essenzialmente un test del tipo:
IF e' finita una parola THEN incrementa il contatore delle paroleOsserviamo che la fine di una parola viene individuata quando, dopo avere letto una sequenza di lettere, si trova un carattere che non è una lettera, oppure quando dopo una sequenza di lettere si trova la fine della riga. Introduciamo dunque una variabile di nome InParola di tipo boolean, che vale true quando ci si trova all'interno di una parola. La fine di una parola viene dunque trovata quando il valore di InParola è true e il carattere che viene letto non è una lettera, oppure quando il valore di InParola è true e viene raggiunta la fine di una riga.
Possiamo dunque riscrivere l'esame di una carattere come:
{se il carattere e' una lettera allora ti trovi in una parola} IF (c >= 'a') AND (c <= 'z') OR (c >= 'A') AND (c <= 'Z') THEN InParola := true {se il carattere non e' una lettera, e se prima eri in una parola} {allora non sei piu' in una parola; incrementa il contatore} ELSE IF InParola THEN BEGIN InParola := false; nparole := nparole + 1 ENDInoltre, alla fine di una riga, occorrerà anche controllare se è finita una parola con il test:
IF InParola THEN nparole := nparole + 1La variabile InParola deve essere inizializzata a false all'inizio di ogni riga.
Utilizzando di nuovo gli insiemi, la condizione (c >= 'a') AND (c <= 'z') OR (c >= 'A') AND (c <= 'Z') può essere riscritta come:
c IN ['a'..'z','A'..'Z']
Ecco il listato completo del programma:
PROGRAM ContaParole (input, output); {conta il numero di parole nel file di input} VAR nparole: integer; c: char; InParola: boolean; BEGIN {ContaParole} nparole := 0; {esamina il testo} WHILE NOT eof DO {esamina una riga} BEGIN InParola := false; WHILE NOT eoln DO {esamina un carattere} BEGIN read(c); {se il carattere e' una lettera allora ti trovi in una parola} IF c IN ['a'..'z','A'..'Z'] THEN InParola := true {se il carattere non e' una lettera, e se prima eri in una parola} {allora non sei piu' in una parola; incrementa il contatore} ELSE IF InParola THEN BEGIN InParola := false; nparole := nparole + 1 END END; {while not eoln ...} {se prima di finire la riga eri in una parola, incrementa il contatore} IF InParola THEN nparole := nparole + 1; {passa alla riga successiva} readln END; {while not eof ...} writeln('Sono state lette ', nparole : 1, ' parole') END. {ContaParole}Per esercizio si può studiare come migliorare il programma, considerando ad esempio come parola una sequenza di lettere delimitata da spazi, da caratteri di punteggiatura o da parentesi.
Nel mezzo del cammin di nostra vita mi ritrovai per una selva oscura che' la diritta via era smarrita. Ahi quanto a dir qual era e' cosa dura esta selva selvaggia e aspra e forte che nel pensier rinova la paura! Tant'e' amara che poco e' piu' morte; ma per trattar del ben ch'i' vi trovai, diro' de l'altre cose ch'i' v'ho scorte. Io non so ben ridir com'i' v'intrai, tant'era pien di sonno a quel punto che la verace via abbandonai.si dovrà produrre in output:
1 Nel mezzo del cammin di nostra vita 2 mi ritrovai per una selva oscura 3 che' la diritta via era smarrita. 4 Ahi quanto a dir qual era e' cosa dura 5 esta selva selvaggia e aspra e forte 6 che nel pensier rinova la paura! 7 Tant'e' amara che poco e' piu' morte; 8 ma per trattar del ben ch'i' vi trovai, 9 diro' de l'altre cose ch'i' v'ho scorte. 10 Io non so ben ridir com'i' v'intrai, 11 tant'era pien di sonno a quel punto 12 che la verace via abbandonai.
NEL MEZZO DEL CAMMIN DI NOSTRA VITA MI RITROVAI PER UNA SELVA OSCURA CHE' LA DIRITTA VIA ERA SMARRITA. AHI QUANTO A DIR QUAL ERA E' COSA DURA ESTA SELVA SELVAGGIA E ASPRA E FORTE CHE NEL PENSIER RINOVA LA PAURA! TANT'E' AMARA CHE POCO E' PIU' MORTE; MA PER TRATTAR DEL BEN CH'I' VI TROVAI, DIRO' DE L'ALTRE COSE CH'I' V'HO SCORTE. IO NON SO BEN RIDIR COM'I' V'INTRAI, TANT'ERA PIEN DI SONNO A QUEL PUNTO CHE LA VERACE VIA ABBANDONAI.Nota: se la variabile
c
di tipo char
contiene una
lettera minuscola, la lettera maiuscola equivalente può essere
ottenuta come risultato dell'espressione
chr(ord(c)-ord('a')+ord('A'))
.
wc
di UNIX conta il numero di righe, di
parole e di caratteri presenti in un file di testo. Si costruisca un
programma che ne simuli il comportamento.
©1999 Giovanni Pighizzini
Il contenuto di queste pagine è
protetto dalle leggi sul copyright e
dalle disposizioni dei trattati internazionali. Il titolo ed i copyright
relativi alle pagine sono di proprietà dell'autore.
Le pagine possono essere riprodotte ed utilizzate liberamente dagli
studenti, dagli istituti di ricerca, scolastici ed universitari
afferenti ai Ministeri
della Pubblica Istruzione e dell'Università e della
Ricerca Scientifica e Tecnologica
per scopi istituzionali, non a fine di lucro.
Ogni altro utilizzo o riproduzione (ivi incluse, ma non
limitatamente a, le riproduzioni a mezzo stampa, su supporti magnetici o
su reti di calcolatori) in toto o in parte è vietata, se non
esplicitamente autorizzata per iscritto, a priori, da parte dell'autore.
L'informazione contenuta in queste pagine è ritenuta essere
accurata alla data della pubblicazione. Essa è fornita per scopi
meramente didattici e non per essere utilizzata in progetti di impianti,
prodotti, ecc.
L'informazione contenuta in queste pagine è soggetta a cambiamenti
senza preavviso. L'autore non si assume alcuna responsabilità per il
contenuto di queste pagine (ivi incluse, ma non limitatamente a, la
correttezza, completezza, applicabilità ed aggiornamento
dell'informazione).
In ogni caso non può essere dichiarata conformità all'informazione
contenuta in queste pagine.
In ogni caso questa nota di copyright non deve mai essere rimossa e deve
essere riportata anche in utilizzi parziali.