i d i d
u o r
u o r
i t o r
i t o r
g
g
l i e l i e
COD. U
35,00
e e
i d i d
u o r
u o r
i t o r
i t o r
g
g
l i e l i e
MANUALI
Angelo Chianese, Vincenzo Moscato, Antonio Picariello
Liguori Editore
Questa opera protetta dalla Legge sul diritto dautore
(Legge n. 633/1941: http://www.giustizia.it/cassazione/leggi/l633_41.html).
Tutti i diritti, in particolare quelli relativi alla traduzione, alla citazione, alla riproduzione
in qualsiasi forma, alluso delle illustrazioni, delle tabelle e del materiale software a corredo,
alla trasmissione radiofonica o televisiva, alla registrazione analogica o digitale,
alla pubblicazione e diffusione attraverso la rete Internet sono riservati, anche nel caso
di utilizzo parziale.
La riproduzione di questa opera, anche se parziale o in copia digitale, ammessa
solo ed esclusivamente nei limiti stabiliti dalla Legge ed soggetta allautorizzazione scritta
dellEditore.
La violazione delle norme comporta le sanzioni previste dalla legge.
Il regolamento per luso dei contenuti e dei servizi presenti sul sito della Casa Editrice Liguori
disponibile al seguente indirizzo: http://www.liguori.it/politiche_contatti/default.asp?c=legal
Lutilizzo in questa pubblicazione di denominazioni generiche, nomi commerciali e marchi
registrati, anche se non specificamente identificati, non implica che tali denominazioni
o marchi non siano protetti dalle relative leggi o regolamenti.
Chianese, Angelo :
Alla scoperta dei fondamenti dellinformatica. Un viaggio nel mondo dei BIT/
Angelo Chianese, Vincenzo Moscato, Antonio Picariello
Napoli : Liguori, 2008
ISBN-13 978 - 88 - 207 - 4305 - 5
Aggiornamenti:
16 15 14 13 12 11 10 09 08 10 9 8 7 6 5 4 3 2 1 0
Indice
PREFAZIONE
CAPITOLO PRIMO
CAPITOLO SECONDO
2. IL MODELLO DI ESECUTORE......................................45
VI
2.6. I microprocessori....................................................................................67
CAPITOLO TERZO
3. ALGORITMI E PROGRAMMI.........................................97
3.1. Informatica come studio di algoritmi ...................................................97
3.1.1. La soluzione dei problemi ...................................................................97
3.1.2. La calcolabilit degli algoritmi............................................................99
3.1.3. La trattabilit degli algoritmi.............................................................103
3.4. I metalinguaggi.....................................................................................111
CAPITOLO QUARTO
4.3. La modularit.......................................................................................135
4.3.1. La parametrizzazione del codice .......................................................137
4.3.2. Le funzioni ........................................................................................140
4.3.3. La visibilit........................................................................................142
4.3.4. L'allocazione dinamica ......................................................................146
4.3.5. La ricorsione......................................................................................149
4.3.6. Gli effetti collaterali ..........................................................................149
4.3.7. Il riuso dei sottoprogrammi ...............................................................151
4.3.8. L'information hiding..........................................................................151
CAPITOLO QUINTO
5. I DATI ...........................................................................153
5.1. Informazione e dato .............................................................................153
5.5. I puntatori.............................................................................................172
5.6. I file........................................................................................................174
5.6.1. I file sequenziali ................................................................................174
5.6.2. I file di caratteri o textfile..................................................................177
5.6.3. La connessione e sconnessione dei file .............................................179
VIII
CAPITOLO SESTO
6. IL LINGUAGGIO C.......................................................185
6.1. Introduzione .........................................................................................185
CAPITOLO SETTIMO
CAPITOLO OTTAVO
CAPITOLO NONO
CAPITOLO DECIMO
CAPITOLO UNDICESIMO
CAPITOLO DODICESIMO
CAPITOLO TREDICESIMO
APPENDICE A
APPENDICE B
Si noti che nel caso di nl > m non tutte le configurazioni possibili (parole
codice) vengono utilizzate per la rappresentazione, come lesempio nella tabella 2
mostra nei primi due casi.
byte: 16, 32, 64 o 128 bit. Poich i calcolatori trattano con molti bit/byte sono state
introdotte le unit di misura binarie riportate in tabella 3.
Con otto bit si rappresentano solo 28 (256) valori diversi. Nel caso in cui un
solo byte non fosse sufficiente per rappresentare i K valori dellinformazione,
allora si individua il numero b di byte tale che:
2 (b*8) > K
Operazione Condizione
200 +100 risultato rappresentabile
730 + 510 Overflow
-500-720 Underflow
2:3 risultato non rappresentabile
a b C a + (b - c) condizione (a + b) c Condizione
100 900 600 100 + ok (100 + 900) - 600 Overflow
(900 - 600)
a b C a (b - c) condizione abac Condizione
200 90 88 200 Ok 200 90 200 88 Overflow
(90 88)
Lalgebra dei numeri a precisione finita deve essere gestita applicando i noti
criteri di periodicit e tenendo in considerazione le condizioni di errore indicate.
Per la periodicit i valori esterni allintervallo di definizione vengono ricondotti ad
esso prendendo il resto della divisione dei valori per il periodo.
1 0 1 0 0 1 0 1 165
0 0 0 0 0 0 0 1 1
1 1 1 1 1 1 1 1 255
0 0 0 0 0 0 0 0 0
Peso Peso Peso Peso Peso Peso Peso Peso
7 6 5 4 3 2 1 0
Tabella 8 Numeri in binario
ci bi + ci - 1 bi - 1 + ci - 2 bi - 2 + + c2 b2 + c1 b1 + c0 b0 + c-1 b-1
+ c-2 b-2 +
Nel caso dei numeri interi scompaiono le potenze negative della base e la
formula diventa:
ci bi + ci - 1 bi - 1 + ci - 2 bi - 2 + + c2 b2 + c1 b1 + c0 b0
Poich basi diverse fanno uso delle stesse prime cifre, si rende necessario
distinguere le rappresentazioni dei numeri con un pedice indicante la base dopo
aver racchiuso la stringa tra parentesi.
basi gli zeri a sinistra possono essere omessi, cos come quelli a destra se il numero
dotato di virgola.
Nel passaggio da una base allaltra alcune propriet dei numeri si perdono: ad
esempio un risultato di una divisione pu essere periodico nella base dieci ma non
detto che lo sia in unaltra base, cos come la propriet di un numero di essere
divisibile per cinque ha senso solo se la base maggiore di cinque.
La introdotta interpretazione del numero secondo il sistema di numerazione
posizionale pesato consente di convertire nella base 10 il valore rappresentato in
una qualsiasi base b, calcolando la sommatoria dei prodotti delle cifre per i pesi:
l 1
Valore ci b i (1)
i 0
Ad esempio:
(142)5 = 1 52 + 4 51 + 2 50 = 25 +20 +2 =
(1001101)2 = 1 26 + 0 25 + 0 24 + 1 23 + 1 22 + 0 21 + 1 20 =
= 64 + 0 + 0 + 8 + 4 + 0 + 1 =
= (77)10
In informatica, per evitare di dover trattare con stringhe di bit troppo lunghe,
sono stati introdotti il sistema ottale ed esadecimale. La tabella seguente mostra la
corrispondenza tra le cifre usate in tali rappresentazioni e i bit che le rappresentano.
Le numerazioni ottale ed esadecimale sono interessanti perch la
trasformazione di rappresentazioni di valori tra esse si e la base 2 (e viceversa)
immediata. Infatti la trasformazione di un valore da binario in ottale molto
semplice dato che una cifra del sistema ottale rappresentabile esattamente con tre
bit del sistema binario il cui valore uguale proprio alla cifra rappresentata.
La conversione avviene raggruppando le cifre binarie in gruppi di tre a partire
dalla posizione di peso minore. La conversione opposta ugualmente semplice:
ogni cifra ottale viene esplosa esattamente nelle tre cifre binarie che la
rappresentano.
12 Capitolo primo
si ottiene:
si ottiene:
Esempio 1 Algoritmo per la conversione della parte intera di un numero in base b nel
corrispondente decimale
14 Capitolo primo
per spostare c-1 a sinistra della virgola che diventa parte intera.
ALGORITMO
1) moltiplicare la parte frazionaria
del numero d per la base b
2) scrivere la parte intera del
prodotto
3) se la nuova parte frazionaria del
prodotto diversa da zero o non
si ripete periodicamente, oppure
si non sono state determinate le
cifre binarie prefissate, usare
tale risultato al posto del
numero d di partenza e
continuare dal punto 1)
4) se la nuova parte frazionaria
verifica una delle tre condizioni
di terminazione, scrivere tutte le
cifre ottenute come parte intera
nellordine in cui sono state
calcolate
Di seguito sono riportati alcuni esempi di operazioni: si noti che nella somma
si deve tener conto del riporto (che si propaga a sinistra cos come nellaritmetica
decimale) mentre nella sottrazione, in presenza delloperazione 0 1, si deve
attivare il prestito dalle cifre pi a sinistra (come nellaritmetica decimale).
[-2l -1
+ 1, 2l -1
- 1]
[-2l -1, 2l -1 - 1]
sono sempre 2 l :
x = 2l |x|
c = b -1 - c
cl - 1 (-2l -1
) + cl - 2 (2l -2
) + + c1 21 + c0 20
x = x - 1
x X
Xi 1 Xi
con un errore massimo sull'ultima cifra di 0.5 (0.5 * 10-4) per le classiche
regole dell'arrotondamento. In generale se -m il peso della cifra meno
significativa, l'errore massimo che si commette :
1 m
10
2
Numero Valore
0,384 3,8400 10-1
1345 1,3450 103
64350 6,4350 104
333 3,3300 102
0,0048 4,8000 10-3
0,0000001 1,0000 10-8
x >9,9999 1099
e di underflow quando:
con il troncamento delle cifre 9099 del numero con esponente pi piccolo.
Le operazioni che richiedono maggiore attenzione sono l'addizione e la
sottrazione. Infatti, la causa principale degli errori di calcolo numerico risiede nella
sottrazione di numeri di valore quasi uguale; in tal caso le cifre pi significative si
eliminano fra loro e la differenza risultante perde un certo numero di cifre
significative o anche tutte (fenomeno detto cancellazione). Altra causa di errori la
divisione per valori molto piccoli, poich il risultato pu facilmente superare il
valore di overflow: deve essere pertanto evitata non solo la divisione per lo zero
ma anche per valori ad esso prossimi.
Il grande vantaggio della rappresentazione in virgola mobile che, se si
conviene che le mantisse siano trasformate in valori minori di 10 con operazioni
interne, un numero reale pu essere rappresentato nella memoria di un calcolatore
con un numero intero indicativo della parte decimale della mantissa e con un altro
numero intero per l'esponente.
Per esempio il numero 0,1230 10-9 viene rappresentato con la coppia di
numeri interi (1230,-9) e gestito con operazioni interne che ne costruiscono
l'effettivo valore.
Nel caso binario la rappresentazione in virgola mobile normalizzata assume la
forma:
Problema Soluzione
Si vuole convertire il Poich la rappresentazione binaria pura consente la
numero 16 in binario codifica di numeri interi postivi allora 16
utilizzando una codificabile attraverso questa rappresentazione.
rappresentazione binaria Utilizzando 8 bit lintervallo di rappresentazione
pura su 8 bit. linsieme dei numeri interi postivi compresi in [0,28-
1], ovvero in [0,255]. Poich 16 racchiuso in tale
intervallo possibile passare alla sua codifica.
Utilizzando lalgoritmo dellesempio (1) si ha:
16:2 -> q1=8, r1=0
8:2 -> q2=4, r2=0
4:2 -> q3=4, r3=0
2:2 -> q4=1, r4=0
1:2 -> q5=0, r5=1
Considerando la successione dei resti invertita
[r5,r4,,r1] e inserendo gli zeri a sinistra del bit pi
siginificativo, si ha che il numero in binario
corrispondente : 00010000.
Si vuole convertire il Poich la rappresentazione per complementi a 2
numero -12 in binario consente la codifica di numeri relativi allora -12
utilizzando una codificabile attraverso questa rappresentazione.
rappresentazione per Utilizzando 5 bit lintervallo di rappresentazione
complementi a 2 su 5 bit linsieme dei numeri interi compresi in [-25-1,25-1-1],
ovvero in [-16,15]. Poich -12 racchiuso in tale
intervallo possibile passare alla sua codifica.
Poich -12 negativo per ottenere il corrispettivo in
binario si codifica il suo modulo su 5 bit, si
complimentano le cifre ottenute e si aggiunge 1.
Utilizzando lalgoritmo dellesempio (1) si ha:
12:2 -> q1=6, r1=0
6:2 -> q2=3, r2=0
3:2 -> q3=1, r3=1
1:2 -> q4=0, r4=1
Considerando la successione dei resti invertita
[r4,,r1] e inserendo gli zeri a sinistra del bit pi
siginificativo, si ha che il numero in binario
corrispondente 01100.
Complementando le cifre ottenute si ha 10011, infine
aggiungendo 1 al numero complementato si ha:
10100
Si vuole convertire il Poich la rappresentazione per complementi a 2
numero -10 in binario consente la codifica di numeri relativi allora -10
utilizzando una codificabile attraverso questa rappresentazione..
rappresentazione per Utilizzando 4 bit lintervallo di rappresentazione
complementi a 2 su 4 bit. linsieme dei numeri interi compresi in [-24-1,24-1-1],
26 Capitolo primo
- il campionamento e
- la quantizzazione.
Il campionamento il processo che provvede a selezionare il sottoinsieme
discreto di informazioni da rappresentare in digitale da quello pi ampio offerto
dalla realt. il partizionamento di un flusso continuo di informazione in quantit
discrete, rispetto al tempo, allo spazio o ad entrambi. La scelta dei campioni viene
effettuata in modo che linformazione rappresentata sia utilizzabile. Ad esempio
non ha molta importanza nel caso delle comunicazioni telefoniche, selezionare un
numero di campioni molto elevato nellunit di tempo della voce, perch lorecchio
umano non coglierebbe affatto le differenze.
<font size=+1>HTML</font>
Il modello di esecutore
Nel caso di load, il tempo di accesso misura il tempo che trascorre tra la
selezione del registro di memoria e la disponibilit del suo contenuto nel registro di
buffer. Il tempo di accesso nel caso dello store misura invece il tempo necessario
alla selezione del registro e il deposito del contenuto del registro di buffer in esso.
Le memorie devono mostrare tempi di accesso adeguati alle capacit della CPU,
nel senso che non devono introdurre ritardi quando essa trasferisce dati. Per tale
motivo gli sforzi tecnologici sono rivolti alla costruzione di memorie con tempi di
accesso bassi anche se tale parametro contrasta con quello del costo degli stessi
componenti.
sistema viene spento; sono, di contro, permanenti gli altri tipi di memorie. Sono
volatili le memorie elettroniche; sono invece permanenti le memorie di tipo
magnetico, ottico e tutti i tipi di ROM.
Lo schema iniziale di Von Neumann stato nel tempo modificato per
affiancare alla memoria centrale delle unit di memoria ausiliarie caratterizzate da
una elevata capacit, dette per questo motivo memorie di massa.
Figura 7 I bus
Il control bus serve alla C.U. per indicare ai dispositivi cosa essi devono fare.
Tipici segnali del control bus sono quelli di read e write mediante i quali la CU
indica ai dispositivi se devono leggere un dato dal data bus (read) o scriverlo su di
esso (write).
od uno di output sono identificati da un indirizzo alla stessa stregua dei registri di
memoria.
Tutti i componenti del sistema (memoria, input, output, memoria di massa,
etc.) devono essere dotati della capacit di riconoscere sulladdress bus il proprio
indirizzo. In altri termini attraverso laddress bus la CU effettua la selezione del
dispositivo a cui sono rivolti i comandi e i dati.
I bus realizzano lo scambio di informazioni tra tutti i componenti
caratterizzato dalle seguenti regole:
- la CPU lunico elemento che fornisce lindirizzo alladdress bus;
- memorie e dispositivi di input ed output devono ascoltare laddress bus
per attivarsi quando su di esso compare il proprio indirizzo identificativo;
nel caso della memoria lattivazione avviene quando viene riconosciuto
lindirizzo corrispondente ad uno dei registri di cui essa composta;
- il dispositivo attivo deve interpretare i segnali del control bus per eseguire
i comandi della CU;
- le memorie prelevano dati dal data bus o immettono dati in esso in
funzione del comando impartito dalla CU;
- i dispositivi di input possono solo immettere dati sul data bus;
- viceversa i dispositivi di output possono solo prelevare dati dal data bus.
Un bus costituito da un solo filo chiamato bus seriale e su di esso i bit
transitano uno dietro laltro. Un bus costituito da n fili chiamato bus parallelo
perch su di esso transitano n bit alla volta. Tipici sono i bus a 8 e 32 fili sui quali
si possono trasferire rispettivamente 8 e 32 bit (4 Byte) alla volta.
Laddress e il data bus sono paralleli e le loro dimensioni caratterizzano i
sistemi di calcolo. Il numero di bit delladdress bus indica la capacit di
indirizzamento della CPU: ossia la sua capacit di gestire la dimensione della
memoria centrale e il numero di dispositivi di input ed output. Infatti un address
bus con n bit consente di selezionare un registro tra 2n. La dimensione del data bus
condiziona invece la velocit di scambio delle informazioni tra i diversi dispositivi
in quanto con m fili solo m bit possono viaggiare contemporaneamente.
2.2.4. Il clock
I componenti del modello di Von Neumann vengono coordinati dalla CU della
CPU secondo sequenze prestabilite che corrispondono alle sue diverse capacit. Ad
ogni operazioni che la CU in grado di svolgere corrispondono ben prefissate
sequenze di attivazione dei diversi dispositivi. Le attivit di tutti i dispositivi non si
svolgono per casualmente ma vengono sincronizzate tra loro mediante un
orologio interno chiamato clock che scandisce i ritmi di lavoro.
Il clock un segnale periodico di periodo fisso, unonda quadra caratterizzata
da un periodo T (detto ciclo) e da una frequenza f (f=1/T) misurata in Hertz (Hz).
Ad esempio un clock composto da 10 cicli al secondo ha la frequenza f = 10 Hz e il
periodo T= 100ms. La frequenza dei clock presenti nei moderni sistemi spazia dai
MHz (1 MHz corrisponde a un milione di battiti al secondo) ai GHz (1 GHz
corrisponde a un miliardo di battiti al secondo).
Il clock un segnale che raggiunge tutti i dispositivi per fornire la cadenza
temporale per lesecuzione delle operazioni elementari.
56 Capitolo secondo
Figura 9 Clock
La velocit di elaborazione di una CPU dipende dalla frequenza del suo clock
come il suono prodotto da un musicista dipende dal metronomo: pi accelerato il
battito del clock maggiore la velocit di esecuzione.
Alla frequenza del clock legato il numero di operazioni elementari che
vengono eseguite nellunit di tempo dalla CU. Ad esempio, se si assume che ad
ogni ciclo di clock corrisponde esattamente lesecuzione di una sola operazione,
allora la frequenza del clock indica il numero di operazioni che vengono eseguite
nellunit di tempo dalla CU. Per esempio con un clock a 3 GHz si ha che il
processore in grado di eseguire 3 miliardi di operazioni al secondo.
In realt tale ipotesi non sempre vera in quanto lesecuzione di una
operazione pu richiedere pi cicli di clock sia per la complessit delle operazioni
che per la lentezza dei dispositivi collegati alla CPU.
Per comprendere limportanza del clock si osservi il trasferimento di dati e
istruzioni tra CPU e Memoria Centrale riportato in una forma semplificata in figura
10.
Figura 15 Middleware
Il modello di esecutore 61
Figura 16 - Interruzioni
e veloce. Nella gerarchia i livelli pi prossimi alla CPU sono anche quelli pi
veloci. Ma sono anche quelli con dimensioni pi piccole visto il loro elevato costo.
Invece, quelli pi lontani sono quelli che mostrano una capacit massima ed anche
tempi di accesso maggiori. Partendo dalla CPU ogni livello fa da buffer al livello
successivo.
separazione netta tra le porzioni di memoria occupate dai dati da quelle occupate
dalle istruzioni. Un tale principio serve a garantire che in ogni istante la CPU stia
eseguendo effettivamente il programma progettato dal programmatore. Infatti
lallocazione in memoria di dati e programmi pu essere statica o dinamica. Nel
primo caso lallocazione avviene prima dellinizio dellesecuzione del programma;
nel secondo durante la sua esecuzione. Nel caso di allocazione dinamica di dati pu
avvenire che, per errori di programmazione, alcuni dati vadano ad allocarsi
nellarea riservata alle istruzioni cambiando il contenuto dei registri di memoria e
cambiando di fatto la struttura del programma.
Lallocazione in memoria comporta unassociazione precisa tra istruzioni e
dati e registri. In un modello di memoria a voce ad ogni istruzione o dato
corrisponde un ed un solo registro di memoria. Nelle memorie a byte istruzioni o
dati possono occupare pi registri di memoria. Per semplicit si proceder
pensando ad una memoria organizzata a voce.
Il riferimento ad una istruzione o ad un dato avviene specificando lindirizzo
di memoria occupato. Lindicazione di un indirizzo di memoria contenente un dato
si dir puntatore a dato, il puntatore a istruzione invece un indirizzo di un registro
di memoria nel quale collocata una istruzione.
Si pu allora definire una istruzione in linguaggio macchina come una
quadrupla: i = (Cop, Pdi, Pdo, Pis), in cui:
- Cop il codice operativo, ossia il codice che indica alla UC della CPU
loperazione da compiere; linsieme dei Cop prendere il nome di
repertorio di istruzioni e dipende dalla specifica CPU;
- Pdi sono i puntatori ai dati che servono per svolgere loperazione Cop detti
anche di input; si noti che esistono istruzioni che non hanno operandi di
input;
- Pdo sono i puntatori ai dati prodotti dalloperazione Cop detti anche di
output; si noti che esistono istruzioni che non hanno operandi di output;
- Pis il puntatore allistruzione da svolgere al termine dellesecuzione di
quella corrente.
Il puntatore Pis serve a comprendere la corrispondenza tra struttura di un
programma e schema di allocazione in memoria. Un programma una sequenza di
istruzioni da svolgere una dopo laltra. Ogni istruzione occupa un registro di
memoria. Il puntatore Pis serve a legare tra loro i registri di memoria contenenti
istruzioni in modo che sia chiara la sequenza del programma.
Il modello di esecutore 65
Perch il ciclo del processore possa avere inizio si deve predisporre in modo
che il registro PI contenga lindirizzo del registro di memoria contenente la prima
istruzione da eseguire. La fase iniziale di boot ha solo il compito di inizializzare il
PI con tale valore. Una volta avviato, il ciclo del processore non termina mai e
quindi ad ogni istruzione deve sempre seguirne unaltra da eseguire
successivamente. Questo spiega perch quando termina unapplicazione un
elaboratore torna ad eseguire i programmi del sistema operativo.
Dai programmi del sistema operativo si passa ad unaltra applicazione in
unalternanza che fa s che la CU possa procedere con il suo ciclo. Ma perch tutto
ci proceda nel rispetto del modello di Von Neumann, deve avvenire che in
memoria siano sempre presenti i programmi e i dati del sistema operativo mentre
quelli delle applicazioni vengono caricati in memoria dal sistema operativo su
richiesta dellutente prima che ne venga attivata la esecuzione. Nella memoria di
un elaboratore moderno si possono pertanto individuare in ogni istante cinque aree
distinte:
- linsieme dei registri nei quali si trovano i programmi del sistema
operativo;
- quelli occupati dai dati del sistema operativo;
- quelli nei quali si trovano le applicazioni di utente;
- quelli con i dati dei programmi di utente;
- ed infine linsieme dei registri che servono come buffer per il
trasferimento dati da e verso i dispositivi di input ed output.
Il modello di esecutore 67
2.6. I microprocessori
Come gi descritto, un processore centrale (CPU) conforme al modello di Von
Neumann si compone logicamente di:
- una unit di controllo (UC) capace di interpretare i comandi ad esso
rivolti (detti anche istruzioni), di svolgere le azioni richieste da tali
comandi e di interagire con lambiente esterno attraverso dispositivi
periferici capaci di trasformare i segnali in modo tale che siano
comprensibili dalla UC da una parte, e, dagli utenti del sistema dallaltra;
- una unit logico aritmetica (ALU) per l'esecuzione delle operazioni di
tipo aritmetico (solitamente somma) e logico (somma, prodotto e
negazione logici).
In uno stato del processore come quello riportato in figura 26, gli effetti delle
diverse tecniche di indirizzamento per quanto attiene alla esecuzione della
istruzione di caricamento dellaccumulatore sono mostrati in figura 27.
72 Capitolo secondo
Immediato Diretto
Indiretto Relativo
La label non sempre presente cos come il commento a fine frase che serve a
spiegare le ragioni per le quali viene introdotta listruzione nel programma. Il
campo Tecnica Indirizzamento indica la modalit di composizione dellindirizzo;
pi precisamente valgono le corrispondenze in tabella 3.
Notazione Tecnica indirizzamento
= Immediata
^ Diretta
(^) Indiretta
(X) Relativa
Tabella 3 Modalit di indirizzamento e notazioni
e ne opera il prelievo ad indirizzi consecutivi a quello espresso dal PI: in tale caso, alla
fine della fase fetch, il PI viene pertanto ad essere incrementato di un valore uguale
proprio alla lunghezza dellistruzione. Il repertorio di istruzioni di MP si compone di:
- istruzione di lettura e modifica dei registri interni e di memoria;
- istruzioni di tipo aritmetico;
- istruzioni di tipo logico;
- istruzioni di salto;
- istruzioni di input ed output per la gestione della interazione con il mondo
esterno.
Nel primo sottoinsieme rientrano tutte le istruzioni di gestione dei registri di
memoria con una delle tecniche di indirizzamento introdotte, dei registri interni
ACC, X, CC e SP. Le istruzioni di tipo aritmetico e logico depositano il loro
risultano nellaccumulatore modificando i bit del CC di segno, riporto, zero ed
overflow. Le istruzioni di salto alterano il normale flusso di controllo del
programma prescrivendo che la esecuzione non proceda con listruzione successiva
il cui indirizzo specificato in PI, ma con quella indicata dal valore delloperando
secondo modalit diverse.
Per ogni istruzione verr riportato il codice operativo (CodOP) in linguaggio
assemblativo, loperando se previsto, la operazione eseguita, una breve descrizione
e le eventuali modifiche apportate ai bit del CC (0 o 1 a seconda dei casi, o A ad
indicare la dipendenza dal valore assunto dallaccumulatore). Con la notazione
[registro] si indicher il contenuto del registro specificato (ad esempio [X] indica il
contenuto del registro indice), mentre con M([registro]) si indicher il registro di
memoria il cui indirizzo specificato in registro. La presenza nel campo operando
di OP indica che listruzione richiede che venga determinato un valore sulla base di
una delle tecniche di indirizzamento specificate:
- proprio OP per limmediato,
- M(OP) per il diretto,
- M([M(OP)]) per lindiretto
- M([X]) per il relativo.
Per semplificare si indicher con [OP] il valore determinato.
Le tre aree devono essere adeguatamente dimensionate affinch non capiti che durante
lesecuzione del programma una di esse sconfini nelle altre. Se ci accadesse si avrebbero
effetti non prevedibili e di difficile diagnosi. Ad esempio, se durante lesecuzione alcuni dati
venissero memorizzati nellarea riservata alle istruzioni, accadrebbe che la UC incontrerebbe
istruzioni diverse da quelle inserite dal programmatore. Altra causa di errore pu generarsi per
effetto di una espansione non controllata dellarea di stack a scapito dellarea dati: la
sovrapposizione cambia il valore dei registri di memoria riservate a contenere i valori delle
informazioni alterando quanto previsto dal programmatore. Se ad esempio in un registro di
memoria stato inserito un valore costante da cui dipende la correttezza dellalgoritmo, e tale
registro viene ad essere modificato per effetto della espansione dellarea stack, si ha come
naturale conseguenza la generazione di una condizione di errore.
La memoria viene pertanto suddivisa ponendo:
- larea istruzioni ad indirizzi bassi in quanto lallocazione delle istruzioni
del programma deve essere fatta ad indirizzi consecutivi crescenti;
- larea stack ad indirizzi alti in quanto le operazioni di inserimento
avvengono decrementando lo SP
- larea dati ad indirizzi intermedi tra le due precedenti aree.
Supponendo quindi di disporre di una memoria con una capacit di 1000
registri, tutti i programmi dovrebbero almeno prevedere come prima istruzione
quella di caricamento di SP con 999. Per comodit si assumer anche che la prima
istruzione sia collocata sempre nel registro di memoria di indirizzo uno. Con tale
ipotesi perch il processore MP inizi lesecuzione di un qualsiasi programma
baster forzare il PI ad azzerarsi in fase di boot.
A seconda delle necessit, il programmatore dovr disporre i dati in indirizzi
compresi tra 1 e 999 dopo aver dimensionato lo spazio necessario a contenere le
istruzioni del programma e larea stack.
Un primo e semplice programma per il nostro MP quello in tabella 15.
Allo scopo viene inserita la tabella di trace (vedi tabella 16) che comprende
tante colonne quanti sono i registri da osservare e tante righe quante sono le azioni
svolte. Ogni riga riporta la fotografia dello stato in cui viene a trovarsi il sistema:
linsieme degli stati nellordine temporale di esecuzione rappresenta il processo
svolto. Nella tabella di trace si aggiungono due colonne per elencare le interazioni
del sistema con il mondo circostante: infatti i processi svolti dipendono fortemente
da tale interazioni e sono casi particolari quelli nei quali ad un programma
corrisponde un unico processo di esecuzione qualsiasi siano i dati immessi. Le due
colonne aggiunte sono pertanto:
- il file di output (FO) nel quale si elenca tutto ci che viene presentato
sulloutput standard per effetto di operazioni d OUT;
- il file di input (FI) dove sono elencati i dati di prova che possono essere
inseriti dallinput standard per effetto di operazioni di IN.
Nel caso in cui si fissi che i programmi siano caricati a partire dallindirizzo 1,
larea dati inizi allindirizzo 500, larea stack parta da 999, si pu strutturare la
tabella di trace del programma precedente nel modo seguente riportando lo stato
del sistema al termine della fase execute dellUC.
Si noti che per comodit di lettura nelle celle vengono riportati solo i
cambiamenti sottintendendo che lultimo valore scritto quello che rimane nel
registro fin quando non interviene una successiva modifica.
Il trace mostra che il programma esegue la somma di due numeri. Non si
comprende per cosa accade dopo lultima istruzione di OUT soprattutto
considerando che il ciclo del processore non ha mai termine e dopo la fase execute
deve sempre occorrere una successiva fase di fetch. Per gestire tale situazione tutti
i programmi che verranno presentati successivamente prevederanno come ultima
listruzione un JMP sis_op che verr illustrata solo alla fine del paragrafo.
82 Capitolo secondo
102 RTS 99
9
10 STA 22
^dato2
Tabella 20 Tabella di trace
lultimo indirizzo inserito a cui tornare. I push conservano gli indirizzi di ritorno
nellordine in cui si sono determinati; i pop li prelevano nellordine inverso.
#START AT 200
#AREADATI AT 500
#DATO vettore SIZE 100
#DATO n SIZE 1
#DATO i SIZE 1
#DATO totale SIZE 1
#DATO max SIZE 1
#DATO min SIZE 1
#AREASTACK AT 999
#AREAPROG AT 200
JSR IN_VET
JSR SOMMA
LDA ^ totale
OUT
JSR MINIMO
LDA ^ min
OUT
JSR MASSIMO
LDA ^ max
OUT
#STOP
AREAPROG AT 300
LEGGI OUT
IN
STA (X)
RTS
CONTA LDA ^ n
INCA
STA ^ n
RTS
STA ^ tot
INCX
LDA ^ i
INCA
STA ^ i
CMPA ^ n
BRNZ CICLO_SOM
RTS
LDS = address_area_stack
JMP start_address
JMP sistema_operativo
al posto della direttiva #STOP.
92 Capitolo secondo
Lultima direttiva utile per comprendere due aspetti della architettura che
sono stati fino a questo momento trascurati:
- come inserire un programma in memoria prima che il processore MP
possa eseguirlo;
- cosa accade quanto tale programma termina coerentemente con lipotesi
fatta sul ciclo del processore che prevede che dopo una fase execute ci sia
sempre una fase fetch successiva.
Per rispondere alla prima domanda si potrebbe far riferimento ai primi sistemi
di calcolo che erano dotati di un lettore di nastro di carta perforato e di due pulsanti
di LOAD e di RUN. Il programmatore era costretto a scrivere il proprio
programma su di un nastro di carta con un sistema perforatore che faceva un buco
ogni volta che un bit era uguale ad uno. Scritto il nastro lo caricava con il pulsante
di LOAD nella memoria centrale del sistema di calcolo e solo al termine del
travaso poteva avviare la CPU con il comando di RUN. Per eseguire un altro
programma si doveva ripetere la procedura di caricamento e di avvio. La modifica
ad un programma costringeva il programmatore alla riscrittura dellintero nastro di
carta. La CPU restava a lungo inutilizzata in quanto iniziava lesecuzione solo
quando il pulsante di RUN veniva premuto dalloperatore di sistema.
E anche larchitettura del processore MP potrebbe funzionare nello stesso
modo. Si pu per costruire un programma che affidi proprio a MP lonere di
effettuare il caricamento in memoria delle istruzioni di un altro programma
prelevandole dallinput standard: a caricamento avvenuto la MP pu procedere alla
sua esecuzione per poi ritornare al caricamento di un successivo programma. Si
chiami LOADER il programma di caricamento e UTENTE il programma caricato. In
ogni istante il sistema si trova o ad eseguire il LOADER o lUTENTE.
Si noti che MEM ed END sono dichiarazioni che vengono introdotte per
guidare il LOADER e non corrispondono quindi a istruzioni che dovranno essere
eseguite da MP. comunque lassemblatore ad introdurle nel programma tradotto
in linguaggio macchina. Il codice del LOADER potrebbe essere allora il seguente.
7 LDA MEM
=MEM
8 STA ^82 MEM
9 LDA END
=END
10 STA ^83 END
13 OUT Prompt>
14 IN LDSP LDSP^999
^999
15 STA ^84 LDSP
^999
16 CMPA ^83 1000
17 BRZ 28
20 BRNZ 24
28 JMP 11
13 OUT Prompt>
14 IN LDSP JMP100
^999
15 STA ^84 LDSP
^999
16 CMPA ^83 1000
17 BRZ 28
20 BRNZ 24
28 JMP 11
13 OUT Prompt>
14 IN MEM MEM
17 BRZ 28
20 BRNZ 24
24 JMP 11
13 OUT Prompt>
14 IN IN IN
15 STA ^84 IN
17 BRZ 28
20 BRNZ 24
26 STA (X) IN
27 INCX 201
28 JMP 11
13 OUT Prompt>
14 IN END END
15 STA ^84 IN
17 BRZ 28
29 JMP 100
201 IN
206 JMP 10
13 OUT Prompt>
Nel trace sono state saltate alcune righe perch identiche alle precedenti se
non nei dati inseriti e nei valori di X, aventi valori via via crescenti. Cos non
96 Capitolo secondo
Algoritmi e programmi
Nel caso a), seppur ne sia noto il risultato, ossia la torta da gustare, non si
riesce a ricavare alcuna indicazione sulla ricetta da seguire che, tra laltro, non
facile individuare in un libro di cucina per la sua formulazione generica. Il caso b)
un noto e semplice problema di matematica per il quale si conosce chiaramente il
procedimento risolvente. Il caso c) un esempio di problema impreciso ed
ambiguo in quanto non specifica se va cercato il valore massimo o la sua posizione
allinterno della terna dei tre numeri assegnati. Il caso d) invece indicativo di un
problema con una soluzione nota che per non arriva a terminazione in quanto le
cifre da calcolare sono infinite. Per il caso e) si pu osservare che esistono sia
soluzioni tradizionali basate sulla posta ordinaria, che soluzioni pi moderne quali i
messaggi SMS dei telefoni cellulari o i messaggi di posta elettronica. Bisogna
quindi scegliere la soluzione pi conveniente: ad esempio quella che presenta un
costo pi basso. Il caso f) un chiaro esempio di problema che non ammette
soluzione: o, come si dice, non risolvibile.
Comunque tutti i problemi risolvibili presentano una caratteristica comune:
prescrivono la produzione di un risultato a partire da fissate condizioni iniziali
secondo lo schema di figura. Lo schema mette in luce gli elementi che concorrono
alla soluzione dei problemi. Essi sono:
- l'algoritmo, ossia un testo che prescrive un insieme di operazioni od azioni
eseguendo le quali possibile risolvere il problema assegnato. Se si indica
Algoritmi e programmi 99
dove:
- A linsieme finito dei simboli di ingresso e uscita;
- S linsieme finito degli stati (di cui uno quello di terminazione);
- fm la funzione di macchina definita come A S A;
- fs la funzione di stato A S S;
- fd la funzione di direzione A S D = {Sinistra, Destra,
Nessuna}
10 20 30 40 50
n 0,00001 sec 0,00002 sec 0,00003 sec 0,00004 sec 0,00005 sec
n2 0,0001 sec 0,0004 sec 0,0009 sec 0,0016 sec 0,0025 sec
n3 0,001 sec 0,008 sec 0,027 sec 0,064 sec 0,125 sec
n5 0,1 sec 3,2 sec 24,3 sec 1,7 min 5,2 min
2n 0,001 sec 1,0 sec 17,9 min 12,7 giorni 35,7 anni
3n 0,059 sec 58 min 6,5 anni 3,855 secoli 200.000.000 secoli
Calcolo
d b 2 4ac
Calcolo la prima radice come
b d
x1
2a
Calcolo la seconda radice come
b d
x2
2a
Calcolo
b2 4ac
Se 0
Allora Calcolo
d
Calcolo la prima radice come
b d
x1
2a
Calcolo la seconda radice come
b d
x2
2a
Altrimenti Calcola radici complesse
Esempio 2 Algoritmo per il calcolo delle radici di unequazione di 2 grado
In questo caso il processo evocato non pi fisso, ma dipende dai dati iniziali
da elaborare. Difatti sono possibili le due sequenze di esecuzione mostrate di
seguito.
Calcolo
b2 4ac
Verifico che 0 risultata vera
Calcolo
d
Calcolo la prima radice come
b d
x1
2a
Calcolo la seconda radice come
b d
x2
2a
Calcolo
b2 4ac
Verifico che 0 risultata non vera
Calcolo radici complesse
Esempio 3 Possibili sequenze di esecuzione per lalgoritmo per il calcolo delle radici di
unequazione di 2 grado
Algoritmi e programmi 109
Mischiate le carte
Prese le 4 carte dal mazzo e messe sul tavolo di gioco
Verificato che (le 4 carte hanno la stessa figura) risultata vera
Tolte le 4 carte dal tavolo di gioco
Esempio 5 Sequenza di esecuzione per lalgoritmo per il solitario del carcerato
Mischiate le carte
Prese le 4 carte dal mazzo e messe sul tavolo di gioco
Verificato che (le 4 carte hanno la stessa figura) risultata falsa
Mischiate le carte
Prese le 4 carte dal mazzo e messe sul tavolo di gioco
Verificato che (le 4 carte hanno la stessa figura) risultata vera
Tolte le 4 carte dal tavolo di gioco
Esempio 6 Sequenza di esecuzione per lalgoritmo per il solitario del carcerato
3.4. I metalinguaggi
I metalinguaggi sono particolari tipologie di linguaggi con i quali si presentano le
regole della grammatica di un qualsiasi linguaggio. Un metalinguaggio descrive
quindi le propriet sintattiche e semantiche relative a un altro linguaggio o al
112 Capitolo terzo
<Soggetto> := io | tu | mario
<Predicato verbale> := "mangia" | beve
<Complemento oggetto> := mela | lacqua
Una frase costruita seguendo le indicazioni delle espressioni del BNF sono
frasi corrette del linguaggio.
Una estensione della BNF, detta Extendend BNF (EBNF), stata introdotta da
Niklaus Wirth per specificare la sintassi del linguaggio di programmazione Pascal.
Nella EBNF i simboli {, }, (, ) e * possono comparire nel lato destro di una frase.
Una sequenza di simboli racchiusa fra {} indica che tale sequenza opzionale,
mentre un simbolo, o una sequenza racchiusa fra (), seguito da *, indica un
numero arbitrario di ripetizioni della sequenza, incluso 0.
Le carte sintattiche sono invece uno strumento di tipo grafico usate per la
descrizione delle regole del linguaggio. Una carta sintattica un grafo in cui linee
orientate uniscono delle scatole di formato differente: seguendo le frecce ed
interpretando opportunamente il contenuto delle scatole si costruiscono frasi
sintatticamente corrette. Le scatole rettangolari in figura sono dette simboli non
terminali in quanto il loro contenuto non compare direttamente nelle frasi. Esse
fanno riferimento ad altre carte sintattiche (quelle il cui nome coincide col
contenuto della scatola) che virtualmente devono essere inserite nella posizione
assunta dal nodo del grafo.
Le scatole tonde della figura sono dette simboli terminali n quanto contengono
caratteri o sequenze di caratteri che devono apparire direttamente nella frase, nella
posizione assunta dal nodo del grafo.
Algoritmi e programmi 113
Figura 6 Simboli Terminali (a) e Non Terminali (b) per le carte sintattiche
che supportano normalmente alcune fasi del processo di produzione del software. I
metodi consistono in un insieme di tecniche il cui uso guidato da indicazioni
formali. Le metodologie comprendono un insieme di tecniche il cui uso guidato
da regole procedurali ben precise. Le metodologie, a partire dalla formulazione del
problema, consentono:
- di individuare gli oggetti da elaborare e l'algoritmo;
- formulare oggetti e algoritmo in modo non ambiguo;
- progettare oggetti e algoritmi nell'ambiente disponibile.
L'obiettivo principale della programmazione strutturata consiste nella
costruzione di programmi con le seguenti caratteristiche:
- leggibilit: un programma non esaurisce la propria esistenza dopo poche
esecuzioni. Pertanto deve essere comprensibile ad altri programmatori;
- documentabilit: un programma deve contenere al suo interno il
significato e la motivazione di tutte le scelte di progetto effettuate;
- modificabilit: un programma deve essere strutturato in modo da
permettere un rapido adattamento ad una eventuale variazione di alcuni
parametri del progetto;
- provabilit: un programma deve essere costruito in modo da facilitare le
attivit di testing e debugging (controllo della correttezza e correzione
degli errori) fin dalle prime fasi del progetto software.
Comunque, si noti che la regola fondamentale per ottenere programmi di
buona qualit di eliminare tutti quei particolari che inficiano la costruzione logica
del programma e quindi la sua chiarezza. Le caratteristiche fondamentali della
programmazione strutturata sono:
- la modularit,
- l'uso di strutture di controllo ad un ingresso e ad una uscita,
- l'applicazione del metodo top-down o di quello bottom-up nella fase di
progettazione.
La modularizzazione ci dice che un programma deve essere composto di
moduli funzionali. Ogni modulo funzionale deve possedere un singolo e ben
precisato compito. Ogni modulo inoltre deve essere dotato di un solo ingresso e di
una sola uscita. La modularizzazione comporta una regolarit della struttura dei
programmi ed un valido supporto per la fase di progetto, in quanto rispecchia la
limitazione degli esseri umani ad esaminare un solo aspetto di un problema alla
volta.
Le strutture di controllo sono da considerarsi degli schemi di composizione
dei moduli costituenti il programma. Esse devono assolutamente avere un solo
ingresso ed una sola uscita. Tale condizione discende dalla necessit che un
modulo, composto dall'integrazione di altri moduli tramite le strutture di un
controllo, abbia un solo punto di ingresso ed un solo punto di uscita cos come i
singoli moduli componenti. Un valido contributo alla scelta delle strutture di
controllo stato apportato dal teorema di Bhm e Jacopini.
Il top-down e lo stepwise refinement costituiscono il modo procedurale di
raggiungimento della soluzione. Tale approccio parte dalla considerazione che la
complessit di un problema da risolvere non consente di tener conto
contemporaneamente di tutte le decisioni realizzative. Sar quindi necessario
procedere per raffinamenti successivi procedendo dal generale al particolare.
Ovvero: si analizza il problema al pi alto livello possibile di astrazione
116 Capitolo terzo
Senza entrare nel merito dei due metodi possiamo sicuramente affermare che
mentre il top-down un metodo deduttivo, quindi pi adatto alla media degli esseri
umani, il bottom-up un metodo induttivo che richiede molta intuizione ed
inventiva.
3.5.1. La progettazione dei programmi di piccole dimensioni
La progettazione dei programmi costituisce un'attivit complessa che richiede
un'attenta fase di analisi del problema e un continuo processo di valutazione delle
decisioni che in ogni momento vengono prese.
Una delle esigenze maggiormente sentita quella di una separazione netta, in
fase progettuale, tra il cosa (analisi dei requisiti e specifiche funzionali) e il
come (progetto a diversi livelli di dettaglio). In effetti la distinzione tra il cosa
e il come comune a qualsiasi tipo di progetto ed un modo per esprimere con
altre parole la separazione fra analisi e sintesi.
Nell'ambito di un progetto software, l'analisi richiede la capacit di acquisire
tutte le informazioni necessarie alla comprensione del problema e di strutturarle in
un modello che esprima, in un linguaggio adeguato, una rappresentazione coerente
e completa di cosa si deve fare.
Molto spesso, in passato, queste informazioni erano frutto di conoscenze
generiche dell'analista sui problemi da affrontare, proprio perch questa fase
veniva ritenuta di poco peso e si passava subito a scelte di realizzazione. Le
informazioni, viceversa, devono essere ricavate sia dai colloqui con gli utenti di
qualsiasi livello, ovvero con coloro che sono coinvolti nell'uso del programma sia a
livello direttivo che esecutivo, che da un esame dell'ambiente in cui il programma
sar utilizzato: queste attivit costituiscono la cosiddetta fase di definizione dei
requisiti.
Il compito dell'analista , a questo punto, quello di trarre, dall'insieme confuso
e a volte contraddittorio di esigenze ed obiettivi, un modello che esprima il
problema con un formalismo atto ad aumentarne la comprensibilit. Questo
modello dovrebbe contenere sia i requisiti funzionali (cosa deve fare il programma
118 Capitolo terzo
e su quali dati deve operare) sia i requisiti non funzionali (quali prestazioni il
programma deve offrire) e dovrebbe fornire un'indicazione delle priorit con le
quali i requisiti, funzionali o no, devono essere presi in considerazione.
Il passaggio dall'acquisizione dei requisiti alla formulazione del modello
rappresenta la cosiddetta fase di analisi di un problema.
Il progetto, invece, richiede la capacit di prendere decisioni che permettano
di realizzare un programma che soddisfi tutti i requisiti funzionali, nei limiti
stabiliti dai requisiti non funzionali. Ai fini della manutenibilit del prodotto,
necessario evitare sconfinamenti dalla fase di analisi a quella di progetto, ovvero
non si devono assumere decisioni atte a definire la soluzione di un problema
(progetto), mentre si sta ancora definendo il problema stesso (analisi); tali scelte,
infatti, sono difficilmente documentabili. In tal modo si opera una separazione, di
tipo verticale, fra le attivit di analisi e quelle di progettazione utile ad riduzione
delle difficolt. Ricapitolando, i motivi alla base di tale separazione sono:
- la possibilit di documentare in maniera completa i requisiti del problema
e quindi, indirettamente, le scelte della fase di progetto;
- l'impossibilit d'inficiare i risultati dell'analisi a causa di scelte anticipate
di progetto;
- la possibilit di rendere indipendente l'analisi dai vincoli di realizzazione.
Un'altra distinzione, in senso orizzontale, avviene fra dati e funzioni che
rappresentano gli aspetti duali, ma strettamente legati, di ogni problema che si
esamina; tale separazione dovuta sia a ragioni intrinseche, in quanto dati e
funzioni sono caratterizzati da propriet differenti, che a ragioni metodologiche in
quanto opportuno trattare separatamente funzioni da un lato e dati dall'altro, in
modo da ottimizzarne gli aspetti relativi in maniera indipendente dai
condizionamenti che, viceversa, un'analisi complessiva comporterebbe.
Ricapitolando, la separazione orizzontale tra dati e funzioni consente di ottenere:
- l'esame accurato dell'aspetto semantico dei dati;
- la completezza dell'analisi, in quanto essa viene compiuta da differenti
punti di vista;
- l'assicurazione che dati e funzioni assumano il loro giusto peso in analisi e
non si enfatizzino gli uni rispetto agli altri.
Le problematiche discusse riguardano sia i progetti software di grandi
dimensioni che la stesura di un semplice programma. Pertanto verr fornita una
descrizione di criteri applicabili alla realizzazione di programmi di piccole
dimensioni quali quelli che nel testo saranno presentati.
Linsieme di tali criteri non pu considerarsi come una metodologia di
progettazione ma un primo approccio introduttivo utile alla comprensione delle
problematiche di carattere generale. Nel seguito, i concetti introdotti verranno
applicati come approccio metodologico alla progettazione dei programmi.
In particolare le principali fasi dello sviluppo di un programma possono essere
ricondotte a:
- fase di analisi dei requisiti e delle funzioni
- fase di progetto
- analisi critica della soluzione
La fase di analisi dei requisiti e delle funzioni ha lo scopo di definire in
maniera non ambigua cosa il programma deve fare. la fase pi importante in
quanto solitamente i problemi si presentano in una forma alquanto imprecisa ed
Algoritmi e programmi 119
fondamentale che le imprecisioni siano eliminate quanto prima per evitare che esse
incidano sulle fasi successive del progetto. Infatti i costi di correzione degli errori
aumentano quanto pi si avanza nel progetto e sono massimi se erroneamente si
risolve un problema diverso da quello desiderato. allora necessario, per scoprire
le ambiguit, verificare che ogni aspetto delle specifiche abbia un'unica
interpretazione nel contesto delle applicazioni che si descrivono ed effettuare delle
esemplificazioni degli effetti delle trasformazioni tra dati di ingresso e dati di
uscita. Tali esemplificazioni costituiscono la base per un confronto del programma
con l'utente che permette di verificare la correttezza delle specifiche. Sempre ai fini
della correttezza occorrer:
- verificare che nelle specifiche non vi siano inconsistenze;
- verificare che le specifiche coprano tutte le possibili situazioni coinvolte
nel problema.
Al termine della fase di analisi si deve disporre della documentazione su:
- la definizione dei dati di ingresso al problema;
- la definizione dei dati in uscita dal problema;
- la descrizione di un metodo risolutivo che sulla carta risolva le specifiche
del problema.
La fase di progetto pu iniziare solo dopo un'accurata fase di definizione dei
requisiti e si articola nelle attivit di raffinamento successivo dei dati e
dell'algoritmo. Si gi detto che la scelta delle strutture dati con cui rappresentare
gli oggetti coinvolti dal problema gioca un ruolo fondamentale nella scelta
dell'algoritmo risolutivo. Inizialmente va scelta la struttura astratta che risponde in
maniera pi naturale alle caratteristiche del problema. Successivamente, tra tutte le
realizzazioni possibili occorrer scegliere quella che opera il compromesso ottimo
dal punto di vista della efficienza e della comprensibilit. Per affrontare la scelta in
modo adeguato fondamentale la conoscenza del numero e tipo di operazioni sui
dati previste dal problema. Costruire un algoritmo equivale ad esaminare una
specifica realt (il problema assegnato), costruirne un'astrazione, e infine
rappresentare una tale astrazione in maniera formale in un linguaggio di
programmazione. Se il problema complesso, le varie scelte di astrazione e di
rappresentazione si sovrappongono, cosicch non si riesce pi a controllare il
processo di progettazione dei programmi. In genere la mente umana non in grado
di tenere contemporaneamente sotto controllo gli effetti intrecciati di tutte le scelte
che occorre effettuare. La tecnica top-down si presenta come un approccio guida
con l'obiettivo preciso di ridurre la complessit e di offrire uno strumento di
composizione di un'architettura modulare dei programmi. Il modo di procedere di
tale approccio una diretta applicazione del metodo analitico deduttivo che si
rilevato, storicamente, il pi adatto alla media degli esseri umani. Una tale tecnica,
come gi visto. viene anche detta per raffinamenti successivi (step-wise
refinement), in quanto consente di trasformare un problema in una gerarchia di
problemi di difficolt decrescente associando a tale gerarchia di problemi una
gerarchia di linguaggi per la descrizione dell'algoritmo. Ogni passo di raffinamento
implica alcune decisioni progettuali ed importante che queste decisioni siano rese
esplicite e che il programmatore sia conscio del criterio decisionale adottato e
dell'esistenza di soluzioni alternative. Fra i criteri progettuali vanno menzionati la
chiarezza e la regolarit della struttura del programma.
120 Capitolo terzo
Si noti che la derivazione delle relazioni logiche tra le variabili obbliga ad una
comprensione profonda del significato del programma. Per questo motivo le
asserzioni possono essere utilizzate per effettuare una prova qualitativa della
correttezza del programma. Tra tutte le asserzioni particolare cura va posta su
quelle riguardanti le variabili di input in quanto con esse si specificano le
condizioni limite nelle quali la soluzione adottata si trover a lavorare.
Capitolo quarto
type t = T;
var v : T;
4.1.2. Le frasi di commento
Le frasi di commento sono frasi in linguaggio naturale prive di ogni valore
esecutivo o dichiarativo che consentono di migliorare la leggibilit e la chiarezza
del programma. Si distinguono in asserzioni e motivazioni.
Le asserzioni sono commenti destinati a fissare in un punto del programma lo
stato di una o pi variabile. Per tale motivo permettono di chiarire le condizioni
nelle quali vengono ad operare le istruzioni successive. Le asserzioni sono molto
importanti per controllare la correttezza dei programmi.
Le motivazioni sono invece commenti destinati a chiarire le ragioni e/o gli
obiettivi per i quali il blocco di programma, successivo al commento, viene scritto.
Le motivazioni sono essenziali perch un programma possa essere compreso da
altre persone.
In altri termini, un'asserzione si riferisce alle conseguenze delle elaborazioni
che la precedono, mentre la motivazione serve a dire a priori cosa si intende fare
con le istruzioni successive.
Per indicare le frasi di commento si usano delle sequenze di caratteri
(marcatori) che indicano linizio (ad esempio /* in C o (* in Pascal) e la fine
(rispettivamente */ e *) ) del commento che cos pu occupare pi righi. In alcuni
casi il commento occupa un solo rigo ed allora lo si indica con un solo marcatore
iniziale (ad esempio // del C). Le due modalit possono coesistere nello stesso
linguaggio.
I linguaggi non forniscono strumenti per specificare il tipo di commento: per
farlo il programmatore potr anteporre alla frase la notazione A: per le asserzioni
e M: per le motivazioni come gli esempi seguenti illustrano.
v espressione;
che indica che lesecutore deve prima risolvere lespressione presente nel
secondo membro e, solo quando ne ha prodotto il risultato, assegnare questultimo
alla variabile v posta a primo membro. Listruzione viene anche scritta in una delle
forme seguenti:
v := espressione;
v = espressione;
i = i + 1;
Infatti lesecutore prima opera sul secondo membro (somma uno al valore di i)
e poi deposita il risultato in i: per cui se in i si trova 5 prima dello svolgimento
dellistruzione, al suo termine si trover 6.
4.1.4. I costrutti di controllo
I costrutti di controllo indicano all'esecutore l'ordine in cui le operazioni devono
essere eseguite. Essi devono essere scelti in modo da rispecchiare quanto pi
possibile i meccanismi che naturalmente vengono usati quando si descrive (ad
esempio in italiano) una qualsiasi successione di operazioni. Essi si dividono in
costrutti di sequenza, selezione ed iterazione.
Il costrutto di controllo pi semplice quello che specifica che due o pi
operazioni (elementari o no) devono essere eseguite una dopo l'altra. Tali costrutti
sono detti costrutti di sequenza e vengono indicati nel seguente modo:
o anche con una notazione pi compatta che usa il carattere punto e virgola
come indicatore dell'istruzione successiva:
stacca il contatore;
sostituisci la presa;
riattacca il contatore
Il punto e virgola consente anche di scrivere pi azioni della sequenza su di
uno stesso rigo:
BEGIN
stacca il contatore; stacca il contatore;
sostituisci la presa; sostituisci la presa;
riattacca il contatore riattacca il contatore
END
RIPETI REPEAT
i compiti i compiti
FINCHE' (suona la sveglia) UNTIL (suona sveglia)
MENTRE (piove) WHILE (piove)
DEVI usare l'ombrello DO usa l'ombrello
PER (10 giorni) FOR giorni:=1 TO 10
DEVI non venire all'universit DO non venire all'universit
if (condizione)
then azione 1
else azione 2
128 Capitolo quarto
che indica che in caso di verit della condizione deve essere svolta la prima
azione, mentre in caso contrario (falsit della condizione) deve essere svolta la
seconda, funzionalmente equivalente alla sequenza dei due if-then:
if (condizione)
then azione 1
if (not condizione)
then azione 2
case (espressione) of
a: azione 1
b: azione 2
c: azione 3
end;
equivalente a:
if (espressione=a)
then azione 1
else
if (espressione=b)
then azione 2
else
if (espressione=c)
then azione 3
if (espressione 1)
then azione 1
else
if (espressione 2)
then azione 2
else
if (espressione 3)
then azione 3
La struttura dei programmi 129
Per quanto riguarda le due strutture iterative while e repeat si noti che
sempre possibile ricondurre l'una all'altra. Il while prescrive prima la valutazione
della condizione e dopo l'esecuzione delle azioni qualora il valore ottenuto sia
vero. Il ciclo termina quando la condizione diventa falsa. evidente che il ciclo
non avviene se la condizione falsa la prima volta che calcolata. Il repeat invece
prescrive l'esecuzione delle azioni e, dopo, la valutazione della condizione per cui
qualsiasi ne sia il valore, le azioni vengono eseguite almeno una volta. La
ripetizione delle azioni termina quando la condizione diventa vera. Si noti che, in
entrambi casi, quando il ciclo ha avuto inizio, si deve far in modo che le azioni
eseguite ad ogni ripetizione alterino le variabili presenti nella condizione se si
vuole che il ciclo termini. Da quanto detto discende che:
repeat Azione
azione while (not condizione)
until (condizione) do azione
100 azione 1;
azione 2M
if (condizione)
then goto 100;
azione 3
Per lunghi anni il goto stato largamente usato dai programmatori. Poi
Dijkstra, in un suo articolo del 1968, notando che la capacit dei programmatori
era inversamente proporzionale all'uso che facevano del goto, propose con molto
scalpore la sua abolizione dai linguaggi di programmazione. Egli fece notare come
l'uso indiscriminato del goto portasse a dei programmi con molti intrecci, senza un
filo logico, e quindi poco chiari. Difatti con i goto si possono creare blocchi che
hanno pi ingressi e pi uscite, possibile entrare nel mezzo di funzionalit ben
evidenziate nel testo, possibile fare tutto quanto inficia la costruzione logica di un
programma. Ad esempio, nel programma riportato di seguito, si nota come ci siano
tre punti di ingresso (istruzioni etichettate con 100, 200 e 300), due punti di uscita
(goto 400 e la fine della sequenza), e l'interruzione del for (goto 300) nonostante
tale costrutto debba essere usato quando il numero di volte sia fissato a priori.
Tutto ci non favorisce la chiarezza del testo, n una sua facile sostituzione o
modifica ed quindi da evitare. In genere l'uso di goto in avanti (verso istruzioni
132 Capitolo quarto
100 azione 1;
azione 2;
200 azione 3;
if (condizione 1)
then goto 200
else goto 400;
300 azione 4;
for i:=e1 to es
do begin
azione 5;
if (condizione 2)
then goto 300;
azione 6
end
In alcuni casi non si pu fare a meno di usare il goto: o perch non sono
presenti altri costrutti di controllo o perch, per ragioni dettate dal tipo di problemi
risulta necessario l'abbandono completo di porzioni di programma (per esempio per
segnalare subito errori catastrofici). In questi casi l'uso dell'istruzione di salto deve
essere valutato con molta attenzione e segnalato con appositi commenti. In
conclusione, l'uso dell'istruzione di salto non favorisce la costruzione logica
dell'algoritmo, in quanto non naturale esprimere i propri ragionamenti in termini
di interruzioni in avanti o verso cose ancora da specificare. Per tale motivo il goto
non verr usato. stato presentato in quanto in alcuni casi diventa indispensabile,
nel processo di raffinamento, per la specificazione di quei costrutti che possono
mancare nel linguaggio di programmazione usato per comunicare i programmi
all'esecutore macchina.
In tutti gli esempi che sono stati introdotti per illustrare le strutture di
controllo, stato usato il termine azione per sottolineare il fatto che in quel punto
della struttura pu comparire sia una istruzione semplice che una non elementare,
ossia composta a sua volta da un insieme di altre istruzioni e strutture di controllo.
In altre parole le strutture di controllo possono essere sia disposte in sequenza tra di
loro che inserite le une dentro le altre con una modalit detta di innestamento o
nidificazione. Se sono disposte l'una dopo l'altra, allora dopo aver espletato le
azioni indicate da una struttura di controllo, si procede prendendo in
considerazione quella ad essa successiva nella sequenza statica. Nel caso della seguente
sequenza statica:
I0;
case (espressione) of
1: if (condizione1)
then begin
I1 ;
I2
end
La struttura dei programmi 133
else I3;
5: for i:=1 to 3
do I4
8: I5
end
if (condizione2)
then I6
I f;
s1 s2 s3 s4 s5 s6 s7 s8 s9 s10
I0 I0 I0 I0 I0 I0 I0 I0 I0 I0
I1 I3 I4 I5 I6 I1 I3 I4 I5 If
I2 I6 I4 I6 If I2 If I4 If
I6 If I4 If If I4
If I6 If
If
s1 s2 s3 s4
if ca then I1 I1 I1 I2 I2
else I2; I3 I4 I3 I4
if cb then I3 If If If If
else I4;
If ;
134 Capitolo quarto
s1 s2 s3 s4 s5 s6 s7 s8
if ca then I1 I1 I1 I1 I1 I2 I2 I2 I2
else I2; I3 I3 I4 I4 I3 I3 I4 I4
if cb I5 I6 I5 I6 I5 I6 I5 I6
then I3 If If If If If If If If
else I4;
if cd
then I5
else I6;
If ;
Tabelle 2,3 Istruzioni selettive e sequenze di esecuzioni evocate
Due sequenze per un solo if, quattro per due e otto per tre. Si comprende
allora che una sequenza di n strutture selettive evoca 2n sequenze di esecuzione.
s1 s2 s3
if ca then I1 I2 I3
if cb then I1 If If If
else I2;
else I3;
If ;
s1 s2 s3 s4
if ca then I1 I2 I3 I4
if cb then If If If If
if cd then I1
else I2;
else I3;
else I4;
If ;
Due sequenze per un solo if, tre per due e quattro per tre. Si comprende che un
tale innesto di n strutture selettive evoca n+1 sequenze di esecuzione.
Infine, la tabella seguente mostra come, nel caso di strutture iterative, il
numero di sequenze di esecuzione vari in funzione dei valori assunti, durante
l'esecuzione, dalle variabili che governano le condizioni della iterazione.
s1 s2 s3
I0; (ei=1 es=3) (ei=8 es=0) (ei=50 es=50)
for i:=ei to es I0 I0 I0
do I1; I1 If I1
If ; I1 If
I1
If
Tabelle 5 Istruzioni iterative e sequenze di esecuzioni evocate
La struttura dei programmi 135
4.3. La modularit
L'applicazione della tecnica top-down permette di ottenere un programma
composto da moduli funzionali, ciascuno dei quali realizza un singolo e ben
preciso compito e avente un solo punto di ingresso e di uscita. Una tale
impostazione risulta un valido aiuto per l'attivit di programmazione, in quanto
rispetta la limitazione degli esseri umani che, solitamente, sono in grado di
esaminare un solo aspetto di un problema alla volta. Inoltre, il processo di
raffinamento iterativo produce una gerarchia di sottoproblemi di complessit
decrescente associando ad essa una gerarchia di linguaggi per la descrizione
dell'algoritmo. Difatti, ogni sottoproblema individuato viene espresso con una frase
del linguaggio naturale sintetica ed espressiva per ritardare decisioni di progetto
relative alla sua implementazione.
Un tale approccio per non consente ancora di affrontare in modo adeguato i
seguenti problemi:
- evitare riscritture inutili di sottoproblemi dello stesso tipo in pi punti del
programma;
- integrare soluzioni gi disponibili di alcuni sottoproblemi;
- non perdere la sinteticit delle frasi in linguaggio naturale introdotte per
descrivere i vari sottoproblemi;
- verificare la correttezza della soluzione del problema per passi: ossia
verificando dapprima la correttezza dei singoli sottoproblemi e
successivamente dell'insieme da essi costituito.
Per ovviare a tutti questi inconvenienti, i linguaggi di programmazione
prevedono l'uso dei sottoprogrammi: permettono cio di assegnare ad una sequenza
di istruzioni un nome che pu essere utilizzato come sua abbreviazione e che pu
essere inserito al suo posto nel programma. Inoltre se il nome rappresenta anche un
risultato, che pu essere inserito in una espressione o in una istruzione, il
sottoprogramma viene chiamato funzione: in tutti gli altri casi viene anche
chiamato procedura.
L'indicazione del nome di un sottoprogramma viene chiamata indicazione
della procedura (o della funzione) corrispondente; l'impiego di una procedura in
un programma viene detto chiamata della procedura (quello di una funzione,
viene detto chiamata della funzione). L'indicazione del sottoprogramma si
compone di due parti: il titolo e il corpo. Nel titolo del sottoprogramma vengono
indicati il suo identificatore (il nome) e altre informazioni; il corpo si compone
della sequenza di dichiarazioni e istruzioni denotata dal nome.
programma torta;
I1: impasta farina, burro, uova, sale e zucchero fino ad ottenere una pasta soffice;
I2: inforna la pasta per una decina di minuti;
I3: prepara_la_crema;
I4: distribuisci la crema sulla sfoglia;
I5: ricopri con frutta di stagione tagliata a dadini e gelatina
Si pu notare nella terza istruzione l'uso del riferimento ad un'altra ricetta che
ha permesso allo scrittore di non perdere tempo a ricopiarla. L'esecutore della torta
dovr:
- spostarsi sulla ricetta della crema quando trova il suo riferimento;
- riportarsi sulla ricetta della torta nel momento in cui ha terminato la
crema.
In altre parole la chiamata di procedura pu essere vista come una particolare
istruzione di salto alla prima istruzione della procedura. La terminazione della
procedura genera poi un ulteriore salto alla istruzione successiva a quella di
chiamata.
I1;I2;SP1;SP2;SP3;SP4;I4;I5;
stampa(messaggio);
leggi(x)
funzione somma(a,b:interi):intera
somma:=a+b
Y:=3*XA+LN(XB-1); K:=SIN(Y)+3*Z+LN(M-1)+X;
WRITELN(SQR(X)-9*X+SQR(X1)-10*X1);
Y:=SIN(X0-Y0/2)*9+5*(SIN(X0+1-(Y0+1)/2)*3);
se vengono definite le tre funzioni riportate di seguito:
Y:=F1(XA,XB); K:=SIN(Y)+F1(Z,M)+X;
WRITELN(F2(X)+F2(X1)-1);
Y:=F3(X0,Y0)*3+5*F3(X0+1,Y0+1);
A.1.1
A.1
A.1.2
A
A.2
program .......;
.....
begin
.....
end.
o in:
differiscono in funzione del modo in cui, nei vari linguaggi, possibile organizzare
il programma nella gerarchia di unit di programma illustrata precedentemente.
A tale proposito esistono dei linguaggi (es. Pascal, Basic) in cui i vari
sottoprogrammi (e funzioni) possono essere inseriti nel corpo del programma
principale. Si definiscono sottoprogrammi interni tale tipo di sottoprogrammi.
Viceversa, esistono altri linguaggi (ad esempio il FORTRAN) in cui ogni unit di
programma risulta staccata da tutte le altre unit di programma. Si definiscono
sottoprogrammi esterni tale tipo di sottoprogrammi.
Nei sottoprogrammi interni esistono dei meccanismi impliciti tali che, se un
identificatore definito in una unit di programma, visibile, allora, da tutte le
unit interne ad essa, salvo il caso in cui esso sia ridefinito localmente in una di
queste unit interne. Ad esempio nel programma:
Procedure A;
var x,y:integer;
......
procedure B;
var x:integer;
......
......
begin
......
......
end;
......
begin
......
B;
......
end;
program main
integer a,b,c,d
common a,b,c,d
......
end
subroutine sub1
integer x,y
common x,y
......
end
subroutine sub2
integer i
common i
......
end
subroutine sub3
integer k,f,v,z
common k,f,v,z
......
end
program P;
var I,A : integer;
procedure Y;
var B: integer;
begin
A:=A+1;
B:=A*A
writeln('B =',B:2)
end;
procedure Z;
var C: integer;
begin
A:=A+1;
C:=C+1;
writeln('C =',C:2)
end;
begin
A:=0;
for i:=1 to 2
do begin
Y;
Z;
end;
writeln('A =',A:2)
end.
OUTPUT:
B=1
C=2
B=9
C =10
A=4
Lo schema che segue uno schema di principio che mostra, passo dopo passo,
come ci avvenga mettendo in evidenza l'allocazione delle variabili subito dopo la
chiamata delle procedure e poco prima della loro terminazione. I passi sono:
1) prima dell'esecuzione del ciclo for del programma principale;
I A
0
2) prima chiamata di Y;
I A B
1 0
3) terminazione di Y;
I A B
1 1 1
148 Capitolo quarto
4) prima chiamata di Z;
I A C
1 1 1
5) terminazione di Z;
I A C
1 2 2
6) seconda chiamata di Y;
I A B
2 2 2
7) terminazione di Y;
I A B
2 3 9
8) seconda chiamata di Z;
I A C
2 3 9
9) terminazione di Z.
I A C
2 4 10
Se invece tutte le variabili, senza distinguere tra locali e non, fossero state
allocate staticamente all'atto dell'attivazione del programma lo schema risulterebbe
uguale a:
I A B C
B= 1
C =98
B= 9
C =99
A= 4
4.3.5. La ricorsione
La ricorsione una propriet dei sottoprogrammi e delle funzioni che consente
l'uso, all'interno della definizione di un'unit di programma, della chiamata ad essa
stessa. una propriet molto importante in quanto molto problemi possono essere
definiti in modo semplice solo usando la ricorsione. La definizione di un oggetto si
dice ricorsiva se contiene un riferimento all'oggetto stesso.
Tale principio usato con naturalezza nella vita comune. Nel linguaggio
matematico numerosi oggetti sono definiti ricorsivamente, per esempio il fattoriale
di un numero n dato dal numero n moltiplicato per il fattoriale di (n-1) con
eccezione del fattoriale di 0 che uguale a 1:
0! = 1
n! = n * (n-1)!
function fatt(n:integer):integer;
begin
if n=0 then fatt:=1
else fatt:=n*fatt(n-1)
end;
che effettua il calcolo della radice quadrata del valore assoluto di un numero
assegnato, fornisce due risultati: il valore assoluto del numero, tramite l'unico
parametro formale, e il valore della radice, attraverso il nome della funzione. In
casi del genere si pu facilmente ovviare all'effetto collaterale sostituendo il
meccanismo di sostituzione per riferimento con quello per valore. Alcune volte
per, si fa volutamente ricorso al meccanismo di sostituzione per riferimento sia
per motivi di efficienza (si evita la ricopiatura) che per non aumentare
l'occupazione di memoria (non si duplicano le informazioni). Si pensi ad esempio
ad un sottoprogramma per il calcolo del determinante di una matrice quadra: la
scelta della sostituzione per valore comporterebbe la copia della matrice nel
parametro formale con una perdita di tempo e di spazio proporzionale alle
dimensioni di tutta la matrice. La scelta della sostituzione per riferimento laddove
dovrebbe essere usata quella per valore, pu essere fatta solo quando, a progetto
terminato, ci si accorge che il sottoprogramma non opera sul parametro formale e,
quindi, non introduce degli effetti collaterali non desiderati.
Per la loro pericolosit, tali scelte vanno documentate in modo chiaro ed
evidente nel corpo del sottoprogramma soprattutto perch un programma non cessa
di essere utilizzato nello stesso stato in cui stato creato la prima volta. Difatti
durante la sua vita molto probabile che dovr subire diverse modifiche sia per
rimuovere eventuali errori di programmazione sia per adattarsi a cambiamenti delle
specifiche iniziali.
A questo riguardo si pensi ad un semplice calcolo della ritenuta IVA su una
fattura: fino a qualche anno fa l'aliquota da applicare era del 18 per cento mentre
oggi del 19 per cento. Intervenendo allora su di un sottoprogramma in cui non
sono state documentate le ipotesi di correttezza, si rischia di alterarne i rapporti con
l'esterno introducendo effetti collaterali che nella versione di partenza non
esistevano.
La struttura dei programmi 151
i tipi astratti. Si visto che non sempre il linguaggio mette a disposizione del
programmatore strumenti che permettono di modellare gli oggetti della realt, e ci
non deve rappresentare un impedimento durante la progettazione. Difatti, se una
informazione essenzialmente caratterizzata da struttura ed operazioni, si pu
sempre raffinare il tipo astratto usando una struttura che si presta ad una sua
realizzazione e costruendo intorno ad essa funzioni e sottoprogrammi che
arricchiscono il linguaggio con parole chiavi tali da dare la sensazione di disporre
proprio di un tale tipo. Per esempio un numero complesso pu essere raffinato
introducendo il seguente tipo:
e le seguenti operazioni:
function somma_complex(x,y:num_complex):num_complex;
function prodotto_complex(x,y:num_complex):num_complex;
if x.parte_reale then
z:=num.parte_reale*3
I dati
ad esempio, pu non avere interesse in alcuni casi trattare separatamente con i tipi
giorno, mese ed anno piuttosto che con un unico tipo data che li accorpa.
Ciascun tipo di dato caratterizzato poi dal numero di elementi di cui
composto. Tale numero detto cardinalit del tipo. In un tipo con cardinalit N, la
scelta di un elemento avviene tra tutti gli N; ne discende che la scelta pi
elementare che si possa effettuare quella fra due soli oggetti e pertanto il dato pi
elementare quello che appartiene ad un tipo con cardinalit 2. Un tale dato viene
detto binario se i valori di un tale insieme sono [0,1], ma anche vero e falso; e si
soliti usare il termine bit per indicare sia l'informazione binaria sia i valori 0 e 1 da
essa assunti.
Da un punto di vista teorico la cardinalit di un tipo pu essere finita o
infinita. Da un punto di vista pratico i processi elaborativi trattano solo tipi a
cardinalit finita. Difatti, per poter essere gestiti da un elaboratore, i dati,
opportunamente rappresentati, devono essere inseriti in contenitori (memoria
dell'elaboratore) con dimensioni proporzionali alla cardinalit dei tipi: chiaro che
a cardinalit infinita corrisponderebbero contenitori di dimensione infinita.
Il valore di un dato un elemento di un tipo. Tale definizione risulta per
generica se non viene collegata alla definizione di tipo. Si osservi che il concetto di
valore un concetto che comporta una certa astrazione e non deve essere confuso
con il problema della rappresentazione dell'informazione che pu essere diversa in
contesti differenti: basti pensare al modo di identificare un oggetto in lingue
diverse (padre e father) o alle differenti rappresentazioni dei valori dei numeri
(1/10 o 0.1).
L'attributo conferisce il significato al valore specificato dal dato. Il tipo e il
valore da soli non sono sufficienti alla caratterizzazione di una informazione. Ad
esempio il valore 1000 del tipo numeri interi non ci dice nulla di significativo:
potrebbe benissimo essere il prezzo di un biglietto del tram o anche il numero di
matricola di uno studente. Il concetto di attributo analogo a quello matematico di
variabile e, come questo, si pu rappresentare mediante simboli letterali o nomi da
associare alle informazioni per specificarne il significato. Si pu cos chiamare rad
la radice di una equazione e totale_fattura l'ammontare di una fattura.
L'individuazione dei tipi di dato con cui trattare strettamente legata alla
natura delle elaborazioni da effettuare e pu variare dunque da elaborazione ad
elaborazione. La definizione di tipo consente di introdurre dei modelli degli oggetti
della realt in quanto ne fornisce le caratteristiche strutturali (cosa sono) e
funzionali (a cosa servono). Una definizione di tipo di dato sufficientemente
rigorosa e particolarmente adatta agli usi di tipo che verranno fatti in seguito la
seguente: per tipo di dato si intende un insieme di valori associati a delle
operazioni definite su di essi. Con le definizioni di tipo possibile trattare
l'informazione in maniera astratta, cio prescindendo dal modo concreto con cui
essa rappresentata all'interno di una macchina.
Un tipo si definisce alla stessa stregua degli insiemi, ossia per:
1) enumerazione degli elementi
{Lunedi, Martedi, Mercoledi, Giovedi, Venerdi, Sabato, Domenica}
2) elencando le propriet di cui godono i suoi elementi
{C: centravanti di una squadra nazionale di calcio di serie A e B}
Elementi fondamentali di una elaborazione non sono soltanto i tipi su cui essa
agisce, ma anche le operazioni che si possono effettuare sui rispettivi valori di un
I dati 155
type t = T;
var v : T;
- il tipo file
- la pila o stack
- la coda
- la tabella
In tutti i linguaggi di programmazione esistono i costruttori per definire array,
record e file.
non si fa la lezione se
il docente malato
O
gli studenti non vengono
l'esame si supera se
si fa bene l'esercizio
E
si risponde bene all'orale
Operazione Operatore
OR ||
AND &&
NOT !
Tabella 3 - Operatori logici
158 Capitolo quinto
P1 P2 P1 AND P2 P1 OR P2 NOT P1
FALSE FALSE FALSE FALSE TRUE
FALSE TRUE FALSE TRUE
TRUE FALSE FALSE TRUE FALSE
TRUE TRUE TRUE TRUE
P1 P2 P1 AND P2 P1 OR P2 NOT P1
0 0 0 0 1
0 1 0 1
1 0 0 1 0
1 1 1 1
commutativa:: X OR Y = Y OR X
(ossia l'insieme dei numeri interi esterni al tipo intero), si ha che le operazioni
elementari della aritmetica applicati a valori del TIPO INTERO valgono se e solo
se (a op b) non assume valori nell'insieme di overflow.
Ossia se e solo se:
| a op b | MAXINT
dove op = {+,-,*},
In pratica la condizione di overflow pu tradursi in vario modo all'interno di
una macchina: per esempio pu generare una condizione di errore con interruzione
del programma.
5.3.4. Il tipo reale
Il tipo reale ( o real) diverso dall'insieme dei numeri reali poich in un
intervallo reale comunque piccolo esistono infiniti valori (i numeri reali formano
un continuo). Allora il tipo reale si definisce come l'insieme che contiene un
numero finito di valori reali ognuno dei quali rappresenta un intervallo del
continuo. In altri termini, diviso l'insieme dei numeri reali in intervalli di fissata
dimensione, si ha, come la figura mostra, che ogni x appartenente all'intervallo
[Xi,Xi+1[ viene sostituito con Xi.
x X
X:
X
dove rappresenta l'errore relativo che si commette sostituendo x con X. dipende
dalla rappresentazione finita (numero finito di cifre) utilizzata per i numeri reali.
Ad esempio disponendo di una calcolatrice con una aritmetica a quattro cifre
decimali che applica le note regole di arrotondamento sull'ultima cifra, si hanno gli
errori di rappresentazione in tabella 8.
lun<mar<mer<gio<ven<sab<dom
maschio<femmina
Il tipo cos generato conterr tutti gli elementi compresi tra i due estremi
(estremi inclusi) ed erediter le operazioni del tipo genitore (anche detto base), cio
quello di cui si scelto l'intervallo.
L'introduzione del tipo subrange importante perch aumenta la chiarezza
del modo in cui analizzata la realt del problema. Difatti in taluni casi un tipo
contiene un numero eccessivo di valori per rappresentare alcune informazioni. Ad
esempio, se risulta naturale rappresentare l'informazione anno come un intero (i
numeri negativi potrebbero servire per gli anni prima di Cristo), eccessivo
attribuire all'informazione giorno tutti i valori dello stesso tipo. Allora definire:
type giorno = 1..31
consente di introdurre dei vincoli sulle informazioni rappresentanti i giorni, vincoli
che si concretizzano in una serie di controlli introdotti implicitamente dal
traduttore del linguaggio che vietano l'assunzione errata di valori esterni
all'intervallo di definizione.
164 Capitolo quinto
A {1,2,3} e B {4,5}
si ha che il prodotto cartesiano di AxB :
{(1,4),(1,5),(2,4),(2,5),(3,4),(3,5)}
s1 = abc s2 = efg
sono equivalenti.
La selezione di un componente si effettua facendo seguire al nome dell'array il
valore dell'indice racchiuso tra parentesi quadre: con vettore[1] si accede al primo
elemento dellarray se lindice parte da uno. anche possibile selezionare un
elemento dell'array mediante espressioni o variabili il cui valore sia un valore
appartenente al tipo indice. In questo caso la posizione viene calcolata
dinamicamente durante l'elaborazione.
Ad esempio vettore[I-1] comporta la determinazione del valore della variabile
I e il suo decremento di una unit per individuare la posizione nell'array. Allora se I
ha il valore 4 vettore[I-1] coincide con vettore[3], mentre vettore[I] coincide con
vettore[4]. Per questo motivo la posizione in genere un parametro d'uso nel senso
che dipende dalla elaborazione in corso: in altre parole la selezione dell'elemento
dell'array fatta dopo aver valutato il valore della variabile o dell'espressione.
Come gi detto, le operazioni sull'array sono tutte quelle definite sul tipo delle
informazioni componenti. Qualche volta possibile l'assegnazione del valore di
tutti i componenti di un vettore B a quelli di un altro vettore A dello stesso tipo. In
questi casi A:=B corrisponde a:
A[1]:=B[1]
A[2]:=B[2]
: ::
: ::
A[n]:=B[n]
A[1]:=B[1]+C[1]
A[2]:=B[2]+C[2]
: :: ::
A[n]:=B[n]+C[n]
ma sono casi molto rari.
In una elaborazione non detto che tutti gli elementi di un array vengono
necessariamente utilizzati. Difatti avviene spesso che solo una parte del numero
totale di elementi utile alla elaborazione in corso. Per tali motivi si soliti
168 Capitolo quinto
accompagnare la struttura array con una ulteriore informazione di tipo intero che
conta quante sono le posizioni occupate durante lelaborazione. A tale
informazione viene dato il nome di grado di riempimento dellarray o
semplicemente riempimento.
Il riempimento rappresenta il numero di componenti effettivamente usato
nella elaborazione in corso e non va confuso con il numero di componenti
dell'array (cardinalit dell'array). Solitamente il riempimento minore o al
massimo uguale alla cardinalit. Allinizio del programma il riempimento deve
assumere il valore zero per indicare che nellarray non sono stati inseriti valori.
Diverse possono essere le strategie per disporre valori diversi nelle diverse
posizioni dellarray. Il modo pi semplice di inserirli in posizioni successive a
partire dalla prima cosicch ad ogni inserimento il riempimento non solo indica
quanti elementi sono stati allocati ma anche quale lultima posizione occupata.
Nella tabella 9 che segue si presentano diversi algoritmi che inseriscono nellarray
vet gli elementi prodotti da una funzione generica produci_valore; in tutti i casi il
riempimento indicato dalla variabile n mentre la cardinalit dalla costante cmax.
Negli esempi si distinguono i casi in cui la prima posizione dellarray zero da
quella in cui uno. Nei primi due esempi il numero di elementi prodotti
predeterminato.
In tutti i casi n indica il riempimento del vettore: nel caso di uso della
posizione zero n indica la posizione successiva a quella occupata per ultima; negli
altri casi indica proprio lultima posizione occupata. Si noti che la scelta della
cardinalit di un array (cmax negli esempi) di estrema importanza in quanto deve
essere fatta pensando che lo stesso programma pu essere eseguito in condizioni
diverse: cmax deve comprenderle tutte in quanto un suo sottodimensionamento
porta il programma in una condizione di errore gestita in modi diversi dal
compilatore del linguaggio.
Negli esempi luscita dal ciclo per n<cmax non pi verificata, comporta che
alcuni dati prodotti dallambiente non vengano presi in considerazione. In
definitiva non si deve sovradimensionare l'array introducendo componenti mai
utilizzati occupando inutilmente la memoria centrale, e non si deve neppure
introdurre un array troppo piccolo per le diverse richieste dell'elaborazione.
I dati 169
per che l'ordinamento tra le stringhe diverso da quello del dizionario per il quale
non si fa differenza tra lettere maiuscole e minuscole e sono assenti i caratteri
speciali e le cifre.
Qualche volta definita una funzione di accesso che consente di estrarre da
una stringa assegnata la sottosequenza che inizia in una posizione specificata ed
costituita da un numero fissato di caratteri, oppure termina in una data posizione.
Un'operazione interna al tipo stringa di caratteri (come gi visto applicabile a
tutte le sequenze) la concatenazione, che applicata a due sequenze, la prima di
lunghezza M, la seconda di lunghezza N, genera una sequenza di lunghezza M+N
avente come primi M simboli quelli della prima sequenza e come successivi N
simboli quelli della seconda sequenza.
Di solito si indica con &,//,+, ed ad esempio, se s1='CA' e s2='SA' allora si
ha che s1 + s2 'CASA'. Si ricordi che per definire una stringa sono necessari:
- l'attributo della stringa;
- il numero massimo di caratteri che la stringa pu contenere (solitamente
0 < K < MAX, con MAX che dipende dal linguaggio di programmazione).
Ad esempio:
type PAROLE = string[10]
type TELEFONO = string[12]
type ANAGRAFICA =
record of
NOME: string[40];
INDIRIZZO: string[30];
ETA: 1..100;
ESAMI_SOSTENUTI: integer
end
172 Capitolo quinto
allora per selezionare il campo NOME si deve scrivere A.NOME, mentre per ETA,
A.ETA e cos via.
Le operazioni sul record sono quelle definite sul tipo delle informazioni
componenti. Ad esempio:
A.NOME *4
A.ESAMI_SOSTENUTI +30
ovviamente possibile.
In alcuni linguaggi possibile l'assegnazione ad un record del valore di un
altro record dello stesso tipo. In questo caso il valore di tutti i campi del record a
secondo membro vengono assegnati ai campi con medesimo nome del record a
primo membro. Ad esempio, se dat1 e daT2 sono due record del tipo DATA, allora
l'assegnazione dat1:=dat2 corrisponde all'insieme delle assegnazioni:
dat1.giorno:=dat2.giorno
dat1.mese:=dat2.mese
dat1.anno:=dat2.anno
5.5. I puntatori
Una variabile detta puntatore se il suo valore individua la posizione di
un'altra informazione. Per posizione di una informazione si intende la posizione da
essa assunta o in memoria o all'interno di una struttura dati. In tal senso sono
puntatori i riferimenti ad un elemento di un array, di un carattere all'interno di una
stringa, e cos via.
Nei linguaggi attuali, nei quali possibile la creazione e la distruzione di
variabili dinamicamente, durante l'esecuzione del programma sotto il diretto
controllo del programmatore, i puntatori sono lunico strumento per poter operare
su tali variabili. Con comandi specifici (ad esempio new) si istanziano variabili
quando servono nel corso della elaborazione; con altri comandi (ad esempio
dispose) si recupera lo spazio di memoria occorso per lallocazione della variabile.
La istanziazione della variabile restituisce lindirizzo di memoria attraverso cui si
accede ad essa per tutte le operazioni consentite compatibili con il tipo di
appartenenza.
Per poter far uso di un tale meccanismo di allocazione dinamico, le variabili
devono essere opportunamente dichiarate per consentire non solo la gestione
dellindirizzo di memoria attraverso cui operare sulla variabile, ma anche la sua
effettiva occupazione in memoria. La definizione di una variabile puntatore si
effettua quindi indicando a quale tipo di variabile essa deve puntare come
lesempio seguente schematizza.
campo2: real;
end;
type puntatore =^tipo_puntato;
crea una variabile il cui contenuto sar l'indirizzo dellarea di memoria gestita
secondo le indicazioni di tipo della definizione e allocata dinamicamente. In altre
parole, l'inserimento all'interno del programma della seguente chiamata:
new(punt);
punt^.campo1 := 1000;
punt1 := punt;
dispose(punt)
rende disponibile per altre allocazioni l'area di memoria a cui fa riferimento punt.
174 Capitolo quinto
5.6. I file
Il file si definisce come una sequenza di elementi tutti dello stesso tipo. Si
possono cos avere file di interi, di caratteri, di record, di array, di reali, etc.
L'importanza di tale struttura che di essa non se ne deve indicare la dimensione
all'atto della definizione in quanto i suoi valori vengono allocati in una delle
memorie di massa disponibili nel sistema informatico. Tra laltro, mentre la
dimensione della memoria centrale da considerare definita e limitata, e quindi
sono limitati gli oggetti in essa inseribili, le memorie di massa hanno una capacit
cos grande da non rappresentare, solitamente, una limitazione al numero di
elementi gestibili. Comunque, pu accadere che durante l'aggiunta di informazioni
ad un file si generi un errore per la saturazione della memoria di massa non
essendo essa di fatto illimitata.
Poich le informazioni contenute nel file devono essere gestite nel rispetto del
modello di Von Neumann, al file viene fatto corrispondere un buffer in memoria
centrale capace di garantire il transito dei valori da e verso la memoria di massa.
Per operazioni di scrittura il buffer viene caricato del valore che successivamente
viene registrato sulla memoria di massa, per quelle di lettura si procede in modo
opposto, il valore viene letto dalla memoria di massa e quindi inserito nel buffer
dove pu essere poi elaborato.
Il file quindi composto da una sequenza di elementi tutti dello stesso tipo T
residenti sulla memoria di massa e da una ulteriore informazione di tipo T che
svolge le funzioni di buffer in memoria centrale. I file possono essere ad accesso
diretto o sequenziale. Nel primo caso si pu accedere direttamente ad un
particolare dato nel file nota la posizione; nel secondo caso laccesso al file
avviene in modo rigorosamente sequenziale (cio prelevando un valore dopo
l'altro), indipendentemente dal particolare supporto impiegato per la sua
memorizzazione.
5.6.1. I file sequenziali
Le operazioni definite su un file sequenziale di nome F sono:
- l'inserimento di un elemento PUT(F);
- l'estrazione di un elemento GET(F);
- l'operazione di riavvolgimento RESET(F) per predisporre il file alle
estrazioni;
- la cancellazione o azzeramento REWRITE(F) per predisporre il file alle
inserzioni
- e il predicato logico EOF(F) che segnala durante le estrazioni se il file
terminato (End Of File).
Il buffer viene inserito automaticamente all'atto della definizione, lo si indica
con F^ se F una variabile di tipo file ed di tipo T se il file composto da
elementi di tipo T. Il buffer l'unica informazione del file in memoria centrale che
I dati 175
F:=Fs+Fd
In tali condizioni EOF(F) assume il valore true se Fd vuoto, negli altri casi
false. Se EOF(F) falso, ossia la parte destra non vuota, si definisce il buffer
come:
F^:=first(Fd)
Fs :=<file vuoto>
Fd :=abcd (*intero file*)
F=<file vuoto>
L'esecuzione di un PUT(F) accoda il contenuto del buffer agli elementi del file
secondo lo schema:
read(nome_variabile)
Essa corrisponde, detto INPUT il nome del file di input, alla sequenza di
operazioni:
nome_variabile:=INPUT^
get(INPUT)
ossia all'assegnazione alla variabile del contenuto del buffer associato al file
di input e ad un successivo get.
Il file di output un file che pu essere solo generato e per tale motivo
generato all'interno del processo di elaborazione ed ammette come unica
operazione il put. Identificheremo l'operazione di scrittura del valore di una
informazione nel file di output con:
write(nome_variabile)
Essa corrisponde, detto OUTPUT il nome del file di output, alla sequenza di
operazioni:
OUTPUT^:=nome_variabile
put(OUTPUT)
viene scritta sul file con un formato che tronca alcune sue cifre significative,
quando se ne rilegge successivamente il valore, lo si trover diverso da quello
iniziale.
Tutti i file di tipo non testo memorizzano i valori delle informazioni cos come
sono rappresentati in memoria centrale, quindi senza alcuna ulteriore elaborazione.
Essi dipendono fortemente dallambiente di programmazione che li genera e
pertanto richiedono un legame molto stretto tra la loro generazione e ispezione, nel
senso che non solo la successione delle informazioni lette deve corrispondere a
quella in cui le stesse informazioni sono state scritte ma anche che la lettura deve
avvenire nello stesso ambiente in cui sono stati generati. Nei casi in cui i file
vengono generati in un ambiente ed elaborati in un altro, si preferisce utilizzare per
essi il tipo textfile per garantire la portabilit dei dati.
5.6.3. La connessione e sconnessione dei file
La gestione delle memorie di massa affidata al Sistema Operativo. Molti
sono i modi di organizzare le memorie di massa sia dal punto di vista logico che da
quello fisico. In questa sede sufficiente ricordare che, da un punto di vista logico,
le memorie di massa sono organizzate in aggregati di informazioni tra loro
omogenee a cui viene dato il nome di file.
Il file system la componente del sistema operativo a cui affidato il compito di
organizzare e gestire i file. Le sue funzioni specifiche variano da computer a
computer, ma tipicamente includono:
- la creazione e la cancellazione dei file;
- la gestione degli accessi alle informazioni del file;
- la gestione e lorganizzazione dello spazio delle memorie di massa;
- la protezione dei file per impedire l'accesso da parte di persone non
autorizzate a farlo.
Poich su una memoria di massa possono risiedere pi file
contemporaneamente, il file system richiede che a ciascun file, all'atto della sua
creazione, venga assegnato un nome. Tale nome viene poi usato come suo
riferimento per effettuare una qualsiasi altra operazione di gestione come
l'aggiornamento, l'ispezione o la cancellazione. Due file diversi non possono avere
ovviamente lo stesso nome. Il file system gestisce per ogni file, oltre al nome,
anche le seguenti informazioni:
- l'allocazione del file sul supporto magnetico;
- la dimensione del file;
- la data di creazione o dell'ultimo aggiornamento del file;
- le informazioni di controllo sui diritti di accesso quali quelli che abilitano
operazioni di modifica del contenuto del file, la lettura del file o la sua
eventuale eliminazione.
L'informazione fondamentale che contraddistingue il file il nome
attribuitogli al momento della sua creazione. I file system dei vari sistemi operativi
prevedono sintassi molto diverse per la costruzione di tali nomi. Solitamente sono
stringhe di caratteri e cifre di lunghezza fissata attribuite al file in modo analogo a
quello in cui si sceglie un identificatore per una variabile: ossia in modo da
ricordarne il contenuto.
180 Capitolo quinto
Linsieme dei quattro elementi detto specificazione del file. I due punti (:)
separano lidentificativo dellunit dal resto; le cartelle del percorso sono separate
tra di loro dal carattere \ e lestensione preceduta dal punto.
In un programma, prima di poter operare su un file si deve interagire con il
file system del sistema operativo fornendogli le indicazioni del file capaci di
individuarlo univocamente. La connessione permette all'interno di un programma
di richiedere al sistema operativo il file sul quale operare. Tutti i linguaggi
prevedono a questo scopo procedure predefinite per consentire la connessione tra
la variabile file e il nome da esso assunto nella memoria di massa gestita dal file
system del sistema operativo.
In modo molto semplice la connessione pu avvenire tramite una procedura
che associa alla variabile di tipo f il suo nome sulla memoria di massa. Ad esempio
con una procedura assign del tipo:
assign(F, c:\programmi\corso2007\ordina.c)
discusso legame tra algoritmo e struttura dati in esso impiegata, importante poter
trattare ad un primo livello di formulazione della soluzione di un problema con
l'informazione astratta, definita cio tramite la struttura e le modalit di
funzionamento, rinviando, quindi, la definizione dettagliata del tipo con gli
strumenti messi a disposizione dal linguaggio dopo la verifica della correttezza
della soluzione stessa. In questo modo si otterranno soluzioni pi aderenti alla
realt del problema e quindi pi facilmente interpretabili e gestibili nel tempo.
In altri termini l'astrazione sui dati un utile metodo per posporre i dettagli di
implementazione in modo da risolvere i problemi di rappresentazione nella
maniera pi adeguata e quanto pi indipendente dall'algoritmo. L'uso di tale
metodo, peraltro, favorita dalla presenza in alcuni linguaggi pi moderni di
meccanismi appositi di definizione. Invece, nei linguaggi tradizionali, si deve
dapprima scegliere tra quelle disponibili la struttura dati che pi simile per
composizione e modalit d'uso a quella astratta e quindi renderla equivalente, da un
punto di vista funzionale, attraverso i meccanismi del linguaggio, quali le funzioni
o i sottoprogrammi.
5.7.1. Il tipo pila
La pila viene anche detta stack. E' una struttura che possiamo definire come
composta da oggetti tutti dello stesso tipo sui quali possibile operare in modo che
il primo oggetto che dalla struttura si pu estrarre l'ultimo che in essa stato
inserito. Per questa modalit di gestione la pila anche detta struttura LIFO dalle
parole inglesi Last In - First Out (ultimo entrato - primo uscente).
Le operazioni definite sulla pila sono:
- PUSH: per aggiungere un elemento alla pila,
- POP: per togliere alla pila l'elemento che stato aggiunto per ultimo,
- TOP: per osservare l'elemento che stato aggiunto per ultimo.
Inoltre sulla pila definito il predicato:
- EMPTY: che quando assume il valore TRUE indica che la pila vuota,
ossia non ha elementi.
Per motivi pratici, legati all'impossibilit di trattare tipi a cardinalit infinita,
viene introdotto anche il predicato:
- FULL: che con il valore TRUE indica quando la pila si riempita.
Come si pu notare, un tale tipo si presta a modellare l'organizzazione di
oggetti della vita quotidiana. Infatti, come una pila di piatti, di libri, di fogli esso
caratterizzato dal fatto che il primo oggetto prelevabile quello che si trova in testa
a tutti gli altri ed proprio quello che stato posato su di essi per ultimo.
Si noti infine che:
- POP e TOP sono possibili se la pila non vuota;
- PUSH possibile se la pila non piena.
5.7.2. Il tipo coda
La coda una struttura composta da oggetti tutti dello stesso tipo sui quali
possibile operare in modo che il primo oggetto estraibile dalla struttura il primo
che in essa stato inserito. Per questo modalit di gestione la coda anche detta
struttura FIFO dalle parole inglesi First In - First Out (primo entrato - primo
uscente).
I dati 183
b:=F(a)
I tipi dei valori possono essere qualsiasi, sia atomici che strutturati. Inoltre il
primo elemento della coppia viene detto chiave della tabella per il fatto che il
mezzo per entrare nella struttura e prelevare il secondo elemento della coppia.
Le operazioni definite sulla tabella sono:
- INSERIMENTO: per aggiungere una coppia alla tabella;
- RICERCA: per estrarre, se esiste, l'elemento b corrispondente al valore della
chiave a specificata;
- CANCELLAZIONE: per eliminare la coppia che ha il primo elemento uguale
al valore della chiave specificata.
Inoltre sulla tabella sono definiti i predicati:
- EMPTY: che quando assume il valore TRUE indica che la tabella vuota,
ossia non ha elementi.
- FAIL: il cui valore TRUE indica il fallimento della ricerca sia per operazioni
di RICERCA che di CANCELLAZIONE, ossia indica che non esiste una
coppia con il valore della chiave specificata.
Per motivi pratici, legati all'impossibilit di trattare tipi a cardinalit infinita,
viene introdotto anche il predicato:
- FULL: che con il valore TRUE indica quando la tabella si riempita.
La tabella una struttura astratta di grande interesse poich permette di
trattare corrispondenze fra insiemi di qualsiasi natura. Ad esempio, si pu pensare
all'elenco telefonico che mette in corrispondenza un nominativo (la chiave) con un
184 Capitolo quinto
Il linguaggio C
6.1. Introduzione
Il C un linguaggio di programmazione molto usato in diversi campi
applicativi. Realizzato nel 1972 da Dennis Ritchie, il linguaggio porta un nome che
deriva dal suo predecessore, il linguaggio B, progettato da Ken Thomson per
implementare il sistema operativo Unix. Ritchie e Thomson, successivamente,
riscrissero il sistema Unix interamente in C.
Sebbene il sistema operativo Unix, il compilatore C e quasi tutti i programmi
applicativi di Unix siano scritti in C, il linguaggio non legato ad alcun sistema
operativo o ad alcuna macchina particolare: viene comunque considerato da molti
come un linguaggio di programmazione di sistema per la sua utilit nella scrittura
dei sistemi operativi.
Il C un linguaggio di alto livello che possiede un ristretto insieme di
costrutti di controllo e di parole chiave ed un ricco insieme di operatori; esso
permette, inoltre, di programmare in modo modulare, usando funzioni e macro. Nel
contempo, il C viene anche considerato un linguaggio relativamente di basso
livello, in quanto prevede operazioni tipiche di un linguaggio macchina, come
lindirizzamento in memoria.
Per scelta di progetto il C non contiene istruzioni di ingresso/uscita (non
esistono istruzioni di READ e WRITE dedicate, e neppure istruzioni per laccesso ai
file) n istruzioni particolari per le operazioni matematiche; mancano anche
operazioni per trattare direttamente oggetti strutturati come stringhe di caratteri,
insiemi, liste ed array, ed infine non esistono operatori con i quali manipolare interi
array o intere stringhe. Il C affida limplementazione di tali tipologie di operazioni
a librerie esterne cos da avere un linguaggio compatto le cui funzionalit si
possono ampliare facilmente sia sviluppando ulteriori librerie che modificando
quelle standard.
alfa = 10
if contatore
ifcontatore
whilecondizione
Il linguaggio C 187
, ; . : ..
(delimitatori e punteggiatura)
188 Capitolo sesto
( ) [ ] { }
(parentesi)
&& || !
(operatori logici)
// /* */
(commento)
/*
questo un possibile
Commento del linguaggio
C
Il linguaggio C 189
*/
e
/************************************************
Nome programma: radice quadrata
Autore: Paolo Rossi
Versione: 1.0
Data: 12/19/2008
*************************************************/
/* frase */
// frase /n
6.2.7. Le costanti
Le costanti del linguaggio si dividono in costanti numeriche e costanti di tipo
carattere. Per ci che attiene le prime, i numeri trattati dal linguaggio sono di due
tipi: interi e reali. Un numero intero una sequenza di cifre eventualmente
preceduta da un segno. Un numero reale ha in aggiunta una parte decimale ed
eventualmente un fattore di scala. In particolare una costante reale in virgola
mobile si compone di una parte intera, un punto decimale, ed una parte decimale,
ad esempio 12.14. possibile inoltre usare il carattere e oppure E per
lesponente intero con segno come fattore di scala, come in 12.14 E-10. Sono
costanti intere:
10
300
-1000
3.400
10.20
190 Capitolo sesto
+1.99E+30
-30.008
-0.7E-10
9.02E3
Una costante di tipo carattere racchiusa tra singoli apici. Il valore di una
costante carattere il valore numerico del carattere nel set di caratteri della
macchina.
Alcuni caratteri non grafici sono rappresentati come di seguito:
newline \n
tab orizzontale \t
backspace \b
carriage return \r
form feed \f
backslash \\
apice singolo \
6.2.8. Le stringhe
Le costanti stringa di caratteri sono sequenze di caratteri racchiuse da una
coppia di caratteri doppi apici ("), come in ''salve''. Il valore della stringa dato
dalla sequenza di caratteri esclusi i doppi apici che fungono da parentesi. Una
costante senza caratteri (una coppia di doppi apici) rappresenta la stringa a
lunghezza nulla. Tutte le stringhe, anche quando sono stringhe identiche, sono in
realt diverse. Il compilatore immette un byte nullo,\0, alla fine di ogni stringa
per individuarne la fine. Sono costanti stringhe di caratteri:
''ALFABETO''
''ciao''
''Minuscolo''
direttive di compilazione
dichiarazione di variabili globali
dichiarazione alias di tipi
dichiarazione prototipi di funzione
f1()
{
variabili locali
sequenza di istruzioni
}
192 Capitolo sesto
f2()
{
variabili locali
sequenza di istruzioni
}
.
.
.
fN-1()
{
variabili locali
sequenza di istruzioni
}
main()
{
variabili locali
sequenza di istruzioni
}
Una funzione C un blocco di frasi a cui viene assegnato una intestazione che
contiene la dichiarazione del tipo della funzione stessa, il nome e la lista dei
parametri di ingresso/uscita. Il blocco poi iniziato da una parentesi graffa aperta
ed chiuso da una parentesi graffa chiusa come mostrato di seguito.
#include <stdio.h>
int main()
{
printf("Salve!\n");
return 0;
}
specfica
{ }
algoritmo
Figura 10 Carta Sintattica per il blocco di un programma
Il linguaggio C 195
Infine per la gestione di variabili booleane o logiche, pur non essendo previsto
nelle prime versioni del linguaggio, stato introdotto il tipo bool. Le variabili di
tipo bool possono assumere i soli due valori logici vero e falso (true e false).
6.3.4. Dichiarazione di variabili
In C necessario dichiarare una variabile prima di poterla utilizzare. La
dichiarazione delle variabili avviene dapprima specificando leventuale
modificatore di tipo, poi il tipo, e, quindi lidentificatore.
allinterno di un blocco sono ad essi locali e visibili alle frasi che seguono la
dichiarazione stessa.
6.3.5. Alias di tipi
Il C non permette di definire un nuovo tipo allinterno di un programma: tuttavia
permette di introdurre un nome (pseudonimo o alias) che corrisponde ad uno dei
tipi definiti.
typedef nomeTipo nomeNuovoTipo;
Il meccanismo degli alias pu essere molto utile soprattutto per aumentare la
leggibilit di un programma e per evitare espressioni complesse.
6.3.6. Il tipo enumerativo
Tra i tipi fondamentali del linguaggio C va infine annoverato il cosiddetto tipo
enumerativo. Lenumerazione un insieme di costanti intere rappresentate da
identificatori. Le costanti sono dette anche costanti di enumerazione e sono delle
costanti simboliche i cui valori sono impostati automaticamente: iniziano da 0 e
sono incrementati di solito di 1. Un esempio di enumerazione il seguente:
creando un nuovo tipo GIORNI i cui identificatori sono associati con gli interi
compresi tra 0 e 6, ovvero:
vet[1]=70;
vet[3]=1;
int mat[10][15];
float matReal[15][15];
mat[1][3]=3;
struct DATA
int giorno;
int mede;
int anno;
;
struct DATA data_mese;
struct
unsigned a:1;
insigned b:7;
variabileBit;
la variabile variabileBit occupa uno spazio in memoria pari alla somma dei bit
utilizzati da ogni campo della struttura. Cos su una word di 16 bit, il campo a
occupa 1 bit; b occupa 7 bit; i restanti 8 sono non utilizzati.
6.3.12. Stringhe di caratteri
Il C non prevede la presenza di un tipo stringa predefinito. Tuttavia, possibile
utilizzare un array di caratteri con alcune convenzioni. Larray:
char stringa[10];
S a l v e M o n D o \0
tipo *var;
x++
++x
x- -
- -x
indirizzo_x=&x;
valore_x=*indirizzo_x;
ha leffetto di porre nella variabile valore_x il valore della variabile che si trova in
memoria allindirizzo contenuto nella variabile indirizzo_x, ovvero il valore di x.
Il seguente esempio cerca di chiarire i concetti prima esposti.
int x;
int* indx=&x;
int valx=*indx;
printf("Benvenuti al corso di Fondamenti di Informatica\n");
printf("Esempio di programma sui puntatori\n");
printf("Inserisci il numero x: ");
scanf("%d",&x);
printf("\nIl valore di x e': %d\n",x);
printf("\nL'indirizzo di memoria (in esadecimale) a cui si trova x e': %x\n",indx);
printf("\nIl valore di x mediante puntatore e': %d\n",x);
int a,b,d,s;
sommadiff (a,b,&d,&s);
Lattivazione della funzione ha come effetto quello di passare una copia dei
valori delle variabili a ed b alla funzione (passaggio per valore) e quello di passare
204 Capitolo sesto
str[4] e *(p1+4)
sono equivalenti in quanto ogni elemento del vettore occupa un solo byte di
memoria.
6.4.6. Operatori logici bit oriented
In C sono presenti operatori che consentono la manipolazione dei bit. Essi sono
elencati di seguito.
variabile=nome_fnzione(par_effettivo1,par_effettivo2,., par_effettivon);
nome_procedura(par_effettivo1,par_effettivo2,., par_effettivon);
x=somma(a,b);
somma(a,b,&c);
6.5.3. Costrutti selettivi
Gli enunciati selettivi permettono la selezione di azioni elaborative in funzione del
valore di un'espressione booleana e non: ossia si valuta l'espressione e si sceglie
l'istruzione successiva in funzione del valore trovato.
If-else
L'enunciato if-else (se - altrimenti) permette di effettuare la scelta tra due
alternative in funzione del valore di una espressione booleana. Se il valore
dell'espressione TRUE, si sceglie l'alternativa (insiemi di enunciati racchiusi tra
parentesi graffe) introdotta dopo lif, in caso contrario (insiemi di enunciati
Il linguaggio C 207
racchiusi tra parentesi graffe) quella aperta dall'else. In entrambi i casi, dopo
l'esecuzione del blocco selezionato, l'esecuzione stessa continua con l'enunciato
successivo all'if. anche possibile usare una notazione abbreviata nel caso in cui
non esista una delle due alternative, non specificando l'else dell'enunciato. Sono
esempi di if:
if (n>10) {
n=n+1;
}
else {
n=n-1;
}
if (n>10) n=0;
Si noti che, nel caso che lazione elaborativa dellif o dellelse sia composta
da un solo statement semplice, le parentesi graffe non sono obbligatorie. Inoltre si
pu notare come l'uso appropriato dell'incolonnamento (o anche indentazione)
delle strutture innestate l'una dentro l'altra migliori notevolmente la chiarezza del
programma in quanto evidenzia l'ordine di esecuzione dei vari blocchi. allora da
usare tale accorgimento anche quando la disposizione delle istruzioni sui righi non
importante, come nel nostro caso.
if espressione
( )
booleana
statement
statement
{ }
composto
else statement
statement
{ }
composto
Figura 19 Carta sintattica per if-else
Il linguaggio C permette anche di realizzare if annidati, ovvero costrutti di
selezione che appartengono al ramo if o al ramo else di unulteriore istruzione di
selezione. Nel caso di costrutti innestati il C dispone di una regola che assegna
ogni else allif pi vicino che non ne possiede gi uno.
Un esempio il seguente, dove lelse relativo al secondo if:
208 Capitolo sesto
if (x>0) {
if (y>0)
printf (x e y maggiori di 0);
else
printf(solo x maggiore di 0);
}
Switch-case
Listruzione switch permette di scegliere tra pi di due alternative (decisioni
multiple) verificando se il valore di una espressione uguale ad un valore tra quelli
specificati in una lista. In particolare consiste di un'espressione (di tipo numerico o
carattere), detta selettore, e di una lista di enunciati, ciascuno dei quali identificato
da uno o pi valori costanti appartenenti al tipo del selettore. L'enunciato scelto
quello identificato dalla costante che uguale al valore calcolato del selettore.
Solitamente la scansione degli identificatori, per cercare quello con valore uguale
al selettore, avviene in modo sequenziale. Per tale motivo si consiglia di disporre
per ultimi gli identificatori che hanno la minore probabilit di essere scelti. Un
esempio di switch il seguente:
switch(x)
case 0:
case 1:
n++;
break;
case 2:
n- -;
break;
default:
n *=2;
Si noti che listruzione break causa luscita dallo switch. Il caso chiamato
default viene eseguito quando non sono stati soddisfatti gli altri casi dello switch.
Un altro esempio il seguente:
SWITCH(numero_mese) {
CASE {1,3,5,7,8,10,12}
printf(mese di 31 giorni);
break;
CASE {4,6,9,11}
printf(mese di 30 giorni);
break;
CASE 2
printf(mese di 28 o 29 giorni);
break;
DEFAULT:
printf(mese non valido);
}
In questo caso viene verificata lappartenenza del valore della variabile dello
switch ad un tipo enumerativo. Si noti che la non introduzione del break porta a
confrontare il valore del selettore con tutti i casi anche quando uguale ad uno di
essi.
Il linguaggio C 209
a = a+x;
x- -;
statement
{ }
composto
statement
Il ciclo do-while
A differenza dei ciclo while, che verifica la condizione allinizio del ciclo (loop)
stesso, il costrutto do-while la verifica alla fine, con la conseguenza che esso viene
eseguito almeno una volta. In particolare, listruzione viene eseguita, poi viene
valutata lespressione: se vera, listruzione viene ancora eseguita e cos via. Il
ciclo termina quando listruzione diventa falsa.
do
statement
{ }
composto
statement
while espressione
( )
booleana
Figura 22 - Carta sintattica del costrutto do-while
do
scanf(%d,&n);
while (n>0)
Il linguaggio C 211
Il ciclo for
Il costrutto for un enunciato iterativo enumerativo o ciclico. Esso deve essere
usato ogni qualvolta il numero di ripetizioni noto a priori.
printf(%d ,i);
somma = somma+i;
Nel primo caso vengono visualizzati a video tutti gli interi compresi tra 0 e
100 (incremento), nel secondo caso gli stessi interi vengono visualizzati ma in
ordine inverso tra 100 e 0 (decremento) e ne viene effettuata la somma. In altri
termini, per il primo for, viene posta inizialmente la variabile di controllo i al
valore 0 e viene richiamata la funzione printf() per la visualizzazione a video di i,
dopodich, al ritorno dalla funzione, viene applicata la condizione di
aggiornamento (in questo caso di incremento) della variabile i e si verifica la
condizione (i<=100). Poich la condizione TRUE si ripete listruzione di
visualizzazione sul nuovo valore della variabile di controllo. Analogamente a
quanto gi descritto, il ciclo si ripete finch i non diventa maggiore di 100. Un
212 Capitolo sesto
for( ; ;)
implementa di contro un ciclo infinito. Per una piena comprensione dei due
costrutti iterativi for e while, si presentano di seguito due costrutti equivalenti.
int i=0;
while (i<=100) {
printf(%d ,i);
i++;
}
#include <conio.h>
int main ()
{
getch();
return 0;
}
Tale funzione apre un file il cui nome (path su disco del file) un insieme di
caratteri puntato dalla variabile filename, mentre la variabile permission
definisce la modalit di apertura del file (e.g., sola lettura, solo scrittura,
lettura/scrittura). La funzione suddetta restituisce poi un identificatore del file noto
anche con nome di puntatore a file, attraverso il quale il file viene referenziato. Se
per vari motivi il file non pu essere aperto viene restituito un puntatore nullo.
Di seguito riportato un esempio dellapertura in sola lettura di un file
pippo.txt contenente un array di numeri interi (i numeri si trovano su righi
differenti di testo separati tra loro dal carattere di fine rigo). La modalit di
apertura a solo lettura utilizza la stringa r, quella a sola scrittura lopzione w,
infine, quella in scrittura/lettura rw.
FILE *fid;
fid=fopen (c:\pippo,txt, r);
if (!fid) printf(FILE NON APERTO);
FILE *fid;
fid=fopen (c:\pippo,txt, w);
if (!fid) printf(FILE NON APERTO);
214 Capitolo sesto
Lettura da file
Dopo la connessione del file in modalit lettura, attraverso lidentificatore del file
possibile eseguire le operazioni di lettura che avvengono con lutilizzo della
funzione fscanf() della libreria stdio.h:
dove fid lidentificativo (puntatore) del file, format indica il tipo degli
elementi da leggere e var la variabile in cui si inseriscono gli elementi letti. La
funzione ritorna il numero di elementi effettivamente letti, se viene restituito EOF
significa che stata tentata la lettura dopo la fine del file.
La variabile format pu assumere i seguenti valori a seconda del tipo di dato
che si vuole leggere:
%d interi
%f,%g reali
%c caratteri
%s stringhe
FILE * fp;
register int i;
int vettore[100];
int riemp, ret;
if (!(fp=fopen("pippo.txt","r"))) {
printf("\n Il file non puo' essere caricato\n");
}
else {
ret=fscanf(fp,"%d",&riemp);
if (ret!=EOF) {
printf("\nNumero di elementi nel vettore: %d\n",riemp);
if (riemp>0) {
for (i=0; i<riemp; i++) fscanf(fp,"%d",vettore[i]);
}
}
else printf("\nfile vuoto!!!\n");
}
int a;
scanf(%d, &a);
Scrittura su file
Dopo la connessione del file in modalit scrttura, attraverso lidentificatore del file
possibile eseguire le operazioni di lettura che avvengono con lutilizzo della
funzione fprintf() della libreria stdio.h:
dove fid lidentificativo del file, format indica il tipo degli elementi da scrivere e
var la variabile in cui si trovano gli elementi da scrivere su file. La funzione
ritorna il numero di elementi effettivamente scritti, se viene restituito un valore
negativo significa che si sono verificati problemi di scrittura su file. La variabile
format pu assumere gli stessi valori illustrati per la lettura dei file. Di seguito
riportato un esempio di scrittura su file di un array di interi (sul primo rigo del file
di testo viene inserito il riempimento del vettore):
register int i;
FILE*fp;
int count;
int tot=0;
int riemp=3;
int vettore[]= {1,2,3};
if (!(fp=fopen("pippo.txt","w"))) {
printf("\n Il file non puo' essere salvato\n");
}
else {
count=fprintf(fp,"%d\n",riemp);
tot=tot+count;
for (i=0; i<riemp; i++) {
count=fprintf(fp,"%d\n",vettore[i]);
tot=tot+count;
}
}
if (tot==riemp+1) printf(Scrittura avvenuta con successo);
La scrittura di informazioni su standard output (monitor) avviene invece,
come gi descritto, attraverso la funzione pritnf() della libreria stdio.h:
int printf(char*format, var)
dove var rappresenta la variabile il cui contenuto sar visualizzato a video, mentre
format ha lo stesso significato dellomonima variabile della funzione printf. Di
seguito e riportato lesempio di scrittura di un intero su monitor:
int a;
printf(%d, a);
216 Capitolo sesto
Tale funzione concatena una copia della stringa str2 alla stringa str1,
concludendo la stringa str1 (che conterr la stringa risultante) con un carattere di
terminazione. Il primo carattere di str2 si sovrappone al terminatore originale di
str1, mentre la stringa str2 rimane inalterata. La funzione restituisce un puntatore
nullo. Di seguito riportato un esempio per la scrittura a video della
concatenazione delle stringhe ciao e come stai?.
Tale funzione copia il contenuto della stringa str2 nella stringa str1,
restituendo un puntatore a str1. Di seguito riportato un esempio per la scrittura a
video della copia di una stringa.
char str[100];
strcpy(str,ciao);
printf(%s,str);
char s1[100];
char s2[100];
scanf(%s,s1);
scanf(%s,s2);
if (strcmp(s1,s2)==0) printf(stringhe uguali);
Tale funzione conta il numero di caratteri da cui composta una stringa str1
che termina col carattere di terminazione, escludendo tale carattere dal conteggio.
Di seguito riportato un esempio di conteggio di caratteri di una stringa.
char s1[100];
scanf(%s,s1);
int len=strlen(s1);
printf(stringa lunga %d caratteri,len);
char*strlwr(char *str1)
char*strupr(char *str1)
o double exp(double x)
per il calcolo dellesponenziale di un numero reale x
o double log10(double x)
per il calcolo del logaritmo in base 10 di un numero
reale x
o double log2(double x)
per il calcolo del logaritmo naturale di un numero reale
x
- altre funzioni
o double sqrt (double x)
per il calcolo della radice quadrata di un numero reale x
o double fabs(double x)
per il calcolo del valore assoluto di un numero reale x
o double ceil (double x)
per il calcolo dellintero pi piccolo non inferiore ad un
numero reale x
o double floor(double x)
per il calcolo dellintero pi grande non superiore ad un
numero reale x
o double fmod(double x, double y)
per il calcolo del resto in modulo della divsione di x per
y
o double pow(double base, double exp)
per il calcolo di baseexp
Implementazione
// FUNZIONE PER LO SCAMBIO DI VALORE
// La funzione void scambia(x,y)
// permette di scambiare il valore delle variabili x ed y
Implementazione
// FUNZIONE PER L'INSERIMENTO DI UN ELEMENTO IN UN VETTORE
// La funzione void inserisci(v,info,posiz,n)
// permette di inserire l'elemento info al posto posiz nel vettore v di riempimento n
// Vettore un alias di un tipo array di float
// contatore di ciclo
register int i;
for (i=*n-1;i>=posiz;i--) {
v[i+1]=v[i];
}
v[posiz]=info;
// aggiorna il riempimento
*n=*n+1;
}
printf("]\n");
system("Pause");
return 0;
}
Implementazione
// FUNZIONE PER L'ELIMINAZIONE DI UN ELEMENTO IN UN VETTORE
// La funzione void elimina(v, posiz)
// permette di eliminare l'elemento al posto posiz nel vettore v di riempimento n
// Vettore un alias di un tipo array di float
// contatore di ciclo
register int i;
Il linguaggio C 223
Implementazione
// FUNZIONE PER L'ELIMINAZIONE DI UNA COLONNA DA UNA MATRICE
// La funzione void eliminacol(A,n,m,col)
// permette di eliminare gli elementi della colonna col dalla matrice A
// Matrice un alias di un tipo array bidimensionale di int
// contatori di ciclo
register int i,j;
// Funzione main
int main () {
Matrice A;
register int k,z;
int n,m,col;
printf("Benvenuti al corso di Fondamenti di Informatica\n");
printf("Esempio di programma che elimina una colonna da una matrice\n");
n=-1;
while ((n<0) || (n>NMAX-1)) {
printf("Inserisci il numero di righe della matrice(>=0 <=50): \n");
scanf("%d",&n);
};
m=-1;
while ((m<0) || (m>MMAX-1)) {
printf("Inserisci il numero di colonne della matrice(>=0 <=50): \n");
scanf("%d",&m);
};
printf("Inserisci gli elementi della Matrice (dopo ogni elem premere INVIO)\n");
for (k=0; k<n; k++) {
for (z=0; z<m; z++) {
printf("elemento[%d,%d]=",k,z);
scanf("%d",&A[k][z]);
}
}
printf("\nMatrice A prima dell'eliminazione\n");
printf("A=\n|");
Implementazione
// contatori di ciclo
register int i,j;
int main () {
Matrice A;
register int k,z;
int n,m,riga;
printf("Benvenuti al corso di Fondamenti di Informatica\n");
printf("Esempio di programma di eliminazione di una riga da una matrice\n");
n=-1;
while ((n<0) || (n>NMAX-1)) {
printf("Inserisci il numero di righe della matrice(>=0 <=50): \n");
scanf("%d",&n);
};
m=-1;
while ((m<0) || (m>MMAX-1)) {
printf("Inserisci il numero di colonne della matrice(>=0 <=50): \n");
scanf("%d",&m);
};
printf("Inserisci gli elementi della Matrice (dopo ogni elem premere INVIO)\n");
trovato=0;
i=0;
posiz=-1;
while (!trovato && i<n) {
if (v[i]==info) {
trovato=1;
posiz=i;
}
i++;
}
Implementazione
// FUNZIONE PER LA RICERCA DI UN ELEMENTO IN UN VETTORE
// La funzione int ricercaseq(v,n,info)
// permette di ricerca la posizione dellelemento info nel vettore v
// Vettore un alias di un tipo array monodimensionale di int
int main () {
Vettore v;
register int j;
int posiz,info,n;
printf("Benvenuti al corso di Fondamenti di Informatica\n");
printf("Esempio di programma per la ricerca di un elemento da un vettore\n");
n=-1;
while ((n<0) || (n>NMAX-1)) {
printf("Inserisci il numero delle componenti del tuo vettore(>=0 <=50): \n");
scanf("%d",&n);
};
printf("Inserisci gli elementi del Vettore (dopo ogni numero premere INVIO)\n");
for (j=0; j<n; j++) {
printf("elemento[%d]=",j);
scanf("%d",&v[j]);
}
printf("\nVettore v prima della ricerca\n");
printf("Vettore = [");
for (j=0; j<n; j++) {
if (j<n-1)
printf("%d,",v[j]);
else
printf("%d",v[j]);
}
printf("]\n");
printf("Inserisci l'elemento da ricercare: ");
scanf("%d",&info);
printf("\nElaborazione in corso....\n");
posiz=ricercaseq(v,n,info);
system("Pause");
printf("\n- Risultati ricerca -\n");
if (posiz!=-1) printf("L'elemento %d e' presente in posizione %d\n",info,posiz);
else printf("L'elemento % d non e presente nel vettore v\n",info);
system("Pause");
return 0;
}
pi log2(n) confronti. Il metodo noto col nome di ricrca binaria si basa sui seguenti
passi:
- determinazione del punto medio dellinsieme in cui cercare;
- confronto dellelemento in questa posizione con quello da cercare;
- individuazione dellinsieme in cui continuare la ricerca se il passo 2 non
ha successo; esso risulta per vettore ordinato in senso crescente: il
sottoinsieme degli elementi in posizioni successive al punto medio se il
valore da cercare maggiore di quello del punto medio, altrimenti quello
caratterizzato da posizioni inferiori;
- ripetizioni dei passi precedenti finch il passo 2 non risulti verificato o
non sia possibile fissare un sottoinsieme in cui continuare la ricerca.
Facciamo ora un esempio. Sia assegnato il vettore:
v=[15 22 29 36 50 55]
(1 2 3 4 5 6)
(tra le parentesi tonde sono indicate le posizioni degli elementi nel vettore) e
sia 21 il valore da ricercare. Allora lalgoritmo procede iterativamente nel seguente
modo:
}
if (info<v[medio]) es=medio-1;
else ei=medio+1;
}
Implementazione
// FUNZIONE PER LA RICERCA BINARIA DI UN ELEMENTO IN UN VETTORE
// La funzione int ricercaseq(v,n,info)
// permette di ricerca la posizione dellelemento info nel vettore v
// Vettore un alias di un tipo array monodimensionale di int
n=-1;
while ((n<0) || (n>NMAX-1)) {
printf("Inserisci il numero delle componenti del tuo vettore(>=0 <=50): \n");
scanf("%d",&n);
};
printf("Inserisci gli elementi del vettore (dopo ogni numero premere INVIO)\n");
for (j=0; j<n; j++) {
printf("elemento[%d]=",j);
scanf("%d",&v[j]);
}
printf("\nVettore v prima della ricerca\n");
printf("Vettore = [");
for (j=0; j<n; j++) {
if (j<n-1)
printf("%d,",v[j]);
else
printf("%d",v[j]);
}
printf("]\n");
printf("Inserisci l'elemento da ricercare: ");
scanf("%d",&info);
printf("\nElaborazione in corso....\n");
posiz=ricercabin(v,n,info);
system("Pause");
printf("\n- Risultati ricerca -\n");
if (posiz!=-1) printf("L'elemento %d e' presente in posizione %d\n",info,posiz);
else printf("L'elemento % d non e' presente nel vettore v\n",info);
system("Pause");
return 0;
}
v=[3 5 6 8 7]
(1 2 3 4 5)
(tra le parentesi tonde sono indicate le posizioni degli elementi nel vettore). Allora
lalgoritmo procede iterativamente nel seguente modo:
Implementazione
// FUNZIONE PER LA RICERCA DEL MASSIMO DI UN VETTORE
// La funzione int posmin(v,n)
// permette di ricercare la posizione del minimo tra gli elementi di un vettore v
// Vettore un alias di un tipo array monodimensionale di int
};
printf("Inserisci gli elementi del vettore (dopo ogni numero premere INVIO)\n");
for (j=0; j<n; j++) {
printf("elemento[%d]=",j);
scanf("%d",&v[j]);
}
printf("\nVettore v prima della ricerca\n");
printf("Vettore = [");
for (j=0; j<n; j++) {
if (j<n-1)
printf("%d,",v[j]);
else
printf("%d",v[j]);
}
printf("]\n");
printf("\nElaborazione in corso....\n");
posiz=posmin(v,n);
system("Pause");
printf("\nLa posizione del minimo del vettore e' %d\n",posiz);
system("Pause");
return 0;
}
v=[9 2 1 4 7]
(1 2 3 4 5)
(tra le parentesi tonde sono indicate le posizioni degli elementi nel vettore). Allora
lalgoritmo procede iterativamente nel seguente modo:
Implementazione
// FUNZIONE PER L'ORDINAMENTO DI UN VETTORE
// La funzione void ordina(v,n) permette di ordinare in maniera crescente gli elementi del
// vettore v
// Vettore un alias di un tipo array monodimensionale di int
v[i]=v[imin];
v[imin]=temp;
}
}
// Direttive di compilazione
#include <stdio.h>
#include <stdlib.h>
Il linguaggio C 241
# define NMAX 50
// Alias di tipi
typedef float Vettore [NMAX];
typedef float Elem;
// prototipi funzioni
void ordina (Vettore v, int n);
int ricercaseq (const Vettore v, int n, Elem info);
Elem max (const Vettore v, int n);
void input_array (Vettore v, int* n);
void output_array (const Vettore v, int n);
void menu();
}
i++;
}
// ritorna la posizione dell'elemento
return posiz;
}
else
printf("%g",v[j]);
}
printf("]\n");
}
void menu () {
Vettore v;
int n, posiz;
Elem massimo, info;
char opzione;
n=0;
while (opzione!='6') {
printf ("\n---PROGRAMMA PER LA GESTIONE DI UN ARRAY---\n");
printf ("[1] Inserimento elementi del Vettore\n");
printf ("[2] Visualizzione elementi del Vettore\n");
printf ("[3] Ricerca elemento nel Vettore\n");
printf ("[4] Visualizza elemento massimo nel Vettore\n");
printf ("[5] Ordina elementi del Vettore\n");
printf ("[6] Esci\n");
scanf("%s",&opzione);
switch (opzione) {
case '1':
input_array(v,&n);
break;
case '2':
if (n!=0) output_array (v,n);
else printf("\nVettore vuoto\n");
break;
case '3':
printf("\nInserisci l'elemento da ricercare: ");
scanf("%g",&info);
posiz=ricercaseq(v,n,info);
if (posiz!=-1)
printf("\nL'elemento %g e' in posizione %d\n",info,posiz);
else printf("\nL'elemento % g non e' presente nel vettore\n",info);
break;
case '4':
massimo=max(v,n);
printf("\nIl massimo del vettore e' %g\n",massimo);
break;
case '5':
ordina(v,n);
break;
case '6':
printf("Uscita programma...\n");
system("PAUSE");
default:
printf("\nOpzione non supportata\n");
}
244 Capitolo sesto
}
}
// MAIN
int main() {
menu();
return 0;
}
fscanf(fp,"%s",imp[i].Dipartimento);
}
}
}
else {
printf("\nArchivio vuoto!!!\n");
num_impiegati=0;
}
}
}
// salva archivio su file
void salva_archivio(const Impiegati imp, Stringa f) {
register int i,j;
FILE*fp;
if (!(fp=fopen(f,"w"))) {
printf("\n L'archivio non puo' essere salvato\n");
}
else {
fprintf(fp,"%d\n",num_impiegati);
for (i=0; i<num_impiegati; i++) {
fprintf(fp,"%d\n",imp[i].matricola);
fprintf(fp,"%s\n",imp[i].Nome);
fprintf(fp,"%s\n",imp[i].Cognome);
fprintf(fp,"%s\n",imp[i].Dipartimento);
}
}
}
// main
int main () {
Impiegati i;
char opzione;
Stringa cogn, file;
while (opzione!='7') {
printf ("\n-PROGRAMMA DI GESTIONE DI UN ARCHIVIO DI IMPIEGATI-\n");
printf ("[1] Inserisci Impiegati\n");
printf ("[2] Ricerca Impiegato\n");
printf ("[3] Modifica Impiegato\n");
printf ("[4] Visualizza Archivio\n");
printf ("[5] Carica Archivio\n");
printf ("[6] Salva Archivio\n");
printf ("[7] Esci\n");
scanf("%s",&opzione);
switch (opzione) {
case '1':
inserisci_impiegato(i);
break;
case '2':
printf("\nInserisci il cognome dell'impiegato che vuoi ricercare: ");
scanf("%s",cogn);
ricerca_impiegato(i,cogn);
break;
case '3':
printf("\nInserisci il cognome dell'impiegato che vuoi ricercare: ");
scanf("%s",cogn);
modifica_impiegato(i,cogn);
break;
248 Capitolo sesto
case '4':
visualizza_archivio(i);
break;
case '5':
printf("\nInserisci il nome del file-archivio: ");
scanf("%s",file);
carica_archivio(i,file);
break;
case '6':
printf("\nInserisci il nome del file su cui salvare l'archivio: ");
scanf("%s",file);
salva_archivio(i,file);
break;
case '7':
printf("Uscita programma...\n");
system("PAUSE");
return 0;
default:
printf("Opzione non supportata\n");
}
}
return 1;
}
Capitolo settimo
function funzioneA(lista_parametri_ingresso)
enunciato 1 della funzioneA
..
enunciato N1 della funzioneA
costruite secondo le regole lessicali). Tali entit sono raggruppate in cinque classi
differenti per finalit e caratteristiche. Le 5 classi sono:
- separatori;
- identificatori e parole riservate;
- simboli speciali (operatori, delimitatori, frasi di commento);
- numeri interi e decimali;
- sequenze di caratteri racchiuse tra apici;
Si noti che le terza e la quarta classe contengono le costanti (numeriche e
alfanumeriche) gestite dal linguaggio.
7.1.2. I separatori
Ciascuna entit lessicale separata dalla successiva mediante opportuni separatori.
I caratteri spazio ed ENTER (fine linea) sono considerati separatori espliciti,
mentre gli operatori (aritmetici e di relazione), il punto e virgola e l'operatore
dell'assegnazione (=) vengono implicitamente identificati come separatori. Cos le
frasi che seguono presentano due entit lessicali distinte:
alfa = 10
if contatore
A
a
ALFA
Alfa
SOL1
PI_greco
equaz_2_grado
+ - * /
(operatori aritmetici)
< > == <= >= ~=
(operatori di confronto)
, ; . : ..
(delimitatori e punteggiatura)
( ) [ ] { }
(parentesi)
& | ~ ()
operatori logici)
%
(commenti)
Di seguito descritto luso dei vari simboli speciali.
Operatori Aritmetici
Gli operatori aritmetici sono i classici operatori binari +,- ,*,/ e loperatore
di modulo %. La divisione tra interi tronca la parte frazionaria, mentre
lespressione x%y fornisce il resto della divisione di x per y. Loperatore di
modulo pu essere applicato solo a tipi integrali. Gli operatori + e hanno la stessa
priorit, priorit inferiore a * e / (che invece hanno identica priorit).
Il linguaggio dellambiente MATLAB 253
Operatori Relazionali
Gli operatori relazionali sono:
> >= < <=
char
variabili array
alfanumeriche
Figura 7 Carta sintattica per i tipi di MATLAB
dipende dalla rappresentazione interna dei numeri interi ed quindi dipendente dal
particolare sistema di calcolo. Sulle variabili di tipo intero sono definite le
operazioni di somma, sottrazione, divisione e moltiplicazione pi moltissime altre
funzioni tra cui ricordiamo: la funzione resto in modulo MOD, la funzione valore
assoluto ABS, la funzione segno SIGN e cos via.
Tipi Semplici: il tipo reale
Il tipo reale contiene un intervallo di estremi predefiniti dell'insieme dei numeri
reali. Sui valori del tipo reale sono definite le operazioni di somma, sottrazione,
divisione e di moltiplicazione, pi un insieme ampio di funzioni pi avanzate. Sono
inoltre definite le due funzioni di conversione da reale a intero floor(x), ceil(x) e
round(x). La prima restituisce la parte intera di x; la seconda restituisce la parte
intera di x+1; la terza calcola l'intero mediante un arrotondamento. Esempio:
floor (0.6) = 0
ceil (0.6) = 1
round(0.6) = 1
Nel primo caso si creato il vettore a di tre elementi con valori 1, 2 e 3; nel
secondo caso si creato il vettore b con quattro elementi 7, 2, 8 e 6.
Un array bidimensionale (matrice) di interi o reali coincide con un oggetto
della classe double array di dimensione MxN, con M pari al numero di righe della
matrice e N pari al numero di colonne. Un matrice di caratteri coincide con un
oggetto della classe char array di dimensione MxN, con M pari al numero di righe
della matrice e N pari al numero di colonne. Gli elementi della matrice vanno
racchiusi tra parentesi quadre, gli elementi delle righe sono separati da uno spazio
bianco o da una virgola, mentre ogni riga separata dalla successiva mediante il
carattere punto e virgola ;. Con:
a=[1 2 3 4; 5 6 7 8; 9 10 11 12];
si crea una matrice di tre righe e quattro colonne i cui elementi hanno i valori
indicati.
1 2 3 4
a 5 6 7 8
9 10 11 12
La selezione di uno dei componenti dell'array si effettua facendo seguire al
nome dell'array il valore dell'indice, o degli indici separati da virgola nel caso di
array a pi dimensioni, racchiusi tra una coppia di parentesi tonde.
Sono esempi di funzioni di accesso:
a(3,(I*J+2))
a(I,J)
a(3)
Nel caso di array con informazioni strutturate, si devono comporre le varie
funzioni di accesso. Le operazioni consentite sugli array sono quelle che si possono
fare sui componenti l'array stesso.
In MATLAB anche i tipi semplici sono visti come casi particolari di quelli
strutturati. In MATLAB le variabili intere, reali e caratteri sono gestite mediante un
oggetto della classe double array di dimensione 1x1, mentre i caratteri mediante
un oggetto della classe char array di dimensione 1x1, come si pu vedere di
seguito.
a=1;
Name Size Class
a 1x1 double array
a=1.5;
Name Size Class
a 1x1 double array
a=a;
Name Size Class
a 1x1 char array
258 Capitolo settimo
identificatore = [ reale ]
carattere
identificatore = [ carattere ]
identificatore ( indice )
indice ;
virgole. Il valore delle variabili deve essere noto allatto della definizione del
record. MATLAB tratta i record come oggetti della classe non elementare cell
array (array di celle). Una cella (campo del record) pu essere a sua volta o di tipo
char arry o di tipo double array.
Nominativo='Angelo Chianese';
Indirizzo='via Claudio 121';
citta='Napoli';
tel=39081777777;
agenda={Nominativo,Indirizzo,citta,tel}
agenda{1}
agenda{3}
array
identificatore = [ ]
subrange
alfa = 3.569
circonferenza = 2 * PI_GRECO * raggio
area_rettangolo = base * altezza
i=i-1
Funzioni predefinite
Il linguaggio mette a disposizione un ampio set di funzioni predefinite applicabili a
tipi semplici e strutturati, per lo pi di natura matematica. Tali funzioni possono
essere introdotte all'interno di un programma, alcuni esempi sono:
cos(x)
sqrt(x)
mean(v)
max(v)
det(A)
char(126) = ~
double(a) = 97
dove fid lidentificativo del file, message il messagio associato allapertura del
file, filename il nome del file (path su disco del file) e permission la modalit
con cui si apre il file (in questo caso lettura).
Dopo lapertura, con lidentificatore del file possibile eseguire le operazioni
di lettura che avvengono con lutilizzo della funzione:
dove fid lidentificativo del file, format indica il tipo degli elementi da leggere ed
A la matrice in cui si inseriscono gli elementi letti. La variabile format pu
assumere i seguenti valori a seconda del tipo di dato che si vuole leggere:
%d interi
%f, %g reali
%c caratteri
%s stringhe
Di seguito riportato un esempio di lettura da file:
[fid,message]=fopen ('c:\file.txt','r);
[v_input]=fscanf(fid,'%s')
v_input = ciao
var_input=input(msg,[s])
var_input=input(Inserisci un numero:)
var_input=input(Inserisci una stringa:,s)
dove fid lidentificativo del file, message il messagio associato allapertura del
file, filename il nome del file (path su disco del file) e permission la modalit
con cui si apre il file (in questo caso scrittura). A questo punto attraverso
lidentificatore del file possibile eseguire le operazioni di scrittura che avvengono
con lutilizzo della funzione:
dove fid lidentificativo del file, format indica il tipo degli elementi da scrivere,
A la matrice in cui sono presenti gli elementi da scrivere e count conta il numero
di elementi effettivamente scritti su file. La variabile format assume gli stessi
valori previsti nella lettura. Di seguito riportato un esempio di scrittura su file:
[fid,message]=fopen ('c:\file.txt','w');
stringa=ciao;
[count]=fprintf(fid,stringa,'%s')
count = 4
[fid,message]=fopen ('c:\file.txt','r');
[v_input]=fscanf(fid,'%s')
v_input = ciao
if (n>10)
n=n+1;
else
n=n-1;
end
if (n>10)
n=0;
end;
n=n-1;
Il costrutto switch-case
L'enunciato switch-case permette di scegliere nel caso in cui le alternative siano
pi di due. Esso consiste di un'espressione (di tipo numerico o carattere), detta
selettore, e di una lista di enunciati, ciascuno dei quali identificato da uno o pi
valori costanti appartenenti al tipo del selettore preceduti dalla parola case.
L'enunciato scelto quello identificato dalla costante che uguale al valore
calcolato del selettore.
Una volta eseguito lenunciato scelto si esce dalla struttura switch. La
scansione dei valori costanti, per cercare quello con valore uguale al selettore,
avviene in modo sequenziale per cui vanno disposti per ultimi gli enunciati che
hanno la minore probabilit di essere scelti. Qualora il valore calcolato dal selettore
Il linguaggio dellambiente MATLAB 267
non uguale ai valori costanti indicati viene eseguito lenunciato preceduto dalla
parola chiave otherwise se presente, altrimenti si esce dallo struttura. Sono esempi
di switch-case:
SWITCH(colore_semaforo)
CASE rosso
msg=stop;
CASE verde
msg=vai;
CASE giallo
msg=attenzione;
OTHERWISE
msg=semaforo rotto;
END
SWITCH(numero mese)
CASE {1,3,5,7,8,10,12}
msg=mese di 31 giorni;
CASE {4,6,9,11}
msg=mese di 30 giorni;
CASE 2
msg=mese di 28 o 29 giorni;
OTHERWISE
msg=mese non valido;
END
SWITCH(valcifra)
CASE 0 carcifra='0';
CASE 1 carcifra='1';
CASE 2 carcifra='2';
CASE 3 carcifra='3';
CASE 4 carcifra='4';
CASE 5 carcifra='5';
CASE 6 carcifra='6';
CASE 7 carcifra='7';
CASE 8 carcifra='8';
CASE 9 carcifra='9';
END
268 Capitolo settimo
Il while
Gli enunciati iterativi permettono l'esecuzione di un blocco di istruzioni un certo
numero di volte. La terminazione della ripetizione avviene quando sono verificate
certe condizioni che sono calcolate internamente al blocco. Se il numero di
ripetizioni noto a priori, la struttura viene anche detta ciclica o enumerativa.
L'enunciato while composto da una espressione logica e da uno o pi
enunciati da ripetere in funzione del valore di tale espressione. L'esecuzione del
while comporta la valutazione dell'espressione e l'esecuzione degli enunciati
compresi tra le parole while ed end nel caso in cui il valore calcolato
dell'espressione sia TRUE. Il ciclo ha cos termine quando l'espressione assume il
valore FALSE per cui, se l'espressione risulta falsa alla prima esecuzione del while,
tali enunciati non vengono mai eseguiti. Si osservi che una volta iniziato, il ciclo
pu terminare solo se all'interno di esso vi sono degli enunciati che modificano il
valore di verit dell'espressione: cio operano sulle variabili che ne fanno parte.
un esempio di while:
somma=0;
while (n<=20)
somma=somma+vettore(n);
n=n+1;
end
Il linguaggio dellambiente MATLAB 269
while espressione
( )
booleana
statement
end
composto
statement
Il for
Il for un enunciato iterativo enumerativo, detto anche ciclico, che deve essere
usato ogni qualvolta il numero di ripetizioni noto a priori. Si presenta nel
seguente modo:
for t = Tin : Tfin
S
end
(double(Tin)-double(Tfin)+1)
Il valore di t non deve essere mai alterato dagli enunciati che compongono S
(se ci fosse consentito, si verrebbe a contraddire il presupposto che il numero di
ripetizioni noto a priori). Inoltre, eventuali modifiche di Tin ed Tfin interne ad S
non alterano il numero di iterazioni del blocco S stesso.
In MATLAB possibile, cos come avviene per la generazione degli,
intervalli specificare anche il passo di iterazione, in tale caso il costrutto assume la
seguente forma:
for t = 10 :-1:1
S
end
si esegue listruzione S comunque 10 volte con il contatore del ciclo che parte
da 10 ed arriva ad 1 in modalit decrescente.
Descrizione dellalgoritmo
Se si associano alle due informazioni due contenitori di liquidi, il problema diventa
quello del loro trasferimento da un contenitore allaltro senza che i liquidi si
mischiano. Risulta allora evidente che non c modo di effettuare tale trasferimento
se non introducendo un terzo contenitore che permette di svuotare uno degli altri
due contenitori. Si pu cos travasare il liquido il liquido dellaltro in quello che si
svuotato e infine ritrasferire in esso il liquido che stato spostato per primo nel
terzo contenitore. Si comprende allora che per scambiare due informazioni dello
stesso tipo necessario introdurre una terza variabile, ovviamente dello stesso tipo.
Ad esempio date le tre informazioni:
x [3] y [5] z[]
si copia dapprima x in z:
x [3] y [5] z[3]
Il linguaggio dellambiente MATLAB 271
poi y in x:
x [5] y [5] z[3]
ed infine z in y:
x [5] y [3] z[3]
lalgoritmo assume la seguente forma :
z=x;
x=y;
y=z;
Implementazione
% FUNZIONE PER LO SCAMBIO DI VALORE
% La funzione [x,y]=scambia(x,y)
% permette di scambiare il valore delle variabili x ed y.
% Esempio:
% x=3, y=5
% [x,y]=scambia(x,y)
% x=5, y=3
Esempio duso
Si riporta un esempio duso del programma mediante la shell (interprete dei
comandi) dellambiente MATLAB.
help scambia
FUNZIONE PER LO SCAMBIO DI VALORE
La funzione [x,y]=scambia(x,y)
permette di scambiare il valore delle variabili x ed y.
Esempio:
x=3, y=5
[x,y]=scambia(x,y)
x=5, y=3
x=9;
y=19;
[x,y]=scambia(x,y)
x = 19
y=9
Descrizione dellalgoritmo
Si pu pensare al vettore come ad uno scaffale di libri allineati verso sinistra. Per
far posto ad un nuovo libro in una posizione assegnata, bisogna spostare tutti i libri
che si trovano in posizione successiva a quella data di una posizione verso destra.
Lo spostamento pu essere fatto un libro alla volta, cominciando da quello in
ultima posizione. Dopo linserimento, il numero di libri o riempimento dello
scaffale aumentato di una unit. Si fa lipotesi che lo scaffale non sia mai pieno.
Nel caso in cui si debba inserire la lettera a nella seconda posizione di un vettore di
4 elementi la procedura per linserimento riportata di seguito:
[x, y, z, f]
si effettua lo spostamento (shift) di tutti gli elementi del vettore,a partire
dallultimo fino a quello in seconda posizione, di un posto a destra:
[x, , y, z, f]
si copia il nuovo elemento nella seconda posizione e si incrementa di una unit il
numero di elementi allinterno del vettore:
[x, a, y, z, f]
utilizzando la sintassi di Matlab, lalgoritmo assume la seguente forma :
for i=n:-1:posiz
v(i+1)=v(i);
end;
v(posiz)=info;
n=n+1;
Implementazione
% FUNZIONE PER L'INSERIMENTO IN UN VETTORE
% La funzione [v,n]=inserimento(v,n,info,posiz)
% permette di inserire nel vettore v di n elementi l'elemento info
% alla posizione posiz.
% Esempio:
% v=[1 2 4 5], n=4, info=3, posiz=3
% [v,n]=inserimento(v,n,info,posiz)
% v=[1 2 3 4 5], n=5
function [v,n]=inserimento(v,n,info,posiz)
Il linguaggio dellambiente MATLAB 273
% sposta gli elementi del vettore di un posto a destra a partire dallultimo fino
% a quello in posizione posiz
for i=n:-1:posiz
v(i+1)=v(i);
end;
% inserisci il nuovo elemento nella posizione specificata
v(posiz)=info;
% aumenta di una unit il numero di elementi nel vettore
n=n+1;
Esempio duso
Si riporta un esempio duso del programma mediante la shell (interprete dei
comandi) dellambiente MATLAB.
help inserimento
FUNZIONE PER L'INSERIMENTO IN UN VETTORE
La funzione [v,n]=inserimento(v,n,info,posiz)
permette di inserire nel vettore v di n elementi l'elemento info
alla posizione posiz.
Esempio:
v=[1 2 4 5], n=4, info=3, posiz=3
[v,n]=inserimento(v,n,info,posiz)
v=[1 2 3 4 5], n=5
v=[10 50 20 40 35];
n=5;
info=66;
posiz=3;
[v,n]=inserimento(v,n,info,posiz)
v =10 50 66 40 35
n=6
Descrizione dellalgoritmo
Si pu pensare al vettore come ad uno scaffale di libri allineati verso sinistra. Dopo
avere tolto il libro dalla posizione assegnata, bisogna spostare tutti quelli che si
trovano alla sua destra di un posto verso sinistra per recuperare lo spazio resosi
disponibile. Lo spostamento viene fatto un libro alla volta, cominciando da quello
successivo al libro nella posizione assegnata e si termina quando si arrivati
allultimo libro. Dopo leliminazione, il numero di libri o riempimento dello
scaffale diminuito di una unit. Si fa lipotesi che lo scaffale contenga almeno un
elemento. Nel caso in cui si l elemento in seconda posizione di un vettore di 4
elementi la procedura per leliminazione riportata di seguito:
[x, y, z, f]
si effettua lo spostamento (shift) di tutti gli elementi del vettore, a partire da quello
in terza posizione fino allultimo, di un posto a sinistra:
[x, z, f]
274 Capitolo settimo
Implementazione
% FUNZIONE PER L'ELIMINAZIONE IN UN VETTORE
% La funzione [v,n]=eliminazione(v,n,posiz)
% permette di eliminare dal vettore v di n elementi l'elemento
% alla posizione posiz.
% Esempio:
% v=[1 2 4 3], n=4, posiz=3
% [v,n]=eliminazione(v,n,posiz)
% v=[1 2 3], n=3
function [v,n]=eliminazione(v,n,posiz)
% sposta gli elementi del vettore di un posto a sinistra a partire
% da quello successivo alla posizione posiz fino all'ultimo
for i=posiz+1:1:n
v(i-1)=v(i);
end;
% diminuisce di una unit il numero di elementi nel vettore
n=n-1;
% aggiorna il vettore in maniera da considerare solo i primi n elementi
v=v(1:n);
Esempio duso
Si riporta un esempio duso del programma mediante la shell (interprete dei
comandi) dellambiente MATLAB.
help eliminazione
FUNZIONE PER L'ELIMINAZIONE IN UN VETTORE
La funzione [v1,n]=eliminazione(v,n,posiz)
permette di eliminare dal vettore v di n elementi l'elemento
alla posizione posiz.
Esempio:
v=[1 2 4 3], n=4, posiz=3
[v,n]=eliminazione(v,n,posiz)
v=[1 2 3], n=3
v=[22 50 30 16 10];
n=5;
posiz=2;
Il linguaggio dellambiente MATLAB 275
[v,n]=eliminazione(v,n,posiz)
v = 22 30 16 10
n=4
Descrizione dellalgoritmo
Leliminazione di una colonna in una matrice si effettua applicando lalgoritmo di
eliminazione di un elemento da un vettore a tutte le righe della matrice. Utilizzando
la sintassi di Matlab, lalgoritmo assume la seguente forma:
for j=col+1:1:M
for i=1:N
A(i,j-1)=A(i,j);
end;
end;
Implementazione
% FUNZIONE PER L'ELIMINAZIONE DI UNA COLONNA IN UNA MATRICE
% La funzione [A]=elimina_colonna(A,N,M,col)
% permette di eliminare la colonna col dalla matrice A di N righe e M colonne.
% Esempio:
% A=[1 2 3; 4 5 6], N=2, M=3, col=3
% [A]=elimina_riga(A,N,M,col)
% A=[1 2; 4 5]
function [A]=elimina_colonna(A,N,M,col)
% applica a tutte le righe della matrice l'algoritmo per l'eliminazione
% di un elemento nella posizione col assegnata
for j=col+1:1:M
for i=1:N
A(i,j-1)=A(i,j);
end;
end;
% diminuisce di una unit il numero di colonne
M=M-1;
% aggiorna la matrice
A=A(1:N,1:M);
276 Capitolo settimo
Esempio duso
Si riporta un esempio duso del programma mediante la shell (interprete dei
comandi) dellambiente MATLAB.
help elimina_colonna
FUNZIONE PER L'ELIMINAZIONE DI UNA COLONNA IN UNA MATRICE
La funzione [A]=elimina_colonna(A,N,M,col)
permette di eliminare la colonna col dalla matrice A di N righe e M colonne.
Esempio:
A=[1 2 3; 4 5 6], N=2, M=3, col=3
[A]=elimina_riga(A,N,M,col)
A=[1 2; 4 5]
A=[10 22 33 50; 20 54 80 41; 30 10 23 31];
N=3;
M=4;
col=2;
[A]=elimina_colonna(A,N,M,col)
A=
10 33 50
20 80 41
30 23 31
Descrizione dellalgoritmo
Leliminazione di una riga in una matrice si effettua applicando lalgoritmo di
eliminazione di un elemento da un vettore a tutte le colonne della matrice.
Utilizzando la sintassi di Matlab, lalgoritmo assume la seguente forma :
for i=riga+1:1:N
for j=1:M
A(i-1,j)=A(i,j);
end;
end;
Implementazione
% FUNZIONE PER L'ELIMINAZIONE DI UNA RIGA IN UNA MATRICE
% La funzione [A]=elimina_riga(A,N,M,riga)
% permette di eliminare la riga assegnata dalla matrice A di N righe e M colonne.
% Esempio:
Il linguaggio dellambiente MATLAB 277
function [A]=elimina_riga(A,N,M,riga)
% applica a tutte le colonne della matrice l'algoritmo per l'eliminazione
% di un elemento nella posizione riga assegnata
for i=riga+1:1:N
for j=1:M
A(i-1,j)=A(i,j);
end;
end;
% diminuisce di una unit il numero di righe
N=N-1;
% aggiorna la matrice
A=A(1:N,1:M);
Esempio duso
Si riporta un esempio duso del programma mediante la shell (interprete dei
comandi) dellambiente MATLAB.
help elimina_riga
FUNZIONE PER L'ELIMINAZIONE DI UNA RIGA IN UNA MATRICE
La funzione [A]=elimina_riga(A,N,M,riga)
permette di eliminare la riga assegnata dalla matrice A di N righe e M colonne.
Esempio:
A=[1 2 3; 4 5 6], N=2, M=3, riga=1
[A]=elimina_riga(A,N,M,riga)
A=[4 5 6]
A=[10 22 33 50; 20 54 80 41; 30 10 23 31];
N=3;
M=4;
riga=1;
[A]=elimina_riga(A,N,M,riga)
A=
20 54 80 41
30 10 23 31
Descrizione dellalgoritmo
La ricerca di un oggetto in un insieme si effettua fissando la strategia con la quale
si possono effettuare i confronti delloggetto cercato con quelli dellinsieme. Se
non si ha una conoscenza dellinsieme, lunico modo di procedere quello di
278 Capitolo settimo
prendere un oggetto alla volta e confrontarlo con quello dato fino a quando non se
ne trova uno uguale ad esso o stato visionato lintero insieme. Tale tipo di ricerca
detto sequenziale. La ricerca di uninformazione in un vettore che contiene valori
tra loro non ordinati procede in maniera sequenziale. Si comincia a confrontare il
primo elemento con quello ricercato. Poi il secondo, poi il terzo e cos via fin
quando il confronto non risulta verificato. In questo modo si verifica lassenza
dellelemento dopo averlo confrontato con tutti gli elementi del vettore. Nel caso
contrario, quando lo s incontra, si ferma lindagine facendo lipotesi che nel vettore
non esistano valori tra di loro uguali. Facciamo ora un esempio. Sia assegnato il
vettore:
v=[9 5 6 8 7]
(1 2 3 4 5)
(tra le parentesi tonde sono indicate le posizioni degli elementi nel vettore) e sia 4
il valore da ricercare. Allora lalgoritmo procede iterativamente nel seguente
modo:
passo confronto corrente trovato fine vettore
(posizione nel vettore)
1 4==9 no no
2 4==5 no no
3 4==6 no no
4 4==8 no no
5 4==9 no si
e la ricerca termina con lindicazione che 4 non presente nel vettore. Cerchiamo
quindi il valore 8:
Implementazione
% FUNZIONE PER LA RICERCA SEQUENZIALE DI UN ELEMENTO IN UN VETTORE
% La funzione [msg,posiz]=ricercaseq(v,info)
% permette di cercare l'elemento info nel vettore v.
% Se la ricerca ha successo viene restituita la posizione posiz
% dell'elemento nel vettore.
% msg una variabile che segnala l'esito della ricerca.
% Esempio:
% v=[1 2 8 9 5 6], info=8
% [msg,posiz]=ricercaseq(v,info)
% msg= 'elemento presente', posiz=3
function [msg,posiz]=ricercaseq(v,info)
% inizializzazione variabili locali
% variabile binaria che indica il successo del confronto
% (0 indica che l'elemento non stato ancora trovato,
% 1 il successo del confronto)
trovato=0;
% riempimento del vettore
n=length(v);
% cerca iterativamente l'elemento nel vettore confrontandolo con i suoi elementi
% dal primo all'ultimo
i=0;
posiz=0;
while (!trovato & i<n)
if (v[i]==info)
trovato=1;
posiz=i;
msg='elemento presente';
end
i++;
end
if trovato==0
msg='elemento non presente';
end
Esempio duso
Si riporta un esempio duso del programma mediante la shell (interprete dei
comandi) dellambiente MATLAB ed il confronto con la funzione find predefinita
nel linguaggio.
280 Capitolo settimo
help ricercaseq
FUNZIONE PER LA RICERCA SEQUENZIALE DI UN ELEMENTO IN UN VETTORE
La funzione [msg,posiz]=ricercaseq(v,info)
permette di cercare l'elemento info nel vettore v.
Se la ricerca ha successo viene restituita la posizione posiz
dell'elemento nel vettore.
msg una variabile che segnala l'esito della ricerca.
Esempio:
v=[1 2 8 9 5 6], info=8
[msg,posiz]=ricercaseq(v,info)
msg= 'elemento presente', posiz=3
v=[9 5 6 8 7];
info=8;
[msg,posiz]=ricercaseq(v,info)
posiz=find(v==8)
posiz =4
Descrizione dellalgoritmo
La ricerca di una informazione in un elenco si pu effettuare come visto nel caso
precedente confrontando uno dopo laltro i valori dellelenco con quello cercato
fino a quando non si trova un elemento uguale o non si analizzato lintero elenco.
Il metodo sequenziale richiede nel caso peggiore n confronti, dove n il
riempimento del vettore. Se lelenco ordinato, si ricorre allora ad una ricerca che
ad ogni passo riduce linsieme in cui cercare mediante unopportuna tecnica di
dimezzamento. In questo modo il caso peggiore richiede al pi log2(n) confronti. Il
metodo noto col nome di ricerca binaria si basa sui seguenti passi:
1. determinazione del punto medio dellinsieme in cui cercare;
2. confronto dellelemento in questa posizione con quello da cercare;
3. individuazione dellinsieme in cui continuare la ricerca se il passo 2 non
ha successo; esso risulta per vettore ordinato in senso crescente: il
sottoinsieme degli elementi in posizioni successive al punto medio se il
valore da cercare maggiore di quello del punto medio, altrimenti quello
caratterizzato da posizioni inferiori;
4. ripetizioni dei passi precedenti finch il passo 2 non risulti verificato o
non sia possibile fissare un sottoinsieme in cui continuare la ricerca.
Facciamo ora un esempio. Sia assegnato il vettore:
Il linguaggio dellambiente MATLAB 281
v=[15 22 29 36 50 55]
(1 2 3 4 5 6)
(tra le parentesi tonde sono indicate le posizioni degli elementi nel vettore) e sia 21
il valore da ricercare. Allora lalgoritmo procede iterativamente nel seguente
modo:
passo punto confronto prossimo fine trovato
medio corrente sottoinsieme
di ricerca
1 29 21<29 [15, 22] no no
2 15 21>15 [22] no no
3 22 21<22 [22, 15] si no
Implementazione
% FUNZIONE PER LA RICERCA BINARIA DI UN ELEMENTO IN UN VETTORE
% La funzione [msg,posiz]=ricercabin(v,info)
% permette di cercare l'elemento info nel vettore v.
% Se la ricerca ha successo viene restituita la posizione posiz
% dell'elemento nel vettore.
% msg una variabile che segnala l'esito della ricerca.
% Esempio:
% v=[1 2 8 9 5 6], info=8
% [msg,posiz]=ricercabin(v,info)
% msg= 'elemento presente', posiz=3
% Nota: la ricerca applicabile solo se il vettore d'ingresso ordinato
function [msg,posiz]=ricercabin(v,info)
% inizializza le varibili locali
% posizione dell'elemento all'interno del vettore
% (se l'elemento non viene trovato la sua posizione nulla)
posiz=0;
% variabile binaria che indica il successo del confronto
% (0 indica che l'elemento non stato ancora trovato,
% 1 il successo del confronto)
trovato=0;
% estremi superiore e inferiore dei sottoinsieme di ricerca
% (inizialmente il sottoinsime di ricerca coincide con l'intero vettore)
ei=1;
es=length(v);
% inizio ricerca
% la condizione di continuazione della ricerca che l'estremo inferiore del
% sottoinsieme corrente di ricerca sia minore di quello superiore e che lelemento
% non sia stato ancora trovato
while (!trovato & ei<es+1)
% calcolo del punto medio
medio=floor((ei+es)/2);
% se l'elmento viene trovato pone ad 1 la variabile trovato
% e arresta la ricerca
if (info==v(medio))
trovato=1;
end;
% se l'elmento non viene trovato aggiorna gli estremi del sottoinsieme di ricerca
% a seconda che l'info da ricercare sia minore o maggiore del punto medio
if (info<v(medio))
es=medio-1;
else ei=medio+1;
end;
end;
% a secona dell'esito della ricerca aggiorna la variabile msg
if (trovato==1)
msg='elemento presente';
posiz=medio;
else
msg='elemento non presente';
end;
Il linguaggio dellambiente MATLAB 283
Esempio duso
Si riporta un esempio duso del programma mediante la shell (interprete dei
comandi) dellambiente MATLAB.
help ricercabin
FUNZIONE PER LA RICERCA BINARIA DI UN ELEMENTO IN UN VETTORE
La funzione [msg,posiz]=ricercabin(v,info)
permette di cercare l'elemento info nel vettore v.
Se la ricerca ha successo viene restituita la posizione posiz
dell'elemento nel vettore.
msg una variabile che segnala l'esito della ricerca.
Esempio:
v=[1 2 8 9 5 6], info=8
[msg,posiz]=ricercabin(v,info)
msg= 'elemento presente', posiz=3
Nota: la ricerca applicabile solo se il vettore d'ingresso ordinato
v=[15 22 29 36 50 55];
info=21;
[msg,posiz]=ricercabin(v,info)
msg = elemento non presente
posiz = 0
info=50;
[msg,posiz]=ricercabin(v,info)
msg = elemento presente
posiz = 5
Descrizione dellalgoritmo
Lindividuazione del massimo in un insieme si effettua osservando uno dopo
laltro gli elementi che lo compongono e memorizzando di volta in volta il valore
pi grande. In particolare, si inizia facendo lipotesi che il massimo sia il primo
elemento del vettore e successivamente lo si confronta con i restanti valori del
vettore. Ogni volta che si incontra un valore pi grande, si effettua laggiornamento
del massimo. Facciamo ora un esempio. Sia assegnato il vettore:
v=[3 5 6 8 7]
(1 2 3 4 5)
(tra le parentesi tonde sono indicate le posizioni degli elementi nel vettore).
Allora lalgoritmo procede iterativamente nel seguente modo:
Implementazione
% FUNZIONE PER IL CALCOLO DEL MASSIMO DI UN VETTORE
% La funzione [max]=massimo(v)
% permette di calcolare l'elemento massimo max del vettore v.
% Esempio:
% v=[2 4 8 7 5]
% [max]=massimo(v)
% max=8
function [max]=massimo(v)
% calcolo riempimento vettore
n=length(v);
% inizializza il massimo al primo elemento del vettore
max=v(1);
% iterativamente scorre il vettore e verifica se ci sia qualche
% elemento maggiore del massimo
for i=2:n
% se l'elmento corrente maggiore del massimo aggiorna il massimo
if (v(i)>max)
max=v(i);
end
end
Esempio duso
Si riporta un esempio duso del programma mediante la shell (interprete dei
comandi) dellambiente MATLAB.
help massimo
FUNZIONE PER IL CALCOLO DELA MASSIMO DI UN VETTORE
La funzione [max]=massimo(v)
permette di calcolare l'elemento massimo max del vettore v.
Esempio:
v=[2 4 8 7 5]
[max]=massimo(v)
Il linguaggio dellambiente MATLAB 285
max=8
v=[3 5 6 8 7];
[max]=massimo(v)
max = 8
Descrizione dellalgoritmo
Lindividuazione della posizione del minimo in un insieme si effettua osservando
uno dopo laltro gli elementi che lo compongono e memorizzando di volta in volta
la posizione dellelemento avente il valore pi piccolo. In particolare, si inizia
facendo lipotesi che la posizione del minimo sia quella del primo elemento del
vettore. Successivamente si confronta lelemento nella presunta posizione di
minimo con i restanti valori del vettore. Ogni volta che si incontra un valore pi
piccolo, si effettua laggiornamento della posizione in modo che alla fine dei
confronti si abbia la posizione del minimo. Si fa lipotesi che nel vettore non
esistano elementi uguali. Facciamo ora un esempio. Sia assegnato il vettore:
v=[9 5 6 3 7]
(1 2 3 4 5)
(tra le parentesi tonde sono indicate le posizioni degli elementi nel vettore).
Allora lalgoritmo procede iterativamente nel seguente modo:
Implementazione
% FUNZIONE PER IL CALCOLO DELLA POSIZIONE DEL MINIMO DI UN VETTORE
% La funzione [posiz]=posmin(v)
% permette di calcolare la posizione dell'elemento minimo posiz del vettore v.
% Esempio:
% v=[2 4 8 7 5]
% [posiz]=posmin(v)
% posiz=1
function [posiz]=posmin(v)
% calcolo riempimento vettore
n=length(v);
% inizializza la posizione del minimo alla prima nel vettore
posiz=1;
% iterativamente scorre il vettore e verifica se ci sia qualche elemento
% minore di quello nella presunta posizione di minimo
for i=2:n
% se l'elmento corrente minore aggiorna la posizione
if (v(posiz)>v(i))
posiz=i;
end
end
Esempio duso
Si riporta un esempio duso del programma mediante la shell (interprete dei
comandi) dellambiente MATLAB.
help posmin
FUNZIONE PER IL CALCOLO DELLA POSIZIONE DEL MINIMO DI UN VETTORE
La funzione [posiz]=posmin(v)
permette di calcolare la posizione dell'elemento minimo posiz del vettore v.
Esempio:
v=[2 4 8 7 5]
[posiz]=posmin(v)
posiz=1
v=[9 5 6 3 7];
[posiz]=posmin(v)
posiz = 4
Descrizione dellalgoritmo
Lindividuazione del minimo e del massimo in un insieme si effettua osservando
uno dopo laltro gli elementi che lo compongono e memorizzando di volta in volta
la posizione i valori dellelemento avente il valore pi piccolo e pi grande. In
Il linguaggio dellambiente MATLAB 287
particolare, si inizia facendo lipotesi che il primo elemento della matrice sia
contemporaneamente il massimo ed il minimo e successivamente lo si confronta
con i restanti valori della matrice. Ogni volta che si incontra un valore pi piccolo
o pi grande, si effettua laggiornamento del minimo e del massimo. Utilizzando la
sintassi di Matlab, lalgoritmo assume la seguente forma :
min=A(1,1);
max=min;
for i=1:n
for j=1:m
if (A(i,j)>max)
max=A(i,j);
else
if (A(i,j)<min)
min=A(i,j);
end
end
end
end
Implementazione
% FUNZIONE PER IL CALCOLO DEL MINIMO e MASSIMO DI UNA MATRICE
% La funzione [min,max]=minmax(A)
% permette di calcolare il minimo e massimo (min e max) di una matrice A.
% Esempio: A=[1 2;3 4]
% [min,max]=minmax(A)
% min=1, max=4
function [min,max]=minmax(A)
% calcolo dimensioni matrice
[n,m]=size(A);
% inizializza il massimo e il minimo al primo elmento della matrice
min=A(1,1);
max=min;
% iterativamente scorre la matrice e verifica se ci sia qualche
% elemento minore o maggiore
% di quello minimo o massimo presunto
for i=1:n
for j=1:m
% se l'elmento corrente maggiore del massimo aggiorna il massimo
if (A(i,j)>max)
max=A(i,j);
else
288 Capitolo settimo
Esempio duso
Si riporta un esempio duso del programma mediante la shell (interprete dei
comandi) dellambiente MATLAB.
help minmax
FUNZIONE PER IL CALCOLO DEL MINIMO e MASSIMO DI UNA MATRICE
La funzione [min,max]=minmax(A)
permette di calcolare il minimo e massimo (min e max) di una matrice A.
Esempio:
A=[1 2;3 4]
[min,max]=minmax(A)
min=1, max=4
Descrizione dellalgoritmo
Ordinare un vettore in senso crescente significa imporre che scelti due qualsiasi
indici i e j, tali che i<j risulti v(i)<v(j). Un meccanismo di ordinamento consiste nel
dividere linsieme da ordinare in due parti: una ordinata ed una disordinata. Si
procede allora estraendo un elemento alla volta dallinsieme disordinato e
accodandolo a quello ordinato in modo che lordinamento non venga alterato.
Allinizio linsieme ordinato vuoto e lalgoritmo termina quando contiene tutti gli
elementi del vettore. Vi sono vari modi di estrarre lelemento dallinsieme
disordinato. Quello che di seguito presentiamo seleziona ad ogni passo lelemento
da accodare. In particolare seleziona il minimo dallinsieme disordinato e lo accoda
allinsieme ordinato. In tal modo si viene ad ogni passo a scegliere il valore pi
grandi di quelli che lo precedono e contemporaneamente pi piccolo di quelli che
lo seguono. Facciamo ora un esempio. Sia assegnato il vettore:
v=[9 2 1 4 7]
(1 2 3 4 5)
(tra le parentesi tonde sono indicate le posizioni degli elementi nel vettore).
Allora lalgoritmo procede iterativamente nel seguente modo:
for i=1:n-1
imin=i;
for j=i+1:n
if (v(j)<v(imin))
imin=j;
end
end
temp=v(i);
v(i)=v(imin);
v(imin)=temp;
end
Implementazione
% FUNZIONE PER L'ORDINAMENTO DI UN VETTORE
% La funzione [v]=selectsort(v)
% permette di ordinare in senso crescente il vettore v.
% Esempio: v=[4 1 3 2 5]
% [v]=selectsort(v)
% v=[1 2 3 4 5]
function [v]=selectsort(v)
imin=i;
% determina il minimo per la parte disordinata
for j=i+1:n
if (v(j)<v(imin))
imin=j;
end
end
% effettua l'accodamento scambiando di posto l'elemento minimo e il valore
% che occupa la posizione di accodamento
temp=v(i);
v(i)=v(imin);
v(imin)=temp;
end
Esempio duso
Si riporta un esempio duso del programma mediante la shell (interprete dei
comandi) dellambiente MATLAB.
help selectsort
FUNZIONE PER L'ORDINAMENTO DI UN VETTORE
La funzione [v]=selectsort(v)
permette di ordinare in senso crescente il vettore v.
Esempio:
v=[4 1 3 2 5]
[v]=selectsort(v)
v=[1 2 3 4 5]
v=[9 2 1 4 7];
[v]=selectsort(v)
v=1 2 4 7 9
Descrizione dellalgoritmo
Ordinare un vettore in senso crescente significa imporre che scelti due qualsiasi
indici i e j, tali che i<j risulti v(i)<v(j). Un meccanismo di ordinamento consiste nel
dividere linsieme da ordinare in due parti: una ordinata ed una disordinata. Si
procede allora estraendo un elemento alla volta dallinsieme disordinato e
accodandolo a quello ordinato in modo che lordinamento non venga alterato.
Allinizio linsieme ordinato vuoto e lalgoritmo termina quando contiene tutti gli
elementi del vettore. Vi sono vari modi di estrarre lelemento dallinsieme
disordinato. Quello che di seguito presentiamo fa in modo che il minimo
dellinsieme disordinato gorgogli in prima posizione cos come una bolla daria
sale dal fondo alla superficie. In questo modo si viene ad ogni passo a scegliere il
valore pi grandi di quelli che lo precedono e contemporaneamente pi piccolo di
quelli che lo seguono. Il gorgogliamento viene eseguito confrontando a due a due
gli elementi dellinsieme disordinato ed effettuando lo scambio di posizione
Il linguaggio dellambiente MATLAB 291
scambio=1;
i=1;
while (i<n & scambio==1)
scambio=0;
for j=i+1:n
if (v(j)<v(i))
temp=v(i);
v(i)=v(j);
v(j)=temp;
scambio=1;
end
end
i=i+1;
end
Implementazione
% FUNZIONE PER L'ORDINAMENTO DI UN VETTORE
% La funzione [v]=bubblesort(v)
% permette di ordinare in senso crescente il vettore v.
% Esempio:
% v=[4 1 3 2 5]
% [v]=bubblesort(v)
% v=[1 2 3 4 5]
function [v]=bubblesort(v)
% variabili locali
% calcolo del riempimento
n=length(v);
% variabile locale che segnala se nell'iterazione corrente ci sono stati scambi
scambio=1;
% contatore di ciclo
i=1;
% ciclo di ordinamento
while (i<n & scambio==1)
scambio=0;
for j=i+1:n
% spostamento del valore minimo verso la parte ordinata
if (v(j)<v(i))
temp=v(i);
v(i)=v(j);
v(j)=temp;
scambio=1;
end
end
i=i+1;
end
Esempio duso
Si riporta un esempio duso del programma mediante la shell (interprete dei
comandi) dellambiente MATLAB.
help bubblesort
FUNZIONE PER L'ORDINAMENTO DI UN VETTORE
La funzione [v]=bubblesort(v)
permette di ordinare in senso crescente il vettore v.
Esempio:
v=[4 1 3 2 5]
[v]=bubblesort(v)
v=[1 2 3 4 5]
v=[9 2 1 4 7];
[v]=bubblesort(v)
v=1 2 4 7 9
Il linguaggio dellambiente MATLAB 293
Esercizio 2
Data una matrice di numeri reali scrivere una funzione che:
1. Memorizzi, nel caso la matrice sia quadrata, in un vettore di uscita gli
elementi della diagonale.
2. Calcoli, nel caso di matrice quadrata, la media degli elementi della
diagonale.
3. Segnali errore nel caso in cui la matrice sia non quadrata.
function [msg,media]=esercizio2(A)
[n,m]=size(A);
if (n==m)
media=0;
for i=1:n
media=media+A(i,i);
end
media=media/n;
msg='';
else
msg='error: matrix must be square';
media='NaN';
end
294 Capitolo settimo
Esercizio 3
Dato un vettore di numeri reali ordinato e due suoi elementi a e b, tali che a<=b,
scrivere una funzione che memorizzi in un vettore di uscita tutti gli elementi del
vettore di in ingresso compresi tra a e b. Esempio.: v_in=[1,3,6,8,70,555], a=6,
b=70, v_out=[70].
function [vout]=esercizio3(vin,a,b)
n=length(vin);
if (a<b)
indice1=1;
indice2=1;
for i=1:n
if (vin(i)==a)
indice1=i;
break;
end
end
for j=1:n
if (vin(j)==b)
indice2=j;
break;
end
end
vout=vin(indice1+1:1:indice2-1);
else
vout='NaN';
end
Esercizio 4
Dato un vettore di caratteri (stringa) scrivere una funzione che:
1. Memorizzi in un vettore di uscita la stringa dingresso invertita.
2. Calcoli il tipo di lettera (a,b,c,d,e,f,) e il numero di caratteri presenti
nella stringa dingresso.
Esempio: v_in=hello, v_out=olleh, ris=a 0, b 0,,e 1, , h 1,, l 2, , o 1,
, z 0
function [vout]=esercizio4(vin)
n=length(vin);
alfabeto=['a','b','c','d','e','f','g','h','i','l','m','n','o','p','q','r','s','t','u','v','z'];
for i=1:n
vout(n+1-i)=vin(i);
end
% la funzione zeros crea 1 array di interi della stessa dimensione del vettore
% alfabeto ed inizializzati a 0
cont=zeros(1,length(alfabeto));
for i=1:n
for j=1:length(alfabeto)
if (vin(i)==alfabeto(j))
cont(j)=cont(j)+1;
break;
end
end
end
for i=1:length(alfabeto)
Il linguaggio dellambiente MATLAB 295
fprintf('%c ',alfabeto(i));
fprintf('%d\n',cont(i));
end
Esercizio 5
Dato un vettore di numeri reali scrivere una funzione che:
1. Effettui lo shift (spostamento) a destra con linserimento di uno zero in
testa degli elementi del vettore di ingresso (in questo caso si perde
lelemento di coda). Esempio.: v_in=[1,2,3,4], v_out=[0,1,2,3]
2. Effettui lo shift (spostamento) circolare a destra con linserimento
dellelemento di coda in testa. Esempio.: v_in=[1,2,3,4], v_out=[4,1,2,3]
function [v_out1, v_out2]=esercizio5(v_in)
n=length(v_in);
for i=2:n
v_out1(i)=v_in(i-1);
v_out2(i)=v_in(i-1);
end;
v_out1(1)=0;
v_out2(1)=v_in(n);
Esercizio 6
Dato un vettore di numeri reali scrivere una funzione che:
1. Effettui lo shift (spostamento) a sinistra con linserimento di uno zero in
coda degli elementi del vettore di ingresso (in questo caso si perde
lelemento di testa). Esempio.: v_in=[1,2,3,4], v_out=[2,3,4,0]
2. Effettui lo shift (spostamento) circolare a sinistra con linserimento
dellelemento di testa in coda. Esempio.: v_in=[1,2,3,4], v_out=[2,3,4,1]
function [v_out1, v_out2]=esercizio6(v_in)
n=length(v_in);
for i=1:n-1
v_out1(i)=v_in(i+1);
v_out2(i)=v_in(i+1);
end;
v_out1(n)=0;
v_out2(n)=v_in(1);
Esercizio 7
Dato un vettore di numeri interi scrivere una funzione che:
1. Memorizzi in primo vettore di uscita solo i numeri pari.
2. Memorizzi in un secondo vettore di uscita solo i numeri dispari.
Esempio.: v_in=[1 2 33 11 8], v_out1=[2 8], vout2=[1 33 11]
function [pari,dispari]=esercizio7(v)
n=length(v);
indice_pari=1;
indice_dispari=1;
for i=1:n
% la funzione mod(x,y) restituisce il resto in modulo della divisione di x per y
if mod(v(i),2)==0
296 Capitolo settimo
pari(indice_pari)=v(i);
indice_pari=indice_pari+1;
else
dispari(indice_dispari)=v(i);
indice_dispari=indice_dispari+1;
end
end
Esercizio 8
Dato una matrice di numeri reali scrivere una funzione che:
1. Memorizzi in un primo vettore di uscita, una dietro laltra, le righe pari.
2. Memorizzi in un secondo vettore di uscita, una dietro laltra, le righe
dispari.
Es.: A=[1 2 3;4 5 6;7 8 9], v1=[4 5 6], v2=[1 2 3 7 8 9]
function [v1,v2]=esercizio8(A)
[n,m]=size(A);
i1=1;
i2=1;
for i=1:n
if mod(i,2)==0
for j=1:m
v1(i1)=A(i,j);
i1=i1+1;
end
else
for j=1:m
v2(i2)=A(i,j);
i2=i2+1;
end
end
end
Il linguaggio dellambiente MATLAB 297
Esercizio 9
Sia assegnato un vettore di numeri reali su un file di ingresso, scrivere una
funzione che:
1. Legga e memorizzi in un vettore gli elementi dal file dingresso.
2. Effettui lordinamento del vettore utilizzando una funzione esterna.
3. Scriva su un file duscita il vettore ordinato.
In questo caso si utilizzano due funzioni, una per lordinamento e una per
linput/output da file.
function [v]=selectsort(v)
n=length(v);
for i=1:n-1
imin=i;
for j=i+1:n
if (v(j)<v(imin))
imin=j;
end
end
temp=v(i);
v(i)=v(imin);
v(imin)=temp;
end
function esercizio9(pathin,pathout)
fid1=fopen(pathin,'r');
v=fscanf(fid1,'%g');
v=selectsort(v);
fclose(fid1);
fid2=fopen(pathout,'w');
fprintf(fid2,'%g ',v);
fclose(fid2);
Nota: per lordinamento si usata la funzione selectsort vista in precedenza.
Esercizio 10
Sia assegnato un vettore sparso (contente molti elementi nulli) di numeri reali su
un file di ingresso, scrivere una funzione che:
1. Legga e memorizzi su un vettore gli elementi da file dingresso.
2. A partire dal vettore ottenuto costruisca un vettore di uscita avente come
elementi solo quelli non nulli (split).
function [vout]=esercizio10(pathin)
fid1=fopen(pathin,'r');
v=fscanf(fid1,'%g');
n=length(v);
j=1;
for i=1:n
if v(i)~=0
vout(j)=v(i);
j=j+1;
end
end
298 Capitolo settimo
Esercizio 11
Scrivere un programma per il calcolo della media degli esami sostenuti e del voto
maggiore.In questo caso si utilizzano tre funzioni, una per il calcolo del massimo
di un vettore, una per il calcolo della media e una per la gestione dellinput/output.
function [max]=massimo_esami(v_input)
max=v_input(1);
n=length (v_input);
for i=2:n
if (max<v_input(i))
max=v_input(i);
end
end
function [media]=media_esami(v_input)
n=length(v_input);
media=0;
for i=1:n
media=media+v_input(i);
end
media=media/n;
function esercizio11
fprintf('\nProgramma per la creazione di statistche sugli esami sostenuti\n');
fprintf('\nSezione per la raccolta dei dati anagrafici\n');
nome=input('Inserire nome:','s');
cognome=input('Inserire cognome:','s');
matr=input('Inserire matricola:','s');
fprintf('\nSezione per la raccolta dei dati relativi agli esami sostenuti\n');
num_esami=input('Inserire numero di esami sostenuti:');
for i=1:num_esami
fprintf('Inserire votazione relativa esame ');
fprintf('%d',i);
voto_esami(i)=input(':');
end
media=media_esami(voto_esami);
voto_massimo=massimo_esami(voto_esami);
fprintf('\nSezione relativa alle statistiche calcolate\n');
fprintf('\nLa media dei tuoi esami : ');
fprintf('%d',media);
fprintf('\nLa tua votazione migliore : ');
fprintf('%d',voto_massimo);
Capitolo ottavo
8.1. Introduzione
La realizzazione di un programma un'attivit complessa che va dalla scelta di un
algoritmo alla sua implementazione in un linguaggio di programmazione. Per il
programmatore il linguaggio di programmazione si configura come una macchina
virtuale che analizza il testo del programma e lo rende eseguibile secondo delle
regole fissate dal linguaggio stesso.
In realt, la traduzione del testo del programma - detto anche testo sorgente o
origine - in operazioni eseguibili sulla macchina reale, avviene secondo due
modalit diverse:
- mediante una preventiva traduzione del testo origine nel linguaggio della
macchina reale e successiva esecuzione del programma tradotto in
linguaggio macchina;
- od attraverso una contestuale interpretazione del testo origine e sua
esecuzione.
In generale un traduttore trasforma un testo espresso in un certo linguaggio A
in quello di un linguaggio B, detto oggetto o target: A un linguaggio simbolico, B
un linguaggio prossimo a quello della macchina, ma non necessariamente
coincidente con esso. Tradizionalmente, bench nei vari casi il processo di
traduzione sia concettualmente lo stesso, i traduttori assumono nomi diversi
secondo il tipo di linguaggio sorgente e del linguaggio oggetto. In particolare, si
distinguono i seguenti traduttori.
- i compilatori che sono i traduttori pi diffusi e utilizzati. Il linguaggio
sorgente un linguaggio ad alto livello (ad esempio C, C++, Pascal,
FORTRAN ), mentre il linguaggio oggetto un linguaggio prossimo a
quello macchina.
- i pre-compilatori che traducono da linguaggio sorgente a linguaggio
sorgente permettendo di estendere a basso costo le caratteristiche di un
linguaggio esistente per il quale sia disponibile un traduttore. In tal caso, il
processo di traduzione avviene in due fasi distinte: una prima traduzione,
ad opera del pre-compilatore, in un linguaggio sorgente in cui i costrutti
estesi sono ricondotti a sequenze elementari, e una seconda fase di
traduzione, ad opera del compilatore, di questa forma espansa in
linguaggio oggetto.
300 Capitolo ottavo
#include <stdio.h>
main()
{
printf(salve, mondo \n);
}
Il prototipo della funzione printf definita nel file header stdio.h, che una
delle librerie messe a disposizione dal linguaggio per svincolare il programmatore
dalla conoscenza dellinsieme di operazioni, sicuramente non banali, per la
scrittura di una stringa su video.
Quando il compilatore incontra il nome della funzione printf, si limita a
prevedere per essa un richiamo, senza per essere in grado di stabilire dove dovr
La traduzione dei programmi 301
permettono di costruire frasi corrette, ed infine non tutte le frasi corrette hanno un
significato valido.
Le regole di costruzione delle parole a partire dallalfabeto sono dette regole
lessicali. Il modo con cui le parole sono concatenate tra loro per costruire frasi
corrette nel linguaggio costituisce la sintassi del linguaggio, mentre il significato
da attribuire alla frase riguarda la semantica del linguaggio. Linsieme delle regole
lessicali e sintattiche costituisce la grammatica del linguaggio. Le regole della
grammatica di un linguaggio vengono illustrate attraverso opportuni
metalinguaggi, come la BNF (Backus Naur Form) o le carte sintattiche.
La figura 2 mostra come le regole della grammatica del linguaggio
condizionano le fasi in cui si articola il processo di traduzione.
Analisi
Analisi Lessicale
Analisi Semantica
x=100;
vengono riconosciuti come validi i tre token (x, =, 100). Mentre ai token
corrispondenti ai simboli chiave del linguaggio non viene aggiunta altro che la loro
classificazione, per quanto attiene x e 100, si aggiungono informazioni quali il
fatto che 100 una costante intera e x un identificatore associato ad un tipo, che
tale identificatore ha una certa visibilit associata al luogo in cui la variabile stata
dichiarata. Lo scanning, infine, costruisce una rappresentazione dellistruzione del
tipo: identificatore, operatore di assegnamento, costante, punto e virgola.
Linsieme delle informazioni ricavate dallo scanning, saranno utili per la fase
di sintesi del codice e verranno perci conservate in apposite tabelle gestite dal
compilatore. Le tabelle sono generate durante la fase di scanning, ma vengono
arricchite anche nel corso della fase di analisi.
La fase successiva a quella di scanning la fase di analisi sintattica o parsing.
Lanalizzatore sintattico, o parser, raggruppa i token in strutture sintattiche
La traduzione dei programmi 303
rappresentate graficamente dagli alberi sintattici. Questi ultimi sono alberi i cui
nodi sono gli operatori del linguaggio e le foglie gli operandi. Ad esempio
listruzione:
x = (a + b) * c
= *
x
c
+
a b
viene trasformata dal parser in una forma pi generale indipendente dalla
specifica frase utilizzando la descrizione dei token con le annesse informazioni
estratte e collocate nelle tabelle interne, ottenendo lalbero di seguito.
= *
identificator
+ identificator
identificator identificator
Solo dopo aver terminato la fase di analisi senza aver rilevato errori il
compilatore pu avviare la fase di sintesi con la generazione del codice in
linguaggio macchina effettivamente eseguibile da una macchina reale. Sono
disponibili gli alberi sintattici con la classificazione delle diverse componenti del
programma e le informazioni riportate nella tabella dei simboli.
Lapproccio pi semplice della fase di sintesi quello di scandire i diversi
alberi e le tabelle e di procedere con una generazione delle singole istruzioni in
linguaggio macchina senza tener conto di possibili ottimizzazioni legate alla
struttura del calcolatore . Ad esempio, dato il blocco:
x = a+b ;
y = a+b ;
che inaccettabile dal punto di vista dellefficienza. Si noti infatti che il risultato
della prima somma (uguale alla seconda) permane nellaccumulatore senza essere
modificato dalla operazione di store in x.
Situazione che il compilatore scopre anche analizzando i due alberi sintattici
rappresentativi delle due istruzioni di assegnazione. In tale caso il compilatore
procede alla ottimizzazione del codice eliminando la seconda ed inutile operazione
di ADD come il codice che segue dimostra.
ADD a,b
STOREA x
STOREA y
x = y =a + b;
La traduzione dei programmi 305
La prima parte del modulo oggetto contiene il nome del modulo, pi altre
informazioni necessarie al linker come le lunghezze delle varie strutture dati del
modulo ed eventualmente la data di compilazione. La seconda parte del modulo
oggetto una lista di simboli interni al modulo a cui possono far riferimento altri
moduli (simboli esportati). La terza parte contiene una lista di simboli usati nel
modulo, ma definiti in altri moduli (simboli importati). La quarta parte del modulo
contiene il codice compilato e le costanti: cio la parte che verr caricata in
memoria per essere eseguita. La quinta parte il dizionario di rilocazione dove
vengono forniti gli indirizzi base e le costanti di spiazzamento relative. La sesta ed
ultima parte contiene una indicazione di fine modulo, a volte un controllo per
rilevare eventuali errori di lettura del modulo e lindirizzo da cui iniziare
lesecuzione.
Molti linker richiedono due passi. Nel primo passo il linker legge tutti i
moduli oggetto e costruisce una tabella dei nomi e delle lunghezze dei moduli oltre
ad una tabella dei simboli globali costituita dai riferimenti esterni. Nel passo due i
moduli oggetto vengono scanditi di nuovo, si calcolano i nuovi indirizzi di
memoria (rilocazione) e si collegano. Pi nel dettaglio, lalgoritmo del linker si
compone dei seguenti passi.
- Costruzione di una tabella con tutti i moduli oggetto con indicazione della
loro dimensione.
- Assegnazione di un indirizzo di caricamento ad ogni modulo riportato in
tabella.
- completamento delle istruzioni che contengono un indirizzo di memoria
con la definizione delleffettivo indirizzo di memoria. Per agevolare tale
lavoro il compilatore non fissa allinterno dei singoli moduli degli
indirizzi definitivi, ma assume che lallocazione in memoria per ogni
modulo compilato avvenga a partire dallindirizzo zero. Cos lindirizzo
fissato dal collegatore, detto anche costante di rilocazione, uguale al
primo indirizzo occupato dal modulo, deve essere sommato a tutti gli
indirizzi presenti nelle istruzioni perch si completi il piano effettivo di
allocazione in memoria.
- Risoluzione dei richiami di dati e procedure esterne ad un modulo. Il
compilatore infatti, in presenza di richiami a moduli esterni non completa
la traduzione perch non pu conoscere dove tali moduli verranno
allocati. Solo il collegatore, che provvede alla disposizione in memoria di
tutti i moduli aggregare, pu quindi il completare la definizione degli
indirizzi nel caso di istruzioni che fanno riferimento ad identificatori non
locali, quali ad esempio i richiami di sottoprogrammi e funzioni.
In figura seguente viene mostrato il linking di tre moduli oggetto A,B,C (nella
figura con JSR si indica il salto a sottoprogramma e con RTS il ritorno al
programma chiamante).
La traduzione dei programmi 307
8.2.3. Il caricamento
Il caricatore ha il compito di disporre nei registri di memoria sia il codice che i dati
del programma da eseguire e di provvedere alla sua attivazione.
Quando i calcolatori erano utilizzati da un solo utente per volta, il problema
del caricamento e dellattivazione di un programma era di facile soluzione: i
programmi erano tradotti in una forma oggetto detta binario assoluto, una forma
che era limmagine esatta del programma da eseguire. In essa tutti i riferimenti
interni e gli indirizzi di caricamento erano definiti dai traduttori e dal linker. Per
poter essere eseguito, il codice binario assoluto deve essere caricato nelle posizioni
esatte per cui stato definito: loperazione di caricamento si riduce quindi alla
308 Capitolo ottavo
-- INIZIO PROGRAMMA
100
101 JSR 200
102
..
300 END
-- FINE PROGAMMA
-- INIZIO PROGRAMMA
1000
1100
1101 JSR 1200
1102
..
1300 END
-- FINE PROGAMMA
Figura 5: Rilocazione di un codice con base di rilocazione B=1000
sino a quando non emergono dallincapacit del sistema a fare quanto richiesto. I
secondi vengono introdotti durante la progettazione o la codifica e diverse possono
esserne la causa: scarsa conoscenza del linguaggio, assunzioni non verificate,
complessit del progetto, e molte volte semplice distrazione.
Le attivit di testing rivestono un ruolo importante nello sviluppo del software
in quanto provvedono a verificare la correttezza dei programmi. importante
sottolineare che la rilevazione degli errori deve avvenire quanto prima possibile per
evitare che tutto quanto sviluppato in condizioni non corrette risulti inutile.
Le tecniche di testing, intese come ricerca degli errori, includono metodi di
prevenzione che portano a verificare i sistemi a partire dalla fase di progettazione.
Inoltre importante che durante la fase di analisi si provveda alla definizione di
piani di test da attuare successivamente a programma terminato e in esecuzione.
Nei modelli dei cicli di sviluppo del software, il testing viene spesso considerato
come quella fase che segue lo sviluppo per sancire se il prodotto pronto per
andare in esercizio. In realt il testing unattivit complessa che accompagna un
programma in tutta la sua vita e nei gradi progetti improntati alla qualit i suoi
costi possono superare anche il 30% di quelli complessivi dellintero progetto. Con
il testing si pu rilevare la presenza di malfunzionamenti, ma non pu dimostrare
che il prodotto privo di errori soprattutto in progetti di grandi dimensioni e
complessit. In generale non esiste un software privo di difetti e il testing
unattivit che non si pu mai considerare definitivamente conclusa. Dal punto di
vista pratico, a seconda della importanza dellapplicazione software, va individuata
la soglia di convenienza tra la completezza dei test da effettuare e i costi da
sostenere.
importante comunque che i casi di test siano definiti sin dalla fase di
progettazione e durante lo sviluppo dei programmi al fine di evidenziare difetti,
errori, e malfunzionamenti. Il difetto (detto anche Fault) un elemento del
software, oppure di una componente della documentazione relativa al prodotto
software, che non risponde ai requisiti. Un solo difetto pu generare diverse
disfunzioni. Lerrore la causa che genera il difetto. Il malfunzionamento (o anche
Failure) invece leffetto dellerrore, quindi la conseguenza di un difetto, che si
manifesta durante lutilizzo del prodotto software. Lobiettivo della fase di test
quello di eliminare la maggior parte possibile di difetti prima che il prodotto
software venga utilizzato, in modo da evitare qualsiasi malfunzionamento
prevedibile.
Le attivit di testing devono verificare se e in quali condizioni un programma
non fa ci che dovrebbe fare o fa quanto non dovrebbe fare. Un malfunzionamento
pu essere di tipo funzionale se non genera i risultati attesi; non funzionale se fa un
cattivo uso di risorse e presenta problemi di prestazioni non adeguate, scarsa
sicurezza; oppure ha una usabilit limitata in quanto la sua interfaccia utente non
ne consente un facile uso. anche possibile classificare i malfunzionamenti in base
allo gravit delle conseguenze sullutilizzo del prodotto software:
- grave se lintero sistema non pu essere utilizzato;
- rilevante se alcune funzionalit critiche del sistema sono indisponibili
per gli utenti;
- media se solo alcune funzionalit non critiche del sistema non possono
essere usate;
La traduzione dei programmi 311
La tecnica del debugging risulta molto utile quando una funzione non viene
eseguita correttamente e, da una prima analisi visiva, non si riesce a determinate il
punto in cui presente lerrore. Inserendo i breakpoint risulta inoltre possibile
stabilire quali sono le parti di codice corrette e quali non lo sono (figura 11 ed 12).
help
help elfun
help abs
ABS Absolute value.
ABS(X) is the absolute value of the elements of X. When
X is complex, ABS(X) is the complex modulus (magnitude) of
the elements of X.
See also SIGN, ANGLE, UNWRAP
a=1;
v=[1 2 3];
who
Your variables are:
a v
whos
Name Size Bytes Class
a 1x1 8 double array
v 1x3 24 double array
Grand total is 4 elements using 32 bytes
A=[1 2 3 4;5 6 7 8]
A=
1 2 3 4
5 6 7 8
A=A'
A=
1 5
2 6
3 7
4 8
322 Capitolo ottavo
A=[1 2;3 4]
A=
1 2
3 4
b=[1;1]
b=
1
1
x1=inv(A)*b
x1 =
-1
1
x2=A\b
x2 =
-1
1
x=[0:0.1:6.28];
y=sin(x);
plot(x,y)
La traduzione dei programmi 323
0.8
0.6
0.4
0.2
-0.2
-0.4
-0.6
-0.8
-1
0 1 2 3 4 5 6 7
A=[1 2;3 4]
A=
1 2
3 4
det(A)
ans =
-2
p=[2 -4 1]
p=
2 -4 1
roots(p)
ans =
1.7071
0.2929
quad('log',1,10)
ans = 14.0257
324 Capitolo ottavo
Al termine della scrittura, la funzione deve essere salvata con il suo stesso
nome in modo tale da poter da essere poi richiamata nella finestra dei comandi, o
allinterno di altre funzioni. Nellesempio la funzione presenta tre variabili
dingresso a,b,c ed una di uscita d e i valori ai parametri di ingresso possono essere
dalla Command Window assegnati nel modo seguente:
[ris]=somma(1,2,3)
ris = 6
I valori dei parametri possono essere passati sia direttamente che per mezzo di
altre variabili; inoltre, si osservi che, quando viene richiamata una funzione, non
necessario che il nome delle variabili di ingresso e uscita coincidano con quelli
utilizzati per la scrittura della funzione, ci che importante che siano per ordine
e tipo coincidenti con esse.
Facendo riferimento allesempio seguente s1, s2 e s3 vengono associate ad
a,b,c.
s1=1;
s2=2;
s3=3;
La traduzione dei programmi 325
[ris]=somma(s1,s2,s3)
ris = 6
function somma
a=input('Inserisci il primo numero: ');
b=input('Inserisci il secondo numero: ');
c=input('Inserisci il terzo numero: ');
d=a+b;
fprintf('La somma %g',d)
somma
Inserisci il primo numero: 1
Inserisci il secondo numero: 2
Inserisci il terzo numero: 3
La somma 3
326 Capitolo ottavo
function [somma]=sommav1(v)
n=length(v);
somma=0;
for i=1:n
somma=somma+v(i);
end;
v=[1 2 3 4 5 6 7 8 9 10];
s=sommav1(v)
s=
55
function sommav2
somma=0;
n=input('Inserisci la dimensione del vettore: ');
for i=1:n
fprintf('Inserisci l''elemento %d-esimo: ',i);
v(i)=input('');
somma=somma+v(i);
end
fprintf('\nLa somma : %g',somma);
sommav2
Inserisci la dimensione del vettore: 3
Inserisci l'elemento 1-esimo: 1
Inserisci l'elemento 2-esimo: 2
Inserisci l'elemento 3-esimo: 3
La somma : 6
function sommav3
somma=0;
k=1;
risp='s';
while (risp=='s')
fprintf('Inserisci l''elemento %d-esimo del vettore: ',k);
v(k)=input('');
somma=somma+v(k);
risp=input('Altro elemento? (s)i o premere un zltro tasto per uscire: ','s');
k=k+1;
end
fprintf('\nLa somma : %g',somma);
sommav3
Inserisci l'elemento 1-esimo del vettore: 2
Altro elemento? (s)i o premere un qualsiasi tasto per uscire: s
Inserisci l'elemento 2-esimo del vettore: 3
Altro elemento? (s)i o premere un qualsiasi tasto per uscire: s
Inserisci l'elemento 3-esimo del vettore: 4
Altro elemento? (s)i o premere un qualsiasi tasto per uscire: n
La somma : 9
function sommav4
somma=0;
k=1;
fine=0;
while (fine~=1)
fprintf('Inserisci l''elemento %d-esimo del vettore(-1 per terminare): ',k);
v(k)=input('');
if (v(k)~=-1)
somma=somma+v(k);
k=k+1;
else
fine=1;
end
end
fprintf('\nLa somma : %g',somma);
sommav4
Inserisci l'elemento 1-esimo del vettore(-1 per terminare): 1
Inserisci l'elemento 2-esimo del vettore(-1 per terminare): 2
Inserisci l'elemento 3-esimo del vettore(-1 per terminare): 3
Inserisci l'elemento 4-esimo del vettore(-1 per terminare): -1
La somma : 6
function [somma]=diagv1(A)
[n,m]=size(A);
somma=0;
for i=1:n
somma=somma+A(i,i);
end
function diagv2
n=input('Inserire ordine Matrice: ');
for i=1:n
for j=1:n
fprintf('Inserire elemento di posto(%d,%d): ',i,j);
A(i,j)=input('');
end
end
fprintf('Matrice letta:');
A
somma=0;
for i=1:n
somma=somma+A(i,i);
end
fprintf('La somma %d',somma);
diagv2
Inserire ordine Matrice: 3
Inserire elemento di posto(1,1): 1
Inserire elemento di posto(1,2): 2
Inserire elemento di posto(1,3): 3
Inserire elemento di posto(2,1): 4
Inserire elemento di posto(2,2): 5
Inserire elemento di posto(2,3): 6
Inserire elemento di posto(3,1): 7
Inserire elemento di posto(3,2): 8
330 Capitolo ottavo
appositi moduli software tutta la logica di gestione dei dati dei pazienti, ed in un
altri la logica di gestione dei file, costringendo i programmatori ad usare tali
moduli per lo sviluppo di nuove procedure che lavorano sui dati in questione.
Su tale presupposto si basa il paradigma Object Oriented, che, a differenza
del paradigma procedurale, accentra lattenzione verso i dati. Lapplicazione viene
suddivisa in un insieme di oggetti in grado di interagire tra di loro e codificati in
modo tale che la macchina sia in grado di comprenderli.
La programmazione orientata agli oggetti rappresenta quindi un nuovo modo
di pensare alla risoluzione dei problemi per mezzo di un calcolatore: invece di
modellare il problema adattandolo alla logica del calcolatore, lapproccio OO
adatta il calcolatore al problema .
I problemi vengono dunque analizzati individuando delle entit indipendenti
che sono in relazione con altre. Tali entit non sono scelte perch facilmente
traducibili nella logica di programmazione, ma in quanto esistono dei vincoli fisici
o concettuali che le separano dal resto del problema e sono rappresentate come
oggetti di un programma. La scelta degli oggetti non va effettuata dunque
adattando il problema alla logica della programmazione, ma guardando al
problema in termini semplici.
Il paradigma object oriented formalizza la tecnica di incapsulare e
raggruppare parti di un programma in oggetti software che rappresentano concetti
ben definiti sia a livello di utente che a livello applicativo. Tali oggetti sono poi
riuniti per formare unapplicazione.
Nellesempio precedente, si pu osservare come lapplicazione di gestione
delle cartelle cliniche dei pazienti si accentri su tre concetti principali: Cartella
Clinica, Gestore Cartelle e Gestore di file. I concetti suddetti rappresentano
dellentit, classi di oggetti, del mondo reale che hanno caratteristiche comuni: ad
esempio tutte le cartelle cliniche avranno come informazioni il nome e cognome
del paziente, la sua tessera sanitaria, il suo gruppo sanguigno, una descrizione di
tutte le attivit sanitarie svolte sul paziente, una descrizione del suo stato di salute,
etcSu tali oggetti sono poi definite una serie di azioni o funzionalit come
quella dellaggiornamento e recupero dei dati di un paziente, eseguite, ad esempio,
dal personale medico ogni qualvolta il paziente si ricovera presso la struttura
ospedaliera. Allo stesso modo possibile pensare al gestore di cartella come
unentit (ad esempio un impiegato della reception della struttura) che si occupa di
gestire (aggiornare, recuperare, creare, etc) le cartelle cliniche, mentre un
Gestore di File rappresenta unentit (ad esempio un impiegato dellufficio
archivi) preposta allarchiviazione delle cartelle.
Lindividuazione degli oggetti e delle azioni su di essi non per sufficiente
alla realizzazione dellapplicazione, il secondo passo quello di definire
linterazione tra gli oggetti. necessario, ad esempio, stabilire come un medico, un
impiegato della reception ed uno dellufficio archivi devono comunicare qualora si
deve aggiornare la cartella clinica di un paziente al momento del ricovero. La cosa
pi ovvia che il gestore della reception, allatto del ricovero del paziente, recuperi
la cartella clinica con laiuto delladdetto agli archivi e la consegni al chirurgo, il
quale dopo loperazione aggiorna lo stato di salute del paziente. Allatto della
dimissione del paziente, limpiegato della reception provveder a conservare la
cartella aggiornata consegnandola alladdetto allarchiviazione.
334 Capitolo nono
In genere una classe identifica un tipo di dato astratto, ovvero un tipo non
predefinito nel linguaggio, ma costruito ad hoc dal programmatore, e gestito
come un tipo primitivo del linguaggio (la sua rappresentazione pu essere
modificata solo mediante gli operatori definiti sul tipo attraverso il meccanismo
dellincapsulamento). Esempi di classi sono: linsieme delle autoradio prodotte da
varie case costruttrici, linsieme dei conto correnti di uno o pi istituti bancari,
linsieme delle cartelle cliniche dei pazienti afferenti ad una data struttura
ospedaliera, etc
Riferendoci allesempio dellautoradio, cos come si possono costruire molte
autoradio diverse sulla base di un unico progetto (e.g., modelli differenti di una
stessa marca), analogamente, si possono costruire molti oggetti descritti da una
stessa classe.
Nella programmazione orientata agli oggetti comune avere molti oggetti
dello stesso tipo con caratteristiche simili. Il processo di creazione di un oggetto a
partire da una classe detto istanziazione, e, un oggetto descritto da una certa
classe detto unistanza di quella classe; istanze diverse di una stessa classe sono
da considerarsi distinte, in particolare, ciascuna istanza possiede e manipola un
proprio stato. Le classi, invece, non hanno uno stato, cos come non ha senso dire
che il progetto di un autoradio spento o acceso.
Il lavoro del programmatore consiste nellindividuazione delle varie tipologie
di oggetti software necessarie per un programma e nella definizioni della classi, per
ciascuna tipologia di oggetto. La progettazione di una classe prevede la definizione
degli attributi e delle operazioni comuni agli oggetti descritti dalla classe, la
progettazione dellinterfaccia con la definizione dei metodi accessibili
pubblicamente, e limplementazione di ciascun metodo.
Una classe deve rappresentare un unico concetto e soddisfare i seguenti
requisiti:
- coesione - gli attributi e i metodi devono essere strettamente correlati al
concetto rappresentato dalla classe
- coerenza - la descrizione delle operazioni deve seguire uno schema
coerente.
opportuno che un oggetto rappresenti un concetto ben definito e che
rimanga in uno stato consistente per tutto il tempo che viene utilizzato, dalla sua
creazione alla sua distruzione. Inoltre in un insieme di classi preferibile avere un
basso livello di accoppiamento, cio di dipendenza tra le classi. Uneccezione
costituita, come vedremo, dal meccanismo dellereditariet.
9.3.2. Oggetti software e classi come implementazioni di tipi di
dato astratto
Da un punto di vista implementativo un dato oggetto pu essere visto come
una sorta di dato strutturato, ad esempio un record Pascal o una struttura C, su cui
agiscono un insieme di metodi che permettono di aggiornare i campi elementari del
dato.
Un oggetto quindi assimilabile ad una struttura che occupa uno spazio di
memoria durante lesecuzione del programma, ed composto da un certo numero
di campi. In figura 6 sono mostrati degli oggetti che descrivono in modo parziale
personaggi di film fantasy.
340 Capitolo nono
Un oggetto spesso include riferimenti ad altri oggetti. Nel caso dei personaggi
di un film, il riferimento sicuramente al film stesso. Un riferimento pu contenere
un oggetto come suo valore (vedi figura 7).
Programmare con il paradigma OO significa creare un gran numero di oggetti
in maniera dinamica, seguendo un pattern che non semplice predire a priori.
Come gi discusso nel paragrafo precedente, nel paradigma OO, invece che
descrivere un unico oggetto, si descrive quanto vi di comune ad una intera
categoria di oggetti, attraverso il concetto di classe.
Da un punto visto implementativo, quindi, una classe simile ad un tipo
record od ad un tipo struttura.
Di seguito viene riportata una possibile definizione in un linguaggio simile al
C, per la classe Personaggio, che descrive i personaggi di film fantasy (ssi suppone
lesistenza del tipo String per la gestione di stringhe di caratteri).
Class Personaggio {
String Nome;
String Razza;
String Classe;
Film NomeFilm;
}
Class Film {
String Nome;
String Regista;
String Nazione;
}
Oltre che dagli attributi, una classe caratterizzata da operazioni o metodi che
modificano il valore dei suoi attributi. Prendiamo ad esempio in considerazione la
classe che descrive i numeri complessi:
class Complex{
float Re;
float Im;
}
classe. Tenendo conto dei metodi associati ad una classe, la definizione del tipo
Complex si modificher allora come nel seguito:
class Complex{
float Re;
float Im;
public:
float getReal();
float getImg();
void setReal(float reale);
void setImg(float immaginario);
float getModule();
}
CreaUnoStack();
push (T elem);
T pop ();
Vuoto();
};
// Metodo pop: elimina l'elemento di testa dallo stack
char Stack::pop () {
if (!Vuoto()) {
top_elem=elem_array[top] ;
top=top-1 ;
}
else top_elem=;
return top_elem;
};
// Metodo Vuoto: controlla se lo stack vuoto (ritornando il valore 1)
int Stack::Vuoto () {
if ( top==-1 ) return 0 ;
else return 1 ;
};
9.3.3. Il meccanismo dellereditariet
Lereditariet sicuramente una delle caratteristiche pi importanti dei
linguaggi orientati agli oggetti. Come visto, il concetto di classe e di oggetto
ricalcano quanto teoricamente previsto negli ADT o in alcuni linguaggi basati sugli
oggetti (object based).
Un linguaggio segue il paradigma di programmazione orientato agli oggetti se
e solo se object based ed implementa un meccanismo linguistico noto con il
termine di ereditariet o inheritance .
Il concetto di ereditariet alla base della riusabilit e dellestensibilit dei
sistemi software. Il mondo reale, daltro canto, permette di definire tipi di oggetto a
partire da tipi di oggetto gi noti, per estensione, specializzazione o combinazione
di questi.
Lereditariet il processo di creazione di una nuova classe con le
caratteristiche di una classe esistente e con altre caratteristiche peculiari. Pi nel
dettaglio, essa rappresenta il meccanismo attraverso il quale possibile riusare
La programmazione orientata agli oggetti 345
Class Poligono {
Vertex v1,,vn;
public:
float area();
float perimetro();
}
Il che vuol dire che anche area e perimetro sono metodi della classe
Rettangolo, ed anche i vertici v1,,vn (eventualmente per n=4) sono dati della
classe stessa. In aggiunta la classe Rettangolo prevede il metodo per il calcolo
della diagonale.
In altre parole, lereditariet permette di riutilizzare i servizi offerti da una
classe padre: se Y ereditata da X, allora tutti i servizi di X sono automaticamente
disponibili in Y, senza ulteriori definizioni, restando inteso che Y pu aggiungere
nuove caratteristiche (dati e servizi) per i prorpri scopi specifici.
Si noti che lereditariet a volte vista come una estensione ed altre volte
come una specializzazione. Dire che un rettangolo discende da poligono, equivale a
346 Capitolo nono
dire che un rettangolo un poligono. Nelle reti semantiche, questo tipo di relazione
anche detta relazione di tipo is_a. Daltro canto, considerando i servizi offerti dal
rettangolo, possiamo dire che essi sono sicuramente i servizi offerti dal poligono
con i pi i metodi particolare del rettangolo: in questo senso un rettangolo anche
una estensione della classe poligono. Un ultima considerazione riguarda la
possibilit di prevedere nei linguaggi orientati agli oggetti la cosiddetta ereditariet
multipla, che permette ad una classe di discendere da pi classi
contemporaneamente.
Lereditariet favorisce la progettazione e lo sviluppo di un sistema software
in modo completamente differente da quello tradizionale: invece di riprogettare ed
implementare tutto da zero, lidea quella di costruire nuovi programmi su quanto
gi fatto, estendendone le caratteristiche.
Per concludere il paragrafo, si vuole infine fare osservare come, nella
progettazione OO, lereditariet non lunica forma di relazione tra oggetti. In
pratica esistono varie tipologie di relazioni, le pi comuni sono:
- generalizzazione/specializzazione ottenute attraverso il meccanismo di
ereditariet;
- composizione/aggregazione che indicano che un dato oggetto di una data
classe costituito da uno o pi oggetti di unaltra classe;
- associazione che indica che ad un dato oggetto di una data classe
associato uno o pi oggetti di unaltra classe.
Infine un ulteriore potente meccanismo della programmazione OO quello
della redefinition o overloading: alcuni metodi offerti da X possono essere
ridefiniti in Y in maniera pi appropriata. La ridefinizione permette di cambiare
limplementazione di un metodo da parte della classe figlia, senza alterazione della
semantica.
9.3.4. Polimorfismo e binding dinamico
Con il termine polimorfismo si intende in generale la capacit di assumere
forme differenti. Nella programmazione orientata agli oggetti, si intende la capacit
di un oggetto di essere istanza di classi differenti. Siano ad esempio date le
dichiarazioni:
Poligono p; Rectangle r;
p=r;
essendo ovviamente p ed r i riferimenti agli oggetti. Ci discende dal fatto che
r, essendo rettangolo, un poligono. E invece scorretta lassegnazione:
r=p;
// Esempio perpolimorfismo
class Animale{
public:
void RiproduciVerso();
};
class Mammifero inherits from Animale;
class Quadrupede inherits from Mammifero;
class Cane inherits from Quadrupede;
class Gatto inherits from Quadrupete;
Cane pluto;
Gatto tom;
Loggetto tom (cos come loggetto pluto) a sua volta un oggetto di tipo
quadrupede, un oggetto di tipo mammifero ed un di tipo oggetto animale. come
dire che i gatti (ed i cani) sono degli animali mammiferi.
Consideriamo, tuttavia, il metodo pubblico della classe animale
RiproduciVerso che evidentemente deve poter esprimere il verso di un animale (se
emette suoni). Si supponga che la classe Cane e la classe Gatto ridefiniscano il
metodo, nel seguente modo:
Cane::RiproduciVerso(){
printf( Bau Bau Bau);
}
Gatto::RiproduciVerso(){
printf( Miao Miao);
}
Si consideri ora il seguente frammento di codice (si nota che con la sintassi
nome_oggetto->nome_metodo si attiva il metodo selezionato della classe a cui
appartiene loggetto):
a=pluto;
a->RiproduciVerso();
a=tom;
a->RiproduciVerso();
10.1. Introduzione
Un Sistema Operativo una componente fondamentale di un sistema di
calcolo e cercare di delineare brevemente le sue caratteristiche fondamentali una
attivit complessa. Per questo, invece di iniziare con definizioni rigorose,
cercheremo di far anzitutto comprendere il motivo per cui sono sorti i sistemi
operativi sulla scena dell'informatica, mostrando di volta in volta le problematiche
che essi sono chiamati a risolvere.
Si consideri un sistema di calcolo secondo il modello di von Neumann: i
processi elaborativi, intesi come programmi o parti di programma in esecuzione
vengono eseguiti di fatto dalla CPU con velocit diverse. Ad esempio un processo
di stampa dei risultati viene eseguito con una velocit differente rispetto ad un
processo di calcolo per la lentezza dei dispositivi (alcuni meccanici) della
stampante. Pi precisamente, prendiamo in esame un sistema come quello in
figura 1 ed un programma che effettui la somma ed il prodotto di due numeri:
inizialmente i valori devono essere prelevati da tastiera e, dopo aver effettuato gli
opportuni calcoli, i risultati devono essere visualizzati sul monitor.
Figura 1 Esempio di sistema con i due canali input standard ed output standard
350 Capitolo decimo
input(dato1);
input(dato2);
somma:=dato1+dato2;
output(somma);
prodotto=dato1xdato2;
output(prodotto);
Nel caso del canale di gestione della tastiera, tale bit 1 quando stato
premuto e rilasciato un carattere nel buffer di lettura, ed 0 quando tale carattere
prelevato dalla CPU. Analogamente per il monitor, il bit uguale ad 1 indica che la
periferica ha terminato loperazione di stampa ed pronta a ricevere un altro
carattere nel buffer (si suppone che la periferica sia in grado di stampare un solo
carattere per volta), il bit viene poi posto a 0 dalla CPU dopo linvio del dato.
In un sistema cos gestito, la CPU per effettuare le istruzioni di input ed
output interroga ciclicamente i registri di stato delle periferiche, aspettando che
abbiano termine eventi esterni non dipendenti da essa, quali linserimento di un
Introduzione ai sistemi operativi 351
dato da parte di un operatore: solo quando i bit di flag sono uguali ad uno, pu dirsi
completata l'operazione di lettura, ovvero:
- nel caso della tastiera prelevando il dato dal buffer dati e ponendo il bit a
0;
- nel caso del monitor inviando il dato nel buffer dati e ponendo il bit a 0.
Quelle finora descritte prendono il nome operazioni di lettura e scrittura a
controllo di programma.
programma successivo. Lobiettivo era quello di limitare i tempi morti della CPU,
riducendo lazione delluomo nellesecuzione dei programmi.
Unulteriore ottimizzazione introdotta dai sistemi operativi di questa
generazione quella di raggruppare pi programmi in un singolo gruppo o lotto
(batch), caricandoli tutti, prima dellesecuzione, su una periferica pi veloce del
lettore di schede, ad esempio su nastro magnetico. Il caricamento su nastro viene
fatto fuori linea da un apposito dispositivo, mentre il controllo della esecuzione ed
il passaggio da un programma allaltro avviene utilizzando un apposito linguaggio
di controllo detto Job Control Language (JCL). Inoltre, i suddetti sistemi operativi
mettono a disposizione dei programmatori delle primitive per linput e output sui
dispositivi standard (schede, nastro, stampante, etc) in modo da svincolare questi
ultimi dalla natura delle periferiche di input/ouput.
Con la gestione a lotti si superano i tempi morti legati allinizio e
terminazione dei programmi, permane per ancora il problema della inattivit della
CPU durante le operazioni di I/O. La successiva generazione di sistemi operativi
introduce il concetto di multiprogrammazione che consiste nel caricare in memoria
centrale pi programmi facendo in modo che condividano la CPU. Tale
caratteristica resa possibile grazie allintroduzione di due importanti innovazioni:
- i processori di I/O, ovvero processori pi semplici della CPU, dotati di un
insieme proprio di istruzioni e registri, ed in grado di gestire la
comunicazione con i dispositivi di I/O;
- le interruzioni, ovvero segnali generati dai controllori dei dispositivi di
I/O in grado, arrivando alla CPU, di interrompere il suo normale ciclo di
esecuzione.
Attraverso lintroduzione dei processori di I/O e del meccanismo delle
interruzioni possibile sovrapporre le operazioni di input ed output con quelle di
calcolo, eliminando i tempi morti della CPU dovuti allattesa del completamento
delle operazioni stesse di I/O. Se la CPU sta eseguendo un programma ed esso
richiede unoperazione di I/O, il programma corrente viene sospeso e loperazioni
di I/O