(Algoritmi, dati e programmi. Scopo della lezione è l`introduzione ai

(Algoritmi, dati e programmi.
Scopo della lezione è l’introduzione ai concetti di: problema, dato, algoritmo, linguaggio di programmazione,
programma, diagramma di flusso.
Cerchiamo prima di tutto di collocare l’argomento nel mondo dell’informatica. Da una parte, l’informatica si occupa di
informazione, argomento nel quale rientra la rappresentazione dell’informazione; possiamo considerare questo ramo
come il più teorico dell’informatica. Dall’altra parte, dal punto di vista più pratico, ci sono i mezzi utilizzati
dall’informatica: i mezzi fisici (quindi i calcolatori ed in particolare i circuiti che compongono i calcolatori – già
introdotti nella lezione sull’HW) ed il software. Di seguito saranno approfonditi gli aspetti di sviluppo del software.
Definiamo gli elementi principali di un problema di elaborazione dell’informazione; vi saranno:
-
dei dati di partenza;
un risultato cercato.
Una soluzione al problema è una procedura che elabora i dati di partenza in modo da ottenere il risultato cercato.
Un banale esempio di problema di elaborazione dell’informazione è il problema di determinare la somma di due
addendi a e b. I dati di partenza sono i dati a e b, il risultato cercato è il numero c = a + b. Una possibile soluzione al
problema è descritta dalla procedura per la somma di due numeri (scrivere i numeri incolonnati correttamente, sommare
le unità con riporto, sommare le decine… ).
Quando si affronta un problema dobbiamo distinguere tra:
-
la conoscenza di come si risolve un problema (che implica l’analisi del problema, l’identificazione di una
soluzione e la descrizione della soluzione stessa);
l’effettiva capacità di risolvere un problema (cioè l’interpretazione della soluzione e la sua attuazione).
Torniamo al problema della somma per esemplificare i diversi passi.
Problema di elaborazione dell’informazione: “Quanto fa 21+32?”
Conoscenza di come si risolve il problema:
Analisi: è un problema di somma tra due numeri interi;
Identificazione di una soluzione: esiste una procedura per la somma tra due numeri interi;
Descrizione della soluzione: i due numeri interi vanno incolonnati, si sommano le unità, il riporto viene
sommato alla somma delle decine, il riporto viene sommato alla somma delle centinaia, ...
Effettiva capacità di risolvere il problema:
Intepretazione della soluzione: un utente italiano legge la descrizione del problema e comprende le operazioni
da eseguire;
Attuazione della soluzione:
21 +
32 =
----53
Dunque, la conoscenza di come si risolve un problema permette di sviluppare un programma.
Ad esempio, possono essere sono necessarie competenze in ambito matematico per capire come si risolve un’equazione
differenziale. Il matematico può quindi fornire una procedura per la risoluzione di un’equazione differenziale, quindi
fornisce una rappresentazione della soluzione al problema (ad esempio specificando una serie di istruzioni programma).
Il programma viene poi interpretato da un utente (es. esecutore umano, calcolatore) che eseguirà le istruzioni
specificate.
Le competenze necessarie per i diversi passi sono dunque diverse:
-
-
servono competenze in ambito matematico per trovare la soluzione del problema;
servono competenze necessarie alla scrittura di un programma per specificare la soluzione in un
linguaggio comprensibile (capacità di descrivere una serie di operazioni utilizzando un opportuno
linguaggio);
servono le competenze duali (capacità di comprendere una serie di operazioni in un opportuno linguaggio)
per interpretare la soluzione, e…
… Servono infine i mezzi per l’attuazione della soluzione.
La conoscenza di come si risolve un problema è ciò che ci permette di sviluppare un programma. La scoperta /
invenzione di un algoritmo per la risoluzione di un problema richiede uno sforzo creativo.
Al contrario, la risoluzione di un problema richiede solo la comprensione di uno specifico linguaggio e l’attuazione di
una serie di operazioni: è dunque un’operazione puramente meccanica. In particolare, non è necessaria la comprensione
del problema per la sua risoluzione! La macchina (o comunque l’esecutore) può calcolare la soluzione al problema
senza avere la più pallida idea del problema in sé.
Per riassumere i diversi ambiti in cui ci si muove: il problema viene definito ed analizzato nell’ambito scientifico
proprio del problema. Una volta individuato l’algoritmo adatto al problema, interviene il programmatore che crea
un’opportuna codifica dell’algoritmo. Infine, il calcolatore interpreta il codice scritto dal programmatore ed elabora dai
dati per arrivare alla soluzione del problema.
Le competenze per l’analisi del problema e l’identificazione della soluzione provengono dunque da diversi ambiti
disciplinari e non ha senso analizzarle in questa sede. Il calcolatore è lo strumento che fornisce l’effettiva capacità di
risoluzione del problema. Quello che ci interessa è il ponte tra questi due mondi, ovvero la descrizione della soluzione ,
cioè la formalizzazione dell’algoritmo che risolve il problema in un opportuno linguaggio di programmazione.
Per la descrizione della soluzione si potrebbe utilizzare il linguaggio naturale (italiano, russo, giapponese, …).
Il problema è che il linguaggio naturale è spesso ambiguo. Ad esempio, consideriamo una ricetta (abbiamo già
accennato al fatto che una ricetta è in effetti un algoritmo); se nella ricetta compare la frase “aggiungere un pizzico di
sale”… Quanto sale dobbiamo aggiungere?
E’ meglio dunque ricorrere, per la descrizione della soluzione, ad un linguaggio formale, il quale è privo di ambiguità.
Sono esempi di linguaggio formale il formalismo matematico, la rappresentazione di un algoritmo in pseudo-codice, i
diagrammi di flusso, i linguaggi di programmazione, il codice macchina, …
E’ importante notare che la descrizione della soluzione può essere fatta a diversi livelli di dettaglio. Ad esempio, nella
ricetta per preparare le lasagne, potremmo trovare una sequenza di 4 istruzioni: 1) preparare il ragù, 2) preparare la
besciamelle, 3) bollire le lasagne, 4) mettere a strati nella teglia e cuocere in forno. L’istruzione 2) potrebbe però essere
di difficile interpretazione per molti esecutori (come si prepara la besciamelle?). Dunque potremmo trovare delle
istruzioni a livello di dettaglio minore all’interno della ricetta, ad esempio, 2.1) setacciare 50g di farina e stemperarla
con 10cc di latte, 2.2) …, 2.3) aggiungere 30cc di latte e 50g di burro a pezzetti, …
Queste istruzioni saranno di “alto livello” (vicino al modo di comprendere di chi ha scritto le istruzioni) o di “basso
livello” (vicino al modo di comprendere dell’esecutore). Per analogia avremo anche a che fare con linguaggi di alto
livello (quelli in cui la sintassi è simile al modo di esprimersi naturalmente – la singola istruzione di un linguaggio ad
alto livello è significativa per l’utente umano, ma richiede un grande numero di operazioni elementari per la macchina
che non è dunque in grado di interpretare l’istruzione in modo diretto) e di basso livello (quelli in cui la sintassi descrive
in maniera dettagliata le operazioni fin nei più piccoli dettagli, es. svuotamento di un registro nella CPU – difficilmente
comprensibile per un operatore umano, esprime operazioni elementari per la macchina).
Dopo questa breve introduzione, andiamo dunque ad elaborare il processo con il quale un problema viene affrontato e
risolto utilizzando un “approccio informatico”. Prima di tutto è necessario dare una formalizzazione del concetto di
algoritmo, dal momento che l’algoritmo costituisce il metodo attraverso il quale il problema viene risolto (sempre che il
problema sia risolvibile mediante un algoritmo!).
La definizione di algoritmo deve essere dato, come in ogni disciplina scientifica, in modo rigoroso (non è sufficiente la
definizione del vocabolario!). Diamo dunque tale definizione.
Definizione di algoritmo:
•
•
•
•
•
•
un insieme ordinato
di passi eseguibili
e non ambigui
che determinano un procedimento atto a risolvere un problema o una classe di problemi
utilizzando dati iniziali e ottenendo dei risultati
in un tempo finito.
Esempi di algoritmo sono i procedimenti per le 4 operazioni che ci sono stati insegnati alle elementari, oppure la ricetta
per fare le lasagne. E’ evidente che, a seconda dell’esecutore (bambino delle elementari, cuoco) il linguaggio utilizzato
è differente; in particolare, il linguaggio utilizzato deve essere tale da essere compreso dall’esecutore! L’algoritmo per
eseguire un origami viene espresso non in linguaggio naturale, bensì attraverso una serie di disegni che possano essere
compresi dall’esecutore.
E’ importante notare che un algoritmo esiste indipendentemente dal linguaggio in cui è espresso. L’algoritmo è un
concetto astratto, mentre la sua rappresentazione in un determinato linguaggio è concreta! L’algoritmo è dunque
indipendente dal linguaggio in cui è espresso. In particolare, nella definizione che abbiamo dato di algoritmo, non vi è
alcun accenno al linguaggio di programmazione!
Il linguaggio è necessario per rappresentare l’algoritmo, in particolare per darne una rappresentazione che sia
comprensibile da parte dell’esecutore.
Ogni linguaggio è costituito da:
-
un vocabolario (l’insieme dei simboli utilizzati dal linguaggio);
una sintassi (un insieme di regole che specificano come comporre i vocaboli per ottenere costrutti ben
formati);
una semantica (associa un significato ad ogni costrutto linguistico sintatticamente corretto).
Una volta che un algoritmo è stato specificato utilizzando un opportuno linguaggio, questo può essere “passato” ad un
esecutore. Un esecutore è un soggetto in grado di eseguire le operazioni specificate in un algoritmo. Quindi, in maniera
duale rispetto al linguaggio, anche un esecutore è caratterizzato da un linguaggio, dall’insieme di azioni che è in grado
di compiere, e dall’insieme delle regole che permettono di associare un’azione da compiere ad ogni costrutto
semanticamente corretto del linguaggio.
A seconda dell’insieme delle azioni che un esecutore può eseguire, esecutori diversi possono utilizzare algoritmi diversi
per risolvere lo stesso problema! Ad esempio, il calcolatore non utilizza l’algoritmo per la divisione che utilizziamo noi!
Oppure, per spostarsi avanti di un metro, noi mettiamo in movimento le gambe, mentre un robot metterebbe in rotazione
le ruote… Anche CPU diverse possono utilizzare operazioni elementari diverse per eseguire semplici istruzioni!
Concentriamoci allora sulla scrittura interpretazione di un programma. Parliamo cioè di linguaggi di programmazione.
Introduciamo prima di tutto il concetto di programma. I programmi sono sequenze finite di istruzioni, ognuna scritta
in un fissato linguaggio (di programmazione). I programmi eseguibili da un computer devono essere scritti usando un
linguaggio che il computer è in grado di “comprendere”. Un algoritmo può essere quindi specificato sottoforma di
programma eseguibile da un calcolatore.
Att.ne però! Programma e algoritmo sono concetti diversi!
Un algoritmo è un concetto astratto.
Un programma può essere la rappresentazione di un algoritmo in un determinato linguaggio. D’altra parte, la sequenza
di istruzioni all’interno del programma potrebbe anche non terminare mai… In tal caso il programma non sarebbe la
rappresentazione di un algoritmo, che, per definizione, termina in un tempo finito!!!
Come abbiamo già introdotto in precedenza, affinché l’esecutore possa eseguire un programma è necessario che questo
sia specificato in un linguaggio comprensibile all’esecutore stesso. Nel caso in cui l’esecutore sia un calcolatore, si
parlerà di linguaggio di programmazione. Ogni linguaggio di programmazione sarà dunque dotato di un vocabolario,
un insieme di regole sintattiche, una semantica.
Rispetto ad un linguaggio naturale umano, un linguaggio di programmazione ha una sintassi molto semplificata. Esiste
una disciplina, nota come teoria dei linguaggi formali, la quale si occupa tra l’altro di classificare i linguaggi in base alla
loro “semplicità” (dunque è in grado di quantificare in modo opportuno la complessità di un linguaggio).
Distinguiamo tra linguaggi di basso livello e linguaggi di alto livello.
I linguaggi di basso livello hanno una sintassi che permette di codificare istruzioni che corrispondono ad azioni molto
elementari da parte del calcolatore. Ad esempio, in un linguaggio di basso livello ci aspettiamo che siano codificate
istruzioni del tipo “riempi un registro della CPU con X”, “preleva dalla memoria il contenuto che si trova in Y”, … Dal
momento che le istruzioni codificate sono di tipo elementare, è richiesto uno sforzo di codifica notevole da parte del
programmatore (Es. per fare una somma, sarebbe necessaria una sequenza di istruzioni del tipo: “preleva dalla memoria
il contenuto della cella A e mettilo nel registro AA della CPU”, “preleva dalla memoria il contenuto della cella B e
mettilo nel registro BB della CPU”, “Somma i registri AA e BB e metti il risultato nel registro CC della CPU”, “Copia
in memoria il registro CC della CPU, in posizione C”). Le istruzioni sono immediatamente comprensibili da parte
dell’elaboratore, ma per codificare quelle che un utente umano ritiene delle operazioni semplici, è necessario un numero
molto alto di istruzioni.
Al contrario, i linguaggi di alto livello permettono di programmare usando un insieme di istruzioni più articolato. Ad
esempio, per sommare due numeri è possibile utilizzare l’istruzione “c = a + b”, che associa alla cella di memoria c il
contenuto di a + il contenuto di b. Mentre nel linguaggio a basso livello erano necessarie 4 istruzioni, nel linguaggio ad
alto livello ne basta una! Dunque, lo sforzo di programmazione da parte del programmatore è inferiore. Le istruzioni
sono più vicine a quelle che un utente umano ritiene operazioni “elementari”. Per contro, ogni istruzione elementare di
alto livello deve essere scissa in una sequenza di istruzioni elementari di basso livello per essere compresa dal
calcolatore.
Il processore è in grado di riconoscere (e quindi di eseguire) solo programmi scritti in un proprio linguaggio di basso
livello (linguaggio macchina). Ogni modello di processore (es: Intel, Pentium, Motorola, PowerPC) ha un proprio
linguaggio macchina diverso da quello degli altri processori. Dunque un programma scritto in linguaggio macchina per
un processore Intel probabilmente non funzionerà per un processore Motorola! Ad esempio, supponiamo che un
processore A sia dotato di N registri, mentre il processore B ne abbia M>N. Ogni istruzione che utilizza un registro da
N+1 a M non ha senso per il processore A! Inoltre i due processori possono utilizzare linguaggi macchina con insiemi
di istruzioni o sintassi diverse…
Dal momento che ogni processore è in grado di intendere solo il proprio linguaggio macchina, quando si programma
utilizzando un linguaggio di alto livello è necessario tradurre il codice in linguaggio macchina per fare eseguire il
codice al processore. Esistono dei programmi, chiamati interpreti e compilatori, che si occupano di tradurre il
linguaggio ad alto livello nel linguaggio macchina comprensibile per il processore.
Un compilatore traduce per intero un programma che è stato scritto in linguaggio ad alto livello. Una volta effettuata la
traduzione il linguaggio macchina, il programma può essere copiato in memoria ed eseguito. E’ questo il modo di
programmare “tradizionale”, ad esempio quello utilizzato quando si programma utilizzando C o C++.
L’interprete, invece, traduce ed esegue immediatamente ogni singola istruzione di alto livello che viene digitata
dall’utente. Questo modo di lavorare è usato ad esempio da Matlab (utilissimo per fare ricerca).
Programma (istruzione)
in linguaggio macchina
Intel
Programma
(istruzione) in
linguaggio ad
alto livello
Interprete o
compilatore
Processore
Intel
Programma (istruzione)
in linguaggio macchina
Athlon
Processore
Athlon
Programma (istruzione)
in linguaggio macchina
Motorola
Processore
Motorola
Analizziamo allora le caratteristiche dei programmi a basso livello rispetto ai programmi ad alto livello.
Per quanto riguarda la velocità di esecuzione, i linguaggi di basso livello sono più vantaggiosi rispetto a quelli di alto
livello (il programmatore può scegliere come fare lavorare la CPU, dunque può ottimizzarne al meglio il
comportamento – al contrario, utilizzando un linguaggio di alto livello, il comportamento reale della CPU viene definito
dalla traduzione che il compilatore / interprete effettua, che può non essere ottimale in termini di efficienza).
Per quanto riguarda la portabilità, cioè la possibilità di eseguire il programma su macchine diverse, i linguaggi di alto
livello offrono sicuri vantaggi rispetto ai programmi di basso livello. Infatti, basta compilare un programma di alto
livello per lo specifico processore per poter utilizzare lo stesso codice su macchine diverse. Al contrario, come abbiamo
già detto, un programma scritto in linguaggio macchina per un processore Athlon non è detto che funzioni su un
processore Motorola! Giocano in questo caso un ruolo fondamentale i compilatori, che devono essere in grado di
generare correttamente il codice di basso livello per diversi processori, a partire dallo stesso codice di alto livello.
Infine, per quanto riguarda la complessità, abbiamo già accennato al fatto che per un utente umano è più facile scrivere
(e capire) codice ad alto livello che a basso livello, in quanto le istruzioni di base utilizzate sono più vicine al modo di
ragionare umano (al contrario, le istruzioni a basso livello sono comprensibili dal calcolatore). Dunque la complessità è
maggiore per il codice a basso livello. Ciò può essere intuito anche pensando al fatto che, per eseguire la stessa
operazione, è necessario specificare un numero di righe di codice molto più alto quando si utilizza un linguaggio di
basso livello che uno di alto livello.
Torniamo allora indietro nel nostro schema generale di elaborazione dell’informazione, ed occupiamoci ora di come si
arriva alla scrittura di un programma. Per arrivare a scrivere un programma, è conveniente muoversi seguendo i passi
già individuati:
1) analisi del problema (richiede competenze specifiche al tipo di problema)
2) individuazione dell’algoritmo di risoluzione a sua prima scrittura in un “linguaggio” adatto all’uomo ma non
troppo lontano dai linguaggi di programmazione
3) scrittura del programma nel linguaggio di programmazione scelto.
In realtà questo modo di procedere può essere espanso come segue:
–
–
–
–
–
analisi del problema e specificazione dei dati in ingresso e in uscita;
identificazione e formalizzazione di una soluzione, definizione dell’algoritmo risolutivo;
programmazione in un linguaggio di programmazione “ad alto livello”;
traduzione in linguaggio macchina;
verifica (testing).
Il modo di procedere individuato, che si sposta dallo sviluppo di una soluzione di massima alla soluzione dei
sottoproblemi, è noto come approccio top-down alla progettazione. Con tale approccio, dunque, si parte da una
descrizione ad alto livello della soluzione, in cui si individuano sotto-problemi; si definiscono quindi le soluzioni dei
sotto-problemi in termini di operazioni più elementari… e così via, fino ad esprimere tutto in termini di problemi
elementari.
Per contro, esiste anche una modalità di programmazione bottom-up, nella quale vengono affrontati e risolti mediante
piccoli programmi prima i sottoproblemi – le soluzioni parziali dei sottoproblemi vengono poi assemblate in maniera
opportuna per arrivare alla soluzione del problema generale. Un esempio di questo modo di procedere è lo sviluppo di
librerie, cioè insiemi di funzioni elementari che vengono utilizzate da altri utenti per la risoluzione di problemi generali.
Quale che sia il paradigma di programmazione scelto (meglio usare top-down perché più intuitivo), nello sviluppo di un
programma vi sono due aspetti da gestire:
1) Gestione dei dati (dati in ingresso, risultati parziali delle operazioni, risultati) – Dove e come memorizzare
i dati?
2) Successione di operazioni da compiere.
Per affrontare questi due problemi, introdurremo la nozione di contenitore di dati come astrazione dalla nozione di
zona della memoria utilizzata da un computer per i dati. Introdurremo quindi i principali tipi di istruzioni.
Descriveremo quindi gli algoritmi mediante diagrammi di flusso, strumento per descrivere una successione di
operazioni adatto all’uomo e orientato alla traduzione in un linguaggio di programmazione
Descriviamo dunque i passi da seguire nella costruzione di un algoritmo, intesa come passo preliminare alla costruzione
di un corrispondente programma.
Tale descrizione è adatta a quella che è nota come programmazione in piccolo, cioè la costruzione di programmi
trattabili da un singolo programmatore. Per contro, la programmazione in grande richiede processi di sviluppo
ingegnerizzati, che non tratteremo.
Abbiamo detto che un problema è caratterizzato da dei dati in ingresso e da un risultato. Per ottenere il risultato, avremo
inoltre bisogno di svolgere dei calcoli e di mettere da parte dei risultati parziali. Per fare ciò, ci viene incontro la
nozione di contenitore dei dati, cioè un’astrazione della nozione di area di memoria contenente dei dati. Tale contenitore
di dati è detta variabile di un programma. In un programma avremmo tante variabili quanti sono i dati che ci interessa
memorizzare!
Ogni contenitore di dati (variabile) ha un tipo; a seconda del tipo di variabile, avremo un insieme di elementi
rappresentabili ed un insieme di operazioni possibili su di essa.
Ad esempio, vi saranno variabili di tipo intero (int); ciascuna di queste variabili occuperà 4 byte in memoria (32 bit) e
permetterà di rappresentare un intervallo di numeri interi. Tra le operazioni possibili vi saranno +, -, *, /, >, <, ==,
avendo presente che l’insieme dei numeri rappresentabili è finito e tutti i numeri sono interi (Æ possibile overflow, in
accuratezza nella divisione, …). Un altro tipo possibile è il tipo BYTE, che permette di rappresentare 256 numeri interi.
Un ultimo tipo presentato qui è il tipo boolean, per la rappresentazioni di variabili booleane (cioè variabili che possono
essere VERE o FALSE,). In questo caso, per esempio, l’operazione di divisione non è definita!
Per la variabili possiamo utilizzare la seguente rappresentazione grafica:
Nome del contenitore: pippo
tipo : intero
pippo: intero
54
Contenuto = dato
(appartenete al tipo di dati
associato al nome, infatti
54 è un numero intero, e su
di esso sono ammesse le
usuali operazioni
aritmetiche)
I linguaggi di programmazione ad alto livello (ma anche Excel e Access) prevedono la tipizzazione dei dati.
Vi è poi da fare la distinzione tra dati di tipo semplice (Es. una variabile di tipo intero, reale, testo o logico) ed i dati di
tipo strutturato. I dati di tipo strutturato sono dati che contengono più di un valore, ad esempio matrici o vettori (es. [1
2 3 4 5]). Un altro dato di tipo strutturato sono i record: strutture non uniformi, cioè contenenti più valori non
necessariamente dello stesso tipo.
Esempi:
Variabile semplice di tipo intero:
a, contiene a = [1];
Variabile strutturata di tipo vettore di reali:
v, contiene v = [0.1 0.5 0.666]
Variabile strutturata di tipo “studente” (un particolare record definito dall’utente), un record:
s, s contiene:
s.nome = ‘Pippo’
s.cognome = ‘ Franco’
s.matricola = 234543
s.mediaesami = 18.1
In quest’ultimo esempio la variabile strutturata s (un record) è di tipo “studente”, un tipo composto da due variabili di
tipo testo, una variabile di tipo intero, una variabile di tipo reale.
Passiamo allora a vedere quali sono le istruzioni fondamentali per lavorare sui dati.
Vi sono tre tipi fondamentali di istruzioni:
1) Ingresso / uscita;
2) Aritmetico / logiche;
3) Di controllo.
1) Ingresso / uscita
Le istruzioni di ingresso / uscita permettono di acquisire dati e di presentare risultati. Ad esempio:
read a - acquisisci un dato da tastiera e mettilo nel contenitore ‘a’
print 'La media dei valori dati in ingresso è ', media - stampa il contenuto di ‘media’ preceduto da un commento
***
2) Aritmetico / logiche
Per quanto riguarda le istruzioni aritmetico / logiche, abbiamo:
Le istruzioni di assegnamento, che modificano lo stato di memoria, cioè i valori dei contenitori dati (detti variabili).
Sono della forma:
CONTENITORE = ESPRESSIONE [(leggi: metti ESPRESSIONE in CONTENITORE)]
Es.: a = b + 3
ESPRESSIONE può essere: una costante, una variabile, un’espressione vera e propria, una funzione
CONTENITORE è il nome di una variabile
l’esecutore valuta l’ESPRESSIONE e mette il valore così calcolato in CONTENITORE, sostituendone il valore
precedente.
Le espressioni aritmetiche esprimono calcoli numerici (somma, sottrazione, prodotto divisione, elevamento a
potenza, radice, logaritmo, esponenziale, ecc.)
Es.:
b**2 – 4 * a * c
Le espressioni sui caratteri modificano parole e testi:
Es. concatenazione, moto&sega (risultato: motosega)
Le espressioni logiche (o booleane) esprimono calcoli logici e possono quindi assumere solo i valori vero o
falso.
Operatori logici relazionali: confronto fra due valori, ad esempio
• x<y
• (x + 5) = y
• ecc.
Operatori logici AND, OR, NOT, ecc. per comporre, ad es.:
• (x < y) AND (y < z)
Un valore booleano (vero, falso) è rappresentabile con un bit. Convenzionalmente si assegna 1 a vero, 0 a
falso.
***
3) Di controllo
Le espressioni logiche assumono un particolare importante quando abbinate alle istruzioni di controllo, in quanto
permettono di prendere delle decsioni. Infatti, le istruzioni di controllo permettono di modificare il flusso di
esecuzione delle istruzioni all’interno di un programma, altrimenti puramente sequenziale.
Es.
Selezione
if, case, ...
Iterazione
while, repeat
for
Si basano sull’uso di espressioni booleane
Es.
se x > 0,
calcola la radice quadrata di x,
altrimenti
calcola la radice cubica di x.
***
Una comoda rappresentazione di un programma, utile anche per la sua traduzione in un linguaggio di programmazione,
è data dalla rappresentazione mediante diagrammi di flusso.
Ogni diagramma di flusso contiene dei blocchi di elaborazione; ogni blocco di elaborazione contiene delle sequenze di
azioni (dunque troveremo all’ingresso di un blocco di elaborazione delle istruzioni aritmetico / logiche o delle
operazioni di input/output).
metti x+y in y
metti x-1 in x
Vi sono poi blocchi decisionali, che contengono un’espressione booleana e dirigono il flusso del programma
diversamente a seconda del risultato dell’operazione booleana.
falso
vero
x = 0
Un diagramma di flusso si ottiene connettendo la frecce uscenti dai blocchi di elaborazione e decisionali.
Le tre principali strutture sono:
sequenza Æ una sequenza di operazioni che vengono eseguite una dopo l’altra;
selezione Æ un’espressione booleana che devia il flusso del programma;
iterazione Æ si ottiene componendo una sequenza con una selezione (non necessariamente in quest’ordine) permette di iterare una sequenza di istruzioni fino al verificarsi di una condizione.
Raggruppando insieme dei blocchi otteniamo quella che è nota come decomposizione modulare: ovvero, è possibile
raggruppare una serie di blocchi che eseguono una serie di operazioni complesse e raggrupparli in un unico blocco che
permetta di ottenere una rappresentazione più semplice.
Proviamo allora a tradurre uno schema a blocchi in codice.
Inizio
Function RadiceQuadrata;
Acquisisci a
SI’
Real a,b; // Si alloca spazio in memoria per a, b
Repeat
a<0?
Display(‘Dammi a’);
read a;
NO
Until (a>=0)
b = sqrt (a)
b=sqrt(a);
Scrivi: “la radice
di a è <b>”
Display(‘La radice di a è: <b>’)
end
Fine
Laboratorio di Informatica
AA 2006/2007
75
La traduzione di uno schema a blocchi (o da un’altra rappresentazione di alto livello) in un linguaggio di
programmazione è l’attività di programmazione.
Esistono diversi paradigmi di programmazione. Approfondiremo la programmazione dal punto di vista del paradigma
procedurale, basato su moduli (funzioni) che permettono di elaborare dei dati fornendo dei risultati; le funzioni possono
essere incapsulate l’una dentro l’altra.
Un diverso paradigma procedurale (ma ve ne sono anche altri!) è quello ad oggetti, nel quale il programmatore crea una
serie di oggetti, ciascuno dotato di uno stato e in grado di svolgere alcune funzioni caratteristiche… Questo paradigma
di programmazione è più vicino come interpretazione alla vita di tutti i giorni, ad esempio pensiamo
all’implementazione di un oggetto “panettiere”, questo oggetto potrà avere più stati (es. ‘dietro al bancone’, ‘al forno’,
…) e potrà svolgere diverse funzioni (es. ‘cuocere il pane’, ‘emettere uno scontrino’, …). L’interazione con altri oggetti
(es. oggetto consumatore) fa poi avanzare il programma…
Approfondiamo dunque un paio di aspetti della programmazione procedurale.
Il programmatore crea diversi moduli (funzioni). Ogni funzione può avere una serie di input (variabili di ingresso – i
dati da elaborare) e di output (variabili di uscita – i risultati). Ad esempio, se pensiamo ad una funzione che esegue la
divisione intera, potremo avere:
[Quoziente, Resto] = DivisioneIntera (Dividendo, Divisore),
dove (Dividendo, Divisore) sono gli input, [Quoziente, Resto] sono gli output, DivisioneIntera è il nome della funzione.
Andiamo allora a vedere come una possibile implementazione della funzione DivisioneIntera, utilizzando la sintassi di
Matlab:
[Quoziente, Resto] = function DivisioneIntera (Dividendo, Divisore);
Quoziente = div (Dividendo, Divisore);
Resto = mod (Dividendo, Divisore);
return;
La prima riga contiene la dichiarazione della funzione. Nella dichiarazione viene utilizzata la parola chiave function,
che specifica che si sta effettuando una dichiarazione di funzione.
La seconda e terza riga effettuano l’elaborazione dei dati. Notiamo che queste righe contengono delle chiamate ad altre
funzioni! Dunque:
-
per dichiarare una funzione, la sintassi è data da:
o [Output1, Output2, … OutputN] = function Funzione (Input1, Input2, … input)
E’ necessario dichiarare una funzione per poterla utilizzare!
-
per utilizzare una funzione, la sintassi è data da:
o [Output1, Output2, … OutputN] = Funzione (Input1, Input2, … input)
Siamo dunque di fronte ad un esempio di incapsulamento delle funzioni! La funzione DivisioneIntera effettua delle
chiamate alle funzioni div e mod!
E’ comodo creare una librearia di funzioni, perché ciascuna funzione può essere utilizzata da altre funzioni quando
necessario (programmazione modulare – ogni funzione è un modulo da riutilizzare).
Infine, la quarta e ultima riga termina l’esecuzione della funzione mediante la parole chiave return (in Matlab, questa
istruzione è in realtà opzionale – se non viene utilizzata, la funzione termina automaticamente dal momento che non
trova più codice da eseguire!).