![]() | ![]() |
|
|
dott. Antonella Carbonaro
Il Pascal è un linguaggio algoritmico per la programmazione sequenziale progettato nel 1968 da N. Wirth.
Carartteristiche:
· 939g62j compatto con formato vincolato
· 939g62j semantica degli statements ben specificata
· 939g62j numero di statements limitato ma completo
· 939g62j utile collezione di predefined data types
· 939g62j meccanismo di subroutine generale
Il Pascal è un linguaggio compilato. Il processo di compilazione è composto da due fasi:
· 939g62j l'analisi (lessicale, sintattica, semantica)
· 939g62j la sintesi (linguaggio intermedio, ottimizzazione, linguaggio target).
La versione standard del Pascal: ISO, non è legata ad alcun tipo specifico di compilatore.
Il linguaggio è definito tramite il Vocabolario e la Grammatica.
La Grammatica è un insieme composto da:
· 939g62j simboli terminali (parole che appartengono al vocabolario),
· 939g62j simboli non terminali (metasimboli per giungere allo scopo),
· 939g62j produzioni (elenco delle sostituzioni ammesse),
· 939g62j scopo
Vocabolario
def. L'INSIEME DEI CARATTERI DI BASE utilizzabili nel descrivere un programma Pascal.
Essi sono:
· 939g62j Le 26 lettere dell'alfabeto inglese (maiuscole A..Z e minuscole a..z)
· 939g62j le 10 cifre arabe (da 0 a 9)
· 939g62j i caratteri speciali: + - * / = < > . , : ; '
· 939g62j i caratteri blank, tab e quelli di fine linea
Unità lessicale
def una sequenza finita dei caratteri dell'alfabeto
La Sintassi del Pascal ci dice quali SEQUENZE di unità lessicali sono riconosciute come frasi corrette.
Il linguaggio Pascal riconosce 4 tipi di unità lessicali:
identificatori
simboli speciali
numeri
stringhe
Sono nomi utilizzati per denotare diverse entità del linguaggio.
Sono identificatori corretti:
ciao CIAO ciAO4 c4iAo
mentre non lo sono
13maggio 66 alfa-beta c&ao $ciao £$
Nota Un identificatore può essere di una lunghezza qualsiasi di caratteri (comunque della lunghezza di una linea) TUTTAVIA solo i primi N sono considerati significativi.
Es. Se N = 8
variabile1 e variabile2 saranno considerate come lo stesso identificatore
Alcuni identificatori hanno in Pascal un significato preciso e congelato a prescindere dal quale non sono utilizzabili correttamente.
Essi sono le PAROLE CHIAVE o RISERVATE
and array begin case const div do downto
else end file for function goto if in
label mod nil not of or packed procedure
program record repeat set then to type until
var while with
Oss. Nelle carte sintattiche appaiono come simboli terminali
Sono soggette a regole sintattiche particolari (es. then deve seguire if..)
Inoltre vi sono anche gli IDENTIFICATORI STANDARD
Essi hanno un significato preciso ma non congelato (cioè possono essere ridefiniti)
e rappresentrano funzioni matematiche, valori logici, procedure standard, costanti, strutture di dati...
abs arctan boolean char chr cos dispose eof
eoln exp false get input integer ln maxint
new odd ord output pack page pred put
read readln real reset rewrite round sin sqr
sqrt succ text true trunc unpack write writeln
Sono caratteri speciali o coppie degli stessi interpretatie come singole unità.
Essi denotano operazioni o punteggiatura.
< > <= >= <> ^ :=
, ; : ' . ..
( ) (. .) (* *)
Nelle carte sintattiche sono ovviamente rappresentati come simboli terminali.
Sono numeri corretti:
1 +1 -1 1.3 +1.3 -1.3 1.3E4 +1E-4 1.3E-4
Una stringa è una qualsiasi sequenza di caratteri delimitata da '
Per scrivere il carattere ' basterà raddoppiarlo ''
sono stringhe corrette:
'12345' 'a' '£$%ç§' 'l''ape' '''' l''l
Nota:
· 939g62j Il "blank" è ammesso all'interno di una stringa
· 939g62j I caratteri di "fine linea" non sono permessi all'interno della stringa.
Segue che la lunghezza di una stringa non può superare la dimensione fisica di una linea.
Un programma Pascal non è altro che la successione di identificatori, numeri, stringhe e caratteri speciali giustapposti secondo le regole che verranno via via specificate.
Supponiamo di voler descrivere un programma che trasferisce un testo riga per riga da un dispositivo di input (terminale) ad uno di output (stampante).
Seguendo l'approccio top-down
· 939g62j ad un primo livello specifichiamo:
tre risorse su cui basare l'algoritmo risolutivo
descrizione della riga (dato su cui si opera)
descrizione di leggi
descrizione di scrivi
l'algoritmo risolutivo
ripeti fino alla fine del testo
leggi una riga (moduli
scrivi una riga
· 939g62j ad un secondo livello siamo interessati a risolvere i sottoproblemi di lettura e scrittura e individuiamo:
le risorse
descrizione del carattere (dato su cui si opera)
descrizione di leggicarattere
l'algoritmo risolutivo
ripeti fino al carattere di fine-linea
leggi un carattere
Come si può notare la descrizione del modulo è identica ad ogni livello di dettaglio
e si compone di due parti
la specifica delle risorse utilizzate
l'elaborazione delle stesse.
Un programma Pascal avrà dunque la seguente sintassi:
programma=program header+blocco .
Dove:
· 939g62j il program header specifica il nome del programma e i suoi rapporti con l'esterno tramite degli identificatori.
· 939g62j il blocco invece include le specifiche delle risorse e dell'elaborazione delle stesse.
Tutte le risorse utilizzate nella parte algoritmica di ogni modulo devono essere precedentemente specificate, ovvero dichiarate.
Ad ogni risorsa verrà associato un identificatore che la denoterà univocamente all'interno del programma e che sarà appunto utilizzato per riferirla nella parte algoritmica.
Le risorse possono essere di diversi tipi:
· 939g62j etichette
· 939g62j costanti
· 939g62j tipi
· 939g62j variabili
· 939g62j procedure o funzioni
Le etichette o label servono per individuare punti particolari del programma a cui ci si potrà riferire per fare un salto all'interno dello stesso.
All'interno di un programma Pascal si possono identificatori come sinonimi di dati che non si modificano mai durante l'esecuzione del programma.
Due i vantaggi principali:
Migliore leggibilità del programma
Migliore manutenibilità: per modificare il valore, basterà modificare la sola dichiarazione anziché ogni sua occorrenza.
Il tipo della costante è determinato dal suo contenuto.
Sono dunque costanti corrette
const
max = +527; (integer)
pigreco =3.14; (real)
nome = 'elena'; (array of char)
min = - max; (integer)
alwaystrue = true; (boolean)
a = 'z'; (char)
Il tipo dei dati definisce l'insieme dei valori e l'insieme delle operazioni che si possono effettuare su di essi.
Una variabile definita nel programma deve appartenere obbligatoriamente ad un solo tipo di dato.
I vantaggi sono:
· 939g62j posso esprimere i dati ad un certo livello di ASTRAZIONE senza entrare nei dettagli della rappresentazione in memoria,
· 939g62j la manipolazione è riferita alla STRUTTURA ASTRATTA e non alla ORGANIZZAZIONE FISICA,
· 939g62j esiste protezione automatica da combinazioni errate di dati.
Definisco un TIPO DI DATO ASTRATTO attraverso :
· 939g62j la struttura fisica cioè i VALORI PERMESSI (correlati con lo spazio fisico di memoria),
· 939g62j gli accessi, cioè gli OPERATORI (procedure e funzioni) definite sulla struttura.
Il Pascal definisce diversi tipi di dati predefiniti e i costrutti che permettono al programmatore di definire i propri a seconda delle esigenze dettate dalla natura del problema.
BOOLEAN ENUMERATIVI ARRAY
INTEGER SUBRANGE RECORD
REAL SET
CHAR FILE
POINTER
Sui tipi torneremo più avanti.
Se la dichiarazione del tipo introduce un sinonimo per un modello di dato, la dichiarazione della variabile esplicita un istanziamento di tale modello.
La dichiarazione associa un nome simbolico ad una variabile di un certo tipo. Pascal è fortemente tipato: le variabili devono essere dichiarate prima di essere utilizzate.
Scopo di una dichiarazione:
· 939g62j definire ed allocare lo spazio di memoria necessario,
· 939g62j definire i valori ammessi,
· 939g62j definire gli operatori.
var
max : integer;
flag : boolean;
radice1, radice2 : real;
Procedure e funzioni rappresentano in pascal dei veri e propri sottoprogrammi.
Nel pascal standard esse devono fare parte integrante del programma globale e costituire quindi una parte di un'unica unità di compilazione. Esistono tuttavia altri linguaggi che accettano una compilazione separata con la possibilità quindi di costruire librerie di sotto-programmi.
Distinguiamo tra procedure e funzioni perché le seconde restituiscono esse stesse un valore assegnabile ad esempio ad una variabile.
Tipo di dato
def. classe dei valori e insieme delle operazioni permesse su tali elementi.
In Pascal ad ogni variabile e costante, nonche funzione, deve essere associato un tipo di dato.
Tale associazione è STATICA ed è effettuata al momento della dichiarazione o definizione dell'oggetto.
Ad esempio:
const
max=120;
var sono oggetti di tipo integer
i : integer;
Tipo non strutturato
def. E' un tipo caratterizzato da un insieme di valori elementari che rappresentano atomi che non possono essere ulteriormente scomponibili.
tipo semplice: tipo enumerativo o tipo subrange o tipo predefinito
Tutto il calcolo esprimibile in Pascal si basa su questi valori atomici.
5.1.1 Tipo enumerativo
Per definire un tipo enumerativo basta elencarne (enumerarne) tutti i possibili valori, ovvero basta elencare tutti gli identificatori con cui sono denotati i valori del tipo, che, in pratica, rappresentano le costanti letterali che caratterizzano il tipo stesso.
Una variabile dichiarata di quel tipo potrà assumere solo tali valori.
Seguono alcuni esempi di tipi enumerativi:
type
colore = (rosso,arancio,giallo,verde,azzurro,indaco,violetto);
frutta = (mela,pesca,arancio,limone);
var
moneta: (dollaro,marco,sterlina,franco,yen,lira);
giorno: (lun,mar,mer,gio,ven,sab,dom);
portata:(antipasto,primo, secondo, contorno, dolce, frutta);
Nota:
Un identificatore di un valore scalare definito dall'utente può comparire nella definizione di un solo type.
L'ordine con cui compaiono nella enumerazione gli identificatori definisce l'ordinamento dei valori denotati da quei nomi.
Ciò permette di
· 939g62j confrontare oggetti dei tipi enumerativi con operatori relazionali
lun < mer
rosso < violetto
· 939g62j applicare le funzioni pred e succ che individuano il predecessore e il successore di un valore nella sequenza data nella definizione del tipo enumerativo
pred(mer) = mar
succ(gio)= ven
pred(lun) = undefined !
· 939g62j ottenere la posizione del valore considerando numerato con zero il primo posto tramite la funzione ord
ord(lun)=0
ord(gio)=3
5.1.2 Tipo subrange
Valori: sottoinsieme dei valori di un ORDINAL TYPE (integer, boolean, char, enumerativi).
E' definito indicando limiti inferiore e superiore.
Deve valere la seguente relazione
limite inferiore <= limite superiore
e le costanti devono essere dello stesso tipo.
Sono pertanto corrette definizioni del tipo
type
feriale = lun..ven;
festivo = sab..dom;
indice = 0..63;
lettera = 'A'..'Z';
mentre non lo è
type
my_week= dom..mer;
perchè dom > mer.
C'è validazione automatica nelle istruzioni, cioè i risultati devono appartenere al tipo scalare associato.
5.1.3 Tipi predefiniti
il Pascal supporta 4 tipi predefiniti denotati da altrettanti identificatori standard i cui valori sono insiti nel linguaggio stesso e non possono essere ridefiniti dall'utente.
a) tipo integer
Rappresenta l'insieme dei numeri interi rappresentabili nella word di memoria della macchina.
Quindi possiamo definire questo tipo standard come:
type
integer = -maxint-1..maxint
dove maxint
è un identificatore standard che denota il più grande numero intero rappresentabile sulla
macchina che dipende dalla lunghezza (numero di bit) della word e dalla notazione adottata.
Ogni operazione che porta fuori dal range -maxint-1..maxint produce un OVERFLOW error.
Tutti i tipi di dati che hanno una natura enumerabile possono essere rappresentati come integer.
const
dozzina = 12;
type
lunghezza = integer;
ora = 0..24;
var
perimetro : integer;
contatore: integer;
Questo tipo è caratterizzato da operazioni specifiche:
somma (operatore diadico) e segno (operatore monadico)
- sottrazione(oper. diadico) e segno(operatore monadico)
moltiplicazione
esponenziazione (non ISO)
div divisione intera
mod modulo (resto della divisione intera)
abs valore assoluto
pred predecessore
succ successore
con risultato integer;
divisione
arctan arcotangente
cos coseno
exp esponenziale
ln logaritmo naturale
sin seno
sqr quadrato
sqrt radice quadrata
con risultato reale;
odd dispari
= <> < > <= >=
con risultato booleano.
b) tipo real
Rappresenta l'insieme dei numeri reali rappresentabili nella word di memoria della macchina.
In questo caso la limitazione fisica della lunghezza della parola di memoria della macchina introduce limiti
- sul range dei valori permessi
- sulla precisione degli stessi, con la conseguenza di rendere discreta la natura continua dei numeri reali
Rappresentazione Floating Point normalizzata, PRECISIONE MACCHINA e NUMERI MACCHINA.
Errori di OVERFLOW ed errori di UNDERFLOW.
Alcuni esempi di definizione con il tipo real sono.
const
g = 9.8;
type
pressione = real;
var
ascissa,ordinata : real;
Non essendo di utilità pratica che il tipo real sia riconosciuto come enumerativo, non si possono definire subrange reali.
Questo tipo è caratterizzato dalle seguenti operazioni specifiche:
trunc troncamento
round arrotondamento
con risultato integer;
somma (operatore diadico) e segno (operatore monadico)
- sottrazione(oper. diadico) e segno(operatore monadico)
moltiplicazione
esponenziazione (non ISO)
abs valore assoluto
divisione
arctan arcotangente
cos coseno
exp esponenziale
ln logaritmo naturale
sin seno
sqr quadrato
sqrt radice quadrata
con risultato reale;
< > = <> <= >=
con risultato booleano.
c) Tipo boolean
La definizione del tipo boolean è la seguente:
type
boolean = (false,true);
I due valori di verità false e true sono identificatori standard.
Per essi vale la convenzione di ordinamento per cui
false<true
Anche per i tipi boolean sono definiti in Pascal operatori logici derivanti dall'algebra booleana; essi sono:
and congiunzione
or disgiunzione
not negazione
= <> < > >= <=
Alcuni esempi di definizione con il tipo real sono:
const
vero = true;
falso = false;
type
bit = boolean;
var
flag,errore : boolean;
d) Tipo char
Il tipo predefinito char è dato dall'insieme finito ed ordinato dei caratteri utilizzabili dalla macchina. L'ordine è dipendente dalla codifica (ASCII, EBCDIC, ...)
Tutti i caratteri grafici sono presenti in questo insieme.
Alcuni esempi di definizioni di tipi char sono:
const
blank = ' ';
quote = '''';
type
lettera = char;
var
simbolo:char;
dove si è utilizzata la carta sintattica delle stringhe di caratteri.
Normalmente in tutte le macchine è vero che:
'a' < 'b' < .... < 'z'
'A' < 'B' < .... < 'Z'
'0'< '1' < .... < '9'
in tal caso la natura enumerativa del tipo permette di definire tipi subrange come:
type
maiuscole = 'A' .. 'Z';
var
cifra: '0'..'9';
Note
· 939g62j Non è ovviamente consentito definire una nuova enumerazione costituita da caratteri come la seguente:
type
vocali ('a','e','i','o','u');
· 939g62j Ricordando che la funzione ord fornisce in genere la codifica interna del simolo che i compilatori pascal fanno corrispondere alla posizione nella definizione, segue che ord('a') ci fornisce la codifica interna del carattere 'a'.
· 939g62j La funzione chr applicata ad un intero da 0 a 255 ritorna il carattere con la rappresentazione uguale all'argomento.
Il Pascal mette a disposizione i seguenti costruttori di tipo:
a) array
b) record
c) set
d) file
a) Il tipo array
L'array
def. è una struttura dati omogenea costituita da un numero predefinito di componenti dello stesso tipo detto tipo base ai quali si accede casualmente mediante uno o più indici. Possono essere a 1, 2, o più dimensioni.
Tale struttura rappresenta bene vettori, matrici, tabelle.
Per gli indici si può adottare un qualsiasi ordinal type (dunque non real).
Ad esempio:
type
chefai = array of (mangi,dormi,lavori,tidiverti)
è un vettore di lunghezza 24
type
giorno = (LUN,MAR,MER,GIO,VEN,SAB,DOM)
è un vettore di lunghezza 7
type
chefaiinsettimana = array giorno of chefai
è una matrice 7 * 24
Dato un array, un suo elemento è individuato dal nome dell'array seguito dal valore dell'indice tra parentesi quadre.
La sintassi specifica anche i riferimenti per record e puntatori, che vedremo in seguito.
Ora, supponendo di avere le variabili:
var
x: chefai;
y: chefainsettimana;
i: 0..23;
j: giorno;
potremo scrivere:
x per accedere all'elemento corrispondente all'ora tra le 15 e le 16
x i+5 per accedere all'elemento corrispondente all'ora individuata da i+5
y mer per accedere all'array di tipo "che fai" corrispondente alla giornata di mercoledì
y succ(j) per accedere all'array di tipo "che fai" corrispondente alla giornata successiva
a quella contenuta nella variabile j
y lun per accedere all'elemento corrispondente all'ora tra le 9 e le 10 della giornata
di lunedì della settimana identificata dalla variabile y
Esempi:
· 939g62j prodotto scalare fra due vettori
· 939g62j somma di due matrici
b) il tipo record
def. struttura dati che permette di aggregare sotto lo stesso nome una sequenza di componenti di tipi arbitrari (che potrebbero a loro volta essere strutturati).
Il concetto è simile a quello di N_PLA, cioè un elemento del prodotto cartesiano dei tipi costituenti.
La sintassi della definizione di un record è la seguente, ove, per ora non esplicitiamo la parte variante.
Così ad esempio si può definire:
type
data = record
giorno: 1..31;
mese: 1..12;
anno: 0..2000
end;
var
scadenza : data;
L'accesso ad una componente del record avviene giustapponendo al nome della variabile il nome del campo preceduto da un '.'
scadenza.mese identifica la componente mese della variabile scadenza
Il record ci permette di definire strutture estremamente articolate, ad esempio:
type
nome = array of char;
anno = 1500..2000;
libro = record
autori: array of nome;
titolo: array of char;
editore: nome;
dtastampa,dataacquisto:anno;
collocazione:record
tipo:(romanzo,saggio,manuale);
scaffale:'A'..'Z';
posizione:1..100
end
end;
biblioteca= array of libro;
Da cui avendo a disposizione una variabile
var miabibl:biblioteca;
si può accedere ad una qualsiasi singola informazione,
ad esempio con miabibl .autori
si accede all'iniziale del primo autore del quarto libro della biblioteca.
I record possono avere anche una parte variante di tipo non completamente noto a priori e più precisamente che può essere scelto in un insieme definito di tipi.
Questo ci permette di definire record che a seconda della costante assunta dall'identificatore che segue il case presenta dei campi diversi.
Se ad esempio dobbiamo definire un tipo fattura che deve presentare campi diversi a seconda che il cliente sia normale, esente da iva, o estero possiamo utilizzare la seguente struttura record:
type
fattura = record
data:record
gg:1..31;
mm:1..12;
aa:00..99
end;
numerofattura:integer;
cliente:record
nome,città:array of char;
indirizzo:array of char;
cap:integer;
partitaiva:integer
end;
case tipocliente:(normale,esente,estero)of
normale:(imponibile:integer;
iva:integer);
esente: (causale: array of char);
estero: (tipovaluta:(lit,usd,dm,lst,ff,fs);
valore unitario:integer;
imponibileinvaluta:integer;
imponibileinlire:integer;
iva:integer);
totale:integer
end;
Si noti che defininendo una variabile di tipo record con varianti viene allocata IN MANIERA STATICA tanta memoria quanta ne servirebbe per la variante più grande tra quelle possibili, le altre utilizzano infatti comunque una sottoparte della stessa. Questo può comportare dei problemi.
Il record può essere utilizzato per costruire strutture più complesse, come ad esempio array di record oppure record di array.
c) il tipo set
def. definisce un insieme di elementi di tipo scalare (real escluso).
Gli operatori disponibili sul tipo set sono, dati a e b insiemi ed x elemento di a:
a + b unione di insiemi
a * b intersezione di insiemi
a >= b espr. booleana "a contiene b"
a <= b espr. booleana "a sottoinsieme di b"
x in a espr. booleana "x appartiene ad a"
Si può considerare anche l'insieme vuoto '
Esempio:
var
minuscole,maiuscole,cifre:set of char;
begin
maiuscole:= 'A'..'Z'
minuscole:= 'a'..'z'
cifre:=
vuoto:=
read(ch);
if ch in maiuscole then
end
d) il tipo file
def. sequenza di valori dello stesso tipo, accesssibili in ordine rigorosamente sequenziale.
in genere è correlata alla presenza di memorie di massa per registrare grandi q.tà di dati.
I file verranno apprpfonditi più avanti (paragrafo 12).
La specifica dell'algoritmo rappresenta la parte algoritmica di un blocco ed è descritta da uno statement composto = sequenza di statement racchiusa da due parole riservate di inizio e di fine.
Ogni statement separato dal successivo da un separatore: il simbolo speciale ; (non occorre prima di un end), può essere: una qualsiasi delle frasi eseguibili del linguaggio ovvero
semplici statement operativi : assegnamento,richiamo a procedura
strutture di controllo: if, case, while, repeat, for with goto.
una sequenza di statement: statement composto
uno statement vuoto.
Le istruzioni di assegnamento permettono di:
· 939g62j calcolare il valore di un'espressione
· 939g62j assegnare tale valore ad una variabile.
Nota
Si è usato il termine generale di variabile e non di identificatore per non considerare le sole variabili di tipo semplice, ma anche variabili di tipi strutturati che vedremo in seguito.
La sintassi delle espressioni è la seguente:
Nota I richiami di funzione non sono altro che espressioni del tipo:
sqrt(delta)
che, accettando uno o più argomenti (nel nostro esempio il delta) restituisce un risultato.
Tali funzioni possono essere pre-definite e disponibili con il pascal standard
o definite dall'utente all'interno del programma che lo intende utilizzare.
Segue una tabella riassunto di tutti gli operatori descritti e delle funzioni standard
OPERATORE |
SIGNIFICATO |
TIPO OPERANDI |
TIPO RISULTATO |
|
somma o segno unione |
integer o real set |
integer o real set |
|
sottrazione o segno |
integer o real |
integer o real |
|
moltiplicazione intersezione |
integer o real set |
integer o real set |
|
divisione |
integer o real |
real |
div |
divisione intera |
integer |
integer |
mod |
modulo |
integer |
integer |
not |
negazione |
boolean |
boolean |
and |
congiunzione |
boolean |
boolean |
or |
disgiunzione |
boolean |
boolean |
|
uguale |
tipi semplici uguali pointers, insiemi |
boolean |
<> |
diverso |
tipi semplici uguali pointers, insiemi |
boolean |
< |
minore |
tipi semplici uguali |
boolean |
> |
maggiore |
tipi semplici uguali |
boolean |
<= |
minore uguale sottoinsieme |
tipi semplici uguali tipi set |
boolean boolean |
>= |
maggiore uguale contiene |
tipi semplici uguali tipi set |
boolean boolean |
in |
appartiene |
elemento e set |
boolean |
abs |
valore assoluto |
integer o real |
stesso tipo argomento |
arctan |
arcotangente |
integer o real |
stesso tipo argomento |
chr |
carattere |
|
char |
cos |
coseno |
integer o real |
real |
eoln |
fine della linea |
file |
boolean |
eof |
fine del file |
file |
boolean |
exp |
esponenziale |
integer o real |
real |
ln |
log naturale |
integer o real |
real |
odd |
dispari |
integer |
boolean |
ord |
ordinale |
scalare |
integer |
pred |
predecessore |
scalare |
stesso tipo argomento |
round |
arrotondamento |
real |
integer |
sin |
seno |
integer o real |
real |
sqr |
quadrato |
integer o real |
stesso tipo argomento |
sqrt |
radice quadrata |
integer o real |
real |
succ |
successore |
scalare |
stesso tipo argomento |
trunc |
troncamento |
real |
integer |
Facciamo un esempio per mostrare alcune diverse assegnazioni per inizializzare delle variabili:
type
sesso = (maschio, femmina);
index = 1..5;
data = record
gg: 1..31;
mm:1..12;
aa: 0..2000
end;
var
s: sesso;
i: index;
a,b: array index of integer;
c,d:data;
r,s: set of char;
begin
s:= maschio;
i:=1;
a i :=6; a:=b;
d.gg := 9; c:=d,
s:= 'a','e','i'
end
def. Strutture di controllo
I modelli di composizione per la descrizione di algoritmi
Esse sono di tre tipi:
sequenza
selezione
iterazione
In Pascal la sequenza è espressa come statement composto, che abbiamo già descritto precedentemente, in cui gli statement sono eseguiti sequenzialmente nell'ordine testuale.
Ogni statement è concatenato al successivo dal simbolo terminale ";".
Il Pascal mette a disposizione dell'utente selezioni singola e multipla.
Lo statement if permette:
· 939g62j l'esecuzione condizionale di uno statement
· 939g62j l'esecuzione selettiva tra due statement in dipendenza della valutazione di una condizione.
La sintassi prevede dunque due diverse "forme":
if <espressione booleana> then
<statement>
if <espressione booleana> then
<statement>
else <statement>
Dovendo calcolare un'equazione di secondo grado avremo qualcosa del tipo
program equaz (input,output);
var
a,b,c,delta,x1,x2:real;
begin
.... (* lettura variabili a,b,c *)
if a = 0 then
x1 := - c/b;
else
begin
delta := (b*b - 4*a*c);
if delta <0 then
... (*messaggio di errore*)
else
if delta = 0 then
x1, x2 := - b/2*a;
else
begin
x1:= (- b - sqrt(delta)/2*a);
x2:= (- b + sqrt(delta)/2*a)
end
end
end
Gli statement nei rami dell'if possono essere:
· 939g62j statement composti
· 939g62j o altri if
· 939g62j o altri generici statement.
Focalizzando l'attenzione sugli if nidificati
if cond1 then
statement 1
else if cond2 then
statement2
else if cond3 then
statement3
else
statementn
può non essere chiaro a quale if si riferisce l'else.
Il Pascal adotta la convenzione secondo cui
l'else si riferisce sempre all'if più vicino (più interno nella nidificazione)
Se dunque ad esempio abbiamo lo statement
if a>0 then if a < 50 then a:= 0 else a:=100;
esso è equivalente a
if a>0 then
begin
if a < 50 then
a:= 0
else a:=100
end;
se vogliamo che l'else sia riferito al primo if dobbiamo scrivere:
if a>0 then
begin
if a < 50 then
a:= 0
end
else a:=100;
Lo statement case specifica la selezione di un ramo tra n possibili in base al valore di una determinata espressione.
L'espressione che segue la parola chiave case è detta "selettore" e può essere di un qualsiasi tipo scalare.
Si ha:
· 939g62j Ogni ramo risulta etichettato da uno o più valori costanti dello stesso tipo el selettore.
· 939g62j Ovviamente ogni etichetta non può apparire in più di un ramo.
· 939g62j L'ordine di scrittura dei rami nel testo è irrilevante.
Esempio di utilizzo del case:
program mese (input,output);
var numMese:1..12;
anno:0..2000;
numGiorni:28..31;
begin
.... (* lettura variabili numMese e anno*)
case numMese of
1,3,5,7,8,10,12:
numGiorni:=31;
2:
if (anno mod 4 = 0) then
numGiorni:=29
else
numGiorni:=28;
4,6,9,11:
numGiorni:=30
end;
....
end.
Nota:
E' banalmente possibile rendere lo statement case con degli statement if.
Allo stesso modo lo statement if si può rendere con il case:
if <condizione> then
<statement1>
else
<statement2>
si può scrivere:
case <condizione> of
true: <statement1>
false: <statement2>
end
Spesso può essere necessario avere la possibilità di poter ripetere le azioni specificate in un determinato algoritmo.
Il Pascal fornisce tre strutture di controllo iterative:
· 939g62j due orientate all'iterazione guidata da una condizione, (statement while e repeat)
· 939g62j una all'iterazione guidata da un contatore. (statement for)
Lo statement while consente di iterare delle operazioni FINTANTOCHE' una condizione risulta verificata.
L'espressione booleana è valutata prima di ogni iterazione.
Se il valore è TRUE , lo statement viene eseguito, mentre se è FALSE lo statement non è eseguito e si esce dall'iterazione.
Supponiamo di voler scrivere un programma per calcolare n!
program nfatt(input,output);
type naturale = 1..maxint;
var contatore,n,risultato: naturale;
begin
.... (* lettura variabili n *)
contatore,risultato:=1;
while contatore <=n do
begin
risultato:=risultato*contatore;
contatore:=contatore+1
end;
.....
end.
Lo statement repeat consente di iterare delle operazioni FINO A CHE una condizionenon diventa vera.
L'espressione booleana è valutata dopo ogni iterazione.
Se il valore è FALSE , la sequenza di statement viene nuovamente eseguita, mentre se è TRUE si esce dall'iterazione.
Supponiamo di voler di nuovo scrivere un programma per calcolare n!, utilizzando però lo statement repeat:
program nfatt(input,output);
type naturale = 1..maxint;
var contatore,n,risultato: naturale;
begin
.... (* lettura variabili n *)
contatore,risultato:=1;
if contatore <= n then (*superfluo perché n è naturale*)
repeat
risultato:=risultato*contatore;
contatore:=contatore+1
until contatore >n;
.....
end.
Nota
E' ovviamente possibile rendere lo statement repeat con lo statement while:
repeat <statement>
<statement> = = while not <condizione> do
until<condizione> <statement>
e analogamente si può rendere il while con il repeat e l'if:
while <condizione> do if <condizione> then
<statement> = = repeat <statement>
until not <condizione>
Lo statement for specifica che uno statement deve essere ripetuto per valori consecutivi di una certa variabile.
Questa struttura di controllo può essere utilizzata per modellare i casi frequenti in cui il numero delle ripetizioni è conosciuto in anticipo.
Si ha:
· 939g62j L'identificatore denota la variabile di controllo, che deve essere stata dichiarata di un qualsiaisi tipo scalare.
· 939g62j Entrambe le espressioni devono essere dello stesso tipo della variabile: esse sono valutate una sola volta e i loro valori costituiscono gli estremi di una successione di valori assunti dalla variabile di controllo.
· 939g62j Lo statement è eseguito una volta per ognuno dei valori di tale successione crescente o decrescente a seeconda se è stata specificata la forma con la parola chiave to o downto.
Le fasi di inizializzazione, controllo ed incremento della variabile di controllo sono eseguite automaticamente dalla struttura for.
Supponiamo di voler riscrivere il programma per calcolare n! utilizzando lo statement for
program nfatt(input,output);
type
naturale = 1..maxint;
var contatore: integer;
n,risultato: naturale;
begin
.... (* lettura della variabile n *)
risultato := 1;
for contatore := 1 to n do
risultato:=risultato*contatore;
.....
end.
Nota: Per il corretto funzionamento della struttura for è necessario che:
· 939g62j gli estremi non vengano alterati
· 939g62j il valore della variabile di controllo al termine dell'iterazione venga considerato
Altri esempi di uso del for:
var g: (lun,mar,mer,gio,ven,sab,dom);
c:char;
for g:= lun to ven do....
for g:= dom downto lun do....
for g:= 'a' to 'z' do....
for g:= '9' downto '0' do....
Questi esempi mostrano come
i valori della successione siano quelli calcolati dalle funzioni pred e succ
Si ha che
se, nel caso di successione crescente, l'espressione iniziale è > dell'espressione finale
for g:= dom yo lun do...
o se, nel caso di successione decrescente, l'espressione iniziale è < dell'espressione finale,
for c:= 'g' downto 'w' do...
non viene attivata alcuna iterazione!!!
La ricca strutturazione dei dati Pascal impone l'uso di identificatori strutturati molto lunghi per l'accesso alle singole componenti.
Lo statement with permette di utilizzare nomi abbreviati per i record.
In tutta la porzione di codice controllata dalla struttura, i records acceduti possono essere referenziati con la sola parte non specificata nella struttura with stessa.
Supponiamo di utilizzare i record della struttura libro già specificata in precedenza:
type
persona = record
nome: array of char;
cognome: array of char;
luogonascita: array of char;
datanascita:record
gg:1..31;
mm:1..12;
aa:0..2000
end
end;
var cliente: array of persona;
begin
with cliente .datanascita do
begin
gg := 13;
mm := 7;
aa := 1970
end;
end
L'istruzione goto permette di effettuare un salto da una parte di programma a un'altra, identificata con un'etichetta adeguata.
L'uso di tale istruzione in Pascal è sconsigliato perchè ad esempio comporta un certo insieme di istruzioni di deallocazione di memoria, per liberare lo stack delle variabili locali della procedura o delle procedure abbandonate, con conseguente consumo del tempo di esecuzione, con conseguenze di inefficienza.
In generale, poi istruzioni di salto in avanti possono essere rese con delle strutture di selezione e istruzioni di salto all'indietro possono essere rese con delle strutture di iterazione, senza così uscire dai principi della programmazione strutturata.
Tuttavia, a fronte di situazioni anomale può rivelarsi utile saltare porzioni significative di programma per effettuare operazioni di recupero, per segnalare errori catastrofici intrerrompendo la normale esecuzione del programma per evitare l'inserimento di condizioni indesiderate nel modello dell'algoritmo.
La sintassi è la seguente:
L'istruzione goto ha l'effetto di trasferire il controllo all'istruzione denotata dall'etichetta che segue la parola goto, la quale deve essere stata precedentemente dichiarata all'interno del modulo in cui compare.
Volendo fare un esempio:
program esempio
label 1;
...
begin
....
if tragicoerrore then
begin
<invia msg. errore>;
goto 1
end;
....
1: <invia msg. esecuzione terminata>
end
Una delle caratteristiche dei calcolatori è quella di poter "comunicare" con il mondo esterno:
· 939g62j per ricevere dati da elaborare,
· 939g62j trasmettere i risultati dell'elaborazione.
In Pascal, nel program header vengono specificati due identificatori standard: INPUT ed OUTPUT, che denotano i dispositivi di ingresso e di uscita standard: la tastiera ed il video.
Un generico programma Pascal che include comunicazione con il mondo esterno può così essere descritto:
program parla (input, output);
<blocco>
Il Pascal mette a disposizione dell'utente le procedure predefinite
· 939g62j read (par)che permette di assegnare alla variabile specificata come parametro il valore letto
dall'input standard
· 939g62j readln(par) che permette di assegnare alla variabile specificata come parametro il valore letto dall'input standard e si posiziona su una nuova linea, ignorando i caratteri che
seguono quello letto e precedono il carattere di fine linea .
I valori dei dati in ingresso sono semplici sequenze di caratteri grafici.
Il linguaggio esegue in lettura una CONVERSIONE AUTOMATICA di tali valori nella corrispondente rappresentazione interna del programma (vedi una determinata codifica binaria) in base al tipo della variabile da leggere. Tale variabile può essere di tipo: char, integer, real o subrange dei primi due.
Una forma notazionale abbreviata consente di esplicitare un numero qualsiasi di parametri nella procedure read o readln:
read (a,b,c);
readln (a,b,c);
dove lo spazio tra un dato in ingresso e l'altro è riconosciuto come separatore .
Anche il carattere di fine linea viene considerato come un separatore indipendentemente dalla sua rappresentazione adottata dal sistema.
Il Pascal fornisce una funzione predefinita booleana eoln che ritorna:
· 939g62j true quando si incontra il carattere di fine linea
· 939g62j false altrimenti.
La chiamata a procedura read(a) dopo avere incontrato il carattere di fine linea causa l'avanzamento alla prossima riga.
Già di per sé invece readln(a) epermette di saltare alla riga successiva senza considerare tutti i caratteri rimanenti della riga attuale.
Supponendo di avere queste tre linee successive di dati:
125 250 500
10 100 1000
13.57 23.2 3.14
esse sono correttamente lette dal seguente programma:
program leggi (input,output);
var i,j,k,l:integer;
r:real;
begin
read(i) (* i=125 *)
readln(j,k,l); (* j=250 k=500 l=10 *)
readln(r) (* r=13.57 *)
end.
Potrebbe essere utile leggere dati finchè ve ne sono di disponibili.
In questo caso il Pascal mette a disposizione la funzione booleana predefinita eof che ritorna:
· 939g62j true se la fine dei caratteri in ingresso è incontrata,
· 939g62j false altrimenti.
Se si proverà a leggere un dato una volta incontrata la fine dei dati si incorrerà in un errore.
Il problema è in questo caso inverso rispetto al precedente: i valori dati nella rappresentazione interna del programma (binaria) devono essere convertiti nelle equivalenti forme simboliche riconoscibili dall'utente.
Il Pascal mette a disposizione dell'utente le procedure predefinite
· 939g62j write (par)che permette di scrivere nel canale di output standard una sequenza di caratteri
corrispondente alla forma simbolica del dell'espressione specificata come
parametro
· 939g62j writeln(par)che permette di scrivere nel canale di output standard una sequenza di caratteri
corrispondente alla forma simbolica del valore dell'espressione specificata come parametro aggiungendo il carattere di fine linea (si posiziona nella linea seguente).
Il tipo dell'espressione può essere uno qualsiasi dei tipi predefiniti, compresi subrange di char, integer, costanti stringhe.
La forma simbolica in uscita è la stessa delle costanti riconosciute dal linguaggio per quei tipi, eccetto per le stringhe di caratteri dove i terminatori non sono riportati in scrittura.
Il linguaggio assume delle lunghezze standard dei campi in cui scrivere i valori, allineati alla destra. Per avere un miglior controllo sulla sequenza in uscita, l'utente può usare delle specifiche di formato. In particolare è possibile specificare il numero complessivo di caratteri in cui scrivere il dato. La forma sintattica:
write(e:n);
include una specifica di formato indicante la scrittura di n caratteri contenenti la rappresentazione simbolica dell'espressione e, allineato a destra.
N.B. Se n non è sufficiente l'indicazione di formato è tralasciata e i caratteri necessari sono
comunque scritti.
Per il caso reale è prevista una specifica di formato aggiuntiva:
write(e:n:m);
specifica la scrittura di n caratteri contenenti la rappresentazione simbolica della notazione senza esponente del valore dell'espressione reale e, allineato a destra, di cui m caratteri sono usati per la parte decimale
Anche in questo caso se m non è sufficiente l'indicazione di formato è tralasciata.
Esempio:
write(215.87:8:4) produce la sequenza in uscita 2.1587
Analogamente alla lettura una forma notazionale abbreviata consente di scrivere un numero qualsiasi di parametri nelle procedure write o writeln.
Esempio:
write (a,b,c); scrive i valori delle variabili a, b e c
writeln ('a','b','c'); scrive i caratteri a, b e c e va a capo
Un'ulteriore sovrastruttura presente di solito nel testo in uscita prodotto da un programma è data dalla procedura predefinita page che termina l'attuale pagina del testo in uscita e ne inizia una nuova.
La sequenza
write(i); scrive il valore di i
page; cambia pagina
writeln; salta una riga
write(j); scrive il valore di j
Esempio di programma per indovinare un numero da 0 a 999 generato pseudocasualmente in base ad un numero generatore:
program gioco (input,output);
var
numero,tentativo:0..999;
numTentativi,generatore:integer;
indovinato: boolean;
begin
write ('Insere il numero di generatore da 0 a 999: ');
readln(generatore),
numero := trunc(1000*random(generatore))
numTentativi:=0;
indovinato:= false;
repeat
write ('Insere un numero da da 0 a 999: ');
readln(tentativo),
numTentativi:=numTentativi + 1;
if tentativo > numero then
writeln('Numero troppo alto')
else
if tentativo < numero then
writeln('Numero troppo basso')
else
indovinato:= true;
until indovinato
writeln ('Hai indovinato in ',numTentativi:1, 'mosse');
end. ** ** *FINO QUI ** ** ********
La costruzione di programmi complessi si attua attraverso la tecnica dei raffinamenti successivi (o sviluppo top-down) che consiste nel
decomporre il problema da risolvere in sotto problemi più semplici
costruire un programma che risolva il problema generale supponendo che i singoli problemi siano già risolti
affrontare con la stessa tecnica i sottoproblemi sino ad arrivare a problemi direttamente risolvibili.
In Pascal questo procedimento si realizza grazie all'uso di risorse quali le procedure e le funzioni.
Esse devono, almeno in Pascal standard, fare parte integrante del programma principale (essere dichiarate e sviluppate all'interno di esso).
Esistono tuttavia molti compilatori che permettono di fare riferimento a librerie di sottoprogrammi esterne al programma contenente il suddetto riferimento.
Ogni sottoprogramma ha la stessa struttura modulare del programma principale: descrizione delle risorse e specifica dell'algoritmo.
Inoltre comunica con l'esterno attraverso:
· 939g62j normali operazioni di I/O,
· 939g62j accesso a variabili del programma "chiamante",
· 939g62j parametri.
In particolare per ogni chiamata a sottoprogramma viene effettuato un cambio di contesto, secondo il seguente modello.
Al programma principale viene associata una precisa area di memoria organizzata a stack (ovvero può essere visitata una variabile alla volta partendo dall'ultima inserita.
In fase di esecuzione come prima cosa vengono allocate in tale area tutte le variabili richieste dal programma principale.
Ogni volta che viene chiamato un sottoprogramma si ha l'allocazione nello stack delle sue variabili "locali" (ovvero che hanno senso e sono definite sono all'interno dello stesso) via via sopra quelle del modulo chiamante.
Una volta terminata l'esecuzione del sottoprogramma le sue variabili locali vengono deallocate, e così via da modulo chiamato a modulo chiamante sino a quando una volta terminato anche il programma principale la memoria ad esso associata viene definitivamente liberata.
Vediamo di specificare questo concetto con un breve esempio considerando un programma che calcola la media tra 100 numeri disposti su un array utilizzando apposite procedure di inserimento dei dati e di somma.
program media (input, output);
type
a=array of real;
var
tab: a;
n : 1..100;
m,s: real;
procedure leggitab;
var
x:real;
i:1.100;
begin
writeln('Inserisci 100 numeri reali');
for i := 1 to 100 do
begin
readln(x);
tab i := x
end
end;
procedure sommatab;
var
i:1..100;
begin
for i := 1 to 100 do
s:=s+tab i
end;
begin
write('Media in n valori di 100 numeri');
write('Inserisci n: ');
readln(n);
leggitab;
sommatab;
m:= s/n;
write('La media in n valori dei numeri
da te inseriti è: ',m)
end.
Nota
Se c'è un caso di 'omonimia' (due variabili con lo stesso nome),
si fa riferimento all'ultima variabile con quel nome inserita.
Situazione dello stack all'esecuzione
delle istruzioni:
begin
tab |
|
n |
m |
s |
|
leggitab;
tab |
|
n |
m |
s |
x |
i |
|
sommatab;
tab |
|
n |
m |
s |
i |
|
Una procedura è un sottoprogramma caratterizzato dal fatto che:
· 939g62j è dotata di nome
· 939g62j accetta informazioni in input (come parametri e non) nel senso che può anche modificare
· 939g62j fornisce risultati in output (come parametri e non) variabili globali senza specificarle come parametri
· 939g62j il richiamo di una procedura avviene con l'inserimento del suo nome come istruzione nel corpo del modulo chiamante
I dati in ingresso e i risultati in uscita non sono sempre necessari: si pensi ad esempio ad una procedura che stampa un messaggio fisso.
Facciamo un esempio con un programma per sommare i primi n naturali.
program somma(input;output);
(* somma i primi n numeri naturali*)
var n,s:integer;
procedure sum;
var i:integer;
begin
for i:=1 to n do
s:=s+i
end;
begin
readln(n);
s:=0;
sum;
writeln('somma = ',s)
end.
La procedura sum eredita dal programma principale la visibilità delle variabili n ed i, che considera rispettivamente come variabili di ingresso e di uscita.
L'invocazione della procedura è fatta esclusivamente tramite il suo nome, proprio come se questo rappresentasse per il programma di cui rappresenta una risorsa una nuova istruzione elementare, esattamente come le altre "procedure predefinite" (read, write, ecc...) che mantenendo la visione gerarchica, possono essere pensate come risorse definite ad un livello superiore a quello del programma principale.
Mantenendo gli stessi principi di visibilità delle variabili secondo una struttura gerarchica è possibile annidare le procedure una dentro l'altra.
Un sottoprogramma (procedura o funzione) ha la possibilità di definire parametricamente le variabili su cui operare utilizzando dei parametri.
Nella dichiarazione della procedura inseriremo una dichiarazione di tali variabili come LISTA di parametri formali, cioè di un insieme di variabili che verranno usate nel corpo della procedura stessa, ma che, durante l'esecuzione, dovranno essere riferite alle informazioni corrispondenti fornite all'atto della chiamata, cioè ai parametri attuali.
Nelle chiamate delle procedure read e write i valori passati come argomento sono proprio parametri attuali, mentre in altri casi è possibile definire parametri formali in una dichiarazione di procedura.
Sono possibili diverse modalità di passaggio di parametri.
Il Pascal ne mette a disposizione due:
passaggio per valore
Il passaggio di parametri per valore prevede che alla procedura venga passato il valore di un'espressione(eventualmente costituita da una sola costante o variabile), la quale viene ricopiata nell'area dello stack locale alla procedura, appositamente riservata mediante la dichiarazione del parametro formale corrispondente.
Esempio:
program cerca(input,output);
(* cerca un valore in una di due tabelle*)
const
n=30;
n1=31;
type
table:array 1..n of integer;
range:1..n;
var
k:range;
i:0..1;
tab1:table; (* tabella *)
key:integer; (* valore da cercare nella tabella *)
found:boolean; (* controllo *)
procedure look (x:table; v:integer; m:range);
begin
repeat
i:=i+1
until (i>m) or (x i =v); (* è finita la tabella *)
if x i =v then (* o si è trovato il valore*)
found:=true
end;
begin
readln(k); (*lettura range tabella*)
for i:=1 to k do (*lettura tabella*)
readln(tab1 i
readln(key); (*lettura valore da cercare nella tabella*)
found:=false;
i:=0;
look(tab1,key,k);
if found then
write('trovato')
else
write('non trovato)
end.
I parametri formali x,v ed m della procedura look sono sostituiti nella chiamata dagli attuali tab1,key e k.Il meccanismo di passaggio di parametro non è altro che una ricopiatura del valore del parametro attuale nella zona dello stack della procedura riservata al parametro formale corrispondente.
Ciò comporta
· 939g62j uno spreco di memoria, e di tempo necessario per il trasferimento,
· 939g62j l'impossibilità che qualsiasi cambiamento effettuato sul parametro passato per valore venga registrato all'esterno della procedura stessa essendo effettuato sulla copia locale della variabile.
Il passaggio dei parametri per valore consente anche parametri attuali definiti tramite espressioni aritmetiche, es. look(tab1,17,k-5). In questo caso il richiamo comporterà il calcolo del valore dell'espressione prima di effettuare il trasferimento del valore.
Nota I parametri attuali devono essere forniti con rispetto di:
· 939g62j numero,
· 939g62j ordine
· 939g62j tipo
rispetto ai parametri formali.
Dunque i parametri sono posizionali, ovvero al primo parametro formale viene fatto corrispondere il primo parametro attuale, e così via per i successivi.
Passaggio per indirizzo
Il meccanismo di passaggio di parametro per indirizzo fornisce al sottoprogramma chiamato esclusivamente l'indirizzo del parametro attuale, cosicchè questo possa essere direttamente raggiunto e operato in lettura e scrittura dalla procedura chiamata.
Considerando il programma precedente possiamo riscrivere la procedura look con una nuova intestazione, specificando un nuovo parametro passato per indirizzo (trovato):
procedure look(x:table;v:integer;m:range; var trovato:boolean);
begin
repeat
i:=i+1
until (i>m) or (x i =v);
if x i =v then
trovato:=true
end;
dove il parametro formale trovato è per indirizzo.
Nel programma principale nel richiamo della procedura verrà passata la variabile found come parametro attuale per trovato.
look(tab1,key,k,found);
Ciò comporta che
· 939g62j L'occupazione della memoria legata al parametro formale che viene allocata nello stack locale alla procedura è esclusivamente quella necessaria per contenere l'indirizzo del parametro attuale.
· 939g62j Cambiando il valore di trovato nella procedura automaticamente viene alterato il valore di found nello stack del programma principale.
parametri procedure/funzioni
In Pascal è possibile passare procedure e funzioni come parametri.
In questo caso il parametro formale corrisponde al modello di una procedura o funzione il cui nome è fittizio, ma di cui vengono specificati i comportamenti (parametri...) e il parametro attuale sarà il nome questa volta reale della procedura/funzione passata.
Una funzione è un sottoprogramma caratterizzato dal fatto che:
· 939g62j è dotata di nome a cui viene associato un valore di ritorno
· 939g62j è dotata di tipo, che viene dichiarato nella testata della funzione
· 939g62j accetta informazioni in input (come parametri e non)
· 939g62j fornisce risultati in output (come parametri e non, oltre al risultato relativo alla funzione stessa).
· 939g62j nel corpo della funzione il suo nome deve apparire come parte sinistra di un assegnamento, che permette di attribuire ad esso il valore che verrà poi trasmesso al programma chiamante.
· 939g62j il richiamo di una funzione avviene con l'inserimento del suo nome in un espressione nel corpo del modulo chiamante
Nota L'uso dei parametri nelle funzioni è assolutamente il medesimo che nelle procedure.
Per esempio consideriamo un programma che genera una successione di n numeri che sembrano distribuiti casualmente in modo uniforme nell'intervallo a,b
program Casuale(input,output);
var
r:real; (* variabile per i numeri da generare*)
i:integer; (* contatore*)
n:integer;
a,b:integer; (* estremi dell'intervallo *)
function rndm(x:intero):real;
(* dato un intero genera un valore reale *)
(* pseudocasuale compreso tra 0 e 1 escluso *)
begin
x:=(25173*(x-1)+13849)mod65536;
rndm:=x/65536
end;
begin
readln(n);
readln(a);
readln(b);
for i:=1 to n do
begin
r:=rndm(i)*(b-a)+a;
writeln(r)
end
end.
Dalle osservazioni fatte emerge che il nome di una funzione:
- a destra di un assegnamento ne rappresenta il valore,
- a sinistra invece ha il significato di locazione di memoria
del tutto compatibilmente con il concetto di variabile.
Tra le risorse che una procedura o funzione può vedere c'è anche sé stessa.
Ciò permette ad una procedura o ad una funzione di richiamarsi, meccanismo che si definisce
Esiste una ricca classe di problemi che si presta ad una formulazione ricorsiva.
Esempio la funzione fattoriale definita come
n!=1*2*...*(n-1) *n
oppure ricorsivamente come
n!=(n-1)!*n
Utilizzando quindi la forma ricorsiva possiamo scrivere
program fattoriale(input,output);
var n:integer;
function fact(x:integer):integer;
(* calcola il fattoriale di x *)
begin
if x=0 then
fact:=1
else
fact:=x*fact(x-1)
end;
begin
readln(n);
writeln(fact(n))
end.
Si noti che ora il nome della funzione compare a destra di un'assegnament ed è ugualmente corretto.
Osserviamo cosa accade con la ricorsione visualizzando graficamente cosa accade nello stack delle variabili.
Supponiamo che in fase di esecuzione venga letto il valore n=2.
n = 2 |
|
Alla prima chiamata sarà allocata una variabile locale, corrispondente al nome fact e verrà copiato il parametro in x.
n = 2 |
fact |
x = 2 |
|
|
siccome x è > 0 viene eseguita la clausola else della struttura if e quindi fact chiama ricorsivamente sé stessa, alla seconda chiamata lo stack sarà divenuto:
n = 2 |
fact |
x = 2 |
fact |
x = 1 |
siccome x èancora > 0 viene eseguita la clausola else della struttura if e quindi fact chiama di nuovo ricorsivamente sé stessa, alla terza chiamata lo stack sarà divenuto:
n = 2 |
fact |
x = 2 |
fact |
x = 1 |
fact 1 |
x = 0 |
|
Poichè ora x vale 0 l'attivazione attuale di fact, viene eseguita la clausola else della struttura if e quindi fact ritornerà con valore 1, da cui, eliminando lo spazio di memoria creato dall'ultima attivazione lo stack è:
n = 2 |
fact |
x = 2 |
fact 1*1 |
x = 1 |
|
Proseguendo, all'uscita della seconda chiamata ricorsiva avremo
n = 2 |
fact 2*1 |
x = 2 |
|
Infine, all'uscita della prima attivazione della funzione avremo di nuovo:
n = 2 |
|
La ricorsione presentata in quest'esempio è detta ricorsione diretta, in quanto il richiamo ricorsivo avviene della funzione (o della procedura) avviene "direttamente" nel corpo della stessa.
Esistono altre situazioni in cui si parla di ricorsione mutua, in cui la procedura A chiama la procedura B che a sua volta richiama la procedura A.
Come abbiamo visto il Pascal alloca durante l'esecuzione le proprie variabili in modo dinamico, avvalendosi di un'area di memoria opportunamente dedicata, detta stack.
Tali variabili, create e distrutte man mano che si eseguono procedure e funzioni, vengono però dichiarate in modo statico dal programmatore che infatti non può determinarne istanze al di fuori "dell'ambiente dichiarativo".
Tuttavia esistono le cosiddette variabili dinamiche che sono create e distrutte dinamicamnete durante l'esecuzione del programma completamente sotto il controllo del programmatore.
Per gestire variabili così strutturate uil Pascal mette a disposizione:
· 939g62j il tipo pointer
· 939g62j le procedure predefinite new e dispose.
Una variabile di tipo "puntatore a tipo" significa che tale variabile contiene l'indirizzo di una seconda variabile del tipo specificato.
Dunque la dichiarazione
type
ptr = ^integer;
var
x:ptr;
y, z:integer;
permette di attribuire:
· 939g62j ad x il valore del puntatore ossia dell'indirizzo della variabile puntata
· 939g62j a x^ il valore della variabile puntata.
Perciò dopo aver effettuato i seguenti assegnamenti
y:=3;
x:=^y;
z:=x^-4;
la situazione dello stack sarà:
x
y 7
z 3
In realtà la memoria non è proprio distribuita così.
In particolare memoria gestita dinamicamente dal programmatore detta heap risulta staccata dallo stack che contiene solo le variabili allocate durante la "lettura della fase dichiarativa".
Onde evitare la presenza di troppe aree di memoria differenti per la gestione delle variabili, STACK e HEAP, che potrebbe portare a situazioni spiacevoli quali:
· 939g62j problemi di sovrapposizione,
· 939g62j problemi di dimensionamento (vedi casi in cui lo heap è quasi vuoto e lo stack è pieno oviceversa).
il Pascal usa una stessa area di memoria per ambedue, facendo crescere le aree in maniera contrapposta:
STACK
HEAP
In questo modo lo stack cresce verso il basso e lo heap verso l'alto e chi necessita maggiormente di memoria occupa quella disponibile.
La memoria si esaurisce quando le due aree collidono, e ciò solitamente comporta un messaggio di errore durante l'esecuzione di tipo "stack overflow".
Il Pascal mette a disposizione una procedura predefinita:
new(x)
che applicata ad una variabile x di tipo pointer ha i seguenti effetti:
alloca nell' apposita zona di memoria heap, un'area adeguata a contenere una variabile del tipo puntato dall'argomento;
inserisce nella variabile puntatore l'indirizzo di tale area di memoria.
Simmetricamente alla procedura new il pascal mette a disposizione la procedura
dispose(x)
che applicata ad una variabile x di tipo pointer:
restituisce lo spazio allocato dall'argomentorendendolo nuovamente disponibile per altre allocazioni.
Nota In realtà molti compilatori considerano in ogni caso tale spazio inusabile, altri ricompattano periodicamente,... il tutto a causa del fatto che non sempre si riesce a riutilizzare correttamente la memoria lascata disponibile.
A livello intuitivo possiamo pensare che un'intero occupi meno spazio di un array di 10 elementi, il chè porta ad avere un resto che può a sua volta essere riutilizzato, ma quasi mai del tutto, con conseguenti "buchi inutrilizzati".
Infine il Pascal mette a disposizione la costante
nil (parola chiave)
Tale costante viene utilizzata per attribuire ad una variabile puntatore ads un qualsiasi tipo
un valore che indica che non sta puntando a nulla.
Def. LISTA
Sequenza di elementi di tipo definito in cui è possibile aggiungere e togliere elementi
struttura dati a dimensione variabile
in cui si accede al primo elemento della lista o a un sottinsieme ristretto di elementi
Un elemento può essere aggiunto o tolto specificandone la "posizione" all'interno della lista e raggiungendolo scandendo sequenzialmente la sequenza degli elementi.
Def. LUNGHEZZA DI UNA LISTA
il numero n degli elementi di L
La lista vuota si indica con L
REALIZZAZIONE DI UNA LISTA CON PUNTATORI
Si utilizzano delle celle t.c. l'i-esima cella contiene: l'i-esimo elemento della lista
e il puntatore (indirizzo) all'elemento successivo
nil
Lista vuota (lista di lunghezza 0)
L
a1 nil
Lista con un elemento (lista di lunghezza 1)
L
a1 a2 ai+1 ai nil an
Lista con n elementi
(lista di lunghezza n)
L ..... .....
N.B. L è il puntatore alla testa della lista
Utilizzando i puntatori del Pascal otteniamo la seguente dichiarazione di tipo:
type
cella = record
elem: tipoelem;
next: ^cella;
end
lista = ^cella;
posizione = ^cella;
.....
p p^.next p^.next^.next
p^.elem p^.next^.elem
Il concetto di file è legato all'idea di poter avere informazioni in grandi quantità su supporti esterni.
Non si può pertanto pensare di allocare una variabile di tale tipo sulla memoria centrale (nello stack).
In particolare la dichiarazione di una variabile di tipo file corrisponde all'allocazione in memoria di:
· 939g62j un'area, detta buffer, organizzzata come uno degli elementi del file,
· 939g62j una variabile puntatore, che punta a tale area, corrispondente al nome del file.
Quindi volendo definire:
type
libro = record
autore,titolo,editore:array[1..30]of char;
prezzo:integer
end;
biblioteca: file of libro;
var
x:biblioteca;
il modo per accedere ad un elemento della variabile x di tipo biblioteca è
x^
che mette a disposizione il valore corrente del buffer.
Vediamo ora come mettere in relazione il buffer con il file vero e proprio.
Il Pascal mette a disposizione le seguenti procedure:
· 939g62j reset(filename) che predispone il file alla lettura partendo dal primo elemento e
se il file è vuoto attribuisce il valore true alla funz. eof e x^ risulta indefinito
altrimenti inserisce nel buffer(x^) il primo elemento del file.
· 939g62j get(filename) che sposta il buffer sul prossimo elemento disponibile sul file;
se il file è terminato attribuisce il valore true alla funzione eof.
e la seguente funzione:
· 939g62j eof(filename) che ritorna true se durante le operazioni sul file è stata raggiunta la sua fine,
false altrimenti.
Essendo il file una variabile esterna al programma, deve essere ad esso passata tramite specificazione nella lista che segue il nome del programma stesso.
Consideriamo ad esempio un programma che suppone di avere a disposizione un file x di interi da leggere e totalizzare:
program somma (input,output,x);
var x:file of integer;
s: integer;
begin
reset(x);
s:=0;
while not eof(x) do
begin
s:=s+x^;
get(x)(*nuovo elemento inserito nel buffer
oppure eof*)
end;
writeln('somma:',s)
end
Altre procedure più sofisticate del get per operare su files in input, è la procedura read con i seguenti funzionamenti:
· 939g62j read(filename,a) che equivale a a:=filename^;
get(filename);
· 939g62j read(a) che equivale a read(input,a);
· 939g62j read(filename,a,b,c,..)che equivale a read(filename,a);
read(filename,b);
read(filename,c);
L'esempio precedente potrebbe quindi essere riscritto:
program somma (input,output,x);
var x:file of integer;
s,i: integer;
begin
reset(x);
s:=0;
while not eof(x) do
begin
read(x,i);
s:=s+i
end;
writeln('somma:',s)
end
Il Pascal mette a disposizione in modo simmetrico rispetto all'input, le procedure:
· 939g62j rewrite(filename) che predispone il file in scrittura,
distrugge eventuali dati presenti sul file,
e setta eof a true.
· 939g62j put(filename) che scrive fisicamente sul file il contenuto del buffer nella prima posizione
disponibile.
Consideriamo come esempio un programma per scrivere un certo insieme di interi su un file.
program writeint(input,output,x);
var x:file of integer;
i:integer;
begin
rewrite(x);
for I:=1 to 100 do
begin
x^:=i;
put(x)
end
end
Sempre simmetricamente a quanto avviene per il caso di file in input, le operazioni di output possono essere facilitate dalla procedura write con i seguenti funzionamenti:
· 939g62j write(filename,a) che equivale a filename^:=a;
put(filename)
· 939g62j write(a) che equivale a write(output,a)
· 939g62j write(filename,a,b,c,...) che equivale a write(filename,a);
write(filename,b);
write(filename,c);
I textfiles sono dei file di caratteri, dotati perciò di tipo predefinito
Perciò sono equivalenti le scritture:
var
x:text;
var
x:file of char;
Su tali tipi di file si possono utilizzare procedure o funzioni più ampio dei normali files e con un significato leggermente diverso.
Supponiamo di voler leggere un intero da tastiera:
la digitazione dei tasti comporta l'invio al calcolatore della corrispondentestringa di caratteri
tale stringa deve essere tradotta nel corrispondente valore intero in rappresentazione binaria.
Analogamente la stampa di un intero su video presuppone
la conversione del valore intero dalla rappresentazione binaria alla stringa di caratteri
quindi l'operazione di trasposizione su video vera e propria.
Tali operazioni vengono fatte automaticamentedalle procedure read e write nell'ambito dei textfiles.
Un'altra caratteristica interessante dei textfiles è la possibilità di considerare il carattere di fine-linea, che significa che i textfiles riconoscono la struttura a linee.
Nota:
Si può osservare che lo standard input e lo standard output sono a tutti gli effetti dei textfiles.
Vediamo meglio quali sono le primitive con cui è possibile operare sui textfiles.
· 939g62j read(filename,a,b,c,...) il comportamento è lo stesso che nei file normali,
a parte il fatto che qualora a, b, c siano variabili integer o real
le stringhe di caratteri in ingresso vengono automaticamente
convertite nella rappresentazione interna dei valori relativi.
· 939g62j readln(fillename,a,b,...) funziona come la precedente, a parte il fatto che alla fine dell'operazione di lettura si posiziona sulla riga successiva
ignorando eventuali altri caratteri sulla riga precedente.
· 939g62j write(filename,a,b,c,...) il comportamento è lo stesso che nei file normali, a parte il
fatto che qualora a, b, c siano variabili integer o real essi
vengono automaticamente convertiti nella forma di caratteri.
· 939g62j writeln(filename,a,b,...) funziona come la precedente, a parte il fatto che alla fine
dell'operazione di visualizzazione si posiziona sulla riga successiva.
· 939g62j eof(filename)funziona come per i file normali.
· 939g62j eoln(filename)è una funzione booleanache assume il valore true
se tutti i caratteri di una linea sono stati letti (nel caso di input)
o se tutti gli spazi di una linea sono stati scritti (nel caso di output)
Privacy |
Articolo informazione
Commentare questo articolo:Non sei registratoDevi essere registrato per commentare ISCRIVITI |
Copiare il codice nella pagina web del tuo sito. |
Copyright InfTub.com 2025