LA PROGRAMMAZIONE NEL LINGUAGGIO C
Cenni storici.
• 1972: Prima implementazione del linguaggio C su elaboratore
PDP11 da parte di D. M. Ritchie presso i laboratori AT&T Bell. È
introdotto per potere riscrivere in un linguaggio di alto livello il codice
del sistema operativo UNIX. È un’evoluzione del B e del BCPL in cui
sono aggiunti i tipi e le strutture di controllo.
• Anni ’80: Sviluppo del “C tradizionale”.
• 1983: Inizia la definizione dello standard “ANSI C” da parte
dell’American National Standards Institute.
• 1990: L’International Standardization Organization (ISO) approva
l’ANSI C (o “ANSI/ISO C”).
È a questo standard che fa riferimento il corso e i libri consigliati.
Per approfondire la storia del C, si veda l’articolo originale “The
Development of the C Language”, di Dennis M. Ritchie, sul sito
http://cm.bell-labs.com/cm/cs/who/dmr/chist.html
Introduzione: la modularità. Un programma ben disegnato è
costruito usando una filosofia di progetto simile a quella usata per
costruire un edificio; anche nel caso di un programma una parte
fondamentale del progetto è costituita dalla struttura.
Nella programmazione il termine “struttura” ha due significati
collegati.
• il primo si riferisce alla costruzione globale del programma
• il secondo alla forma usata per eseguire i singoli compiti all’interno
del programma.
In relazione al primo significato, i programmi la cui struttura consiste in
segmenti correlati, disposti in un ordine logico e facilmente
comprensibile a formare una unità integrata e completa sono detti
programmi modulari.
I programmi modulari sono più facili da sviluppare, correggere e
modificare di quelli costruiti in altro modo.
Nella terminologia generale della programmazione, i più piccoli
segmenti usati per costruire un programma modulare sono detti
moduli.
Ogni modulo è progettato e sviluppato per svolgere un compito
specifico, ed è in realtà un sotto-programma.
Un programma in C completo è costruito combinando tutti i moduli
che servono per produrre il risultato desiderato.
Il vantaggio della costruzione modulare è che il progetto complessivo
del programma può essere sviluppato prima di scrivere qualsiasi
modulo singolo.
Dato che un modulo è in realtà un piccolo sotto-programma, ogni
modulo deve eseguire ciò che si richiede a tutti i programmi:
ricevere ed elaborare i dati e produrre un risultato.
Tuttavia, a differenza di un programma più grande, un modulo compie
una operazione singola, ed è finalizzato a gestire, al massimo una o
due funzioni richieste dal programma completo.
Per questa ragione, in C i moduli sono detti funzioni.
Funzioni
È utile pensare a una funzione come a una macchina che trasforma i
dati che riceve in un prodotto finito.
La figura illustra una funzione che accetta due numeri in ingresso e li
moltiplica per produrre l’uscita.
Il modo in cui gli ingressi sono trasformati in risultati è incapsulato e
nascosto all’interno della funzione
Nel costruire una funzione è opportuno darle un nome che richiami
l’idea di ciò che essa fa: ad es. si potrebbe chiamare
gradi_rad() una funzione che converta i gradi in radianti.
I nomi validi per le funzioni sono usati anche per denominare altri
elementi del linguaggio C, e sono complessivamente detti
identificatori.
Gli identificatori sono composti da qualsiasi combinazione di lettere,
cifre e carattere di sottolineatura (_), scelti secondo le seguenti
regole:
1. il primo carattere deve essere una lettera o il segno di
sottolineatura (_ );
2. dopo il primo carattere ci possono essere solo lettere, cifre o segni
di sottolineatura; non sono consentiti spazi vuoti, punti, virgole o
simboli speciali quali ( ) & $ # ! \ ?
3. il nome non può avere più di 31 caratteri.
4. il nome non può essere una delle parole chiave indicate in tabella
Oltre a rispettare le regole del C per gli
identificatori, il nome di una funzione
deve essere seguito da una coppia di
parentesi tonde.
Quindi, ad es.:
Si tenga presente che i nomi delle funzioni sono tradizionalmente scritti
in lettere minuscole, e che il C è un linguaggio sensibile al carattere
(case sensitive) maiuscolo/minuscolo.
La funzione main(). Una volta assegnato un nome alle funzioni,
esse devono essere combinate in un programma completo.
In C si può pianificare un programma decidendo dapprima quali
funzioni servano e come vadano collegate, e solo
successivamente scrivendo le funzioni effettive.
Per fornire un ordinato posizionamento ed esecuzione delle funzioni,
ogni programma in C deve avere una funzione detta main() che
indica al compilatore il punto in cui inizia l’esecuzione del
programma.
main() è detta anche funzione pilota, dato che dice alle altre funzioni
la sequenza in cui devono operare.
Lo schema seguente illustra una funzione main() completa
La prima linea della funzione, void main(void), è detta linea di
intestazione della funzione. Essa contiene tre tipi di informazioni:
La parola chiave void, posta prima del nome della funzione, indica
che essa non restituirà alcun valore una volta eseguita.
Invece, posta dentro le parentesi che seguono il nome della
funzione, void indica che non verranno passati dati alla
funzione quando sarà eseguita (i dati passati a una funzione sono
detti i suoi argomenti).
Le parentesi graffe { } determinano l’inizio e la fine del corpo della
funzione e racchiudono le istruzioni che la costituiscono.
Tali istruzioni determinano cosa farà la funzione, e ciascuna di esse va
terminata con un punto e virgola (;).
La precedente funzione main() consiste in quattro istruzioni,
ciascuna delle quali è il comando di eseguire un’altra funzione,
nell’ordine: gross_pay(), taxes(), net_pay(), output().
La funzione printf(). Il C utilizza numerose funzioni di biblioteca,
tra le quali una delle più diffuse e utili è printf().
Essa, come suggerisce il nome, è una funzione di stampa che
formatta i dati che le vengono forniti e li visualizza sullo schermo.
A printf()s può fornire, ad es., un messaggio quale Salve a
tutti!. Il fornire dati o messaggi a una funzione è detto anche
passare dati alla funzione.
Per passare il messaggio Salve a tutti! a printf()è
sufficiente inserirlo nelle parentesi del nome della funzione come
segue:
printf(“Salve a tutti!”);
Possiamo pensare alle parentesi nei nomi di funzione come a un
imbuto attraverso il quale si passa l’informazione alla funzione.
Come abbiamo già osservato, i dati passati alla funzione attraverso
le parentesi sono detti gli argomenti della funzione.
Possiamo mettere insieme tutti questi elementi nel seguente
programma.
#include <stdio.h>
void main(void)
{
printf(“Salve a tutti!”);
}
La prima linea
#include <stdio.h>
è un comando al preprocessore.
I comandi al preprocessore cominciano con il segno #, ed
eseguono determinate azioni prima che il compilatore traduca il
programma sorgente in codice macchina.
In particolare, il comando al preprocessore #include inserisce il
contenuto del file indicato - in questo caso stdio.h, - nel punto in
cui compare il comando #include.
Il file stdio.h è detto file header, dato che è collocato in cima a
un programma C tramite il comando #include.
Esso fornisce una interfaccia opportuna alla funzione printf(), e
deve essere inserito in tutti i programmi che usano tale funzione.
Come si nota nel programma precedente,
i comandi al preprocessore non terminano con un punto e virgola.
Dopo il comando al preprocessore c’è l’inizio della funzione main(),
costituita da una sola istruzione. Ricordiamo che le istruzioni
terminano con punto e virgola.
L’istruzione in main() chiama la funzione prinf() e le passa un
argomento, costituito dal messaggio Salve a tutti!.
Dato che prinf() è una funzione predefinita, essa non va scritta,
ma viene utilizzata semplicemente chiamandola in modo corretto.
Come tutte le funzioni C, prinf() è stata scritta per svolgere un
compito specifico, cioè stampare i risultati. È versatile e può
stampare i risultati in molte forme differenti.
In C i messaggi sono chiamati stringhe, dato che consistono in
stringhe di caratteri costituite da lettere, numeri e caratteri speciali.
L’inizio e la fine di una stringa di caratteri sono contrassegnati con
doppi apici (“).
Perciò, per passare un messaggio a prinf(), la stringa di caratteri
che costituisce il messaggio deve essere racchiusa tra doppi apici,
come abbiamo visto.
Sequenza di escape \n. Ecco un altro programma che illustra le
possibilità di prinf():
#include <stdio.h>
void main(void)
{
printf(“Computer, computer”);
printf(“\n
dovunque io guardi”);
}
I due caratteri \ (barra trasversa) e n, se usati insieme, sono detti
sequenza di escape di linea nuova. Essi non vengono visualizzati
nella stampa del risultato, ma indicano a printf() di iniziare a
stampare in una linea nuova.
In particolare, il carattere \ indica al computer di “uscire” (escape)
dalla interpretazione normale del carattere che lo segue,
alterandone il significato.
Altre sequenze di escape sono indicate in tabella, con i loro significati.
Sebbene ciascuna sequenza sia costituita da due caratteri distinti, la
loro combinazione fa memorizzare al computer il codice ASCII di
un singolo carattere (vedi Fonda1, diap. 48).
Una o più sequenze di escape di linea nuova possono essere
inserite in un punto qualsiasi del messaggio passato a printf().
Ad es., il programma seguente
#include <stdio.h>
void main(void)
{
printf(“Computer, computer\n ovunque\n\nio guardi”);
}
produce la seguente visualizzazione:
Computer, computer
ovunque
io guardi
L’ultimo carattere stampato da qualsiasi funzione che esegua una
visualizzazione dovrebbe essere sempre \n, in modo che la
funzione lasci il cursore nella posizione iniziale di una linea nuova.
Infatti ogni nuova visualizzazione comincia nel punto in cui è terminata
la visualizzazione precedente.
Commenti. I commenti sono note esplicative all’interno di un
programma. Se usati con accortezza, aiutano a chiarire cosa fa
l’intero programma, un gruppo di istruzioni o una singola linea.
Un commento inizia con i caratteri /* (senza spazi intermedi), e
termina con i caratteri */.
I commenti possono essere situati in un punto qualsiasi del
programma, e non hanno effetto sulla sua esecuzione, dato che il
compilatore li ignora.
Il loro uso è illustrato dal seguente programma.
#include <stdio.h>
void main(void)
/* questo programma stampa un messaggio */
{
printf(“Salve a tutti!”); /* chiamata a printf() */
}
Esso produce la stessa uscita del programma precedente.
Operatori aritmetici
I numeri interi, in virgola mobile e in doppia precisione possono essere
sommati, sottratti, divisi e moltiplicati.
Sebbene sia preferibile non mescolare gli interi con gli altri due tipi dati
numerici nell’eseguire operazioni aritmetiche, se nella medesima
espressione aritmetica si usano tipi dati differenti si ottengono
risultati prevedibili.
Anche i dati carattere si possono sommare e sottrarre con i dati
carattere e intero, ottenendosi risultati utili.
Gli operatori usati per queste operazioni sono detti operatori aritmetici:
Ciascun operatore aritmetico è di tipo binario e richiede due
operandi. Una semplice operazione aritmetica consiste in un
operatore aritmetico che collega due operandi aritmetici. Esempi:
18 – 3
12.62 + 9.8
.08 * 12.2
12.6 / 2.
In questi esempi gli spazi prima e dopo gli operatori sono inseriti solo
a scopo di chiarezza, e si possono omettere senza alterare il valore
delle espressioni.
Un’espressione che contenga solo operandi interi è detta
espressione intera, e ha come risultato un valore intero.
Analogamente, un’espressione che contenga solo operandi in virgola
mobile (in singola e doppia precisione) è detta espressione in
virgola mobile, e ha come risultato un valore in doppia precisione.
Un’espressione che contenga operandi sia interi sia in virgola mobile
è detta espressione mista, e ha come risultato un numero in doppia
precisione.
Tuttavia è meglio non mescolare nella stessa espressione operandi
interi e in virgola mobile.
Divisione intera (operatore modulo %). La divisione di due interi
può produrre risultati strani, dato che anche il risultato fornito è un
intero. Così 15/2 fornisce il risultato 7, con la parte decimale
troncata.
Per ottenere il resto di una divisione tra interi, C fornisce l’operatore
%, detto operatore modulo. Esempi:
9 % 4 fornisce 1
17 % 3 fornisce 2
14 % 2 fornisce 0
Operatore unario (negazione). Oltre agli operatori binari visti, C
fornisce anche degli operatori unari.
Uno di essi impiega lo stesso segno meno della sottrazione binaria
(-), e serve per cambiare segno a un numero positivo.
La tabella seguente riassume le 6 operazioni aritmetiche viste finora,
e mostra i tipi dati dei risultati prodotti da ciascun operatore, a
seconda del tipo dati degli operandi coinvolti.
Precedenza e associatività degli operatori. Il C, come tutti i linguaggi
di programmazione, prevede delle regole per la precedenza e
l’associativtà degli operatori. Esse sono:
1. I simboli di due operatori aritmetici binari non devono mai essere
situati vicini. Ad es., 5 * %6 non è valido.
2. Si posono usare le parentesi per formare dei gruppi, e tutte le
espressioni racchiuse tra parentesi sono valutate per prime.
Si possono anche racchiudere gruppi di parentesi all’interno di altri
gruppi di parentesi, e in tal caso vengono valutate per prime le
espressioni dentro le parentesi più interne.
Il numero di parentesi aperte deve essere uguale al numero di
parentesi chiuse.
3. Per indicare le moltiplicazioni non si possono usare le parentesi, e
va usato invece l’operatore*.
Ad es., l’espressione (3+4)(5+1) non è valida e va sostituita con
(3+4)*(5+1).
La precedenza di un operatore stabilisce la sua priorità relativamente
agli altri operatori.
Nella tabella seguente, gli operatori in cima alla tabella hanno priorità
più alta di quelli in fondo.
Le espressioni contenenti operatori con la stessa precedenza sono
valutate in base alla loro associatività, cioè o da sinistra a destra o
viceversa.
Vediamo, come esempio, come verrebbe valutata la seguente
espressione:
8
8
8
8
+ 5 * 7 % 2 * 4 =
+
35 % 2 * 4 =
+
1
* 4 =
+
4
= 12
Sequenze di controllo di conversione
In aggiunta alla visualizzazione di messaggi, la funzione printf()
permette di valutare espressioni aritmetiche e di visualizzarne i
risultati.
A tale fine si devono passare a printf(), inserendoli nelle sue
parentesi, almeno due argomenti, separati da una virgola:
- una stringa di controllo, che dice alla funzione dove e in che forma
visualizzare il risultato;
- l’espressione di cui vogliamo che sia visualizzato il risultato.
Sequenza %d. Consideriamo, ad es., l’istruzione
printf(“La somma di 6 e 15 è %d”, 6 + 15);
1° argomento
2° argomento
Il primo argomento passato a printf() deve essere sempre un
messaggio. Un messaggio che comprenda anche una sequenza di
controllo di conversione, quale %d, è detto stringa di controllo.
Messaggio +
Sequenza di
controllo di =
conversione
Stringa di
controllo
Le sequenze di controllo di conversione (dette anche specificazioni di
conversione e specificatori di formato) hanno un significato speciale
per la funzione printf(), in quanto le dicono quale tipo di valore
visualizzare e dove.
Una sequenza di controllo di conversione inizia sempre con il simbolo
% e termina con uno dei caratteri di conversione d, ld, u, f, e, c.
Come vedremo, tra il simbolo % e il carattere di conversione si
possono inserire caratteri aggiuntivi.
Quindi, in definitiva,
• il segno % in una sequenza di controllo di conversione dice a
printf() di stampare un valore nella posizione del messaggio
dove si trova il %.
• la d, subito dopo il %, dice che il valore deve essere stampato come
un intero.
Quando printf() vede nella sua stringa di controllo la sequenza
di controllo di conversione, sostituisce al suo posto il valore
dell’argomento successivo.
Dato che l’argomento successivo è l’espressione 6 + 15, che ha il
valore 21, è questo il valore che viene visualizzato.
Perciò la precedente istruzione
printf(“La somma di 6 e 15 è %d”, 6 + 15);
produce la visualizzazione
La somma di 6 e 15 è 21
Interi lunghi (long, %ld). In genere i numeri interi sono usati nei
programmi come contatori che tengono traccia del numero di volte
che un evento si è verificato.
Di solito i computer destinano agli interi 2 byte, con i quali si possono
memorizzare numeri interi non superiori a 32.767; tale numero è
più che sufficiente per la maggior parte dei conteggi necessari.
Talvolta, tuttavia, sono necessari numeri interi più grandi.
Ad es., in molte applicazioni finanziarie le date sono convertite nel
numero di giorni dall’inizio del secolo, in modo da disporre di un solo
numero per memorizzarle e ordinarle, e ciò può richiedere numeri
maggiori di 32.767.
Per tali esigenze il C fornisce i tipi dati
intero lungo
intero corto
intero senza segno
che si ottengono aggiungendo rispettivamente i qualificatori
long
short
unsigned
alle normali istruzioni di dichiarazione di interi.
Ad es., l’istruzione di dichiarazione
long int giorni;
dichiara la variabile giorni come intero lungo. In essa la parola
int è opzionale, cosicché si può scrivere anche
long giorni;
La quantità di memoria allocata per un intero lungo dipende dal
computer, e può essere conosciuta usando l’operatore sizeof che
vedremo in seguito.
Una volta che una variabile sia stata dichiarata come intero lungo, un
valore intero le può essere assegnato o come si fa con gli interi
soliti, oppure aggiungendo una lettera l (o L) all’intero (senza
spazi). Ad es., l’istruzione di dichiarazione
long giorni = 38276L;
dichiara la variabile giorni di tipo intero lungo, e le assegna la
costante 38.276 di tipo intero lungo.
Per visualizzare un valore intero lungo con la funzione printf() si
deve usare la sequenza di controllo di conversione %ld.
Interi corti e interi senza segno (short, unsigned, %u). In
aggiunta al qualificatore long, C fornisce:
- il qualificatore
- il qualificatore
short
unsigned
per gli interi corti;
per gli interi senza segno.
Anche la quantità di memoria allocata per gli interi corti dipende dal
computer e può essere conosciuta con l’operatore sizeof.
Il tipo dati intero senza segno è usato per memorizzare solo interi
positivi, e in effetti raddoppia i valori positivi che possono essere
memorizzati, senza aumentare il numero di byte allocati per un
intero.
Per dichiarare, ad es., la variabile giorni di tipo intero senza segno
si scrive:
unsigned int giorni;
o anche
unsigned giorni;
Per visualizzare un intero senza segno con printf(), o leggerlo
con scanf(), si usa la sequenza di controllo di conversione %u.
Numeri in virgola mobile (%f). Come la sequenza di controllo di
conversione %d fa visualizzare a printf() un valore intero,
così la sequenza %f (f sta per floating point) fa visualizzare un
numero in virgola mobile (ossia con il punto decimale). Ad es.,
l’istruzione
printf(“La somma di %f e %f è %f.”, 12.2,15.754,12.2+15.754);
produce la visualizzazione
La somma di 12.200000 e 15.754000 è 27.954000.
Come si vede, la sequenza di controllo di conversione %f fa
visualizzare a printf() 6 cifre alla destra del punto decimale,
aggiungendo degli zeri se il numero non ha 6 cifre decimali,
arrotondando la parte decimale se il numero ne ha più di 6.
Dato che printf() non controlla i valori che le sono passati, se si
usa una sequenza di controllo di conversione intera (%d) ma il valore
passato alla funzione è un numero in virgola mobile o a doppia
precisione, la visualizzazione dipenderà dal computer.
Analogamente, se si usa una sequenza di controllo di conversione in
virgola mobile e il numero corrispondente è intero, si otterrà un
risultato imprevisto.
Il programma seguente illustra l’uso di printf() per visualizzare i
risultati di quattro espressioni in virgola mobile.
#include <stdio.h>
void main(void)
{
printf(“%f più
printf(“%f meno
printf(“%f per
printf(“%f diviso
}
%f
%f
%f
%f
uguale
uguale
uguale
uguale
%f\n”,
%f\n”,
%f\n”,
%f\n”,
15.0,
15.0,
15.0,
15.0,
2.0,
2.0,
2.0,
2.0,
15.0
15.0
15.0
15.0
Esso produce la seguente visualizzazione:
15.000000
15.000000
15.000000
15.000000
più
meno
per
diviso
2.000000
2.000000
2.000000
2.000000
uguale 17.000000
uguale 13.000000
uguale 30.000000
uguale 7.500000
+
*
/
2.0);
2.0);
2.0);
2.0);
Si noti che ogni istruzione del programma precedente passa 4
argomenti alla funzione printf(): una stringa di controllo e 3
valori.
In ogni stringa di controllo vi sono tre sequenze di controllo di
conversione (una per ciascun valore che deve essere visualizzato).
Notazione esponenziale (%e). La sequenza di controllo di
conversione %e (e sta per exponent) fa visualizzare un numero in
virgola mobile nella notazione esponenziale.
Perciò, se si modifica il programma precedente come segue,
#include <stdio.h>
void main(void)
{
printf(“%f più
printf(“%f meno
printf(“%f per
printf(“%f diviso
}
%f
%f
%f
%f
uguale
uguale
uguale
uguale
%e\n”,
%e\n”,
%e\n”,
%e\n”,
15.0,
15.0,
15.0,
15.0,
2.0,
2.0,
2.0,
2.0,
15.0
15.0
15.0
15.0
+
*
/
si ottiene la seguente visualizzazione:
15.000000
15.000000
15.000000
15.000000
più
meno
per
diviso
2.000000
2.000000
2.000000
2.000000
uguale
uguale
uguale
uguale
1.700000e+01
1.300000e+01
3.000000e+01
7.500000e+00
2.0);
2.0);
2.0);
2.0);
Sequenza %c. I caratteri singoli sono visualizzati usando la sequenza
di controllo di conversione %c. Ad es., l’istruzione
printf(“La prima lettera dell’alfabeto è %c.”, ‘a’);
produce la visualizzazione
La prima lettera dell’alfabeto è a.
Sequenza %s. Le stringhe di caratteri sono visualizzate usando la
sequenza di controllo di conversione %s. Ad es., il programma
#include <stdio.h>
void main(void)
{
printf("Il mio nome è
: %9s", "Angelo");
printf("\nIl mio cognome : %9s", "Gallippi");
}
produce la visualizzazione
Il mio nome è
: Angelo
Il mio cognome è: Gallippi
Specificatori di larghezza di campo
Numeri interi. Oltre a visualizzare risultati corretti, è importante che
un programma li presenti in maniera ordinata.
Il formato dei dati visualizzati da printf() può essere controllato
da specificatori di larghezza di campo inclusi come parte di ciascuna
sequenza di controllo di conversione.
Ad es., l’istruzione
printf(“La somma di%3d e%4d è%5d.”, 6, 15, 21);
produce la visualizzazione
La somma di
6 e
15 è
21
I numeri 3, 4 e 5 nella stringa di controllo sono gli specificatori di
larghezza di campo.
Lo specificatore di larghezza di campo per la prima sequenza di
controllo di conversione, %3d, fa visualizzare il primo numero in
una larghezza totale di campo di 3 spazi, in questo caso 2 spazi
vuoti seguiti dal numero 6.
Lo specificatore %4d fa visualizzare 2 spazi vuoti e il numero 15,
per una larghezza totale di campo di 4 spazi.
L’ultimo specificatore, %5d, fa visualizzare 3 spazi vuoti e il numero
21, per una larghezza totale di campo di 5 spazi, che comprende.
Osservazione. Mentre in assenza di uno specificatore di larghezza
di campo la giustificazione avveniva a sinistra,
la presenza di uno specificatore di larghezza
di campo determina la giustificazione a destra.
Il programma seguente illustra come una colonna di interi viene
allineata in assenza di specificatori di larghezza di campo
#include <stdio.h>
void main(void)
{
printf(“\n%d”, 6);
printf(“\n%d”, 18);
printf(“\n%d”, 124);
printf(“\n---“);
printf(“\n%d”, 6+18+124);
}
Esso produce l’uscita
Invece il programma seguente illustra come gli specificatori di
campo permettano di allineare i numeri secondo le cifre delle unità.
#include <stdio.h>
void main(void)
{
printf(“\n%3d”, 6);
printf(“\n%3d”, 18);
printf(“\n%3d”, 124);
printf(“\n---“);
printf(“\n%3d”, 6+18+124);
}
L’uscita prodotta è:
Pertanto gli specificatori di larghezza di campo sono utili per
visualizzare colonne di numeri in modo che essi risultino incolonnati
correttamente.
Numeri in virgola mobile. I numeri in virgola mobile formattati
richiedono due specificatori di larghezza di campo:
• il primo determina la larghezza di visualizzazione totale, compreso il
punto decimale;
• il secondo determina il numero di cifre visualizzate alla destra del
punto decimale.
Ad es., l’istruzione
printf(“|%10.3f|” ,25.67);
produce la visualizzazione
|
25.670|
Lo specificatore 10.3 fa visualizzare il numero in un campo totale
di 10 spazi, che comprende il punto decimale e tre cifre alla sua
destra.
Dato che 25.67 contiene solo due cifre alla destra del punto
decimale, a esse viene aggiunto uno zero. Se ne contenesse di
più, esse verrebbero arrotondate.
Modificatori di formato
In aggiunta alle sequenze di controllo di conversione e agli
specificatori di larghezza di campo che si possono usare con esse,
il C fornisce anche dei modificatori di formato che forniscono due
controlli di formato aggiuntivi:
• la giustificazione sinistra e
• la visualizzazione del segno +.
Se usati, i modificatori di formato devono essere situati sempre
subito dopo il simbolo %.
Giustificazione a sinistra (%-). Abbiamo visto che uno specificatore
di larghezza di campo fa giustificare a destra i dati visualizzati. Per
forzare la giustificazione a sinistra, si usa il modificatore di formato
segno meno (-). Ad es., l’istruzione
printf(“|%-10d|”,59);
produce la visualizzazione
|59
|
(con 8 spazi vuoti che seguono il numero 59).
Visualizzazione del segno + (%+). Di solito il segno di un numero è
visualizzato solamente se il esso è negativo.
Per forzare la visualizzazione del segno, sia negativo sia positivo, si
usa il modificatore di formato segno più (+). Ad es., l’istruzione
printf(“|%+10d|”,59);
produce l’uscita
|
+59|
(con 7 spazi vuoti prima di +59). In assenza del segno + subito
dopo il simbolo % nella chiamata della funzione printf(), la
visualizzazione non conterrebbe il segno del numero positivo.
I modificatori di formato possono essere combinati. Ad es., la
sequenza di controllo di conversione %-+10d fa sì che un numero
intero visualizzi il suo segno e sia giustificato a sinistra in un campo
largo 10 spazi.
Dato che l’ordine dei modificatori di formato non è critico, la stessa
sequenza di controllo si sarebbe potuta scrivere %+-10d.
Determinazione della occupazione di memoria (sizeof()). Per
determinare la quantità di memoria che il computer alloca per ciascun
tipo dati, il C fornisce l’operatore sizeof().
Esso fornisce il numero di byte dell’oggetto o tipo dati racchiuso tra
parentesi. Un esempio del suo uso è fornito dal programma seguente:
#include <stdio.h>
void main(void)
{
char ch;
int num1;
printf(“Byte di memoria usati per un carattere: %d “,
sizeof(ch));
printf(“\nByte di memoria usati per un intero: %d “,
sizeof(num1));
}
Esso produce l’uscita
Byte di memoria usati per un carattere: 1
Byte di memoria usati per un intero: 4
Variabili e dichiarazioni
Tutti i dati usati in un programma vanno memorizzati e richiamati dalla
memoria del computer.
Consideriamo i 4 byte di memoria illustrati in figura, supponendo che i
due byte con indirizzi 1321 e 1322 siano usati per memorizzare
un intero, e che i due byte con indirizzi 2649 e 2650 siano usati
per memorizzare un secondo intero.
Nei linguaggi di alto livello, quale il C, al posto degli indirizzi effettivi
di memoria si usano nomi simbolici detti variabili.
Le variabili sono semplicemente nomi assegnati dai
programmatori alle locazioni di memoria del computer.
Si usa il termine “variabile” perché il valore memorizzato nella
locazione può cambiare.
Per ogni nome usato dal programmatore, il computer tiene traccia
dell’indirizzo effettivo.
Dare un nome a una variabile equivale ad
assegnare un nome a una stanza di
albergo, e riferirsi a essa con questo nome
- quale Suite imperiale - anziché con il suo
numero effettivo.
La scelta dei nomi delle variabili è lasciata al
programmatore, purché segua le regole già
indicate per i nomi di funzione.
In particolare, i nomi delle variabili sono scritti di solito in minuscolo,
ed è opportuno che siano il più possibile mnemonici, ossia
indicativi del tipo dato memorizzato.
Istruzioni di assegnazione. Osserviamo la seguente figura
Supponiamo che:
i due byte che iniziano all’indirizzo 45 abbiano il nome di variabile risultato
i due byte che iniziano all’indirizzo 1321 abbiano il nome di variabile num1
i due byte che iniziano all’indirizzo 2649 abbiano il nome di variabile num2
Usando questi nomi di variabile, le operazioni di
memorizzare 62 nella locazione 1321
sono eseguite
memorizzare 17 nella locazione 2649
dalle seguenti
sommare i contenuti delle due locazioni
istruzioni
num1 = 62;
num2 = 17;
risultato=num1+num2;
Esse sono chiamate istruzioni di assegnazione, dato che dicono al
computer di assegnare un valore a una variabile.
Le istruzioni di assegnazione hanno sempre un segno di uguale (=) e
un nome di variabile subito alla sua sinistra.
Il valore alla sua destra viene dapprima determinato e quindi assegnato
alla variabile alla sinistra del segno di uguale.
Gli spazi vuoti sono inseriti a solo scopo di leggibilità.
I nomi di variabili sono utili perché evitano al programmatore di doversi
preoccupare di dove i dati siano fisicamente memorizzati nel
computer.
Istruzioni di dichiarazione. Tuttavia, prima di memorizzare i valori
nelle variabili, il C richiede che venga indicato esplicitamente il tipo
dati che sarà memorizzato in ciascuna variabile.
Vanno indicati in anticipo al computer i nomi delle variabili che
saranno usate per i caratteri, per gli interi e per gli altri tipi dati
supportati dal C. In tal modo, quando si userà il nome di variabile, il
computer saprà a quanti byte di memoria accedere.
Per assegnare un nome a una variabile e definire il tipo dati che vi
può essere memorizzato si usano le istruzioni di dichiarazione.
Esse vanno collocate subito dopo la parentesi { di apertura di una
funzione e, come tutte le istruzioni C, terminano con un punto e
virgola.
La forma generale è:
nome della funzione()
{
istruzioni di dichiarazione;
altre istruzioni;
}
Nella loro forma più semplice, le istruzioni di dichiarazione forniscono
un tipo dati e un nome di variabile.
Ad es., l’istruzione di dichiarazione
int total;
dichiara total come nome di una variabile che memorizza un valore
di tipo intero.
Le variabili usate per contenere valori in virgola mobile sono dichiarate
con la parola riservata float
quelle usate per valori in doppia precisione sono dichiarate con la
parola riservata double e
quelle usate per contenere un carattere sono dichiarate con la parola
riservata char.
Le variabili dello stesso tipo dati possono essere raggruppate insieme
e dichiarate con una singola istruzione di dichiarazione. Ad es., le
quattro dichiarazioni
Le istruzioni di dichiarazione possono essere usate anche per
memorizzare un valore iniziale nella variabile dichiarata.
Ad es., l’istruzione
int num1 = 15;
dichiara la variabile num1 intera e le assegna il valore 15.
La prima volta che si memorizza un valore in una variabile si dice che
essa è inizializzata. Così, nell’esempio precedente si può dire che
num1 è stata inizializzata a 15.
Istruzioni di dichiarazione e di definizione
In aggiunta al loro ruolo software, le istruzioni di dichiarazione
svolgono anche un altro compito hardware.
Dato che ogni tipo dati ha le sue proprie esigenze di memoria, il
computer può allocare sufficiente memoria per una variabile solo
dopo avere saputo il suo tipo dati.
Poiché le dichiarazioni di variabile forniscono questa informazione,
esse possono essere usate per forzare il computer a riservare
sufficiente spazio di memoria fisica per ciascuna variabile.
Le istruzioni di dichiarazione usate per questo scopo hardware sono
dette anche istruzioni di definizione, dato che definiscono o indicano
al computer quanta memoria è necessaria per la memorizzazione dei
dati. Tutte le istruzioni di dichiarazione viste finora erano anche
istruzioni di definizione.
Più avanti vedremo istruzioni di dichiarazione che non causano
alcuna nuova allocazione di memoria, e sono usate semplicemente
per dichiarare o avvertire il programma dei tipi dati delle variabili
create in precedenza ed esistenti.
Assegnazione
Come abbiamo visto, un’istruzione di assegnazione è un’espressione
di assegnazione che termina con un punto e virgola (in C qualsiasi
espressione che termini con un punto e virgola diventa
un’istruzione).
La forma generale di un’espressione di assegnazione è:
variabile = operando
dove il segno di uguale (=) è un operatore binario detto operatore di
assegnazione. L’operando può essere una costante, una variabile o
un’altra espressione valida.
L’operatore di assegnazione fa sì che il valore dell’operando sia
memorizzato nella variabile.
Esempi di espressioni di assegnazione valide sono:
anno = 2006
somma = 90.2+80.3
tasso = primo
totale = totale + nuovoval
L’operatore di assegnazione ha la precedenza più bassa tra tutti gli
operatori aritmetici binari e unari; perciò ogni altro operatore che si
trovi in un’espressione insieme a un operatore di assegnazione
viene sempre valutato prima di esso.
Ad es., nell’espressione
imposta = netto * iva
dapprima viene valutata l’espressione netto * iva, quindi il
valore viene memorizzato nella variabile imposta.
Valore di un’espressione di assegnazione. Come tutte le
espressioni, anche quelle di assegnazione hanno un valore.
Il valore dell’espressione di assegnazione completa è
quello assegnato alla variabile a sinistra del segno di =.
Ad es., l’espressione a = 5 assegna il valore 5 alla variabile a, e
l’espressione stessa stessa ha valore 5.
Il valore dell’espressione può sempre essere verificato usando
un’istruzione quale
printf(“Il valore dell’espressione è %d”, a = 5);
Essa visualizza il valore dell’espressione stessa e non il contenuto
della variabile a (anche se hanno entrambe lo stesso valore).
Il fatto che le espressioni di assegnazione abbiano un valore sarà
importante quando considereremo gli operatori relazionali.
Assegnazioni multiple. Dato che il segno di uguale è un operatore,
nella stessa espressione (o nella sua istruzione equivalente) sono
possibili assegnazioni multiple.
Ad es., l’espressione
a = b = c = 25
ha l’effetto di assegnare il numero 25 a ciascuna variabile
individualmente.
Dato che l’operatore di assegnazione ha associatività da destra a
sinistra, la valutazione procede nell’ordine
c = 25
b = c
a = b
Operatori di assegnazione (+=, -=, *=, /=, %=). Sebbene in
un’espressione di assegnazione sia consentita una sola variabile a
sinistra del segno =, tale variabile può essere usata anche a destra
del sego di =.
Per esempio, è valida l’espressione di assegnazione
somma = somma + 12
Se fosse un’equazione sarebbe impossibile, mentre come espressione
viene valutata in due passaggi:
• il primo calcola il valore di somma + 12,
• il secondo memorizza tale valore in somma.
Espressioni di questo tipo, che usano la stessa variabile da entrambi i
lati dell’operatore di assegnazione, possono essere scritte usando i
seguenti operatori di assegnazione
+=
-=
*=
/=
%=
Quindi,
Operatori di incremento e di decremento (++, --). Nel caso
particolare in cui una variabile debba essere incrementata o
decrementata di 1, il C fornisce due operatori unari.
Tramite l’operatore di incremento, ++, l’espressione
variabile = variabile + 1
può essere sostituita dall’espressione
++variabile
Altri esempi:
Tramite l’operatore di decremento, --, l’espressione
variabile = variabile – 1
può essere sostituita dall’espressione
--variabile.
Altri esempi:
Quando ++ si trova davanti a una variabile è detto operatore di
incremento prefisso.
Oltre a trovarsi prima di una variabile, l’operatore di incremento si può
trovare anche dopo una variabile, per esempio nell’espressione
n++; in tale caso esso è detto operatore di incremento postfisso.
Entrambe le espressioni
++n
e
n++
corrispondono all’espressione più lunga n = n + 1, ma hanno
effetti differenti quando sono usate in espressioni di assegnazione.
Infatti,
Analogamente, entrambe le espressioni
--n
e
n-—
diminuiscono di 1 il valore di n e sono equivalenti all’espressione
più lunga n = n - 1. Tuttavia producono effetti differenti se usate
in espressioni di assegnazione.
Infatti,
Si noti che, sebbene tutte e tre le seguenti istruzioni
cont = cont + 1;
cont += 1;
++cont;
producano lo stesso effetto quando sono compilate per l’esecuzione, le
loro esigenze di memoria sono rispettivamente 9, 4 e 3 byte.
Scarica

Fonda11