5. Codifica degli Algoritmi in C
La traduzione di algoritmi in linguaggi di programmazione rende possibile l’esecuzione di programmi da parte del calcolatore. In questo corso codificheremo gli algoritmi nel linguaggio di programmazione C. Questo linguaggio non è direttamente eseguibile dalla macchina ma viene trasformato dal compilatore in un file eseguibile linkando eventualmente porzioni di codice esterno.
Introdurrremo ora brevemente gli elementi di un linguaggio di programmazione facendo riferimento al linguaggio C. (La seguente sezione non è intesa essere una presentazione esaustiva del linguaggio C, per la quale si rimanda al Corso di Laboratorio di Programmazione).
Un linguaggio è caratterizzato da una sintassi ed una semantica. La sintassi è l’insieme delle regole che caratterizzano le frasi del linguaggio che sono ben formate e quindi accettabili per il compilatore. La semantica permette di specificare che cosa fa un programma quando viene eseguito.
Gli elementi fondamentali di un linguaggio quale il C sono le variabili. Ogni variabile ha un nome che la identifica (ed in particolare denota la porzione di memoria ad essa associata), ed un tipo, che esprime quali oggetti possono essere assegnati ad essa (e quindi la dimensione dell’area di memoria associata) e in quali operazioni può essere utilizzata. Il nome di una variabile può essere una qualsiasi sequenza di lettere e cifre e può contenere il simbolo "_" (underscore). Ogni nome legale inizia con una lettera o con "_".
Come abbiamo già visto nel linguaggio dei diagrammi di flusso, una variabile può essere utilizzata in due modi: leggendone il contenuto o assegnandole un valore. A differenza dei diagrammi di flusso, in C, prima di usare una variabile, è neccesario dichiararla. Per esempio, la dichiarazione
int var;
dice che la variabile di nome var è di tipo intero.
I tipi di base disponiblili nel linguaggio C sono i seguenti:
TIPO DIMENSIONE VALORI
int da 16 a 32 bit da -2147483648 a 2147483647
float 32 bit reali in precisione singola
double 64 bit reali in precisione doppia
char 8 bit da –128 a 127
E’ possibile cambiare la dimensione del tipo usando i modificatori: long (per lavorare con valori molto grandi) e short (per limitare la capacità della variabile).
In C non esiste un tipo booleano: gli interi diversi da zero vengono riconosciuti come valore True, mentre a zero viene associato il valore False.
5.1. Espressioni Semplici
Le espressioni del C possono contenere variabili, numeri e i seguenti operatori:
OPERATORE SIGNIFICATO
* prodotto
/ diviso
+ somma
- sottrazione
% Modulo (resto delle divisione intera)
Sebbene ci sia un unico simbolo per ognuno degli operatori *, / , + e –, in realtà ce ne sono molte versioni, a seconda del tipo di variabili cui sono applicati. Per esempio, c’è un operatore * che, due interi, restituisce un intero; un altro che, applicato a due numeri float, restituisce un float, e così via. La scelta dell’operatore sarà fatta dal compilatore in base al tipo dei suoi operandi. Nella maggioranza dei casi questa situazione non provoca problemi. Comunque, per la divisione, il comportamento può risultare non ovvio. Ad esempio, 1 / 2 restituirà 0, perché gli operandi sono entrambi di tipo intero e quindi viene selezionato l’operatore / fra interi, che restituisce l’intero che approssima il risultato; al contrario, 1.0 / 2.0 restituirà 0.5, perché gli operandi sono entrambi float e quindi viene selezionata la divisione fra float.
5.2. Assegnamento
L’ istruzione che assegna il valore ad una variabile è, come nel caso dei diagrammi di flusso, l’assegnamento, che in C è denotato dall'operatore "=". Ad esempio, se la variabile a è stata dichiarata di tipo intero, l'assegnazione
a = 100;
attribuisce ad a il valore 100. A sinistra dell'operatore di assegnamento comparirà una variabile e a destra una espressione che deve avere tipo compatibile con quello della variabile.
5.3. Schema di un Programma C
Un programma in C è costituito da dichiarazioni di variabili e di funzioni e di una particolare funzione di nome main, che costituisce il codice eseguito quando si fa partire l’eseguibile generato dalla compilazione del programma. Ad esempio:
#include <stdio.h>
Dichiarazione di variabili e di funzioni
int main(void) {
.... istruzioni ...
}
La funzione main viene richiamata per prima in fase di esecuzione; anch’essa restituisce un valore al sistema operativo.
5.4. Funzioni di I/O
In C non esistono funzioni predefinite per l’input e l’output; per questo è necessario includere nel file sorgente delle librerie esterne; questo è fatto dalla direttiva
#include <stdio.h>
che permette di utilizzare le funzioni contenute nella libreria stdio, che mette a disposizione, fra le altre, la funzione:
printf(stringa_formato,... espressioni…);
La funzione printf ha un numero di parametri variabile. Il primo specifica il formato del testo che dovrà essere stampato, mentre le espressioni che seguono sono i valori che desideriamo stampare e la cui stampa è determinata dalla stringa_formato.
Esempio 27 – Sia a una variabile dichiarata di tipo intero, con valore 100 e sia f una variabile float con valore 3.14. L'istruzione
printf(“La variabile a = %d e la variabile f = %f”, a, f);
stampa:
La variabile a=100 e la variabile f=3.14. ❏ I due simboli % indicano che in quel punto dovrà comparire il valore delle variabili elencate in coda, mentre la lettera indica il tipo di dato che ci si aspetta di trovare. Per una tabella dei codici di formato si rimanda ad un qualsiasi testo sul C. Si faccia attenzione perchè non c’è alcun controllo da parte del compilatore che ci sia consistenza fra il numero dei % (e il loro tipo) nella stringa di formato ed i parametri che seguono. Per cui si possono ottenere risultati inaspettati.
5.5. Operatori Brevi
Il linguaggio C permette di utilizzare delle scorciatoie nella scrittura delle istruzioni.
Quando a sinistra e a destra di un assegnamento compare la stessa variabile, come ad esmpio in: i = i+1, si può scrivere i += 1. In generale ogni assegnamento del tipo:
v = v op espressione,
dove op è uno degli operatori della tabella data in precedenza ed espressione è una qualsiasi espressione, può essere abbreviato in
v op = espressione
L’incremento e il decremento di 1 della variabile v possono essere ulteriormente abbreviati in: v++, ++v, v--, e –v.
5.6. Istruzioni Strutturate
Le istruzioni di assegnamento possono essere combinate attraverso la sequenzializzazione espressa da “;” nel modo seguente:
a=3; b=a+1
Dapprima si segue l’assegnamento a=3 e poi b=a+1. Inoltre, più istruzioni possono essere raggruppate in un blocco usando parentesi graffe {}.
Ci sono infine costrutti di selezione e di iterazione che presenteremo brevemente.
if … (else) : permette di eseguire condizionalmente delle istruzioni. Le sintassi del costrutto sono le seguenti:
if(condizione) istruzione1;
else
istruzione2;
if (condizione) istruzione1;
La condizione è un’espressione; se il suo valore è diverso da 0, viene eseguita l’istruzione1 altrimenti nel primo caso è eseguita l’istruzione2, e, nel secondo, l'istruzione che segue. Nella condizione possono essere usati i seguenti operatori relazionali:
OPERATORE SIGNIFICATO
<= Minore o uguale
< Minore
> Maggiore
>= Maggiore o uguale
== Uguale
!= Diverso
I precedenti operatori, quando applicati, ritorneranno un valore intero che è diverso da zero se la condizione è vera o uguale a zero altrimenti.
Visti come diagrammi di flusso i costrutti precedenti corrispondono ai seguenti:
Nel caso si voglia eseguire più di un’istruzione, basterà usare un blocco al posto di istruzione1 e/o istruzione2. La keyword else viene sempre riferita al costrutto if più vicino; nel caso di costrutti del tipo:
if (condizione1) if (condizione2) istruzione1;
else istruzione2;
istruzione3;
l’else è riferito al test sulla condizione2. Nel caso si volesse far riferire l’else alla condizione1 si dovrebbero usare le parentesi per raggruppare il secondo costrutto if nel modo seguente:
condizione ≠ 0
si no
istruzione1
istruzione2
condizione ≠ 0
istruzione1 si no
if (condizione1) {if (condizione2) istruzione1; }
else istruzione2;
istruzione3;
Notare che, come un blocco è considerato un’unica istruzione, anche un costrutto if o if-else viene considerato come un’unica istruzione.
I cicli (loop) permettono di eseguire un blocco di codice un numero di volte indefinito, finché risulta vera una condizione. Il linguaggio C mette a disposizione tre cicli fondamentali: while, for e do-while (qui descriveremo solo i primi due).
while: La sintassi del costrutto è la seguente
while (condizione) istruzione;
La condizione è come nel caso del costrutto if una espressione. Se il suo valore è diverso da zero si esegue l’istruzione e poi si ritorna alla valutazione del costrutto while.
Anche in questo caso, se si vogliono eseguire più istruzioni si può usare un blocco.
for : questa è l'istruzione iterativa più complessa. La sua sintassi è la seguente:
for (istruzione1; condizione; istruzione2) istruzion3;
L’ istruzione1 viene eseguita prima di entrare nel ciclo, che corriponde ad un ciclo while in cui viene eseguita prima l’istruzione3 (che è il body del for) e poi la istruzione2 (che è l’istruzione di iterazione). Una o più delle precedenti istruzioni può mancare. Il for corrisponde al seguente diagramma di flusso:
condizione ≠ 0 si
istruzione1
istruzione3 istruzione2
no condizione ≠ 0 si istruzione
no
I seguenti cicli for corrispondono ad un ciclo while:
for ( ; condizione; ) istruzione;
for ( ; condizione; istruzione);
5.7. Commenti
Il commento è una porzione del testo che non viene considerata dal compilatore.
Durante la fase di compilazione, i commenti vengono trascurati, pertanto non compaiono all’interno del file eseguibile. Servono per esplicitare affermazioni non altrimenti riconoscibili, o per chiarire il significato di una porzione di codice.
Verranno usati per scrivere, tra le altre annotazioni, l’invariante del ciclo. All’interno del commento può comparire qualsiasi simbolo; non esiste una lunghezza massima consentita per i commenti. Il compilatore C riconosce due tipi di commento:
/* questo è un commento racchiuso tra le due barre con gli asterischi e può trovarsi su più righe
*/
// questo è un commento che termina alla fine della riga.
// Per proseguire è necessario riscrivere il doppio slash all'inizio // di ogni riga