Sei sulla pagina 1di 60

Il linguaggio C

Piero Gallo – Pasquale Sirsi


Il linguaggio C
Generalità sul linguaggio C
I linguaggi di programmazione possono essere classificati in base a diversi crite-
ri. Le classificazioni non servono per stabilire se certi linguaggi sono migliori o
preferibili rispetto ad altri, ma per distinguere che cosa possono fare e a quale
scopo possono essere utilizzati. Una tra le più importanti classificazioni distingue
i linguaggi di basso livello da quelli di alto livello.

I linguaggi di basso livello permettono di programmare il computer inserendo


le istruzioni che possono essere eseguite direttamente dal microprocessore.
Sono linguaggi più “vicini” alla logica della macchina che a quella del program-
matore.

L’Assembly è il classico linguaggio di basso livello attraverso il quale si manipola


direttamente la memoria, i registri del microprocessore e, tramite altre funzioni
di basso livello fornite dal sistema operativo, si può accedere direttamente all’har-
dware (per esempio a una porta, a una unità a disco e così via).

I linguaggi di alto livello sono più intuitivi e comprensibili per il programmato-


re, ma le loro istruzioni non sono direttamente eseguibili dal microprocessore,
è necessario che siano trasformate in istruzioni di basso livello a opera di spe-
cifici software (compilatori/interpreti).

I linguaggi di alto livello (come il Pascal, il Basic e così via) aiutano molto il pro-
grammatore nella fase di stesura del codice. Questi linguaggi trattano i dettagli ri-
guardanti il computer in maniera astratta: non è necessario conoscere come il
processore riesce a sommare due numeri, basta usare il simbolo +. Non è ne-
cessario conoscere la locazione di memoria in cui registrare i dati da utilizzare,
poiché è possibile assegnare ai dati un nome simbolico e mnemonico (una varia-
bile). Allo stesso modo, non è necessario conoscere i dettagli del sistema operati-
vo e dell’hardware per poter leggere o scrivere un file su disco.

Il C è un linguaggio di programmazione di alto livello. Progettato e realizzato da


Dennis Ritchie nel 1972, fu sviluppato presso gli AT&T Bell Laboratories allo sco-
po di ottenere un linguaggio di alto livello per l’implementazione dei sistemi ope-
rativi. Il linguaggio C fu utilizzato per la prima volta in modo estensivo per la ri-
scrittura del codice del sistema operativo UNIX dal linguaggio Assembly;
inevitabilmente, molte delle sue caratteristiche derivano dalle specifiche esigen-
ze legate alla realizzazione di un sistema operativo in generale, e di UNIX in par-
ticolare. Benché sia strettamente legato a quest’ultimo sistema, il C può essere
impiegato con i principali sistemi operativi oggi disponibili. Steve Johnson, nella
metà degli anni Settanta, scrisse un compilatore C trasportabile su sistemi diver-
si dal PDP-11: da allora il C cominciò a essere utilizzato come linguaggio in altri si-
stemi operativi, come MS-DOS e CPM/80.

Nel 1978 fu pubblicato il libro The C Programming Language di Brian Kernighan e


Dennis Ritchie (K&R): la pubblicazione servì come definizione ufficiale del lin-
guaggio, anche se già dalla distribuzione della versione V7 erano presenti alcune
caratteristiche assenti in nel volume citato.

Nella metà degli anni Ottanta il comitato X3JI1 dell’ANSI (American National
Standards Institute) sviluppò uno standard per il linguaggio C che aggiungeva im-
portanti elementi e ufficializzava molte caratteristiche presenti nei diversi compi-
latori realizzati successivamente alla pubblicazione del libro di Kernighan e
Ritchie. Da allora l’ANSI C cominciò a essere considerato la versione “ufficiale”
del linguaggio rimpiazzando quella di K&R.

2 IL LINGUAGGIO C
Il linguaggio C

Le principali caratteristiche del linguaggio C


Elenchiamo di seguito le caratteristiche principali del linguaggio C.
• È un linguaggio general purpose, ossia può essere impiegato per codificare
progetti software di natura diversa, da quelli real time a quelli che operano su
basi di dati, da quelli tecnico-scientifici fino alla realizzazione di moduli per il
sistema operativo.
• È un linguaggio strutturato di tipo imperativo (o procedurale).
• È un linguaggio case sensitive, ossia distingue tra lettere minuscole e lettere
maiuscole. Ad esempio, la variabile PIPPO è diversa dalle variabili Pippo e pippo.
• È un linguaggio compilato. Il problema dei linguaggi imperativi compilati è
che sono legati alla piattaforma su cui vengono compilati: se compiliamo un
programma sotto UNIX, quel programma potrà essere eseguito esclusivamen-
te su sistemi operativi UNIX.
• Nella versione ANSI, è facilmente portabile e perciò multipiattaforma; infatti,
può essere compilato su un’ampia gamma di computer.
• Pur essendo un linguaggio di alto livello (in quanto possiede strutture di alto li-
vello), e nonostante possa definirsi potente ed efficiente, il C è un linguaggio
relativamente di medio livello, in particolare per quanto riguarda la rappresen-
tazione dei dati.
• Produce programmi efficienti. Essendo nato per implementare sistemi opera-
tivi, il C evita, ove possibile, qualsiasi operazione che possa determinare un de-
cadimento del livello di efficienza del programma eseguibile.
• Produce programmi di dimesioni ridotte. Il C ha, infatti, una sintassi che si
presta alla concisione e alla scrittura di codice compatto.

Descrizione sintattica di linguaggi di programmazione


Per descrivere la sintassi di un linguaggio di programmazione ci serviamo di un
formalismo molto diffuso: la BNF (Backus-Naur Form o Forma Normale di Backus).
Al fine di rendere lo studio semplice ed efficace, nella nostra trattazione impie-
gheremo soltanto alcuni simboli propri della BNF e poche regole. In particolare:
• le parole chiave del linguaggio saranno scritte in grassetto;
• le parole racchiuse tra parentesi angolari < > rappresentano le categorie sin-
tattiche, ossia elementi generali del linguaggio che, nei vari programmi, sa-
ranno sostituiti con opportune occorrenze; ad esempio:
A=B
<NomeVariabile>=<Espressione> A=C+2*D
C=0

• le parentesi quadre [ ] racchiudono blocchi opzionali, ossia che possono anche


non essere presenti. Ad esempio:
 int <NomeVariabile> [= <ValoreIniziale>]

• le parentesi graffe { } racchiudono blocchi con possibilità di ripetizione. Ad


esempio:
 int <NomeVariabile> {, <NomeVariabile>}
indica che subito dopo il metodo è possibile inserire una lista opzionale di pa-
rametri separati da una virgola;
• il simbolo | ha il significato di OR. Ciò significa che all’interno di una lista di
parole chiave separate da questo simbolo occorre sceglierne soltanto una. Ad
esempio, in base alla seguente sintassi:
 int | float <NomeVariabile>
la variabile di nome <NomeVariabile> dovrà essere preceduta dalla parola
chiave int oppure da float.

IL LINGUAGGIO C 3
Anatomia
di un programma C
La struttura di un programma in linguaggio C è la seguente:

Facoltativo
Direttive al preprocessore

Facoltativo Dichiarazione di variabili globali


e/o dei prototipi delle funzioni

Obbligatorio
Programma principale (main)

Facoltativo
Sottoprogramma 1

Facoltativo
Sottoprogramma 2
.
.
.
Facoltativo
Sottoprogramma N

Come si evince dalla figura, un programma in linguaggio C è formato da un insieme


di sottoprogrammi (che impareremo a chiamare con il loro vero nome, funzioni).
Riportiamo di seguito una schematizzazione più generale della struttura di un
programma in linguaggio C.
 direttive per il preprocessore
 dichiarazioni globali – variabili, costanti e prototipi
 main()
{
 variabili locali alla funzione main
 istruzioni della funzione main
}
 f1()
{
 variabili locali alla funzione f1
 istruzioni della funzione f1
}
 f2()
{
 variabili locali alla funzione f2
 istruzioni della funzione f2
}
.
.
.
 fn()
{
 variabili locali alla funzione fn
 istruzioni della funzione fn
}

4 IL LINGUAGGIO C
Anatomia di un programma C

main rappresenta il programma principale e come tale deve essere sempre pre-
sente, poiché il compilatore inizia l’esecuzione proprio da esso. Al main è asse-
gnato il compito di gestire il controllo generale dell’attività del codice.
La sintassi della funzione main è la seguente:

 [<TipoRestituito>] main([<ElencoParametri>])
{

 <Istruzione1>;
 <Istruzione2>;
 ...
 <IstruzioneN>;
}

dove:
• <TipoRestituito> indica il tipo di dato restituito dalla funzione, il quale, in
mancanza di esplicita dichiarazione (cioè per default), è un valore intero (int);
• <ElencoParametri> rappresenta l’insieme dei parametri che il main accetta
in ingresso (i parametri saranno trattati approfonditamente in seguito). Nel
caso in cui il main non abbia parametri in ingresso occorre comunque far se-
guire la parola main da una coppia di parentesi tonde. Ciò perché, come vedre-
mo meglio più avanti, il main può essere considerato una funzione che può ri-
cevere degli argomenti (ossia i parametri): essi, se presenti, dovranno essere
racchiusi all’interno di tali parentesi.

Hai notato che nella sintassi le varie istruzioni (<Istruzione1>, >Istruzione2> e


così via) sono spostate un po’ a destra rispetto alle parentesi graffe? Le istru-
zioni vengono rientrate per indicare la loro dipendenza, cioè per evidenziare
che l’istruzione è contenuta in un’altra. È buona abitudine curare questi inco-
lonnamenti poiché, così facendo, si garantisce una migliore leggibilità del codi-
ce. La tecnica di questi incolonnamenti è chiamata indentazione.

Come si evince dalla sintassi, quando il compilatore richiama il main esegue il


gruppo di istruzioni racchiuso all’interno della coppia di parentesi graffe { } che
definiscono il corpo del programma principale. Dal punto di vista tecnico, le pa-
rentesi graffe permettono di incapsulare istruzioni composte. Tali istruzioni pos-
sono consistere nella definizione del corpo di un sottoprogramma (come accade
nel nostro caso per il main), oppure nel riunire più linee di codice che dipendono
dalla stessa istruzione di controllo, come nel caso in cui diverse istruzioni vengo-
no eseguite a seconda del risultato vero/falso del test di un’istruzione.
Dalla sintassi si ricava, inoltre, che ogni istruzione C termina con un punto e vir-
gola (;). Su una singola riga è anche possibile scrivere più istruzioni, ma è buona
norma scriverne solo una al fine di migliorare la leggibilità del codice.
Notiamo l’utilizzo delle parentesi tonde e graffe. Le parentesi tonde () vengono
utilizzate in unione con i nomi dei sottoprogrammi (le funzioni), mentre le paren-
tesi graffe {} vengono utilizzate per delimitare le espressioni. Infine, il punto e
virgola (;) indica la fine di un’istruzione.

Il linguaggio C ha un formato piuttosto flessibile: istruzioni lunghe possono esse-


re continuate su righe successive senza problemi: il punto e virgola segnala al
compilatore che è stata raggiunta la fine dell’istruzione. Inoltre, è possibile intro-
durre spazi allo scopo di garantire una maggiore leggibilità del programma.
Un errore molto comune è proprio quello di dimenticare il punto e virgola: in que-
sto caso il compilatore concatenerà più linee di codice, generando un’espressio-
ne priva di qualsiasi significato. Per questo motivo il messaggio d’errore che il
compilatore restituisce non indica la mancanza del punto e virgola, quanto la pre-
senza di qualcosa di incomprensibile. Attenzione dunque a non scordare i punti e
virgola e a interpretare correttamente i messaggi del compilatore.

IL LINGUAGGIO C 5
Alfabeto e regole
lessicali
L’alfabeto del linguaggio C
L’alfabeto del linguaggio C è formato da:
1. le lettere minuscole e maiuscole dell’alfabeto inglese (dalla a fino alla z):
abcdefghijklmnopqrstuvwyz
ABCDEFGHIJKLMNOPQRSTUVXYZ
2. le dieci cifre decimali (dallo 0 al 9):
0 1 2 3 4 5 6 7 8 9
3. i seguenti caratteri speciali:
– i simboli di punteggiatura . , : ;
– i segni matematici + – * / = % < >
– i simboli spazio # $ & ! ^ | \ ‘ “ _ ? ~
– le parentesi [ ] ( ) { }
4. i simboli non grafici (o non stampabili), come quelli di:
– nuova linea (new line);
– nuova pagina (form feed);
– spazio indietro (backspace);
– tabulazione orizzontale.

Le regole lessicali
Grazie all’alfabeto del oinguaggio, il programmatore piò definire le parole da utiliz-
zare nel programma sorgente. Naturalmente tali parole devono essere valide, ossia
essere riconosciute come formalmente valide dal compilatore del linguaggio.
Ogni linguaggio prevede un certo numero di categorie lessicali; in C possiamo di-
stinguere le seguenti categorie:
• parole riservate;
• identificatori (creati dal programmatore);
• costanti letterali;
• segni di punteggiatura e operatori;
• commenti.

Le parole chiave
Il linguaggio C (come qualsiasi altro linguaggio di programmazione) consente la
realizzazione di programmi utilizzando un insieme di “parole chiave” (keyword)
che non possono essere utilizzate dal programmatore come nomi di variabili.
Lo standard ANSI ha definito il seguente insieme di parole chiave:

auto break case const


continue default do double
else enum extern float
for goto if int
long register return short
signed sizeof static struct
switch typedef unsigned void
volatile while

6 IL LINGUAGGIO C
Alfabeto e regole lessicali

Gli identificatori
Gli identificatori sono i nomi definiti dal programmatore per riferirsi a sei diverse
categorie di oggetti:
• variabili;
• costanti simboliche;
• etichette;
• tipi definiti dal programmatore;
• funzioni;
• macro.
Le variabili sono contenitori di valori; ogni variabile può contenere un singolo va-
lore, che può cambiare nel tempo. Il tipo di una variabile viene stabilito una volta
per tutte e non può essere modificato.

Le costanti simboliche servono a identificare valori che non cambiano nel tem-
po. Per tale motivo non possono essere considerate veri contenitori, ma solo
come nomi utilizzati per identificare un valore.

L’etichetta è un nome che identifica una istruzione del programma; è utilizzata


dall’istruzione di salto incondizionato (goto).

Il tipo identifica un insieme di valori e di operazioni definite su di essi; ogni lin-


guaggio (o quasi) fornisce un certo numero di tipi primitivi (a cui è associato un
identificatore predefinito) e dei meccanismi che permettono la costruzione di
nuovi tipi (ai quali il programmatore deve poter associare un nome) a partire da
questi.

Funzione (in C function) è il termine che viene utilizzato per indicare i sottopro-
grammi.

La macro è in sostanza un alias utilizzato per contrassegnare una porzione di co-


dice. Come vedremo più avanti, le macro non sono trattate dal compilatore, ma
da un precompilatore che si occupa di eseguire alcune elaborazioni sul codice sor-
gente prima che questo venga effettivamente compilato.

Gli identificatori in C devono iniziare con una lettera o con un carattere di under-
score _. Possono essere seguiti da un numero qualsiasi di lettere, cifre o undersco-
re; viene fatta distinzione tra lettere maiuscole e minuscole (abbiamo già detto
che il C è un linguaggio case sensitive). Tutti gli identificatori presenti in un pro-
gramma devono essere diversi tra loro, indipendentemente dalla categoria a cui
appartengono. Gli identificatori non possono iniziare con una cifra numerica e
non possono contenere spazi. Ad esempio, non sono identificatori validi:

2biciclette primo numero

mentre sono validi i seguenti:

j MIN max eta_studente _secondo_nome

Sebbene il linguaggio non preveda un limite alla lunghezza massima di un identi-


ficatore, è praticamente impossibile non prescrivere un limite al numero di carat-
teri considerati significativi, per cui ogni compilatore distingue gli identificatori in
base a un certo numero di caratteri iniziali tralasciando i restanti.
Il numero di caratteri considerati significativi varia comunque da sistema a siste-
ma, anche se lo standard ANSI C ha stabilito che sono validi i primi 1024 caratte-
ri di un identificatore.

IL LINGUAGGIO C 7
Gli operandi: variabili
e costanti
In C un’espressione è una combinazione di operatori e operandi che definisce
un’elaborazione e dà origine a un valore. A elaborazione terminata, l’espressione
restituisce un risultato il cui tipo di dato determina il tipo dell’espressione stessa.
Gli operandi sono combinazioni di costanti, variabili semplici e strutturate, chia-
mate di funzioni o, ancora, di altre espressioni, e rappresentano i valori che ven-
gono manipolati nell’espressione. Nello studio degli operandi, particolare impor-
tanza assumono le variabili, le costanti e il tipo che può essere loro assegnato.
Gli operatori sono simboli che specificano come devono essere manipolati gli
operandi dell’espressione. La valutazione di un’espressione viene effettuata ese-
guendo le operazioni indicate dagli operatori sui loro operandi secondo le regole
di precedenza degli operatori tipiche dell’aritmetica.

Variabili e costanti
L’identificatore serve per individuare univocamente un dato. Ricordiamo che è
buona norma utilizzare nomi che consentano di richiamarne immediatamente il
significato. In funzione della possibilità di cambiamento del loro valore durante
l’elaborazione, i dati si classificano in variabili (il cui valore può variare nel corso
dell’elaborazione) e costanti (il cui valore non cambia).
Le costanti o valori letterali rappresentano quantità esplicite e come tali non
vanno dichiarati. Una generica costante può essere:
• un numero intero (positivo o negativo);
• un numero intero decimale in virgola mobile (in notazione scientifica o in
quella standard);
• un carattere singolo;
• una stringa (sequenza di caratteri), che può terminare con:
– un valore nullo;
– un carattere speciale.

Le costanti numeriche intere


Nel linguaggio C una costante intera si può rappresentare in sistemi di numera-
zione diversi. In particolare:
• una costante intera decimale è scritta utilizzando una sequenza di numeri
decimali (da 0 a 9 senza virgola); ad esempio, 85 è una costante numerica de-
cimale;
• una costante intera ottale è scritta utilizzando una sequenza di numeri ottali
(da 0 a 7) preceduta dal carattere 0; ad esempio, 042 rappresenta una costan-
te intera ottale;
• una costante intera esadecimale è scritta utilizzando una sequenza di simbo-
li esadecimali (0, 1, 2, …, 9, A, B, C, D, E, F) preceduta dalla coppia di caratte-
ri 0x oppure 0X; ad esempio: 0XA, 0X18f sono costanti intere esadecimali.
Come si evince dall’ultimo esempio (0X18f), le lettere dalla A alla F possono
anche essere scritte in minuscolo.

Le costanti numeriche decimali o in virgola mobile


Una costante numerica decimale o in virgola mobile rappresenta un numero rea-
le che può essere scritto in forma decimale oppure mediante la notazione scien-
tifica. Nella forma decimale le costanti si compongono di due parti, quella intera
e quella decimale, separate dal punto (si utilizza infatti la notazione anglosassone,
che prevede il punto al posto della virgola).
I compilatori riconoscono una costante come numerica decimale soltanto se vie-
ne rappresentata con la seguente configurazione, che riportiamo secondo le rego-
le della BNF:

 [<Segno>][<ParteIntera>][.<ParteDecimale>][E[<Segno>]<Esponente>]

dove la lettera E indica la base 10.


8 IL LINGUAGGIO C
Gli operandi: variabili e costanti

Ad esempio, le seguenti costanti sono corrette:

 0 // 0 in virgola mobile
 480E+4 // 480 * 10000 (10 elevato a 4)
 .14e–2 // 0.0014
 –3.5e+3 // –3500.0

mentre non sono riconosciute dal compilatore le seguenti costanti:

 .58E E25

I caratteri
Le costanti di tipo carattere sono singoli simboli racchiusi tra singoli apici, ad esempio: ‘b’, ‘!’, ‘3’.
In C++, come in tutti i linguaggi di programmazione, il numero 8 è diverso dal simbolo ‘8’ che, in-
vece, è un carattere:
I caratteri sono generalmente memorizzati come codici ASCII a 8 bit (dipende comunque dall’im-
plementazione). È anche possibile definire caratteri estesi di tipo wchar_t (wide character type),
memorizzati come codici Unicode a 16 bit.

I caratteri speciali
All’interno delle istruzioni C è possibile inserire caratteri speciali non stampabili. Ad esempio, per vi-
sualizzare sul monitor una stringa con un carattere di “a capo” si utilizza una sequenza speciale di ca-
ratteri che inizia con una barra rovesciata \, in inglese backslash, definita sequenza di escape: il pri-
mo carattere permette di uscire dalla normale rappresentazione, il secondo specifica quale carattere
non stampabile si desidera produrre (ovviamente utilizzando un carattere stampabile).
Un esempio di carattere speciale è l’apice (‘), che spesso sostituisce l’apostrofo: in quest’ultimo
caso va indicato come carattere speciale facendolo precedere da una barra rovesciata. In generale,
quindi, utilizzeremo le sequenze di escape per rappresentare caratteri non stampabili, numeri ot-
tali, numeri esadecimali e caratteri che non possono essere scritti direttamente.
La tabella seguente riporta le sequenze di escape definite dal C.

Sequenza di escape Descrizione


\n Nuova riga (new line)
\t Tabulazione (tab)
\a Segnale sonoro (alert)
\f Nuova pagina (form feed)
\r Ritorno a capo della stessa riga (ritorno carrello o carriage return)
\v Tabulazione verticale
\b Una battuta indietro (backspace)
\\ Barra rovesciata (backslash)
\’ Apice singolo
\” Doppi apici
\? Punto di domanda
\0 Fine stringa
\<CifraOttale> Numero ottale
\<CifraEsadecimale> Numero esadecimale

Le costanti stringa
Una costante stringa (o stringa letterale) è composta da una sequenza di caratteri terminata con
la sequenza di escape \0 e racchiusa all’interno di una coppia di virgolette. Un carattere è un qual-
siasi componente di un insieme predefinito di caratteri, o alfabeto. Molti computer impiegano l’in-
sieme di caratteri ASCII o Unicode. Ad esempio: “Ciao mondo!” è una costante stringa formata da
dodici caratteri (compreso il carattere speciale e lo spazio, che è anch’esso un carattere). Il caratte-
re speciale \0 viene inserito automaticamente dal compilatore (e come tale può non essere speci-
ficato esplicitamente dal programmatore) nel momento in cui questo riconosce una costante strin-
ga all’interno del programma sorgente.
IL LINGUAGGIO C 9
Tipi di dato primitivi
e dichiarazione
di variabili e costanti
I tipi primitivi (built-in)
I tipi rappresentano le categorie di informazioni che il linguaggio consente di ma-
nipolare. I tipi di dati elementari gestiti dal linguaggio C dipendono dall’architet-
tura del computer, perciò è particolarmente difficile definire la dimensione delle
variabili numeriche; noi ci limiteremo a fornire solo definizioni relative.
Normalmente, il riferimento è dato dal tipo numerico intero (int) la cui dimensio-
ne in bit corrisponde a quella della parola, ovvero dalla capacità dell’unità aritme-
tico-logica del microprocessore. A seconda delle varie architetture, la dimensione
di un intero normale è di 32 o 64 bit. I valori minimi e massimi consentiti e le-
gati al compilatore sono definiti all’interno di 2 file: limits.h e float.h.
I tipi di dati primitivi, che riportiamo nella seguente tabella, rappresentano un va-
lore numerico singolo:

Tipo Descrizione
char Carattere (generalmente di 8 bit; anche il tipo char è trattato come un numero)
int Intero normale
float Virgola mobile a precisione singola
double Virgola mobile a precisione doppia

I tipi primitivi possono essere estesi utilizzando appositi qualificatori quali short,
long, signed e unsigned. I primi tre fanno riferimento al numero di byte occupa-
ti, mentre gli altri modificano il modo di valutare il contenuto di alcune variabili.
Nella seguente tabella riassumiamo i tipi primitivi con le combinazioni ammissi-
bili dei qualificatori.

Tipo Abbreviazione Descrizione

caratteri char Tipo char per il quale non conta sapere se il segno è considerato o meno
signed char Tipo char usato numericamente con segno
unsigned char Tipo char usato numericamente senza segno
interi short int short Intero più breve di int, con segno. Ad esempio, se il tipo int occupa 4 byte,
signed short int signed sort il tipo short int ne occuperà 2
unsigned short int unsigned short Tipo short senza segno
int Intero normale, con segno
signed int
unsigned int unsigned Tipo int senza segno
long int long Intero più lungo di int, con segno. Ad esempio, se il tipo int occupa 4 byte,
signed long int signed long il tipo long int ne occuperà 8
unsigned long int unsigned long Tipo long senza segno
reali float Tipo a virgola mobile a precisione singola
double Tipo a virgola mobile a precisione doppia
long double Tipo a virgola mobile “più lungo” di double

10 IL LINGUAGGIO C
Tipi di dato primitivi e dichiarazione
di variabili e costanti
Il classico range dei singoli tipi è riportato nella seguente tabella:

TIPO BYTE OCCUPATI RANGE


char 1 da –128 a 127
unsigned char 1 da 0 a 255
short int 2 da –32768 a +32767
int 4 da –21474863648 a +21474863647
long int 8 da 10–309 a 10+308
usigned short 2 da 0 a 65535
unsigned int 4 da 0 a 4294967295
unsigned long 8 notazione IEEE standard
float 32 notazione IEEE standard
double 64 notazione IEEE standard
long double 128 notazione IEEE standard

Fra i tipi primitivi non abbiamo menzionato il tipo booleano. Come sappiamo, l’insieme del tipo booleano è co-
stituito da due valori di verità: true (vero) e false (falso). Nel linguaggio C non esiste un tipo per rappresenta-
re questi due valori booleani; si rimedia simulando variabili booleane con variabili intere: in un’espressione lo-
gica, infatti, un valore uguale a 0 equivale al valore false mentre un valore diverso da 0 equivale a true.
Verso la fine degli anni ’90 lo standard del linguaggio C venne sottoposto a una revisione che portò alla pubbli-
cazione ISO 9899/1999 nel 1999. Questo standard, adottato dell’ANSI nel marzo 2000 e indicato con la sigla
C99, prevede tra l’altro le seguenti nuove caratteristiche:
• le dichiarazioni delle variabili possono essere collocate ovunque (come in C++); prima, erano consentite una
di seguito all’altra e all’inizio dell’enunciato di composizione;
• nuovi tipi di dato inclusi bool e complex;
• lunghezza variabile degli array;
• introduzione del commento di una linea con // (preso dal C++);
• nuove librerie di funzioni come snprintf();
• nuovi header file come stdint.h.

Dichiarazione di variabili e costanti


Una variabile è un oggetto a cui è associato:
• un nome (identificatore);
• un tipo (insieme dei valori che può assumere);
• un’area di memoria (per contenere il valore assunto);
• un indirizzo (riferimento all’area di memoria);
• un valore (in istanti diversi la variabile può assumere valori differenti, purché siano tutti coerenti con il tipo).
La dichiarazione di una variabile in C avviene in base alla seguente sintassi:

 [<Qualificatore>] <Tipo> {, <Identificatore> [=<ValoreIniziale>]};

Ad esempio:

 int b;
 char carattere, lettera;
 unsigned int tappo = 10;

 char frase[20];

sono dichiarazioni di variabili valide in C. In dettaglio, con la prima viene dichiarata la variabile intera b, con la se-
conda vengono dichiarate le variabili carattere e lettera, entrambe di tipo carattere, con la terza viene dichiarata la
variabile tappo di tipo intero senza segno, alla quale è assegnato il valore iniziale 10, e infine con la quarta viene
dichiarata una variabile stringa di nome frase che potrà contenere un testo lungo al massimo 20 caratteri.

Non si deve mai dimenticare che il C è case sensitive, cioè distingue le lettere maiuscole da quelle minuscole.
Notiamo, infine, il punto e virgola che segue sempre ogni dichiarazione.

IL LINGUAGGIO C 11
L’operatore
di assegnamento
Un operatore è un simbolo che “opera” su una o più espressioni, producendo un
valore che può essere assegnato a una variabile.
Gli operatori in programmazione permettono di ottenere un determinato valore
da un’operazione che si compie su una o più variabili all’interno del programma;
così come l’operatore + serve per sommare due numeri in matematica, analoga-
mente serve per compiere la stessa operazione in un programma scritto in C.
Ovviamente ci sono delle differenze: innanzitutto le operazioni del C sono quelle
basilari (per funzioni più avanzate dobbiamo usare librerie apposite) e hanno un
risultato finito, contrariamente a quelle matematiche che possono avere un risul-
tato simbolico o infinito.
Tutti gli operatori producono sempre un risultato e alcuni possono anche modifi-
care uno degli operandi. Alcuni operatori sono rappresentati simbolicamente con
un singolo carattere, altri con due caratteri senza spazi separatori. Se non è espli-
citamente evidenziato altrimenti, tutti gli operatori operano su tipi primitivi.

Gli operatori che andremo ad analizzare si dividono in:


• operatori di assegnamento che consentono di assegnare un valore alle varia-
bili;
• operatori aritmetici che comprendono somma, sottrazione, moltiplicazione,
divisione intera e divisione con modulo;
• operatori di confronto che permettono di verificare determinate condizioni,
come ad esempio l’uguaglianza o la disuguaglianza;
• operatori logici da utilizzare con le istruzioni condizionali e iterative;
• operatori sui bit;
• operatori speciali.

L’operatore di assegnamento
Sappiamo che il C è un linguaggio profondamente basato sul paradigma impera-
tivo e questo significa che un programma C è fondamentalmente una sequenza
di assegnamenti di valori a variabili.
L’operatore di assegnamento è denotato dal simbolo = (segno di uguale) e viene
applicato con la sintassi:

 <Lvalue> = <Rvalue>;

Il termine <Lvalue> indica una qualsiasi espressione che si riferisca a una regio-
ne di memoria (in generale un identificatore di variabile), mentre il termine
<Rvalue> indica una qualsiasi espressione la cui valutazione produca un valore.
Il risultato dell’assegnamento è, pertanto, il valore prodotto dalla valutazione del-
la parte destra (<Rvalue>) e ha come effetto collaterale l’assegnazione di tale va-
lore alla regione di memoria denotato dalla parte sinistra (<Lvalue>). Ecco alcu-
ni esempi:

 a = 5;
 orazio = ‘a’;

 pluto = orazio;

 a = a + 7;

 b = 4 + 25;

Come si evince dal precedente esempio, l’uso del segno di uguale in questo
caso non ha niente a che vedere con la matematica, poiché serve ad assegna-
re un valore e non a effettuare confronti; per questi ultimi in C si usa l’opera-
tore == (attenzione a non inserire spazi tra i due caratteri =).

12 IL LINGUAGGIO C
L’operatore di assegnamento

Una variabile può apparire sia a destra che a sinistra di un assegnamento: se si


trova a destra produce il valore contenuto nella variabile, se invece si trova a sini-
stra denota la locazione di memoria a cui riferisce. Poiché un identificatore di va-
riabile può trovarsi contemporaneamente su entrambi i lati di un assegnamento,
è necessario disporre di una semantica non ambigua: come in qualsiasi linguag-
gio imperativo (Pascal, Basic e così via), la semantica dell’assegnamento impone
che prima si valuti la parte destra e poi si esegua l’assegnamento del valore pro-
dotto all’operando di sinistra.
Ad esempio, supponiamo di avere la variabile A con valore 6; con l’istruzione:

 x = 3 * a;

alla variabile x viene assegnato il risultato di 3*a, cioè 18, quindi il risultato del-
l’operazione di assegnamento (x = 3*a) è proprio 18.
Esaminiamo ora un particolare. L’assegnazione stessa è un’espressione con un
valore (il valore dell’espressione x = 3*a è 18), che può venire usato in un’altra
assegnazione. Ad esempio, l’istruzione:

 y = (x = 3*a);

contiene un’assegnazione multipla: dapprima viene assegnato il valore 3*a alla


variabile x e poi si assegna il risultato ottenuto alla variabile y. Con l’istruzione:

 y = x;

il valore di x viene “copiato” in y: le due variabili rimangono completamente in-


dipendenti, cioè la modifica di una delle due non influenza l’altra. Ciò è vero solo
se x e y sono variabili di tipo primitivo. Il comportamento di questo operatore nel
caso di operandi non primitivi verrà esaminato più avanti.
Occorre prestare attenzione: un’assegnazione multipla non può essere utilizzata
come una inizializzazione in una dichiarazione. Ad esempio, l’istruzione:

 int k = h = 33;

è errata perché le inizializzazioni non sono assegnazioni; sono simili, ma il com-


pilatore le tratta in maniera differente. La maniera corretta di procedere in questo
caso è:

 int k = 33, h = 33;

L’assegnazione di valori a una variabile segue specifiche regole in funzione del


tipo di dato associato alla variabile in questione. In tal senso occorre tener presen-
te che:
• le variabili intere (int) si assegnano utilizzando un numero senza parte decimale;
• le variabili reali (float, double) si assegnano utilizzando per la virgola la notazio-
ne inglese, cioè quella in cui si usa un punto (e non una virgola) per dividere
la parte intera da quelle frazionaria;
• le variabili carattere (char) si assegnano racchiudendo il carattere tra due apici;
• le variabili stringa (una stringa è una concatenazione di caratteri) si assegnano
racchiudendo la stringa tra virgolette.
Riportiamo un esempio riassuntivo di ognuna delle precedenti assegnazioni:

 int b =20;
 char carattere = ‘b’, lettera = ‘m’;

 float tappo = 10.2;

 char frase[20] = “Ciao a tutti”;

IL LINGUAGGIO C 13
Operatori aritmetici,
di confronto e logici
Gli operatori aritmetici
Gli operatori aritmetici si dividono in:
1. operatori unari, che possono essere postfissi e prefissi:

 <Operando> <Operatore> (postfissi)


 <Operatore> <Operando> (prefissi)

2. operatori binari, che hanno la forma:

 <Operando1> <Operatore> <Operando2>

Gli operatori unari aritmetici si applicano a un solo operando e ne modificano il


valore. Si distinguono i seguenti:
• incremento: ++
Ad esempio:

 p++; /* equivale a p = p+1 */

• Decremento: ––
Ad esempio:

 p–– /* equivale a p = p–1 */

Gli operatori unari aritmetici possono essere posti prima dell’operando (prefissi) o
dopo l’operando (postfissi) e il loro valore varia secondo questa posizione: l’opera-
tore prefisso modifica l’operando prima di utilizzarne il valore, mentre l’operatore
postfisso modifica l’operando dopo averne utilizzato il valore. Ad esempio, con:

 x = 10;
 y = x++; /* postfisso: si assegna prima il valore della variabile x
 alla variabile y e poi si incrementa la variabile x
 In altri termini equivale a:
 y = x;
 x++ */

si ottiene y = 10 e x = 11. Invece con:

 x = 10;
 y = ++x; /* prefisso: prima si incrementa la variabile X e poi
 si assegna il valore alla variabile Y. In altri termini
 equivale a:
 x++;
 y = x; */

si ottiene y = 11 e x = 11.

Gli operatori binari aritmetici si applicano a due operandi e non ne cambiano il


valore, ma memorizzano il risultato. In C si utilizzano gli operatori binari riporta-
ti nella tabella all’inizio di pagina seguente.

14 IL LINGUAGGIO C
Operatori aritmetici, di confronto e logici

Operatore Simbolo Risultato


Addizione + Addiziona due operandi o concatena due stringhe
Sottrazione – Sottrae il secondo operando dal primo
Moltiplicazione * Moltiplica i due operandi
Divisione / Divide il primo operando per il secondo. L’operatore di divisione, applicato a variabili di tipo
intero produce un risultato troncato della parte decimale
Resto (modulo) % Fornisce il resto della divisione tra due operandi interi. Non è possibile utilizzare l’operatore
di calcolo del modulo quando gli operandi sono in virgola mobile. Se si divide il modulo per
zero, si avrà un’eccezione: se l’operazione eccede il limite inferiore (underflow) il risultato
sarà zero, se eccede il limite superiore (overflow) si avrà un’approssimazione

L’operatore di assegnamento può anche essere compattato, cioè abbinato a un


operatore aritmetico. In genere le espressioni del tipo:

 <NomeVariabile> = <NomeVariabile> <Operatore> <Espressione>

possono essere scritte nella seguente forma abbreviata:

 <NomeVariabile> <Operatore> = <Espressione>

Scrittura compatta Scrittura equivalente Esempio


x += y x=x+y totale += 52; /* equivale a totale = totale + 52 */
x –= y x=x–y totale –= 40; /* equivale a totale = totale – 40 */
x *= y x=x*y totale *= 10; /* equivale a totale = totale * 10 */
x /= y x=x/y totale /= 2; /* equivale a totale = totale / 2 */
x %= y x=x%y totale %= 2; /* equivale a totale = totale % 2 */

Gli operatori di confronto (relazionali)


Gli operatori di confronto richiedono operandi di tipo primitivo e producono sem-
pre un risultato booleano. Sono i seguenti:

> maggiore >= maggiore o uguale == uguale


< minore <= minore o uguale != diverso

Gli operatori di confronto restituiscono il valore logico true se la relazione è veri-


ficata, altrimenti restituiscono il valore false.

Gli operatori logici


Gli operatori logici richiedono come operandi delle espressioni booleane e produ-
cono un risultato booleano. Sono i seguenti:

|| OR && AND ! NOT

Gli operatori && e || non valutano l’operando destro se non è necessario al risul-
tato, quindi il risultato dell’operatore è indipendente dal valore dell’operando de- X Y X && Y X || Y !X
stro. Ad esempio:
false false false false true
 (3>1) || (x>7) non viene valutato false true false true true
true false false true false
Le tabelle di verità degli operatori logici sono riportate a lato.
true true true true false

IL LINGUAGGIO C 15
Le direttive
al preprocessore
Siamo pronti per realizzare il primo programma in C. Come è tradizione nei ma-
nuali di linguaggi di programmazione, ne realizziamo uno che visualizza sul video
la frase Ciao mondo!:

 #include <stdio.h>
 main( )
{

 /* Questo programma scrive la frase Ciao mondo! sul video */


 printf(“Ciao mondo!”);
 system(“PAUSE”);
}

Il programma, dopo essere stato digitato, compilato ed eseguito, visualizza sul vi-
deo quanto segue:

Diciamo subito che l’istruzione:

 system (“PAUSE”);

fa restare il computer “in attesa” (è proprio questa istruzione che fa apparire la


frase Premere un tasto per continuare). In altri termini, permette di mantenere il
programma aperto fino a quando l’utente non preme un tasto qualsiasi per chiu-
derlo. Consente, pertanto, la visualizzazione del risultato evitando la chiusura im-
mediata della finestra al termine dell’esecuzione.

L’istruzione system (“PAUSE”); funziona in ambiente Windows. Alcuni compila-


tori C richiedono l’inclusione della libreria stdlib.h per poterla utilizzare.
stdlib.h è il file header che dichiara funzioni e costanti di utilità generale: allo-
cazione della memoria, controllo dei processi, conversione tra tipi e così via.

Il C è un linguaggio compilato. Come sappiamo, i compilatori sono programmi


che accettano in ingresso un programma scritto in linguaggio di alto livello (detto
programma sorgente) e lo traducono interamente in un programma in linguag-
gio macchina (detto programma oggetto): a traduzione ultimata, il programma
potrà essere eseguito. Lo scopo di questo processo è quindi di tradurre il program-
ma originale (codice sorgente) in uno semanticamente equivalente, ma eseguibi-
le su una determinata macchina.
A differenza di altri linguaggi, la compilazione in C prevede una prima fase di
preelaborazione, detta precompilazione, svolta da un apposito modulo denomi-
nato preprocessore.

Il processo di creazione e compilazione di un progetto C dipende strettamente


dal tipo di computer e di sistema operativo che si utilizza. Possiamo compila-
re ed eseguire programmi con il C in qualsiasi piattaforma, ma per ognuna di
essa (Linux, Windows e così via) occorrerà scegliere un opportuno ambiente di
sviluppo. Per poter utilizzare sia il linguaggio C sia il linguaggio C++ si può, tra
l’altro, usare l’ambiente di sviluppo integrato (IDE – Integrated Development
Environment) Visual C++ (che fa parte della suite Microsoft Visual Studio) op-
pure Dev-c++, liberamente scaricabile da Internet, o, ancora, utilizzare l’am-
biente testuale DOS utilizzando appositi comandi per compilare, linkare ed
eseguire i programmi digitandoli nella finestra del “Prompt dei comandi”.

16 IL LINGUAGGIO C
Le direttive al preprocessore

Il preprocessore è un programma richiamato automaticamente durante la pri-


ma fase della compilazione, che esamina ed effettua un’elaborazione prelimi-
nare del sorgente. Il preprocessore (o precompilatore), seguendo alcune diretti-
ve scritte dal programmatore, modifica il file sorgente effettuando sostituzioni
di alcune parti di codice (preprocessing). Il file sorgente così modificato viene
successivamente elaborato dal compilatore vero e proprio.

Lo scopo del preprocessore è quello di rendere un programma sorgente più sem-


plice da modificare e da adattare a diversi ambienti operativi. Normalmente il
preprocessore è incluso nel compilatore del linguaggio, oppure è richiamato in
modo assolutamente trasparente al programmatore. In fase di preelaborazione
non viene eseguita alcuna azione di traduzione del codice, viene soltanto effettua-
ta una preparazione del programma sorgente per la successiva compilazione.
Le istruzioni di dichiarazione per il preprocessore vengono chiamate dichiarative
di precompilazione o direttive al preprocessore e iniziano con il carattere # se-
guito dalla parola chiave (if, elif, else, endif, define, undef, ifdef, ifndef, include, line,
error, pragma) e dalla dichiarazione della direttiva. Le direttive al preprocessore
non terminano con il punto e virgola, vengono solitamente inserite in testa al
programma (ma possono trovarsi anche altrove, tra due funzioni, all’interno degli
altri sottoprogrammi o del main) e vengono valutate in fase di compilazione, dove
vengono sostituite con codice C appropriato.

Il simbolo # viene chiamato “pound” negli Stati Uniti, “hash” al di fuori degli
Stati Uniti e “cancelletto” in Italia. Verosimilmente il termine hash deriva da
“hatch” ossia “portellone”, “cancello”, porta composta da sbarre incrociate
così raffigura il simbolo stesso.

Per ora concentriamo l’attenzione sulla direttiva #include; delle altre ci occupere-
mo dettagliatamente più avanti.

La direttiva #include consente di inserire nel file sorgente i file header, ossia spe-
cifici file di intestazione con estensione ‘.h’ (da header) che contengono infor-
mazioni necessarie al programma. In particolare, essi contengono le definizio-
ni delle variabili, delle espressioni costanti e dei prototipi delle funzioni (ossia
il nome, i parametri e il valore restituito dalla funzione). Questi file costituisco-
no le librerie standard del linguaggio.

Ad esempio, nel file header stdio.h (STandarD Input/Output) sono contenute le de-
finizioni delle variabili, delle espressioni costanti e i prototipi delle funzioni di in-
put e di output della libreria standard del C. Se il file stdio.h non venisse incluso
all’interno del programma, il compilatore non riconoscerebbe le funzioni stan-
dard e genererebbe un errore di funzione non definita. Il preprocessore, leggen-
do la direttiva #include, copia (o meglio “include”) il file dichiarato all’interno del
sorgente del programma. Una volta elaborato dal preprocessore quindi, il file sor-
gente conterrà al posto della riga #include il file stdio.h stesso (perciò aumenterà
di dimensioni). Il file così modificato verrà poi elaborato dal compilatore. La diret-
tiva va racchiusa all’interno di parentesi angolari o di doppi apici. Ad esempio,:

 #include <stdio.h>
 #include “stdio.h”

indicano al compilatore di includere e compilare insieme al programma il file


stdio.h che contiene i prototipi delle funzioni standard di input/output (I/O) del C,
richiamabili poi da un qualsiasi punto del programma. Se si racchiude la direttiva
tra parentesi angolari (< >), il file verrà cercato nelle apposite cartelle definite
dal compilatore, mentre se la si racchiude tra i doppi apici (“ “), il file verrà dap-
prima ricercato nella cartella corrente. Quest’ultima soluzione viene adottata
quando si scrivono funzioni appositamente sviluppate per realizzare un program-
ma e le si vuole racchiudere tutte in un apposito file.

IL LINGUAGGIO C 17
Costanti e tipi di dato
definiti dall’utente
Dichiarazione delle costanti
In precedenza abbiamo esaminato la direttiva #include che ci ha permesso di “in-
cludere” nel file sorgente i file header specificati.
Esistono altre direttive di precompilazione: una di esse ci sarà molto utile per di-
chiarare le costanti. In precedenza abbiamo visto che esistono globalmente due
tipi di costanti:
• esplicite: esprimono direttamente dei valori:
– 24 costante di tipo int
– 24L costante di tipo long
– 3.145 costante di tipo double
– ‘A’ costante di tipo char
• simboliche: sono rappresentate da nomi simbolici che il programmatore adot-
ta per indicare valori prefissati. Queste costanti hanno un tipo espresso impli-
citamente nel valore e devono essere dichiarate in precedenza.
Dichiarare una costante significa associare un simbolo a un valore. A differenza di
quanto avviene per le variabili, tale valore non cambierà mai durante l’esecuzio-
ne del programma. In C esistono due modi per dichiarare una costante:
• attraverso la parola chiave const;
• attraverso la direttiva #define.
Con il qualificatore const è possibile definire costanti tipizzate, ossia che accetta-
no un valore ammissibile per il tipo di dato al quale è associato. La sintassi è la se-
guente:

 const {, <Tipo> <NOMECOSTANTE> = <ValoreIniziale>};

Ad esempio:

 const float PGRECO = 3.14;


 const char C = ‘a’;
 const int POSTI = 100;

 const char[20] ERRORE = “Rilevato errore!”;

Le costanti simboliche di questo tipo sono sostanzialmente delle variabili per le


quali il compilatore non concede la modifica. Per una questione di stile, utilizze-
remo nomi interamente in maiuscolo per riferirci alle costanti.
Se si definisce una costante tramite const, il compilatore farà sì che in fase di ese-
cuzione vi sia una locazione di memoria relativa alla costante, in cui sarà presen-
te il valore associato alla stessa.
La definizione di costante tramite la direttiva #define ha la seguente forma sintat-
tica:

 #define <Identificatore> <TestoSostitutivo>

Dopo la direttiva #define si indicherà l’identificatore della costante, che prende il


nome di costante palese, e il relativo valore. Come tutte le direttive al precompi-
latore, anche #define non deve terminare con il punto e virgola. Riprendiamo le
precedenti dichiarazioni fatte con il qualificatore const e riproponiamole con la di-
rettiva #define:

 #define PGRECO 3.14


 #define C ‘a’
 #define POSTI 100

 #define ERRORE “Rilevato errore”

18 IL LINGUAGGIO C
Costanti e tipi di dato definiti dall’utente

Se si definisce una costante tramite la direttiva #define, il compilatore sostituirà a


tutte le occorrenze dell’identificatore il valore associato; in questo modo non vi
sarà alcuna locazione di memoria associata alla costante e il valore sarà inserito
direttamente all’interno del codice.
La direttiva #define può essere utilizzata anche per scopi che esulano dalla dichia-
razione di costanti il cui valore sarà oggetto di calcolo all’interno del programma.
Prendiamo in esame le due seguenti dichiarazioni:

 #define BEGIN {
 #define END }

Grazie a queste direttive, all’interno del programma sarà possibile evitare di inse-
rire le parentesi graffe e digitare, al loro posto, le parole dichiarate, quindi begin
per la parentesi graffa aperta ed end per la parentesi graffa chiusa: sarà il prepro-
cessore, poi, a sostituire i valori specificati. Nel seguito amplieremo la conoscen-
za di questa direttiva introducendo le macro.

Tipi definiti dall’utente


Il linguaggio C offre la possibilità di assegnare un alias (soprannome) a un qual-
siasi tipo primitivo oppure derivato (ossia quelli introdotti dall’utente e derivati
dai tipi fondamentali, che esamineremo più avanti), il che contribuisce a rendere
il codice più portabile, più leggibile e a evitare espressioni altrimenti complesse.
Tale assegnazione si effettua utilizzando la clausola typedef. Con essa non si de-
finisce un nuovo tipo, ma si introduce un nome che corrisponde a un tipo defini-
to. La sintassi è la seguente:

 typedef <NomeTipo> <NuovoNomeTipo>;

A titolo di esempio consideriamo la seguente dichiarazione:

 typedef float tiporeale;


 tiporeale variabile;

che rinomina il tipo primitivo float con l’alias tiporeale.


Questa clausola può essere molto utile in determinate situazioni. Prendiamo in
esame l’assenza del tipo booleano. Servendoci di typedef possiamo ovviare all’in-
conveniente. Ad esempio, con la dichiarazione:

 typedef int boolean;

è possibile dichiarare variabili di tipo boolean che possono assumere tutti i valori
interi, poiché sappiamo che la regola base è che i valori diversi da zero rappresen-
tano il vero, mentre il valore zero rappresenta il falso:

 int a, b;
 boolean finito;

Considerato che in moltissimi programmi vi è l’esigenza di evidenziare che una


variabile è utilizzata esclusivamente per memorizzare un valore booleano, si pos-
sono premettere definizioni analoghe alle seguenti che ci permettono di definire
proprio le costanti TRUE e FALSE:

 #define TRUE 1
 #define FALSE 0
 typedef int BOOL;

IL LINGUAGGIO C 19
Il main e i commenti
Il programma principale
Dopo le direttive al precompilatore, il nostro programma che scrive sul video la
frase Ciao mondo! presenta il suo programma principale caratterizzato dalla paro-
la chiave main.
Il main deve essere sempre presente poiché costituisce il punto di ingresso di un
programma C: l’esecuzione di un programma, infatti, inizia dalla prima istruzio-
ne del main e termina con l’ultima. Eventuali altri sottoprogrammi (ossia altre
funzioni) entreranno in gioco solo se e quando richiamate (direttamente o indiret-
tamente) dal main. In assenza di una funzione main, il compilatore non può pro-
durre un programma eseguibile.
La struttura generale del main è la seguente:

 main ( )
 {

 }

che può anche essere scritta come segue:

main
(
)

 {
 }

oppure:

 main( )

 { }

o, ancora:

 main( ) {

 }

Questi vari modi di scrivere il main possono sembrare strani (e in effetti lo sono)
ma sono corretti e comprensi dal compilatore. In sostanza non è importante la po-
sizione dei simboli, ma è importante che ogni parentesi aperta venga poi chiusa.
Questi due programmi non producono alcun risultato in quanto non contengono
istruzioni: sono programmi vuoti; possono produrre un errore di tipo warning da
parte del compilatore, ossia degli avvertimenti che non bloccano la creazione del-
l’eseguibile, si limitano ad avvertire il programmatore della possibilità che venga
prodotto un programma eseguibile errato e malfunzionante. Generalmente i mes-
saggi di tipo warning possono anche essere ignorati.

Per inserire le parentesi graffe utilizzando una tastiera che non riporta esplici-
tamente il tasto, si può usare una tra le seguenti combinazioni di tasti:
{ Alt+123 (tastierino numerico) Ctrl+Alt+Shift+[ Shift+AltGr+[
} Alt+125 (tastierino numerico) Ctrl+Alt+Shift+] Shift+AltGr+]

All’interno di un programma in linguaggio C compaiono statement (istruzioni),


ossia righe che rappresentano dichiarazioni di oggetti e risorse che saranno usa-
te nel programma, e righe che racchiudono le istruzioni del programma che go-
vernano l’elaborazione.

20 IL LINGUAGGIO C
Il main e i commenti

Gli statement vengono costruiti utilizzando l’alfabeto, le parole chiave e le regole


lessicali del linguaggio.

I commenti
Riprendiamo il nostro primo programma realizzato in precedenza. Subito dopo le
direttive al precompilatore è presente la frase Questo programma scrive la frase
Ciao mondo sul video: è un commento.
In un programma C, come in qualsiasi altro linguaggio, i commenti hanno valore
soltanto per il programmatore e vengono ignorati dal compilatore. Nel linguaggio
C è possibile inserire i commenti in due modi diversi.
1. Secondo il puro stile C, ovvero racchiudendoli tra i simboli /* e */, ad esempio:

 /* Questo è un commento in stile C */

Questo metodo, detto commento a blocchi, permette di commentare più li-


nee di codice senza dover ripetere a ogni linea i simboli di commento.
2. Secondo lo stile proprio del C++ (se il compilatore è conforme a standard più
recenti, come il C99), ovvero facendoli precedere dal simbolo //. In questo
modo il commento si estende solo fino al termine della linea. Ad esempio:

 // Questo è un commento in stile C++

Nel primo caso è considerato commento tutto ciò che è racchiuso tra /* e */; il
commento, quindi, si può anche trovare in mezzo al codice. Analizziamo il se-
guente esempio:

 main( )
{

 int x = 10; /* Questo è un commento diviso


 su tre righe.
 Questa è l’ultima riga di commento */
 x = /* Questo commento è valido */ = 5;
}

Nel secondo caso, proprio del C++, è invece considerato commento tutto ciò che
segue // sino alla fine della linea. Ne consegue che non è possibile inserire un
commento di questo tipo in mezzo al codice, o dividerlo su più righe (a meno che
anche l’altra riga non cominci con //). Vediamo un esempio:

 main( )
 {
 int x = 10; // Questo è un commento valido
 x = 100; // Attenzione! La riga successiva produce un errore
 Questa riga non è un commento: non è preceduta da //
}

Diversi ambienti di sviluppo evidenziano i commenti in uno specifico colore per


permettere al programmatore di distinguere meglio tra il codice e i commenti
stessi.

IL LINGUAGGIO C 21
La gestione
dell’output
Riprendiamo il programma di esempio con il quale abbiamo visualizzato sul vi-
deo la frase Ciao mondo!. All’interno del main del nostro programma è presente
la funzione printf, che ci permette di controllare ciò che viene stampato, nel sen-
so che permette di decidere che cosa stampare e in quale forma. Il funzionamen-
to più semplice di questa funzione è il seguente: la printf scorre la stringa di for-
mato racchiusa tra virgolette da sinistra verso destra e scrive a video (il dispositivo
di uscita standard, stdout) tutti i caratteri che incontra. È proprio il caso del no-
stro programma in cui è presente l’istruzione

 printf(“Ciao mondo!”);

che scrive sul video la frase racchiusa tra virgolette.


Tuttavia la funzione printf, oltre a stampare semplici stringhe di testo, può anche
essere utilizzata per stampare il contenuto di variabili. In questo caso, la funzione
printf riceve sempre come primo argomento tra le parentesi tonde una stringa di
formato (cioè una sequenza di caratteri delimitata dalle virgolette), ma dopo di
essa si dovrà inserire un numero qualsiasi di altri argomenti, in base alla necessi-
tà. La forma sintattica più generale è, quindi, la seguente:

 printf(<StringaDiFormato> , <Espressione>)

All’interno della <StringaDiFormato> dovranno essere presenti appositi segna-


posto per ciascuna espressione successivamente elencata, in modo da specifica-
re in quale punto della stringa deve essere posizionato il valore dell’espressione e
di che tipo di valore si tratta.
Tali segnaposto prendono il nome di specificatori di formato e sono introdotti dal
simbolo %. Quando si è in presenza di specificatori di formato, il funzionamento
è il seguente: la printf scorre la stringa di formato da sinistra verso destra e scrive
a video tutti i caratteri che incontra; quando trova un carattere %, identifica il tipo
di dato che deve essere stampato mediante il carattere (o i caratteri) che lo seguo-
no. L’elemento % indica che in quel punto è necessario stampare il valore di
un’espressione che è di un tipo ben preciso. La funzione printf utilizza tale infor-
mazione per convertire e formattare il valore ottenuto dall’espressione che segue
la stringa di formato. Una volta valutato, formattato e scritto a video il valore del-
l’espressione, la funzione continua nell’analisi della stringa di formato, fino al
prossimo % o alla fine della stringa. Facciamo alcuni esempi:

Espressione che
 int a = 20; /* dichiarazione ed inizializzazione */ segue la stringa
 printf(“Il valore della variabile a e’: %d”, a);

Stringa di formato Specificatore di formato

%d specifica che si deve valutare l’espressione che segue la stringa come un nu-
mero intero decimale. Il risultato a video sarà la scritta:

Invece della variabile a è anche possibile scrivere un’espressione, ad esempio:

 int a = 20;
 printf(“Il valore della variabile a e’: %d”, a+2);

che scriverà il valore della variabile a aumentato di due unità. Il valore della varia-
bile resta però invariato, in quanto non è stato riassegnato.

22 IL LINGUAGGIO C
La gestione dell’output

Il seguente esempio evidenzia, invece, come inserire più specificatori di formato all’interno del-
la stringa. L’importante è inserirli nel punto in cui occorra scrivere il valore di un’espressione:

 int a = 10;
 b = 20;
 printf(“Il valore di a e’ %d e il valore di b e’ %d ”, a, b);

Il risultato sarà:
Le specifiche introdotte dal simbolo % non sono soltanto identificatori di formato, ma an-
che specificatori di conversione: indicano, infatti, il tipo di valore risultante dall’espressio-
ne e come tale tipo di dato deve essere convertito in caratteri da visualizzare sullo schermo.
Riprendiamo gli esempi appena visti. Se per un qualsiasi motivo l’espressione che segue la
stringa ha un valore reale (ad esempio, una variabile dichiarata di tipo float) e si utilizza lo
specificatore %d, verrà comunque stampato qualcosa, che però non corrisponderà al valo-
re esatto. La ragione è che un int utilizza la metà dello spazio occupato da un float. Per tale
motivo, verrà visualizzato solamente il contenuto dei primi due byte, che verranno interpre-
tati come la rappresentazione in complemento a due di un numero intero con segno. Tutto
ciò è molto lontano dal corrispondere anche solo alla parte intera del numero reale, rappre-
sentato in complemento a due ma notazione a virgola mobile.
I due aspetti importanti da ricordare sono quindi i seguenti:
1. l’identificatore che segue il % specifica il tipo di variabile che deve essere visualizzato e
il formato dell’espressione che segue;
2. nel caso in cui vi sia una differenza tra l’identificatore indicato e il valore calcolato del-
l’espressione, il dato visualizzato non è necessariamente corretto e può causare errori an-
che sugli altri elementi della printf.
Alla luce di quanto detto, esaminiamo un programma completo in C:
 #include <stdio.h>
 int a = 20;
 main()
{
 printf(“Il valore della variabile a è: %d”, a+2);
 system(“PAUSE”);
}

Compilando ed eseguendo il programma otterremo il risultato riportato nella figura, che è


corretto ma presenta due inconvenienti:
1. la è accentata non viene visualizzata correttamente;
2. dopo aver visualizzato il valore della variabile viene visualizzato il messaggio Premere un
tasto per continuare… sulla stessa riga rendendo il tutto non proprio leggibile.

Per la gestione dell’output si utilizzano anche le funzioni putchar() e puts() che visualizzano a video, rispettiva-
mente, un carattere e una stringa. Queste funzioni non utilizzano gli specificatori di formato. Un esempio del
loro utilizzo è il seguente:
 char a = ‘y’;
 char nome[10] = “piero”;
 putchar(a);
 puts(nome);
Se si desidera stampare sulla carta anziché sul video, occorre utilizzare l’istruzione fprintf che funziona con le
stesse modalità di printf. È necessario soltanto specificare il nome della stampante, stdprn (stampante stan-
dard o predefinita), per indicare la ridirezione dal video alla stampante (cioè da stdout alla stampante). Ad
esempio, la seguente istruzione invia l’output alla stampante:
 fprintf(stdprn, “Somma totale = %d “, Tot);

IL LINGUAGGIO C 23
Gestione dell’output
e sequenze di escape
Gli specificatori di formato
In precedenza abbiamo esaminato gli specificatori %d e %f per visualizzare ri-
spettivamente il risultato di espressioni intere e reali. Gli specificatori di formato
previsti dall’ANSI C sono i seguenti:

Specificatore di formato Espressione A video


%c char carattere singolo
%d (%i) int intero decimale con segno
%e (%E) float o double formato esponenziale
%f float o double reale con segno
%g (%G) float o double utilizza %f o %e in base alle esigenze
%o int valore in base 8 senza segno
%p pointer valore di una variabile puntatore
%s array of char stringa (sequenza) di caratteri
%u int intero senza segno
%x (%X) int valore in base 16 senza segno

Tra il simbolo % e lo specificatore di formato è anche possibile inserire altre op-


zioni di formato:
• un numero intero specifica l’ampiezza del campo, ossia imposta il numero di
posizioni nelle quali il valore deve essere visualizzato;
• uno zero (0) indica che le posizioni a sinistra, occupate dal numero da visua-
lizzare, devono essere riempite con zeri;
• il segno meno (–) è utilizzato per eseguire la giustificazione a sinistra del dato;
• due numeri separati dal punto servono per indicare rispettivamente l’ampiez-
za del campo e il numero di cifre decimali.
Consideriamo le seguenti dichiarazioni di variabili e analizziamo le nuove opzioni:
 int a = 2;
 char x = ‘s’; /* l’assegnazione di un carattere a una variabile char avviene
 racchiudendo il carattere tra due singoli apici */
 float y = 3.14; /* la notazione usata per rappresentare la virgola è quella
 inglese, cioè quella in cui si usa un punto (e non una
 virgola) per dividere la parte intera da quella frazionaria */
 char nome[10] = “Piero”; /* per memorizzare le stringhe (che tratteremo
 detttagliatamente in seguito, si usa il tipo char
 indicando il nome della variabile seguito dal
 numero massimo di caratteri previsto per la
 stringa, racchiuso tra parentesi quadre e il
 deve essere racchiuso tra virgolette*/

Istruzione Output
printf(“%c”, x); s
printf(“%d”, a); 2
printf(“%20d”, a); 2
printf((%020d”, a); 00000000000000000002
printf(“%f”, y); 3.140000
printf(“%20.2f”, y); 3.14
printf(“%20d %10.2f”, a, y); 2 3.14
printf(“%-20d %-10.2f”, a, y); 2 3.14
printf(“%s”, nome); Piero
printf(“%10s”, nome); Piero
printf(“%-10s”, nome); Piero
printf(“%-20s 10.2f”, nome, y); Piero 3.14

24 IL LINGUAGGIO C
Gestione dell’output e sequenze di escape

Poiché il simbolo % è utilizzato per introdurre uno specificatore di formato, per


visualizzare questo simbolo (ad esempio, per visualizzare una percentuale) oc-
corre riportarlo due volte. Ad esempio, per visualizzare a video la frase Il tasso
di sconto è del 10% scriveremo:
 printf(“Il tasso di sconto e\’ del 10%%”);

Le sequenze di escape
In precedenza abbiamo visto che alcuni caratteri non sono stati visualizzati cor-
rettamente a video (ad esempio la è accentata) e che non abbiamo avuto modo di
scrivere frasi su righe diverse. A risolvere tali problemi pensano alcuni codici di
controllo detti tecnicamente sequenze di escape, che non stampano caratteri vi-
sibili ma contribuiscono a formattare ciò che viene stampato. Le sequenze di
escape iniziano con il carattere backslash (\) e sono interpretate come un singolo
carattere:
Presentiamo subito degli esempi. Il seguente programma:

 #include <stdio.h>

 main()
{

 printf(“prima riga ...\n”);


 printf(“seconda riga ...\n”);
 system(“PAUSE”);
}

Visualizza quanto è riportato nella figura.


Vediamo un altro esempio:

 #include <stdio.h>
 int a = 0;

 main()
 {
 printf(“%a”); /* emette un beep */
 printf(“Il valore di a e\’%d\n”, a);
 printf(“E l\’altro valore qual e\’\? %d\n”, a+10);
 system(“PAUSE”);
}

Sequenza di escape Significato Descrizione


\b backspace Cancella (una battuta indietro)
\f Form feed avanzamento carta
\n New line nuova linea
\r Carriage return a capo (senza una nuova linea)
\t Tab tabulatore
\’ Single quote Apice
\” Double quote Doppi apici
\0 End of string Fine stringa
\\ Backslash Barra contraria
\? Question mark Punto di domanda
\a Bell Segnalazione acustica

IL LINGUAGGIO C 25
Gestione dell’output
ESEMPI
e sequenze di escape
Esempio 1
Realizzare un programma che visualizzi i limiti di tutti i tipi interi; questi limiti
sono costanti memorizzate nel file header limits.h.
 #include <stdio.h>
 #include <limits.h>
 #include <stdlib.h>



 main()
 {
 printf(“minimum char = %d\n”, CHAR_MIN);
 printf(“maximum char = %d\n”, CHAR_MAX);
 printf(“minimum short = %d\n”, SHRT_MIN);
 printf(“maximum short = %d\n”, SHRT_MAX);
 printf(“minimum int = %d\n”, INT_MIN);
 printf(“maximum int = %d\n”, INT_MAX);
 printf(“minimum long = %d\n”, LONG_MIN);
 printf(“maximum long = %d\n”, LONG_MAX);
 printf(“minimum signed char = %d\n”, SCHAR_MIN);
 printf(“maximum signed char = %d\n”, SCHAR_MAX);
 printf(“maximum unsigned char = %d\n”, UCHAR_MAX);
 printf(“maximum unsigned = %u\n”, UINT_MAX);
 printf(“maximum unsigned short = %d\n”, USHRT_MAX);
 printf(“maximum unsigned long = %u\n”, ULONG_MAX);
 system(“PAUSE”);
}

Esempio 2
Realizzare un programma che visualizzi sul video la frase ciao mondo servendosi
di sole variabili char.
 #include <stdio.h>
 main()
{
 char a=99,b=105,c=97,d=111,e=32,f=109,g=111,h=110,i=100,j=111;
 printf(“%c%c%c%c%c%c%c%c%c%c\n”, a,b,c,d,e,f,g,h,i,j);
 system(“PAUSE”);
}

Esempio 3
Realizzare un programma che visualizzi sul video la tavola pitagorica riportata
nella figura servendosi delle tabulazioni.
x 2 3 4 5 6 7 8 9
2 4
3 6 9
4 8 12 16
5 10 15 50 25
6 12 18 24 30 36
7 14 21 28 35 42 49
8 16 24 32 40 48 56 72
9 18 27 36 45 54 63 72 81

26 IL LINGUAGGIO C
Gestione dell’output e sequenze di escape

ESEMPI
 #include <stdio.h>
 main()
{
 printf(“\n\nx \t 2 \t 3 \t 4 \t 5 \t 6 \t 7 \t 8 \t 9 \n”);
 printf(“2 \t 4 \n”);
 printf(“3 \t 6 \t 9 \n”);
 printf(“4 \t 8 \t 12 \t 16 \n”);
 printf(“5 \t 10 \t 15 \t 20 \t 25 \n”);
 printf(“6 \t 12 \t 18 \t 24 \t 30 \t 36 \n”);
 printf(“7 \t 14 \t 21 \t 28 \t 35 \t 42 \t 49 \n”);
 printf(“8 \t 16 \t 24 \t 32 \t 40 \t 48 \t 56 \t 64 \n”);
 printf(“9 \t 18 \t 27 \t 36 \t 45 \t 54 \t 63 \t 72 \t 81 \n \n \n”);
 system(“PAUSE”);
}

Esempio 4
I seguenti frammenti di codice chiariscono la funzione degli operatori unari ++
e ––.
 int i;
 i = 15;
 printf(“%d, %d, %d”, ++i, i++, i);
...

Il risultato che si ottiene è:

 16, 16, 17

Infatti, l’espressione ++i incrementa i prima di stamparlo a video (quindi i è 16);


la successiva i++ viene valutata al valore corrente di i (16) che verrà poi incre-
mentato (a 17); infine, l’espressione i è il valore di i dopo il postincremento (17).
 #include <stdio.h>
 #include <stdlib.h>
 main()
{
 int i, n;
 i=4;
 n=i;
 printf(“Valore di n all’inizio: %d\n”,n);
 n=i++;
 printf(“Valore di n dopo i++: %d\n”,n);
 n=++i;
 printf(“Valore di n dopo un ulteriore ++i: %d\n”,n);
 n+=i*(n–4);
 printf(“Valore finale di n: %d\n”,n);
 system(“PAUSE”);
}

La variabile n è inizialmente uguale a i, ovvero vale 4. Poi, n=i++; ma siccome


l’operatore ++ segue il nome della variabile, prima il valore di i viene memorizza-
to in n, poi il valore di i viene incrementato. Ecco così che n vale ancora 4, men-
tre i vale 5. Successivamente n=++i; in questo caso l’operatore ++ precede il
nome della variabile, quindi prima il valore di i viene incrementato (era 4, era sta-
to incrementato a 5 dall’istruzione i++, ora diventa 6) e poi il risultato viene me-
morizzato in n (che, quindi, vale 6). Infine, l’espressione i*(n–4) ovvero 6 * (6 – 4)
= 12 viene sommata al precedente valore di n (per via dell’operatore +=), che
era 6, quindi 12 + 6 = 18.

IL LINGUAGGIO C 27
Le istruzioni di input
In C esistono vari modi per effettuare l’acquisizione di dati, ma tutti hanno in co-
mune il fatto che prelevano i dati dal dispositivo standard di input: la tastiera.
Prima di esaminarli, descriviamo brevemente i dispositivi standard.
In C esistono tre dispositivi standard predefiniti (denominati anche stream):
stdin (standard input), stdout (standard output) e stderr (standard error).
Il file stdin è associato alla tastiera e ogni lettura da tastiera viene vista come una
lettura dal file stdin.
Il file stdout (che abbiamo analizzato trattando la funzione printf) è associato al di-
spositivo di visualizzazione (il monitor) e ogni scrittura su di esso viene vista
come una scrittura sul monitor.
Il file stderr è associato comunemente con il monitor ed è utilizzato per visualiz-
zare messaggi di errore: ogni scrittura sul file stderr viene vista come una scrittu-
ra sul monitor.

La funzione getc
La funzione getc (non annoverata tra le funzioni ANSI C) legge un carattere dal di-
spositivo standard indicato e lo restituisce convertito in un intero. In altri termini,
questa funzione attende che venga premuto un tasto sulla tastiera, dopodiché ne
restituisce immediatamente il valore. Il carattere digitato non viene visualizzato
sullo schermo (la funzione analoga che però visualizza il carattere digitato sullo
schermo è getche()).

 #include <stdio.h>
 int c;
 main()

{

 c = getc(stdin);
 printf(“\n%d”, c);
 system(“PAUSE”);
}

Nell’esempio viene letto un carattere dallo standard input (stdin) e memorizzato


nella variabile c come un intero. Ad esempio, se digitiamo la lettera A, nella varia-
bile c viene memorizzato il numero 65, ossia il suo codice ASCII. Questa funzio-
ne è molto utile per leggere singoli caratteri.

La funzione getchar
La funzione getchar() equivale a getc(stdin) e, di conseguenza, legge un carattere
da stdin. Nonostante getchar() sia una funzione prevista dallo standard ANSI, il
suo comportamento può differire a seconda dell’implementazione del compilato-
re. In alcune versioni, infatti, restituisce immediatamente il valore del carattere
immesso da tastiera, in altre, invece, attende fino a quando non viene premuto il
tasto Invio.

 #include <stdio.h>
 int c;

 main()

{

 c = getchar();
 printf(“\n%d”, c);
 system(“PAUSE”);
}

28 IL LINGUAGGIO C
Le istruzioni di input

La funzione scanf
La funzione scanf consente di acquisire una sequenza di caratteri (lettere o cifre)
dalla tastiera (il dispositivo di ingresso standard, stdin) e di memorizzarli all’inter-
no di opportune variabili. Questa funzione lavora in modo simile a printf, che ab-
biamo esaminato in precedenza. La sua sintassi è la seguente:

 scanf(<StringaDiFormato>,<Variabile>)

dove, esattamente come per la funzione printf, la <StringaDiFormato> rappre-


senta lo specificatore di formato ed è introdotta dal simbolo %, mentre
<Variabile> rappresenta il nome della variabile all’interno della quale dovrà es-
sere memorizzato il dato proveniente dalla tastiera. Ad esempio, l’istruzione:

 scanf(“%d”, &a);

legge un valore intero (indicato dallo specificatore di formato %d) e lo memorizza


nella variabile a. Notiamo come davanti al nome della variabile è stato posto il sim-
bolo & (ampersand o “e commerciale”): esso indica che il valore acquisito dalla ta-
stiera deve essere registrato all’indirizzo della variabile a in memoria centrale.
Quando il programma durante l’esecuzione raggiunge l’istruzione scanf, si ferma e
lascia all’utente la possibilità di digitare qualcosa alla tastiera. Il programma non
prosegue fino a che l’utente non ha inserito qualcosa e ha premuto poi il tasto Invio.
Una volta acquisito quanto inserito dall’utente, il valore viene memorizzato nella va-
riabile a e poi il programma procede con le istruzioni successive. In questo modo, a
ogni esecuzione l’utente può inserire valori diversi, producendo risultati diversi.
Il programma seguente legge un numero da tastiera e lo ristampa a video; abbia-
mo utilizzato gli specificatori di formato per scanf() in modo analogo a quello de-
scritto per la funzione printf().

 #include <stdio.h>
 int i;
 main()

{

 scanf(“%d \n”, &i);


 printf(“%d \n”, i);
 system(“PAUSE”);
}

La funzione scanf elabora la stringa di controllo da sinistra a destra e a ogni segna-


posto cerca di interpretare i caratteri ricevuti in ingresso in relazione all’identifica-
tore (gli identificatori sono gli stessi della funzione printf). Se vengono specificati più
valori nella stringa di controllo, si presuppone che questi vengano immessi da ta-
stiera separandoli con spazi, Invio o il tabulatore. Questo significa che per inserire
tre interi è possibile digitare i dati in una delle due forme seguenti:
345
oppure:
3
4
5
Per esempio, l’istruzione:

 scanf(“%d %d”,&i,&j);

acquisisce due numeri interi e li memorizza rispettivamente nelle variabili i e j. I


due valori possono essere inseriti separandoli con uno spazio (o un numero qual-
sivoglia di spazi), oppure andando a capo ogni volta. Unica eccezione è il caso di
%c: viene acquisito un carattere, qualunque esso sia; quindi qualsiasi tasto venga
premuto sulla tastiera, quello è l’unico carattere acquisito.

IL LINGUAGGIO C 29
Le istruzioni condizionali
(o di selezione)
Il blocco
Il linguaggio C utilizza quattro istruzioni condizionali principali: if, if..else, ? e
switch. L’istruzione ?, per la sua estrema semplicità, è già stata esaminata in pre-
cedenza. Prima di affrontare lo studio delle altre istruzioni di selezione è dovero-
so introdurre il concetto di blocco.
Le istruzioni condizionali consentono di eseguire in modo selettivo una singola
riga di codice o una serie di righe che costituiscono un blocco di codice. Quando
all’istruzione condizionale è associata una sola riga di codice, non è necessario
racchiudere l’istruzione da eseguire fra parentesi graffe. Se invece sono associate
più righe di codice, le stesse (ossia il blocco) dovranno essere racchiuse fra paren-
tesi graffe. Volendo, è comunque possibile utilizzare tali parentesi anche quando
l’istruzione da eseguire è soltanto una: il compilatore non segnalerà alcun errore.

L’istruzione if..else
La struttura alternativa viene implementata in C attraverso l’istruzione if..else. È
un’istruzione molto intuitiva, se si considera che le parole inglesi if ed else corri-
spondono rispettivamente alle italiane “se” e “altrimenti”. Secondo i canoni della
programmazione strutturata, la struttura alternativa può presentarsi con o senza
il ramo “altrimenti”. In C la sintassi da seguire per codificare le due forme è la se-
guente:

 if (<Condizione>)
 [{]
 <Istruzione/i da eseguire se la condizione è vera>;
 [}]

 if (<Condizione>)
 [{]
 <Istruzione/i da eseguire se la condizione è vera>;
 [}]

 else

 [{]

 <Istruzione/i da eseguire se la condizione è falsa>;


 [}]

Nel caso in cui sia necessario condizionare anche le istruzioni rette da else (le co-
siddette if in cascata) si impiega la seguente sintassi:

 if (<Condizione1>)
 [{]
 <Istruzione/i da eseguire solo se la Condizione1 è vera>;
 [}]

 else if (<Condizione2>)

 [{]

 <Istruzione/i da eseguire se la Condizione1 è falsa e la Condizione2 è vera>;


 [}]

 else if (<Condizione3>)

 [{]

 <Istruzione/i da eseguire se la Condizione1 è la Condizione2 sono false>;


 [}]

 ...

30 IL LINGUAGGIO C
Le istruzioni condizionali (o di selezione)

A titolo di esempio risolviamo un semplice problema: dato in input un numero N,


stabilire se è divisibile per 3. Il codice C è il seguente:

 #include <stdio.h>
 int n;
 main( )

{

 printf(“Inserire un numero “);


 scanf(“%d”,&n);
 if ((n % 3) == 0)
 printf(“Il numero inserito e’ divisibile per 3\n”);
 else
 printf(“Il numero inserito non e’ divisibile per 3\n”);
 system(“PAUSE”);
}

All’interno di un’istruzione if è possibile inserire qualsiasi tipo di istruzione, an-


che una nuova istruzione if: si parla, in tal caso, di if annidate o nidificate.
Quando si utilizzano più istruzioni if annidate, occorre sempre essere sicuri del-
l’azione else che verrà associata a una determinata if. Proviamo a capire che cosa
accade nel seguente esempio:

 if (voto < 10)


 if (voto < 5) printf( “Sei totalmente insufficiente! \n”);
 else printf(“ Forza! Ricomincia a studiare! \n”);

L’assenza di indentazione e la presenza di più istruzioni su una stessa riga non


rendono il codice ben leggibile. In queste circostanze è facile che il programmato-
re poco esperto possa avere difficoltà a comprendere il flusso di esecuzione, non
capendo a quale istruzione if fa riferimento l’istruzione else finale.
Il principio base, in queste situazioni, stabilisce che un’istruzione else è sempre ri-
ferita all’ultima istruzione if presente nel codice. È utile non confondersi troppo le
idee con questo principio, poiché ci si potrebbe imbattere in casi in cui compaio-
no tante istruzioni if..else annidate, con la conseguenza che la lettura del codice
sarebbe davvero difficile per chiunque. Per una buona leggibilità del codice è per-
ciò buona norma far sempre uso di una corretta indentazione e, ove necessario,
delle parentesi graffe. L’esempio precedente, indentato e corredato di parentesi,
diviene:

 if (voto < 10)


{

 if (voto < 5)
 {
 printf( “Sei totalmente insufficiente! \n”);
 }
 else
 {
 printf(“Forza! Ricomincia a studiare! \n”);
 }
}

che è senz’altro più comprensibile. Adesso, infatti, diventa chiaro che l’istruzione
else è riferita all’istruzione if(voto < 5).

IL LINGUAGGIO C 31
Le istruzioni condizionali
ESEMPI
(o di selezione)
Considerata la semplicità degli esercizi proposti, riportiamo solo la codifica in C
evitando l’analisi del problema.
Dati in input tre voti, fornire in output la media in decimi o trentesimi
in funzione della scelta fatta dall’utente
 #include <stdio.h>
 #include <stdlib.h>
 int a,b,c;
 float media;
 char scelta;
 main()
{
 printf(“Inserisci il primo voto: “);
 scanf(“%d”,&a);
 printf(“Inserisci il secondo voto: “);
 scanf(“%d”,&b);
 printf(“Inserisci il terzo voto: “);
 scanf(“%d”,&c);
 if ((a>=18) && (a<=30) && (b>=18) && (b<=30) && (c>=18) && (c<=30))
 {
 printf(“\nVuoi visualizzare la media in trentesimi o in centesimi [t/c]? “);
 scanf(“\n%c”,&scelta);
 ...
 }
 system(“PAUSE”);
}

Attenzione: nell’istruzione scanf interna al costrutto if abbiamo inserito la se-


quenza di escape \n:

 scanf(“\n%c”,&scelta);

Ciò ha una spiegazione importante. Il problema è che ogni volta che viene premu-
to un tasto, il carattere corrispondente viene inserito in un’opportuna area di me-
moria, chiamata buffer. Quando viene eseguita un’istruzione scanf, i caratteri
vengono letti dal buffer e portati nella variabile che deve essere letta.
Quando si inserisce da tastiera il terzo numero (quello che deve essere inserito
nella variabile c), si premono i tasti corrispondenti alle cifre (ad esempio 27), e
poi si preme Invio. Nel buffer vengono quindi inseriti 3 caratteri: il 2, il 7 e l’invio.
Il carattere ‘2’ e il carattere ‘7’ vengono convertiti in numero intero e assegnati
alla variabile c. Il carattere ‘invio’ rimane nel buffer. Se la lettura successiva è:

 scanf(“%c”,&scelta);

il carattere ‘invio’ viene letto dalla scanf e, quindi, nella variabile scelta verrà inse-
rito il carattere ‘invio’.
Uno dei modi per risolvere questo inconveniente è quello di costringere la scanf
a leggere esplicitamente il carattere ‘invio’, che nel linguaggio C è rappresentato
con ‘\n’.
Quindi il codice:

 scanf(“\n%c”,&scelta);

legge il carattere invio e lo scarta, poi legge il carattere successivo e lo assegna alla
variabile scelta. Completa l’esercizio inserendo al posto dei puntini le istruzioni
per calcolare la media e visualizzarla.

32 IL LINGUAGGIO C
Le istruzioni condizionali (o di selezione)

ESEMPI
Dato in input un numero intero positivo che indica la misura del
raggio, fornire in output la misura della circonferenza
 /* Prima soluzione */
 #include <stdio.h>
 int r;
 float circ;
 main()
{
 printf(“Inserire la misura del raggio ”);
 scanf(“%d”, &r);
 if (r>=0)
 {
 circ= 2*3.14*r;
 printf(“Circonferenza e\’ %3.2f\n”, circ);
 }
 else
 printf(“Il valore del raggio deve essere positivo”);
 system(“PAUSE”);
}

 /* Seconda soluzione – if annidati */


 #include <stdio.h>
 int r;
 float circ;
 main()
{
 printf(“Inserire la misura del raggio “);
 scanf(“%d”, &r);
 if (r>=0)
 {
 if (r<=32767)
 {
 circ= 2*3.14*r;
 printf(“Circonferenza e\’ %3.2f\n”, circ);
 }
 }
 else
 printf(“Il valore del raggio deve essere positivo”);
 system(“PAUSE”);
}

 /* Terza soluzione – if con condizione composta */


 #include <stdio.h>
 int r;
 float circ;
 main()
{
 printf(“Inserire la misura del raggio “);
 scanf(“%d”, &r);
 if ((r>=0) && (r<=32767))
 {
 circ= 2*3.14*r;
 printf(“Circonferenza e\’ %3.2f\n”, circ);
 }
 else
 printf(“Il valore del raggio deve essere positivo”);
 system(“PAUSE”);
}

IL LINGUAGGIO C 33
Le istruzioni iterative:
i costrutti while
e do...while
Nella risoluzione dei problemi capita molto spesso di dover ripetere molte volte
una serie di istruzioni. È evidente, quindi, la necessità di disporre di una struttu-
ra in grado di consentire la ripetizione di una sezione di codice per un numero fi-
nito di volte: la struttura iterativa o ciclica.
Il linguaggio C dispone di tre tipi di istruzioni iterative: while, do..while, for.
Ognuno di essi ha il proprio specifico utilizzo. I costrutti while e do..while consen-
tono l’esecuzione ripetuta di una sequenza di istruzioni in base al valore di verità
di una condizione.Vediamo innanzitutto la sintassi di while:
 while (<Condizione>)
 <Istruzione>;

Al solito, <Istruzione> indica un’istruzione singola o una sequenza di istruzioni


(un blocco); in quest’ultimo caso va racchiusa tra parentesi graffe. Ciononostante,
può essere utile racchiudere tra parentesi <Istruzione> anche se è una sola.
La semantica del costrutto while è la seguente: si valuta <Condizione> e, se essa
risulta vera (ossia se è diversa da zero) si esegue <Istruzione>, poi si ripete il tut-
to; l’istruzione termina quando la valutazione di <Condizione> fornisce il risul-
tato false (ossia assume valore uguale a zero).
Vediamo subito un semplice esempio che consente di comprendere l’utilizzo del
costrutto while. Il problema che risolviamo è il seguente: dato in input un nume-
ro, calcolare la somma di tutti i suoi predecessori compreso il numero stesso.
 #include <stdio.h>
 int num;
 int k;
 int sommanumeri;
 main( )
 {
 k = 1;
 sommanumeri = 0;
 printf(“Inserire un numero positivo “);
 scanf(“%d”, &num);
 while(k <= num)
 { /* le parentesi del blocco potevano essere omesse */
 sommanumeri += k++;
 }
 printf(“La somma dei numeri da 1 a %d e\’ %d \n”, num, sommanumeri);
 system(“PAUSE”);
 }

Analizziamo ancora un altro esercizio che contiene alcuni cicli while annidati:
dati in input N numeri interi, scomporli in fattori primi.
 #include <stdio.h>
 #define TRUE 1
 #define FALSE 0
 typedef int bool;
 int count;
 char risposta;
 bool continuo;
 unsigned int n, d;

34 IL LINGUAGGIO C
Le istruzioni iterative:
i costrutti while e do...while
 main( )
 {
 continuo = TRUE;
 while(continuo == TRUE)
 {
 printf(“Inserisci un numero intero “);
 scanf(“%d\n” &n);
 d = 2;
 while(n >1)
 {
 while (n%d != 0)
 d++; /* trova i vari divisori primi di N partendo dal più piccolo */
 n /= d;
 count = 1;
 while(n%d == 0) /* trova quante volte il divisore attuale divide N */
 {
 n /= d;
 count++;
 }
 printf(“\n %d elevato a %d”, d, count);
 }
 printf(“\nVuoi continuare? (s/n)”);
 scanf(“\n%c”, &risposta);
 if (risposta == ‘n’)
 continuo = FALSE;
 }
 system(“PAUSE”);
 }

Esaminiamo ora il costrutto do..while; la sua sintassi è:


 do
 <Istruzione>;
 while(<Condizione>);

Ancora una volta, <Istruzione> indica un’istruzione singola o una sequenza di


istruzioni (in tal caso va racchiusa tra parentesi graffe).
do..while differisce dall’istruzione while in quanto prima si esegue <Istruzione>
e poi si valuta <Condizione>: se questa è vera (true) si riesegue il corpo, altri-
menti l’istruzione termina. È evidente che il corpo del ciclo do..while viene sem-
pre eseguito almeno una volta.
Un classico utilizzo di una simile iterazione (detta iterazione postcondizionale) è
quello relativo al controllo di validità dei dati di ingresso. Analizziamo il seguente
programma, che continua a richiedere in input un numero fino a quando non si
inserisce un valore compreso tra 10 e 20.
 #include <stdio.h>
 int n;
 main( )
 {
 do
 {
 printf(“Inserire un valore intero compreso tra 10 e 20: “);
 scanf(“%d”, &n);
 } while(n < 10 || n > 20);
 printf( “Il numero inserito e\’ %d\n”,n);
 system(“PAUSE”);
 }

IL LINGUAGGIO C 35
Le istruzioni iterative:
ESEMPI
i costrutti while e do...while
Dato in input un numero intero positivo che indica la misura del
raggio, fornire in output la misura della circonferenza
Riprendiamo questo problema già analizzato precedentemente e vediamo, ora,
come fare per accettare in input soltanto valori che soddisfino il problema (nel
nostro caso, solo numeri interi positivi). Dal codice si evince, infatti, che dal ciclo
while si esce solo quando l’utente inserisce un valore valido. È un tipico esempio
di validazione dell’input dell’utente:
 #include <stdio.h>
 int r;
 float circ;
 main()
 {
 r=-1; /* inizializzazione che permette di entrare nel ciclo la prima volta */
 while (r<0 || r>32767)
 {
 printf(“Inserire la misura del raggio “);
 scanf(“%d”, &r);
 if ((r<0) || (r>32767))
 printf(“Il valore del raggio deve essere positivo minore di 32767”);
 } /* fine del while */
 circ= 2*3.14*r;
 printf(“Circonferenza è %f”, circ);
 system(“PAUSE”);
 }

Vediamo, ora, lo stesso esercizio svolto con l’istruzione do..while:


 #include <stdio.h>
 int r;
 float circ;
 main()
 {
 do
 {
 printf(“Inserire la misura del raggio “);
 scanf(“%d”, &r);
 if (r<0 || r>32767)
 printf(“Il valore del raggio deve essere positivo minore di 32767”);
 } while ((r<0) || (r>32767));
 circ= 2*3.14*r;
 printf(“Circonferenza è %f”, circ);
 system(“PAUSE”);
 }

Come si vede, si può sempre passare da un ciclo precondizionale a uno postcon-


dizionale. In questo caso il ciclo postcondizionale è utile in quanto si deve eseguire
almeno una volta l’operazione di scanf e si evita l’inizializzazione fittizia (r = –1)

Stampare la tabella di conversione Fahrenheit – Celsius


Nella scala Celsius delle temperature il punto di congelamento dell’acqua è a 0
gradi Celsius, mentre quello di ebollizione è a 100 gradi Celsius. Nella scala
Fahrenheit il punto di congelamento dell’acqua è a 32 gradi Fahrenheit, mentre il
punto di ebollizione si trova a 212 gradi Fahrenheit, suddividendo così la distan-
za fra i due estremi in 180 gradi. L’unità di questa scala, il grado Fahrenheit (°F)
36 IL LINGUAGGIO C
Le istruzioni iterative: i costrutti while e do...while

ESEMPI
è 5/9 di un grado Celsius. Notiamo che la temperatura di 32 °F corrisponde a
0°C. Quindi, un metodo per convertire gradi Celsius in gradi Fahrenheit è quello
di moltiplicare per 9/5 e aggiungere 32, mentre per convertire gradi Fahrenheit in
gradi Celsius occorre sottrarre 32 e moltiplicare per 5/9.
 #include <stdio.h>
 int fahr, celsius, lower, upper, step;
 main()
 {
 lower = -300;
 upper = 300;
 step = 10;
 fahr = lower;
 printf(“Fahr\tCelsius\n”);
 while(fahr <= upper)
 {
 celsius = 5 * (fahr-32) / 9;
 printf (“%d\t%d\n”, fahr, celsius);
 fahr = fahr + step; /* fahr avanza di 10 in 10) */
 }
 system(“PAUSE”);
 }

Calcolare il massimo comune divisore di due numeri applicando


l’algoritmo euclideo con un programma in C
L’algoritmo originale di Euclide è basato sulle sottrazioni successive. Il procedi-
mento risolutivo è il seguente. Dati due numeri naturali a e b, si controlla se b è
zero. Se lo è, a è il MCD. Se non lo è, si divide a / b e si assegna a r il resto della
divisione (operazione indicata con “a modulo b” in seguito). Se r = 0 allora si può
terminare affermando che b è il MCD cercato, altrimenti occorre assegnare a = b e
b = r e ripetere nuovamente la divisione.
 #include <stdio.h>
 int x, y, t;
 main()
 {
 do /* Lettura dati di input con controllo di validità */
 {
 printf(“Inserisci due interi maggiori di zero:\n”);
 scanf(“%d %d”, &x, &y);
 } while (x <= 0 || y <= 0);
 while (x != y) /* Programma in C con algoritmo di Euclide */
 {
 if (x < y)
 {
 t = x;
 x = y;
 y = t;
 }
 x = x - y;
 }
 printf(“Il Massimo comun divisore e\’ %d\n”, x);
 system(“PAUSE”);
 }

IL LINGUAGGIO C 37
Il costrutto for
L’istruzione for viene utilizzata tradizionalmente per codificare la cosiddetta ripe-
tizione enumerativa o ripetizione con contatore: istruzioni cicliche che devono es-
sere ripetute per un numero prestabilito di volte. Come i più esperti sapranno, il
ciclo for rappresenta una specializzazione del ciclo while; tuttavia, nel linguaggio
C la differenza tra for e while è così sottile che i due costrutti possono essere lib-
eramente scambiati tra loro.
La sintassi dell’istruzione for è la seguente:

 for ([<InizializzazioneContatore>] ; [<Condizione>] ; [<IncrementoContatore>])


 <Istruzione>;

dove:
• <InizializzazioneContatore> può essere un’espressione che inizializza le va-
riabili del ciclo o una dichiarazione di variabili (in quest’ultimo caso le variabi-
li dichiarate hanno visibilità limitata a tutto il ciclo.
• <Condizione> è una qualsiasi espressione booleana; il corpo del ciclo for
sarà eseguito fintanto che la condizione si mantiene vera;
• <IncrementoContatore> è solitamente una istruzione di incremento o di
decremento del contatore da eseguire dopo ogni iterazione, ma può anche es-
sere un’istruzione che comprende altre manipolazioni sulla variabile;
• <Istruzione>, come nei precedenti cicli esaminati, indica un’istruzione sin-
gola o una sequenza di istruzioni racchiusa tra parentesi graffe.

Inizializzazione Test Modifica

for ( i = 0; i < 10; i = i + 1 ) {


printf(“%d”, i); Blocco di istruzioni da
} eseguire ciclicamente

Come si evince dalla sintassi, i primi tre elementi appena descritti sono opzion-
ali; in particolare, se <Condizione> non viene specificata, si assume che essa sia
sempre verificata.
Occorre prestare molta attenzione ai punti e virgola presenti nell’istruzione,
poiché in assenza di qualche elemento è comunque obbligatorio inserirli tra le in-
formazioni non dichiarate.
Facciamo subito un semplice esempio: supponiamo di voler ottenere la somma
di cinque numeri interi immessi dall’utente. Il codice è il seguente:

 #include <stdio.h>
 int i, numero, somma;

 main( )
 {
 somma = 0;
 for (i = 1; i <= 5; i++)
 {
 printf(“Inserire il %d numero ”, i);
 scanf(“%d”, &numero);
 somma += numero;
 }
 printf(“\nLa somma e\’ %d\n”, somma);
 system(“PAUSE”);
 }

38 IL LINGUAGGIO C
Il costrutto for

Il programma, quando incontra l’istruzione for, assegna il valore 1 alla variabile I


(la prima espressione del for), quindi controlla se il valore di I è inferiore a 5 (la
seconda espressione): poiché l’espressione risulta vera, vengono eseguite le
istruzioni inserite nel corpo del ciclo (l’input del numero e l’aggiornamento della
variabile Somma). Terminate le istruzioni che compongono il ciclo, si esegue l’ag-
giornamento di I così come risulta dalla terza espressione contenuta nel for, si
ripete il controllo contenuto nella seconda espressione e si continua come prima
finché il valore di I non rende falsa la condizione.
Questo modo di agire del ciclo for è quello comune a tutti i cicli di questo tipo
messi a disposizione dai compilatori dei diversi linguaggi di programmazione. Il
linguaggio C offre delle opzioni che espandono abbondantemente le potenzialità
del ciclo for generalizzandolo in maniera tale da inquadrarlo, come abbiamo det-
to, quale caso particolare del ciclo while. Il costrutto for, infatti, rappresenta
l’istruzione iterativa universale poiché, a differenza di quanto accade in altri lin-
guaggi di programmazione, consente di implementare sia iterazioni definite, sia
indefinite. Il programma per la somma di cinque numeri positivi scritto in prece-
denza potrebbe, ad esempio, venire riscritto nel seguente modo, che lo rende più
generale e molto più potente:

 #include <stdio.h>
 int numero, somma;
 main( )
 {
 printf(“Inserire u numero intero positivo (0 per terminare) ”);
 scanf(“%d”, &numero);
 for (somma = 0; numero;)
 {
 somma += numero;
 printf(“Inserire u numero intero positivo (0 per terminare) ”);
 scanf(“%d”, &numero);
 }
 printf(“\nLa somma e\’ %d \n”, somma);
 system(“PAUSE”);
 }

Il ciclo esegue l’azzeramento della variabile somma (che verrà effettuato una sola
volta) e, subito dopo, controlla se il valore di numero è diverso da zero: nel caso in
cui ciò sia vero verranno eseguite le istruzioni del ciclo. Terminate le istruzioni,
poiché manca la terza espressione del for, viene ripetuto il controllo su numero.
L’inizializzazione della variabile somma avrebbe potuto essere svolta fuori dal ci-
clo for: in tal caso sarebbe mancata anche la prima espressione.
Poiché nel linguaggio C ogni ciclo while può essere codificato utilizzando un ciclo
for e viceversa, è bene tenere presente che la scelta del tipo di codifica da effet-
tuare va sempre fatta in modo da ottenere la massima chiarezza e leggibilità del
programma.
Con il ciclo for si utilizza spesso la cosiddetta istruzione nulla. Essa contiene solo
il ; e può apparire ovunque è consentita un’istruzione; quando viene eseguita non
succede sulla. L’istruzione nulla viene utilizzata nei casi in cui è richiesta almeno
un’istruzione, ad esempio nei cicli e nelle istruzioni condizionali.
Nel seguente caso, ad esempio, l’istruzione for è utilizzata esclusivamente come
un ciclo di ritardo software e, di conseguenza, non è necessario che venga ese-
guita alcuna istruzione:

 ...
 for(i=0; i < 500000; i++)
 ;
 ...

IL LINGUAGGIO C 39
IL costrutto for
ESEMPI

Dati in input due interi n > 0 e k > 0, stampare i primi n multipli di k.


 #include <stdio.h>
 #include <stdlib.h>
 int i, n, k, p;
 main()
 {
 do
 {
 printf(“Inserisci un numero > 0 ”);
 scanf(“%d”,&n);
 } while (n <= 0);
 do
 {
 printf(“Inserisci un altro numero > 0 ”);
 scanf(“%d”,&k);
 } while (k <= 0);
 for (i=1; i<=n; i++)
 {
 p = k*i;
 printf(“%d\n”, p);
 }
 system(“PAUSE”);
 }

Dati in input due interi n > 0 e k > 0, stampare il risultato


della sommatoria k +k2 +k3 +...+kn..
 #include <stdio.h>
 #include <stdlib.h>
 int i, n, k, p, s;
 main()
 {
 do
 {
 printf(“Inserisci un numero > 0 ”);
 scanf(“%d”,&n);
 } while (n <= 0);
 do
 {
 printf(“Inserisci un altro numero > 0 ”);
 scanf(“%d”,&k);
 } while (k <= 0);
 s = 0;
 p = 1;
 for (i=1; i<=n; i++)
 {
 p = p*k;
 s = s+p;
 }
 printf(“%d\n”, s);
 system(“PAUSE”);
 }

40 IL LINGUAGGIO C
Il costrutto for

ESEMPI

Dato in input un intero n>0 e n numeri floating point, stampare il massimo e il minimo
 #include <stdio.h>
 #include <stdlib.h>
 float max, x, min;
 int n,i;
 main()
 {
 do
 {
 printf(“Di quanti numeri vuoi calcolare max e min? ”);
 scanf(“%d”, &n);
 } while (n<1);
 printf(“Inserisci un numero: ”);
 scanf(“%f”, &max);
 min=max;
 for(i=1; i<n; i++)
 {
 printf(“Inserisci un numero: ”);
 scanf(“%f”, &x);
 if(x<min)
 min=x;
 else
 if(x>max)
 max=x;
 }
 printf(“Il max degli interi digitati e\’ %.3f, e il minimo e\’ %.3f.\n”, max, min);
 system(“PAUSE”);
 }

Dati in input 5 numeri, visualizzare il maggiore tra essi, la loro media


e la radice quadrata della somma
 #include <stdio.h>
 #include <math.h>
 int i, a, max, somma;
 float r;
 main()
 {
 max = 0;
 for (i=0; i<5; i++)
 {
 printf (“Inserisci il %d° numero: ”,i);
 scanf(“%d”,&a);
 if(max < a)
 max = a;
 somma += a;
 }
 r = somma / 5;
 printf (“Il valore massimo inserito e\’: %d\n”, max);
 printf (“La radice quadrata della somma e\’: %.3f\n”,
 sqrt(somma));
 printf (“La media e\’: %.3f\n”, r);
 system(“PAUSE”);
 }

IL LINGUAGGIO C 41
Training
PROVE APERTE PER LA VERIFICA DELLE ABILITÀ

1. Tra le seguenti istruzioni di input è presente una priva 4. Il seguente programma C presenta degli errori e non è
di errori mentre le altre presentano ognuna un errore. scritto secondo le regole dell’indentazione. Interpreta il
Sapresti identificare quella corretta e trovare gli errori programma e prova a impostarlo in maniera corretta.
presenti nelle altre?
a scanf(“%d, k”);  #include <stdio.h>;
b scanf(%d, k);  int h;
c scanf(“%d, &k);
 main( )
d Scanf(“%d, &k);  {
e scanf(%d, “k”);  printf(“Inserisci un numero ”);
f scanf(%”d”, $k);  scanf(“%d”, &x);
 if(h=3);
2. Il seguente programma C non usa l’istruzione if in ma-  printf(Numero uguale a tre);
niera ottimale; inoltre presenta degli errori e non è scrit-
to secondo le regole dell’indentazione. Interpreta il pro-  x+;
gramma e prova a impostarlo in maniera più efficiente.  else
 printf(Numero diverso da tre);
 #include <stdio.h>  system(PAUSA)
 int k;
 }
 main( )
 {
 printf(“Inserisci un numero ”); 5. Enuncia il testo del problema relativo al seguente pro-
 scanf(“%d”, &k); gramma C:
 if(k==5)
 printf(“\nNumero = 5 \n”);  #include <stdio.h>
 k++;  int a, i, t;
 printf(“Il valore di k e\’ %d\n”, k);  float r;
 printf(“Inserisci un numero “);
 scanf(“%d”, &k);  main()
 if(k==5)  {
 {  t = 0;
 printf(“\nNumero = 5\n”);  i = 0;
 k++;  printf (“Numeri da inserire: ”);
 }  scanf(“%d”,&a);
 printf(“Il valore di k e\’ %d\n”, k);  while (a >= 0)
 system(“PAUSE”);
 {
 }
 i++;
 t += a;
3. Il seguente programma C presenta degli errori e non è  r = t/i;
scritto secondo le regole dell’indentazione. Interpreta il
programma e prova a impostarlo in maniera corretta.  printf (“La media attuale e\’: %.3f\n”, r);
 printf (“Inserisci un numero: ”);
 #include <stdio>  scanf(“%d”,&a);
 int a;  }
 main  printf (“La media finale e\’: %.3f\n”, r);
 {  system(“PAUSE”);
 printf(“Inserisci un numero ”);  }
 scanf(“%d”, &x);
 if (x=3); Quale accorgimento puoi adottare per evitare di
 printf(“Numero uguale a tre”) richiedere in input il dato sia fuori dal ciclo, sia
 system(PAUSE) all’interno di esso.
 }

42 IL LINGUAGGIO C
Training
PROVE APERTE PER LA VERIFICA DELLE ABILITÀ

6. Enuncia il testo del problema relativo al seguente pro-  }


gramma C:
 else
 #include <stdio.h>  {
 int a, t;  printf (“Non è un triangolo”);
 main()  }
 {  system(“PAUSE”);
 t = 0;  }
 printf (“Inserisci un numero: “);
Ci sono delle righe che potevano essere omesse?
 scanf(“%d”,&a); Se sì, quali sono?
 while (a >= 0)
 { 8. Quale valore assume la variabile z alla fine del pro-
gramma?
 t += a;
 printf(“Somma attuale =: %d\n”, t);
 #include <stdio.h>
 printf (“Inserisci un numero: ”);
 char c;
 scanf(“%d”,&a);
 int z;
 }
 main( )
 printf (“Somma finale =: %d\n”, t);
 {
 system(“PAUSE”);
 z=0;
 }
 c=‘z’;
 for (; c < ‘a’; c++)
7. Enuncia il testo del problema relativo al seguente pro-  z++;
gramma C:
 printf(“%d\n”, z);
 #include <stdio.h>  system(“PAUSE”);
 unsigned int x, y, z;  }
 main()
 { 9. Enuncia il testo del problema relativo al seguente pro-
gramma C:
 scanf (“%u”, &x);
 scanf (“%u”, &y);
 #include <stdlib.h>
 scanf (“%u”, &z);
 #include <stdio.h>
 if ((x < y + z) && (y < x + z) && (z < x + y))
 int n, i;
 {
 long a, b, c;
 if (x == y && y == z)
 main()
 {
 {
 printf (“Triangolo equilatero”);
 scanf(“%d”, &n);
 }
 a = 1;
 else
 b = 1;
 {
 printf(“%ld %ld “, a, b);
 if (x == y || y == z || x == z)
 for (i=2; i<n; i++)
 {
 {
 printf (“Triangolo isoscele”);
 c = a+b;
 }
 printf(“%ld “, c);
 else
 a = b;
 {
 b = c;
 printf (“Triangolo scaleno”);
 }
 }
 system(“PAUSE”);
 }
 }

IL LINGUAGGIO C 43
Le funzioni
Nel linguaggio C la componente principale dei codici è costituita da funzioni. Per
poter simulare le procedure (che non restituiscono alcun valore) è stato introdot-
to il tipo void. Ricordiamo, comunque, che il tipo void o tipo indefinito è utilizza-
to dal C ogni volta che il valore di ritorno di una funzione non deve essere preso
in considerazione. In altri termini, nel linguaggio C le procedure sono funzioni
che restituiscono un void. Possiamo riassumere il processo di costruzione e uso
di una funzione nelle seguenti tre fasi.
1. Definizione della funzione, cioè scrittura dell’elenco delle operazioni da essa
svolte. La definizione specifica nell’ordine il tipo di valore restituito, il nome
scelto dal programmatore (valgono le stesse regole viste per l’assegnazione dei
nomi alle variabili) e, infine, l’elenco dei parametri. All’interno del corpo della
funzione vengono inserite le dichiarazioni delle variabili della funzione e le
istruzioni. La sintassi generale è quindi la seguente:
 [<TipoRestituito>] <NomeFunzione>([<ElencoParametri>])
 {
 <Istruzioni>
 [return [(]<Risultato>[)]]
}

Come si evince dalla sintassi, la specifica del <TipoRestituito> è opzionale: si


assume, in sua assenza (per default), che il tipo restituito sia int, sebbene, per
motivi di comprensibilità, sia sempre opportuno dichiarare esplicitamente il
tipo. Nel caso in cui si desideri implementare una procedura occorre specifica-
re come <TipoRestituito> il tipo void.
Anche <ElencoParametri>, che tratteremo più avanti, è opzionale: in sua as-
senza occorre comunque far seguire il nome della funzione da una coppia di pa-
rentesi tonde ( ), oppure inserire la parola chiave void all’interno delle parentesi
tonde proprio per specificare che nessun parametro viene passato alla funzione.
Ad esempio, void Funz() è equivalente a void Funz(void). Ricordiamo che Lo stan-
dard C stabilisce che una funzione che non richiede parametri deve utilizzare
l’identificatore void in modo esplicito, all’interno delle parentesi.
Nel caso in cui <ElencoParametri> sia presente, cioè se la funzione riceve pa-
rametri, è necessario specificare il tipo, seguito dal nome del parametro, pro-
prio come abbiamo fatto per dichiarare una variabile. Il tipo dei parametri è
quello che abbiamo già studiato (int, float e così via). Se a una funzione si pas-
sano più parametri, questi dovranno essere separati da una virgola.
Le definizioni di funzioni possono essere scritte in qualsiasi punto del pro-
gramma: verranno eseguite in seguito alla chiamata, perciò non ha alcuna im-
portanza dove siano fisicamente poste. Lo standard ANSI ha però fissato delle
convenzioni secondo le quali è bene codificare le definizioni delle funzioni
dopo il main.
Fra le istruzioni contenute nella definizione della funzione assume una parti-
colare importanza return, utilizzata per restituire un valore al chiamante. La
sintassi dell’istruzione prevede di specificare dopo la parola chiave return un
valore costante, o una variabile, o un’espressione compatibile con il tipo resti-
tuito dalla funzione. Ad esempio:

 return 2; /* Restituisce il valore 2 – si può anche scrivere (2)*/


 return x; /* Restituisce il valore contenuto nella variabile x */
 return (a+b); /* Restituisce il risultato dell’espressione specificata */
 return; /* Ritorna senza restituire alcun valore */

Non è importante che le definizioni di tutte le funzioni usate in un programma


seguano l’ordine con cui sono chiamate, anche se, per motivi di chiarezza e
leggibilità, è opportuno che sia così e che si segua l’ordine specificato in pre-
cedenza. In ogni caso, la funzione con il nome main è eseguita per prima, in
qualunque punto sia posta, e le funzioni sono eseguite nell’ordine in cui ven-
gono chiamate.

44 IL LINGUAGGIO C
Le funzioni

2. Dichiarazione del prototipo della funzione. Si tratta di ripetere all’inizio del


programma, prima della definizione del main, l’intestazione delle funzioni de-
finite in seguito. In pratica, si riscrive quanto specificato nella prima riga della
definizione della funzione, includendo però un punto e virgola alla fine. I pro-
totipi sono stati introdotti per consentire al compilatore un maggiore controllo
sui tipi di parametri: conoscendoli in anticipo, infatti, all’atto della chiamata è
possibile stabilire se i parametri passati sono congruenti con quelli attesi. Nella
costruzione di programmi complessi capita di utilizzare molte funzioni. In
questo caso le funzioni sono raccolte in librerie e i rispettivi prototipi sono rag-
gruppati nei file di intestazione (o file header). Gli header sono file di testo che
hanno estensione .h.
3. Chiamata della funzione. Consiste semplicemente nell’indicare, nel punto in
cui occorre utilizzare l’elaborazione fornita dalla funzione, il nome della fun-
zione stessa accompagnato dall’elenco dei parametri da trasmettere. Il pro-
gramma chiamante può utilizzare il valore restituito dalla funzione: in tal caso
il nome della funzione figurerà, ad esempio, in un’espressione. Il chiamante
può trascurare il valore restituito anche se non è di tipo void: è sufficiente uti-
lizzare la funzione senza assegnare il valore da questa restituito.

Giunti a questo punto del nostro percorso, è doveroso ricordare che anche il
main è una funzione ed è proprio questo il motivo della presenza della coppia
di parentesi tonde dopo la parola main. È una funzione speciale, poiché viene
eseguita per prima all’avvio del programma. L’istruzione return può essere pre-
sente anche nella funzione main, in tal caso il valore viene restituito diretta-
mente al sistema operativo, che è il programma chiamante della funzione.
Da questo momento, quindi, nei successivi esercizi sarà indicato il tipo int prima
di main, per indicare il tipo del valore restituito, e alla fine del main verrà indica-
ta l’istruzione return seguita dal valore 0, il modo più comune per indicare la ter-
minazione di un programma che non ha rilevato errori durante l’esecuzione.

Vediamo ora un esempio che mostra un prototipo e una funzione in un program-


ma: dati in input due numeri interi, fornire in output il loro prodotto.
 #include <stdio.h>
 double Moltiplica(); /* questo e’ il prototipo della funzione */
 int x, y;
 int main( )
{
 printf(“Inserire un numero intero “ );
 scanf(“%d”, &x);
 printf(“\nInserire un altro numero intero “ );
 scanf(“%d”, &y);
 printf(“Il prodotto e\’ %5.0f\n “, Moltiplica()); /* chiamata della funzione */
 system(“PAUSE”);
 return 0;
}
 double Moltiplica() /* questa è l’intestazione della funzione */
{
 /* questo e’ l’inizio del corpo della funzione */
 return (x * y);
 } /* questa e’ la fine del corpo della funzione */

Non è superfluo far notare che in questo programma la funzione è totalmente inu-
tile e può solo moltiplicare interi. L’abbiamo inserita unicamente per mostrare
come si costruisce una funzione.

IL LINGUAGGIO C 45
Le funzioni
ESEMPI

Scrivere un programma per il calcolo delle soluzioni di un’equazione


di secondo grado
Questo problema è stato già risolto precedentemente trattando il costrutto di se-
lezione. Ora lo risolviamo con l’ausilio delle funzioni e utilizzando una diversa lo-
gica di elaborazione.
Partiamo come al solito dall’analisi del problema.
Per risolvere un’equazione di secondo grado scritta nella forma canonica:
ax2+bx+c = 0
occorre acquisire i tre coefficienti a, b e c, poiché le soluzioni sono determinate
dai valori loro assegnati. Una volta acquisiti, occorre calcolare il discriminante (∆)
utilizzando la formula b2 – 4ac. Il valore del discriminante può fare riconoscere
tre distinte situazioni:
• se ∆ < 0 non esistono soluzioni reali;
• se ∆ = 0 l’equazione ammette due soluzioni reali e coincidenti;
• se ∆ > 0 l’equazione ammette due soluzioni reali e distinte.
Nel caso in cui esistano radici reali, queste si ottengono dalla formula:
–b± b2 – 4ac
2a
Affinché il programma possa comprendere tutte le situazioni, è necessario fare
attenzione al valore assegnato al coefficiente a, perché nel caso in cui questo sia
uguale a zero, non solo non sarà possibile applicare la formula, ma non ci si tro-
verà più di fronte a un’equazione di secondo grado e occorrerà procedere con la
risoluzione di un’equazione di primo grado.
L’analisi appena condotta ci porta a individuare cinque sottoprogrammi. Il top-
down è pertanto il seguente:

Equazione
secondo grado

Calcolo Risoluzione
Inserimento
Main Calcolo Delta e visualizzazione equazione
dei coefficienti
soluzioni di primo grado

Le variabili che utilizzeremo sono le seguenti:

NOME FUNZIONE TIPO DESCRIZIONE


a I Intero Coefficiente a
a I Intero Coefficiente b
a I Intero Coefficiente c
x1, x2 O Reali Soluzioni dell’equazione di secondo grado
x O Reale Soluzione dell’equazione di primo grado
delta L Reale Valore del discriminante

46 IL LINGUAGGIO C
Le funzioni

ESEMPI
 #include <stdio.h>
 #include <stdlib.h>
 #include <math.h> /* la libreria math consente di utilizzare funzioni
 matematiche */
 void Acquisisci_Coefficienti();
 void Calcola_Delta();
 void Visualizza_Soluzioni();
 void Risolvi_Equazione();
 int a, b, c;
 float x, x1, x2, d;
 int main()
{
 Acquisisci_Coefficienti();
 if(a != 0)
 {
 Calcola_Delta();
 Visualizza_Soluzioni();
 }
 else
 Risolvi_Equazione();
 system(“PAUSE”);
 return 0;
}
 void Acquisisci_Coefficienti()
{
 printf(“Inserisci il valore del coefficiente a ”);
 scanf(“%d”, &a);
 printf(“\nInserisci il valore del coefficiente b ”);
 scanf(“%d”, &b);
 printf(“\nInserisci il valore del coefficiente c ”);
 scanf(“%d”, &c);
}
 void Calcola_Delta()
{
 d=(b*b)–(4*a*c);
}
 void Visualizza_Soluzioni()
{
 if(d<0)
 printf(“\nL\’equazione non ammette soluzioni reali\n”);
 else
 {
 x = (–b – sqrt(d))/(2*a);
 x = (–b + sqrt(d))/(2*a);
 printf(“\nx1= %–3.1f x2= %–3.1f\n”, x, x2);
 }
}
 void Risolvi_Equazione()
{
 if((b==0) && (c==0))
 printf(“\nEquazione indeterminata\n”);
 else
 if(b==0)
 printf(“\nEquazione impossibile\n”);
 else
 x = –c/b;
}

IL LINGUAGGIO C 47
Passaggio dei
parametri per valore
Le funzioni comunicano tra di loro attraverso i parametri. L’operazione attraverso
la quale la funzione main o un’altra funzione chiamante invia i valori alla funzio-
ne, assegnandoli ai parametri, si chiama passaggio dei parametri. In particolare:
• le variabili indicate nella chiamata della funzione prendono il nome di para-
metri attuali;
• le variabili indicate nell’intestazione della funzione e che accettano i valori dei
parametri attuali prendono il nome di parametri formali.
Il passaggio di parametri (dal chiamante al chiamato) può avvenire secondo due
specifiche modalità:
• per valore (by value) se il chiamante comunica al chiamato il valore che è con-
tenuto in quel momento in una sua variabile (parametro attuale). Il chiamato
accoglierà tale valore all’interno di una variabile locale opportunamente predi-
sposta (parametro formale). Il chiamato può operare su tale valore, anche mo-
dificandolo, ma tali modifiche riguarderanno solo la copia locale su cui sta la-
vorando. Terminato il sottoprogramma, il parametro formale viene deallocato
e viene ripristinato il parametro attuale con il valore che conteneva prima del-
la chiamata al sottoprogramma.
• per riferimento o per indirizzo (by reference) se il chiamante comunica al chia-
mato l’indirizzo di memoria di una determinata variabile (parametro attuale).
Il chiamato, per accogliere il parametro attuale, può utilizzare un parametro
formale con un nome diverso, ma le locazioni di memoria a cui ci si riferisce
sono sempre le stesse. Viene soltanto stabilito un riferimento diverso alle stes-
se posizioni di memoria: ogni modifica effettuata sul parametro formale si ri-
percuoterà su quello attuale, anche se il nuovo nome cessa di esistere alla con-
clusione del sottoprogramma.
In mancanza di specifiche indicazioni (cioè per default) si assume che il pas-
saggio di parametri avvenga per valore. È doveroso ricordare che la lista dei pa-
rametri attuali (quelli inviati dal programma chiamante) e quella dei parametri
formali (quelli presenti nell’intestazione della funzione) devono essere entrambe
coerenti rispetto a tre elementi fondamentali:
• numero: il numero dei parametri formali deve essere uguale a quello dei para-
metri attuali;
• tipo: parametri attuali e parametri formali corrispondenti devono essere dello
stesso tipo;
• ordine: il passaggio dei parametri è posizionale, nel senso che il primo para-
metro attuale invierà il suo contenuto al primo parametro formale, il secondo
attuale al secondo formale e così di seguito.
Al fine di chiarire ulteriori dettagli sul passaggio dei parametri, riprendiamo
l’esercizio svolto a pagina 91, che calcolava il prodotto di due numeri interi. Il pro-
gramma che abbiamo realizzato faceva uso di di variabili globali che, essendo vi-
sibili all’interno di tutte le funzioni, non necessitavano di essere trasmesse alla
funzione che si occupava di calcolare il prodotto. Nei due esercizi che seguono,
invece, le variabili che utilizzeremo saranno dichiarate locali al main, per cui oc-
correrà “farle conoscere” alle funzioni che si occuperanno di svolgere determina-
te operazioni.
Per comprendere meglio le due modalità di passaggio dei parametri, risolviamo
due semplici problemi. Per la risoluzione del primo ci serviremo di un passaggio
di parametri per valore: dati in input due numeri interi a e x, calcolare il valo-
re della potenza ax.
 #include <stdio.h>
 #include <stdlib.h>
 long int Potenza(int b, unsigned int e);

48 IL LINGUAGGIO C
Passaggio dei parametri per valore

 int main( )
 {
 int a;
 unsigned int x;
 printf(“Inserire la base “);
 scanf(“%d”, &a);
 printf(“Inserire l’esponente “);
 scanf(“%ud”, &x);
 printf(“La potenza e\’ %ld\n” , Potenza(a, x)); /* parametri attuali */
 system(”PAUSE”);
 return 0;
 }

 long int Potenza(int b, unsigned int e) /* parametri formali passati per valore */
 {
 long int p;
 p=1;
 while(e>=1)
 {
 p *= b;
 e ––;
 }
 return p;
 }

Nel passaggio dei parametri per valore (by value) si ha soltanto una copia dei va-
lori dei parametri attuali nei rispettivi parametri formali. Durante l’esecuzione del
sottoprogramma, qualsiasi modifica apportata ai parametri formali sarà visibile
solo all’interno della funzione e non verrà riportata sui parametri attuali (che con-
tinueranno, così, a conservare il valore inizialmente trasmesso). I parametri for-
mali vengono considerati come variabili locali il cui valore, al ritorno dalla funzio-
ne, perde il suo significato.
Nel passaggio dei parametri per valore, all’atto della chiamata della funzione, vie-
ne allocata un’area di memoria utilizzata per contenere i parametri formali. Si ha,
così, una duplicazione dello spazio di memoria riservato ai parametri. Al passag-
gio, i parametri formali verranno inizializzati con il valore dei rispettivi parametri
attuali. In questo modo, il processore opera su questa nuova area di memoria la-
sciando inalterato il valore dei parametri attuali. Al rientro dalla funzione, que-
st’area viene rilasciata, proprio come avviene per le variabili locali.

Un ultimo particolare: nella dichiarazione del prototipo della funzione abbiamo


scritto:
 long int Potenza(int b, unsigned int e); // il prototipo della funzione
cioè abbiamo indicato i parametri formali completi di tipo e di nome. Nel caso
in cui non avessimo inserito i parametri, il compilatore C non avrebbe segnala-
to un errore: l’elenco dei parametri, infatti, può essere completo, o può riporta-
re solo l’indicazione del tipo. Ciò perché il compilatore non esegue alcun con-
trollo sui nomi dei parametri formali del prototipo. A tal proposito,
relativamente alla nostra funzione sono validi entrambi i prototipi:

 long int Potenza(int b, unsigned int b); // prototipo valido


 long int Potenza(int, unsigned int); // prototipo valido

IL LINGUAGGIO C 49
Passaggio dei parametri
ESEMPI
per valore
Stampare tutti i numeri primi compresi tra due numeri forniti in input
Per risolvere il problema ci serviamo di due funzioni: la prima, che abbiamo chia-
mato Numeriprimi, non fa altro che richiamare la funzione Numeroprimo per ogni
valore compreso nell’intervallo [i, j] fornito in input. La funzione Numeroprimo
controlla se il numero passato come parametro è primo. Tale controllo viene ef-
fettuato dividendo il valore del parametro ricevuto per tutti i numeri compresi tra
2 e il valore del parametro stesso. Nel momento in cui si trova un numero per il
quale la divisione fornisce come resto zero, si comprende che il numero non è pri-
mo e la funzione restituisce il valore booleano FALSE attraverso la variabile primo.
 #include <stdio.h>
 #include <stdlib.h>
 #define TRUE 1
 #define FALSE 0
 int Numeroprimo(int);
 void Numeriprimi(int, int);

 int main()
 {
 int i,j;
 printf(“Inserisci il primo numero: “);
 scanf(“%d”, &i);
 printf(“Inserisci il secondo numero:”);
 scanf(“%d”, &j);
 Numeriprimi(i,j);
 system(“PAUSE”);
 return 0;
 }

 int Numeroprimo(int n) /* verifica se il numero passato come parametro


 è primo */
 {
 int primo, i;
 primo = TRUE;
 i = 2;
 while (primo == TRUE && i<n)
 {
 if (n%i == 0)
 primo=FALSE;
 i++;
 }
 return(primo);
 }

 void Numeriprimi(int a, int b) /* Stampa tutti i numeri primi compresi


 tra gli interi positivi a e b */
 {
 int i;
 for (i=a; i<=b; i++)
 if (Numeroprimo(i))
 printf(“%d e\ numero primo\n”,i);
 }

50 IL LINGUAGGIO C
Passaggio dei parametri per valore

ESEMPI
Riportiamo una versione più compatta della funzione Numeroprimo:
 int numeroprimo(int n) /* versione compatta */
 {
 int primo=TRUE, i=2;
 while(primo && (i*i<=n))
 primo=n%i++;
 return(primo);
 }

Dati in input il valore della base e dell’esponente,


calcolare la potenza
Questo problema è già stato risolto. Vediamo ora una nuova versione più comple-
ta che fa uso del costrutto for e non del costrutto while.
 #include <stdio.h>
 #include <stdlib.h>
 double Potenza(double base, int esponente);
 int main()
 {
 double base;
 int esponente;
 printf(“Inserisci la base: ”);
 scanf(“%lg”,&base);
 printf(“Inserisci l’esponente (intero): ”);
 scanf(“%d”,&esponente);
 if(esponente<0)
 printf(“Solo esponenti positivi sono permessi!\n”);
 else if(esponente==0)
 {
 if(base==0)
 printf(“Risultato indeterminato.\n”);
 else
 printf(“Risultato = 1\n”);
 }
 else
 printf(“Risultato = %g\n”,Potenza(base,esponente));
 system(“PAUSE”);
 return(0);
 }
 double Potenza(double base, int esponente)
 {
 int i;
 double risultato;
 risultato=1.0;
 for(i=1;i<=esponente;i++)
 risultato*=base;
 return(risultato);
 }

IL LINGUAGGIO C 51
Passaggio dei
parametri per indirizzo
Talvolta la modifica dei parametri formali diviene necessaria per il corretto fun-
zionamento del programma. Per esempio il programma seguente, che effettua lo
scambio del contenuto di due variabili, non funziona correttamente perché il pas-
saggio di parametri per valore non consente di risolvere il problema.
 #include <stdio.h>
 #include <stdlib.h>
 void Scambia(int x, int y);

 int main()
 {
 int a, b;
 printf(“Inserire il primo numero “);
 scanf(“%d”, &a);
 printf(“\nInserire il secondo numero “);
 scanf(“%d”, &b);
 Scambia(a, b); /* chiamata della funzione */
 printf(“\nI valori scambiati sono:\n”);
 printf(“a = %d\nb = %d\n”, a, b);
 system(“PAUSE”);
 return 0;
 }

 void Scambia(int x, int y)


 {
 int appoggio;
 appoggio = x;
 x = y;
 y = appoggio;
 }

Questi tipi di problemi richiedono che la funzione non operi su una copia locale
dei dati, ma direttamente sui dati originali. È necessario, quindi, effettuare un
passaggio di parametri per indirizzo.
Tecnicamente, il passaggio per indirizzo differisce da quello per valore in quanto,
all’atto della chiamata della funzione, non viene allocata nessuna area di memo-
ria per valori dei parametri formali. Poiché essi si riferiscono alla stessa area di
memoria allocata per i parametri attuali, la stessa cella di memoria sarà indivi-
duabile con due nomi distinti. In questo tipo di passaggio non viene trasmesso il
valore dei parametri attuali, bensì l’indirizzo della cella di memoria a essi asse-
gnata; di conseguenza, la modifica di un parametro comporterà la modifica del-
l’altro.
Per passare l’indirizzo di una variabile è necessario anteporre al nome del para-
metro attuale l’operatore unario di indirizzo & che restituisce, appunto, l’indiriz-
zo della variabile alla quale è applicato.
D’altra parte, la funzione che riceve una variabile con passaggio per indirizzo
deve essere in grado di risalire dall’indirizzo della cella di memoria al valore in
essa contenuto; per permettere ciò è necessario che i parametri formali siano
preceduti dall’operatore * che, applicato al nome di una variabile di tipo indiriz-
zo, ne restituisce il valore.
Risolviamo ora il precedente problema relativo allo scambio del contenuto di due
variabili, servendoci questa volta del passaggio di parametri per indirizzo.

52 IL LINGUAGGIO C
Passaggio dei parametri per indirizzo

 #include <stdio.h>
 void Scambia(int *x, int *y);

 int main()
 {
 int a, b;
 printf(“Inserire il primo numero “);
 scanf(“%d”, &a);
 printf(“\nInserire il secondo numero “);
 scanf(“%d”, &b);
 Scambia(&a, &b); /* chiamata della funzione */
 printf(“\nI valori scambiati sono:\n”);
 printf(“a = %d\nb = %d\n”, a, b);
 system(“PAUSE”);
 return 0;
 }
 void Scambia(int *x, int *y)
 {
 int appoggio;
 appoggio = *x;
 *x = *y;
 *y = appoggio;
 }

Nella terminologia informatica le variabili *x e *y si chiamano puntatori, cioè va-


riabili che non contengono un dato, ma l’indirizzo di una locazione di memoria.

Per consolidare la comprensione della differenza tra passaggio per valore e pas-
saggio per indirizzo presentiamo un ulteriore esempio:
 #include <stdio.h>
 void Incrementa(int x, int *y);

 int main()
 {
 int a=0, b=0;
 Incrementa(a, &b);
 printf(“Nel main la variabile a vale %d e la variabile b vale %d \n\n”, a, b);
 system(“PAUSE”);
 return 0;
 }
 void Incrementa(int x, int *y)
 {
 x++;
 (*y)++;
 printf(“Nella funzione il parametro x vale %d e il parametro y vale
%d \n\n”, x, *y);
 return;
 }

Dal risultato ottenuto si nota come l’incremento del parametro x (trasmesso per
valore) all’interno della funzione non influenza il valore della variabile a, mentre
l’incremento della variabile indirizzata da y (trasmesso per indirizzo) modifica il
valore iniziale della variabile b.

IL LINGUAGGIO C 53
Gli array
In precedenza i dati da elaborare sono sempre stati considerati contenuti all’in-
terno di comuni variabili. Quando però il numero dei valori da gestire diventa ele-
vato, tali variabili si rivelano inadatte a soddisfare tutte le esigenze del program-
matore; in questo caso si ricorre a specifiche strutture di dati denominate array.
Gli array sono variabili in grado di ospitare diversi valori omogenei (cioè tutti del-
lo stesso tipo) in opportuni spazi numerati chiamati elementi. A un elemento di
un array si può accedere indicando il nome della struttura e il numero corrispon-
dente alla posizione, detto indice.
Il tipo di un elemento può essere un tipo primitivo o un oggetto più complesso:
avremo quindi array di numeri interi, di stringhe o di array, ma non array che
contengono tipi di dati diversi (per esempio numeri interi e stringhe, i cosiddetti
array densi possibili invece in altri linguaggi).
Un array può contenere più indici. Nel caso in cui contenga:
1. un solo indice, si dice che l’array è a una dimensione e si parla specificamen-
te di vettore (una rappresentazione è visibile nella figura);
2. due indici, si parla di array bidimensionale o matrice;
3. più di due indici, si parla di array multidimensionali.

Valori dell’indice
0 1 2 3 4 5 6 7
3 8 150 10 85 90 7 34

Valori degli elementi

Tutti gli elementi di un array vengono memorizzati uno di seguito all’altro nella
memoria del computer e, cosa importante, l’indice del primo elemento è 0. Il
nome del vettore è un valore costante che rappresenta l’indirizzo di memoria del
primo elemento del vettore stesso.

Dichiarazione di un vettore
Per dichiarare un vettore si deve scrivere il tipo degli elementi, seguito da un
nome valido e da una coppia di parentesi quadre che racchiudono un’espressio-
ne costante. Tale espressione costante definisce le dimensioni dell’array, ossia il
numero di elementi che esso contiene.
La sintassi generale è dunque la seguente:

 <TipoElementi> <NomeArray>[<Dimensione>];

Per esempio:

 int numeri[10]; // Array non inizializzato di 10 interi


 char lettere[20]; // Array non inizializzato di 20 caratteri

All’interno delle parentesi quadre non è consentito, in fase di dichiarazione del-


l’array, utilizzare nomi di variabili. Per specificare la dimensione di un array è in-
vece possibile usare delle costanti definite, come negli esempi:

 #define LIMITE_NUMERI 10
 #define LIMITE_LETTERE 20
 int numeri[LIMITE_NUMERI];

 char lettere[LIMITE_LETTERE];

Inizializzazione di un vettore
Un vettore può essere inizializzato esplicitamente, al momento della creazione,
fornendo le costanti di inizializzazione dei dati, oppure durante l’esecuzione del
programma, assegnando o copiando dati nell’array.
54 IL LINGUAGGIO C
Gli array

Per inizializzare l’array numeri degli esempi precedenti in fase di creazione, con
dieci numeri interi, scriveremo:
Array dimensionato

 int numeri[10] = {12, 0, 4, 150, 4500, 2, 34, 5599, 22, 83};

Un metodo alternativo è il seguente:


Array non dimensionato

 int numeri[ ] = {12, 0, 4, 150, 4500, 2, 34, 5599, 22, 83};

In questo secondo modo si crea un array di dimensione pari al numero di ele-


menti inseriti. Tutti gli elementi devono essere del tipo dichiarato (nel nostro caso
int), altrimenti si avrà un errore in fase di compilazione.
Per inizializzare un array durante l’esecuzione del programma occorre invece ac-
cedere, generalmente con un ciclo, a ogni elemento dell’array stesso e assegnar-
gli un valore. Come abbiamo detto, l’accesso a un elemento di un array avviene
indicando il nome dell’array seguito dall’indice dell’elemento desiderato, in base
alla sintassi:

 <NomeArray>[<IndiceElemento>]

dove <IndiceElemento> inizia da 0 e non da 1. Il primo elemento dell’array è


quindi <NomeArray>[0], il secondo <NomeArray>[1] e così via. Pertanto, nu-
meri[2] indicherà il terzo elemento dell’array numeri e non il secondo.

Rappresentazione in memoria dei vettori


All’interno della memoria gli elementi di un vettore sono memorizzato in locazio-
ni consecutive a partire da quella di indirizzo più basso secondo l’ordine crescen-
te del loro indice. Quando si dichiara un array si ottiene solo il riferimento al suo
inizio. Alla luce di questa importantissima informazione, supponiamo di avere a
che fare con un vettore temperature di 7 elementi di tipo int contenente le tempe-
rature medie della settimana. Sapendo che un int occupa 4 byte e ipotizzando
che il primo elemento del vettore venga memorizzato nella cella di memoria di
indirizzo 00100, la memorizzazione avverrà nel seguente modo:

Indirizzo Contenuto
00100 23,5 temperature[0]
00104 23,0 temperature[1]
00108 22,8 temperature[2]
00112 21,6 temperature[3]
00116 23,9 temperature[4]
00120 20,6 temperature[5]
00124 22,9 temperature[6]

Quindi l’indirizzo dell’I-esimo elemento si calcola con la seguente formula:

 <IndirizzoI-esimoEelemento> = <IndirizzoPrimoElemento> + I *
<DimensioneElemento>

IL LINGUAGGIO C 55
Gli array
ESEMPI
Caricare un vettore con N elementi interi, con N ≤ 100
Questo è un problema di caricamento determinato ossia un caricamento di N va-
lori con N noto a priori. Il problema è molto semplice, perciò possiamo passare
direttamente alla codifica.
 #include <stdio.h>
 #define MASSIMO 50
 int DimensionaVettore( );

 int main( )
 {
 int vettore[MASSIMO];
 int k, numelementi;
 numelementi = DimensionaVettore( );
 for (k=0; k < numelementi; k++)
 {
 printf(“\nInserire l’elemento di posizione %d: ”, k);
 scanf(“%d”, &vettore[k]);
 }
 system(“PAUSE”);
 return 0;
}

 int DimensionaVettore( )
{
 int elementi;
 do
 {
 printf(“Quanti elementi vuoi inserire? ”);
 scanf(“%d”, &elementi);
 } while(elementi < 1 || elementi > MASSIMO);
 return(elementi);
}

Una variante di questo problema potrebbe prevedere di caricare il vettore con N


elementi interi, utilizzando solo numeri pari. Riportiamo solo il ciclo for del main,
poiché il resto è identico all’esempio precedente.
 ...
 for (k=0; k < numelementi; k++)
 {
 printf(“\nInserire l’elemento di posizione %d: “, k);
 scanf(“%d”, &vettore[k]);
 if (vettore[k] % 2==0)
 continue;
 k––;
 }
 ...

Scrivere un programma che chieda in input una serie di numeri


positivi (finchè non viene inserito –1) e ne calcoli la somma e la
media. Il numero inserito deve essere compreso tra 1 e 10000
Questo problema ha a che fare con un caricamento indeterminato (ossia con un
caricamento di N valori con N non noto a priori). Lo risolviamo senza l’ausilio di
sottoprogrammi.
56 IL LINGUAGGIO C
Gli array

ESEMPI
 #include <stdio.h>
 int main()
{
 int i, numeri=0, valori[200];
 long somma;
 double media;
 do
 { /* ripetiamo il ciclo fino a quando il numero inserito è valido */
 do
 {
 printf(“\nInserire il numero %d :”, numeri + 1);
 scanf(“%d”,&valori[numeri]);
 } while((valori[numeri] < 1 || valori[numeri] > 10000) && !(valori[numeri] == –1));
 } while(valori[numeri++] != –1);
 numeri––; /* decremento perché l’ultimo valore non si deve considerare */
 somma=0;
 for(i = 0; i < numeri; i++)
 somma+=valori[i];
 media=0;
 for(i = 0; i < numeri; i++)
 media=(double)somma / numeri;
 printf(“\nNumero valori inseriti = %d”, numeri);
 printf(“\nSomma = %d”, somma);
 printf(“\nMedia = %.2f \n”, media);
 system(“PAUSE”);
 return 0;
}

Scrivere un programma che visualizzi i numeri primi minori di 100 utilizzando il crivello
di Eratostene
Il crivello di Eratostene è una specie di setaccio che scartando i numeri composti permette di determinare i nu-
meri primi. Supponendo di voler determinare tutti i numeri primi minori di 100, si procede nel seguente modo:
si memorizzano in un vettore i numeri fino a 100 (partendo da 2), si eliminano, dopo il 2, tutti i numeri pari
perché multipli di 2. A partire dal 3 si eliminano successivamente tutti i multipli di 3 (cioè un numero ogni tre).
Dopo il 3 si incontra il 5 e si eliminano quindi tutti i multipli di 5 (cioè un numero ogni 5) e così via finchè non
sono stati eliminati tutti i numeri composti. Il procedimento termina quando si arriva a 100.
 #include <stdio.h>
 main()
{
 int i, j, v[100];
 for (i=2; i<100; i++)
 v[i] = i;
 for (i=2; i<100; i++) /* useremo 1 per indicare che il numero è primo e 0 se non lo è */
 if (v[i] == 1) /* se i è primo cancelliamo i suoi multipli */
 for (j=2; j*i<100; j++ )
 v[i*j] = 0;
 for (i=2; i<100; i++) /* stampiamo i numeri primi */
 if (v[i] == 1)
 printf(“%d\t”, i);
 printf(“\n”);
 system(“PAUSE”);
 return 0;
}

IL LINGUAGGIO C 57
Training
PROVE APERTE PER LA VERIFICA DELLE ABILITÀ

1. Scrivi in corrispondenza di ogni valore da memorizza- 4. Scrivi le scritture compatte equivalenti alle seguenti
re il tipo di dato più appropriato: istruzioni:
Valore Tipo di dato X = X + Y;
2 X = X – Y;
125000000 X = X * Y;
A X = X / Y;
Ab X = X % Y;
0.125
125520.25 5. Trova l’errore presente in ognuna delle seguenti di-
chiarazioni di variabili:
2. Completa la seguente tabella riportando la descrizio-
ne dei vari tipi di dati presenti:  Int a;
 Float b;
Tipo Descrizione  int a = 12.5;

char  char parola = “Ciao”;


 char lettera = “C”;
caratteri signed char
unsigned char
short int 6. Qual è il valore della variabile b al termine dell’esecu-
signed short int zione del seguente programma?
unsigned short int
 #include <stdio.h>
int
 #include <stdlib.h>
interi signed int
 int a, b, c;
unsigned int  main()
long int {
signed long int  a = 10;
unsigned long int  b = 1;
float  c = 2;
 b = a++;
reali double
 printf(“%d\n”, b);
long double  b = ++a;
 printf(“%d\n”, b);
3. Completa la seguente tabella riportando i byte occu-  system(“PAUSE”);
pati dai singoli tipi di dato: }

Tipo Byte occupati Intervallo di definizione


7. Qual è il valore della variabile b al termine dell’esecu-
char da –128 a 127 zione del seguente programma?
unsigned char da 0 a 255
 #include <stdio.h>
short int da –32768 a +32767
 #include <stdlib.h>
da –21474863648 a  int a, b, c;
int
+21474863647  main()
long int da 10–309 a 10+308 {

usigned short da 0 a 65535  a = 10;


 b = 1;
unsigned int da 0 a 4294967295  c = 2;
unsigned long notazione IEEE standard  b *= a++;
float notazione IEEE standard
 printf(“%d\n”, a);
 b *= ++a;
double notazione IEEE standard  printf(“%d\n”, a);
long double notazione IEEE standard  system(“PAUSE”);
}

58 IL LINGUAGGIO C
Training
PROVE APERTE PER LA VERIFICA DELLE ABILITÀ

8. Osservando le porzioni di codice seguenti, scrivi il valo- 12. Qual è l’output fornito dal seguente programma?
re contenuto nella variabile c. Il primo caso appare ri-
solto, come esempio:
 #include <stdio.h>
Codice Contenuto della variabile c
dopo l’esecuzione del codice  #include <stdlib.h>

int a = –20; c contiene –20  int a, b;


int b = 10;
int c = (a *= 2, b += 10, c = a + b);
int a = –20;
 main()
int b = 10; {
int c = (b = 2, b = a, c = a + b);
 a=10;
int a = –20;
int b = 10;  b=3;
int c = (b = 2, b = a++, c = a + b);
 printf(“a + b = %d\n”, a + b);
int a = –20;
int b = 10;  printf(“a - b = %d\n”, a - b);
int c = (b = 2, b = ++a, c = a + b);  printf(“-b = %d\n”, -b);
 printf(“a * b = %d\n”, a * b);
9. Trova gli errori presenti in ognuna delle seguenti di-  printf(“a / b = %d\n”, a / b);
chiarazioni di costanti:  printf(“a %% b = %d\n”, a % b);
 system(“PAUSE”);
 const flot PGRECO = 3.14;
}
 const char C “a”;
 const int POSTI = 100,00;
 const char[2] MESS = “Buongiorno!”;
13. Qual è l’output fornito dal seguente programma?
 #define PGRECO = 3,14;
 #define C = ‘a’;
 #define POSTI =100;  #include <stdio.h>
 #define ERRORE “Rilevato errore’  #include <stdlib.h>

 int a, b;

10. Trova gli errori presenti nel seguente codice:


 main()
 #include <iostream.h> {
 main( )  a = 10;
{  b = 3;
 float a, b;/* questo è un  printf(“a = %d b= %d\n”, a, b);
 commento corretto.
 a = 100; // questo commento è  ++a;
 valido //  ––b;
 b = 10 */ questo commento è valido /*  printf(“a = %d b= %d\n”, a, b);
 system(“PAUSE”);  a++;
}  b––;
 printf(“a = %d b= %d\n”, a, b);
11. Senza usare il compilatore, prova a calcolare il valore  system(“PAUSE”);
risultante dal seguente frammento di programma: }

 int i, i1;
 double d, d2;
 long l;
 double res;
 i=10;
 i1=20;
 d = 12.5;
 d2 = 5.777;
 l = 140000000;
 res = ((i * d) / (l / d2)) + i1;

IL LINGUAGGIO C 59
Training
PROVE APERTE PER LA VERIFICA DELLE ABILITÀ

14. Qual è l’output prodotto dal seguente programma? 16. Gradi, primi e secondi
Scrivi un algoritmo che, data in input la misura di un
 #include <stdio.h> angolo in gradi (G), primi (P) e secondi (S), determini
la sua ampiezza espressa in secondi.
 #include <stdlib.h>
 int a, b, c, d;
17. Media di tre numeri
Scrivi un algoritmo che, dati in input tre numeri,
 main()
ne determini la media.
{
 printf(“Inserisci il primo numero ”); 18. Area del cerchio
 scanf(“%d”, &a); Scrivi un algoritmo che determini l’area del cerchio
 printf(““Inserisci il secondo numero ”); circoscritto a un quadrato.
 scanf(“%d”, &b);
 printf(“Inserisci il terzo numero ”); 19. Perimetro e area del triangolo
 scanf(“%d”, &c); Scrivi un algoritmo che, date in input le dimensioni
 printf(“Inserisci il quarto numero ”); AB e BC del seguente triangolo isoscele, ne
 scanf(“%d”, &d); determini perimetro e area.
 printf(“%d\n”, (a>b && c<=d));
 printf(“%d\n”, (a>b || c<=d)); A
 printf(“%d\n”, (a<=b && c>d || a));
 printf(“%d\n”, (a<=b && (c>d || a)));
 system(”PAUSE”);
 }

B C
15. Qual è l’output prodotto dal seguente programma?
20. Età di una persona
 #include <stdio.h> Scrivi un algoritmo che, dati in input il cognome,
il nome e l’anno di nascita di una persona, fornisca
 #include <stdlib.h>
in output la sua età.
 int a,b,c;

21. Equazione di primo grado


 main() Scrivi un algoritmo che calcoli la radice
{ dell’equazione di primo grado ax=b.
 printf(“Inserisci un numero ”);
 scanf(“%d”, &a); 22. Calcolo dei secondi
 printf(“++a = %d\n”, ++a); Dati un numero di giorni, uno di ore, uno di minuti
 printf(“a = %d\n”, a); e uno di secondi, calcola il numero di secondi
 printf(“a++ = %d\n”, a++); corrispondente.
 printf(“a = %d\n”, a); Esempio: Giorni = 2, Ore = 3, Minuti = 23,
 Secondi = 7 ? N=184987.
 printf(“Inserisci un numero “);
 scanf(“%d”, &b); 23. Calcolo litri di vino
Supponiamo di essere andati da un contadino a
 c = ++b==3; comprare il vino. Il contadino ci vende il vino a 1,70
euro il litro. Costruisci un algoritmo che, ricevuto in
 printf(“b = %d\n”, b); ingresso il numero di litri acquistati, comunichi
 printf(“c = %d\n”, c); l’importo da pagare.

 c = b++==3; 24 Vernici e additivi


 printf(“b = %d\n”, b); Per produrre una vernice sono necessari 10 grammi
 printf(“c = %d\n”, c); di additivo ogni chilo di prodotto fino a 10 chili
 system(“PAUSE”); e 5 grammi al chilo per i chili eccedenti.
 } Stabilisci la quantità di additivo necessaria in base
al quantitativo di vernice richiesto.

60 IL LINGUAGGIO C

Potrebbero piacerti anche