UNIVERSITÀ DEGLI STUDI DI MILANO – BICOCCA
Luca Bernardinello
LABORATORIO DI INFORMATICA
1
Corso di laurea in fisica
Anno accademico 2015/2016
1
CHE COS ’ È UN PROGRAMMA ?
Programma = oggetto multiforme. Distinguiamo tre forme sotto le quali ci si
può presentare un programma.
1. Forma dinamica: il programma in esecuzione (processo). Può manifestarsi
in molti modi, può compiere dei calcoli e produrre un risultato, oppure
reagire ai comandi dell’utente, oppure restare silenzioso e produrre effetti
visibili solo indirettamente.
2. File eseguibile (che cos’è un file? è un oggetto che occupa dello spazio
nella memoria del calcolatore; esempi: una canzone in formato mp3, un
testo in formato pdf ). È un file che contiene istruzioni, che il calcolatore
esegue quando si esegue il programma corrispondente. Le istruzioni sono
scritte in una notazione simbolica che riflette la caratteristiche costruttive
del calcolatore (linguaggio macchina).
3. File sorgente. È un file che contiene le istruzioni del programma, scritte in un linguaggio di programmazione, cioè in una notazione simbolica,
più vicina al nostro modo di ragionare (analogia con la notazione musicale). Un linguaggio di programmazione ha una grammatica: morfologia, sintassi e semantica. Esistono molti linguaggi di programmazione;
noi ne studieremo uno (chiamato C), ma i princìpi di programmazione si
applicano a una gamma molto vasta di linguaggi.
Il file sorgente è prodotto dal programmatore. Per arrivare all’esecuzione del
programma è necessario generare una versione del programma in linguaggio
macchina, cioè il file eseguibile. A questo provvede il compilatore, cioè un programma speciale che traduce da un linguaggio di programmazione a un linguaggio macchina. Cenno ai linguaggi assembly.
Quindi, trafila di soluzione di un problema per via algoritmica:
1. Enunciato del problema e requisiti della soluzione
2. Ideazione dell’algoritmo risolutivo e rappresentazione dei dati
3. Stesura del codice sorgente che esprime l’algoritmo in un linguaggio di
programmazione
4. Traduzione (compilazione) in linguaggio macchina
5. Esecuzione
6. Emendazione
Nota: noi ci occuperemo di programmi che mettono in opera un algoritmo per
trattare dei dati e “calcolare” una soluzione, ma esistono molti altri tipi di programma (programmi reattivi).
2
/**
* Genera una tabella di conversione delle temperature
* da Fahrenheit a Celsius
*
* Tratto, con adattamenti, da Kernighan, Ritchie: The
* C programming language, Prentice Hall PTR.
*/
#include <stdio.h>
#include <stdlib.h>
int main ()
{
int
lower, upper, step;
float fahr, celsius;
lower = 0;
upper = 300;
step = 20;
/* Valore minimo, in gradi F, della tabella */
/* Valore massimo, in gradi F, della tabella */
/* Passo di incremento della tabella
*/
fahr = lower;
while ( fahr <= upper )
{
celsius = (5.0/9.0) * (fahr - 32.0);
printf ("%3.0f %6.1f\n", fahr, celsius);
fahr = fahr + step;
}
}
exit(EXIT_SUCCESS);
Figura 1: Il codice sorgente di un programma.
2
IL CODICE SORGENTE COME TESTO
La natura testuale del codice sorgente. Un programma non è solo destinato a essere eseguito, ma anche a essere letto (per ragioni di studio, per manutenzione,
per migliorie, varianti, etc.); perciò deve avere qualità di leggibilità. Osserviamo
il codice sorgente di un programma. Notiamo alcune particolarità: l’impaginazione, la presenza di simboli e di parole inglesi. Un programma è scritto in un
linguaggio di programmazione, cioè in una lingua, con una propria grammatica. È importante abituarsi a considerare un programma (codice sorgente) come
un testo destinato a essere letto, oltre che a essere eseguito. Ne segue che un
elemento nella qualità complessiva di un programma è la sua leggibilità. Questa
caratteristica è irrilevante rispetto alla correttezza, all’efficienza del programma
3
una volta eseguito, ma è comunque importante. Chi vorrà o dovrà leggere un
programma? L’autore stesso del programma, durante il suo sviluppo, che può
durare mesi, e in seguito, quando verranno alla luce difetti, errori, mancanze da
correggere; altri programmatori che debbano rimediare agli errori, estendere o
modificare il programma, docenti che devono correggere i programmi consegnati per un esame. Impareremo poco per volta quali elementi contribuiscono
a elevare la leggibilità di un programma.
3
LA TRAFILA DI COMPILAZIONE
Lo sviluppo di un programma attraversa diverse fasi (che di solito non sono
strettamente sequenziali, ma si intrecciano fra di loro): rappresentazione dei
dati (strutture dati), disegno degli algoritmi, stesura del codice sorgente, analisi
del codice sorgente, compilazione (cioè traduzione in codice eseguibile), saggi di esecuzione su dati scelti, esercizio, scoperta di difetti, errori, mancanze,
riesame, e così via.
La compilazione avviene per mezzo di un programma particolare, il compilatore, specifico a un linguaggio di programmazione e a un linguaggio-macchina.
4
LA PROGRAMMAZIONE IMPERATIVA
In questo paragrafo trattiamo il nòcciolo della programmazione imperativa (paradigmi di programmazione).
Per cogliere l’essenza della programmazione imperativa, conviene farsi un
modello mentale dell’esecuzione di un programma. Questo modello ricalca in
parte la struttura di un calcolatore, come descritta nella lezione precedente.
Immaginiamo così l’esecuzione di un programma: una persona, seduta a una
scrivania, con un foglio a quadretti e una matita.
Ogni quadretto del foglio reca stampato in un angolino un numero, che corrisponde al suo indirizzo; gli indirizzi partono da zero e crescono progressivamente.
Un’area del foglio ospita le istruzioni del programma. Un segnale indica la
prossima istruzione da eseguire. Il resto del foglio è destinato a contenere i dati
su cui opera il programma. Ogni singolo dato può occupare una serie di quadretti contigui. Quindi possiamo immaginare che il foglio sia composto di una
serie di aree delimitate, ognuna delle quali ospita un dato.
I dati formano la rappresentazione (modello) della realtà che riguarda il problema in esame. Il programmatore deve imparare a riplasmare la realtà nelle
forme concesse dal linguaggio (conosceremo i modi per costruire rappresentazioni complesse a partire dai tipi semplici). Esempio: il mazzetto di biglietti da
riordinare può essere rappresentato da una fila di aree, che riflette l’ordine dei
biglietti.
Chiameremo “variabile” ognuna di queste aree. Ogni variabile ha un nome,
o identificatore, che possiamo usare nel codice sorgente per fare riferimento a
4
quel dato, ed è di un tipo. Esempi semplici di tipo: numeri interi, numeri reali,
simboli (caratteri). Il tipo determina la dimensione dell’area occupata da una
variabile (il numero di quadretti), ma anche le operazioni che si possono applicare. Per i tipi numerici, le operazioni aritmetiche, e così via. Altre operazioni
tipiche: confronto per stabilire se due variabili hanno lo stesso valore, e così via.
Digressione sui tipi numerici: solo una gamma finita di valori; per i numeri
interi, minimo e massimo; per i numeri reali, minimo, massimo e buchi nella
retta reale.
Ogni numero intero occupa la stessa area (ad es. 4 quadretti).
Un programma deve sempre dichiarare le variabili che usa, indicando il loro
tipo. Esempi di dichiarazione di variabili in C:
int base, altezza; float temperatura; char m;
In questo esempio, le due variabili base e altezza sono entrambe di tipo “numero intero”.
4.1
L’ ASSEGNAMENTO
Nella programmazione imperativa c’è un’operazione fondamentale: l’assegnamento, ossia l’operazione di scrivere un valore in una variabile (nell’area della
memoria di lavoro riservata a quella variabile).
In C, l’istruzione di assegnamento comprende tre elementi: il nome della
variabile alla quale si deve assegnare un valore; il simbolo dell’istruzione di assegnamento, cioè il carattere =; un’espressione che dev’essere valutata, e il cui
valore viene assegnato alla variabile.
Esempi di istruzioni di assegnamento in C:
base = 10; altezza = 18; area = (base * altezza)/2;
Nei primi due esempi, l’espressione è formata da un valore numerico fisso; nel
terzo caso è un’espressione aritmetica che dipende dal valore corrente (cioè il
valore nel momento in cui viene eseguita l’istruzione) di altre due variabili (qual
è il tipo del valore di questa espressione?).
Attenzione: in C, l’assegnamento è denotato dal segno di uguaglianza, ma
si tratta di un’operazione, non di una relazione! Meglio leggerlo “diventa”: base
diventa 10, e così via. È allora chiaro che ha senso un assegnamento di questo
tipo: x = x + 1; (incremento del valore corrente di x), mentre non ha senso
questo: x + y = 0. Soffermatevi sul significato del tutto diverso che ha il nome
di una variabile quando compare a sinistra di un assegnamento oppure a destra;
nel primo caso, il nome serve a individuare l’area di memoria corrispondente alla variabile, e nella quale si dovrà scrivere un nuovo valore; nel secondo caso, il
nome denota il valore presente (operazione implicita di lettura). Quindi l’assegnamento si esegue così: si deve valutare l’espressione scritta a destra, eventualmente leggendo i valori correnti di una o più variabili; poi si scrive questo valore
nell’area di memoria indicata dal nome a sinistra.
5
Siamo ora in grado di scrivere semplici istruzioni. Ma per scrivere un programma dobbiamo dare una “struttura” logica alle istruzioni. La forma strutturale più semplice è la sequenza: un programma deve poter specificare che un’istruzione va eseguita prima di un’altra. Soluzione: l’ordine in cui compaiono
nel codice sorgente. Esempio:
float base, altezza, area;
base = 7.15;
altezza = 12.368;
area = (base * altezza)/2;
Con assegnamento e sequenza, però, non si possono risolvere molti problemi.
Riesaminando l’algoritmo di ordinamento per selezione, possiamo rilevare due
altre forme: in alcuni casi, scegliamo la prossima istruzione da eseguire sulla
base di un confronto fra due valori (o, più in generale, fra due espressioni); è la
struttura della scelta. Esempio:
int a, b, max, min;
a = 17653;
b = 9543;
if ( a <= b )
{
max = b;
min = a;
}
else
{
max = a;
min = b;
}
Oltre a poter scegliere fra due istruzioni, o serie di istruzioni, alternative, vogliamo anche poter specificare che una certa serie di istruzioni deve essere ripetuta
fino a quando si avvera una certa condizione. Esempio:
int euro;
float lire, cambio;
cambio = 1936.27;
euro = 4;
while ( euro < 10000 ) {
lire = euro * cambio;
/* Stampa il valore di euro e di lire */
euro = euro * 2;
}
Sequenza, scelta, e ripetizione (iterazione) formano un insieme completo di
strutture di controllo per la programmazione imperativa. Le strutture di controllo possono annidarsi una nell’altra:
6
if ( ... ) {
a = b/2;
while ( ... ) {
....
}
}
else
...
Oppure:
if ( a < b ) {
if ( b < c ) {
...
}
}
else
...
while ( i < 10 ) {
while ( h < 5 ) {
....
}
}
Una serie di istruzioni ordinate in un’intelaiatura stabilita da diverse strutture
di controllo può essere rinchiusa in una funzione, alla quale si può dare un nome, che diventa una specie di nuova istruzione, che estende il linguaggio (ma
di questo diremo meglio nella prossima lezione). Ogni programma in C deve
contenere una funzione speciale, chiamata main.
Struttura generale di un programma semplice in C.
7