Sei sulla pagina 1di 498

H A GUIDA COMPLETA

+
Indice

Prefazione xv

PARTE PRIMA . LE BASI DEL C++: IL LINGUAGGIO C 1

Capitolo 1 Una panoramica sul linguaggio C 3


1.1 Le origini del linguaggio C 3
l.2 Il e un linguaggio di medio livello 4
1.3 Il e un linguaggio strutturato 5
1.4 Il e un linguaggio per programmatori 7
1.5 L'aspetto di un progranuna C 9
1.6 La libreria e il linker 10
l.7 Compilazione separata 12
1.8 Le estensioni di file: .c e .cpp 12

Capitolo2 Le espressioni 15
2.1 I cinque tipi di dati principali 15
2.2 Modificare i tipi principali 16
2.3 Nomi degli identificatori 18
2.4 Le variabili 19
2.5 I modifi~atori di accesso 25
2.6 Specificatori di classe di memorizzazione 27
2.7 Inizializzazione delle variabili 34
2.8 Le costanti 35
2.9 Gli operatori 38
2.10 Le espressioni 56

Capitolo 3 Le istruzioni 61
3.1 La verit e la falsit in C e C++ 62
3.2 Le istruzioni dLselezioae .. 62
VI IN 111-C E - INDICE __ VII

3.3 Le istruzioni di iterazione 74 Capitolo 7 Strutture, unioni, enumerazioni e tipi


3.4 La dichiarazione di variabili nelle istruzioni definiti dall'utente 169
di selezione e iterazione 85 7.1 Le strutture 170
3.5 Le istruzioni di salto 86 7 .2 Gli array di strutture 174
3.6 Le espressioni 92 7 .3 Il passaggio di strutture alle funzioni 175
3. 7 I blocchi 93 7 .4 I puntatori a strutture 177
7.5 Gli array e strutture ali' interno di altre strutture 181
7.6 I campi bit 182
Capitolo 4 Gli array e le stringhe 95 7.7 Le unioni 185
4.1 Gli array monodimensionali 95 7.8 Le enumerazioni 188
4.2 La generazione di un puntatore a un array 97 7.9 Uso di sizeof per assicurare la trasportabilit
4.3 Come passare un array monodimensionale del codice 191
a una funzione 98 7.1 O La parola riservata typedef 193
4.4 Le stringhe 99
4.5 Gli array bidimensionali 102
4.6 Gli array multidimensionali 108 Capitolo8 Operazioni di I/O da console 195
4.7 L'indicizzazione dei puntatori 109 8.1 Un'importante nota applicativa 196
4.8 L'inizializzazione degli array 111 8.2 La lettura e la scrittura di caratteri 196
4.9 L'esempio del tris (tic-tac-toe) 114 8.3 La lettura e la scrittura di stringhe 199
8.4 Le operazioni di I/O formattato da console 202
8.5 La funzione printf() 203
Capitolo 5 I puntatori 119 8.6 La funzione scanf() 211
5.1 Che cosa sono i puntatori? 119
5.2 Variabili puntatore 120
5.3 Gli operatori per i puntatori 121 Capitolo9 Operazioni di I/O da file 219
5.4 Espressioni con puntatori 122 9.1 Operazioni di I/OC e C++ 219
5.5 Puntatori e array 127 9.2 Strearn e file 220
5.6 Indirizzamento multilivello 129 9 .3 Gli strearn 220
5. 7 Inizializzazione di puntatori 131 9.4 I file 221
5.8 Puntatori a funzioni 133 9.5 Prinipi di funzionamento del file system 222
5.9 Le funzioni di allocazione dinamica del C 136 9.6 fread() e fwrite() 235
5.1 O Problemi con i puntatori 138 9.7 fseek() e operazioni di I/O ad accesso diretto 237
9.8 fprint() e fscanf() -- - ----- 239
9 .9 Gli strearn standard 240
Capitolo 6 le funzioni 143
6.1 La forma generale di una funzione 143
6.2 Regole di visibilit delle funzioni 144 Capitolo 10 Ilpreproces~ore e i commenti 245
6.3 Gli argomenti delle funzioni 145 10.1 Il preprocessore 245
6.4 Gli argomenti di main(): argc e argv 150 10.2 La direttiva #define 246
6.5 L'istruzione retum 154 10.3 La direttiva #error 249
6.6 Ricorsione 160 10.4 La direttiva #include 250
6. 7 Prototipi di funzioni 162 10.5 Le direttive per compilazioni condizionali 250
6.8 Dichiarazione di elenchi di parametri 10.6 La direttiva #undef 254
di lunghezza variabile 165 10.7 Uso di defined 255
6.9 Dichiarazione di parametri con metodi vecchi 10.8 -La direttiva #line 256
e nuovi 165 10.9 La direttiva #pragma 256
6.10 Elementi implementativi_ 166 1O. I O Gli operatori del preprocessore # e ## 257
l'
-- -_---1
--- --- -- .. i - - -----
----- --- - .:.V:.:.:111__1:. :.N.:. .;D:. .:. .C
;I =-E=---===='------------------- INDICE IX

10.11 Le macro predefinite 258


259 13.5 I puntatori a tipi derivati 345
10.12 I commenti
13.6 I puntatori ai membri di una classe 348
13.7 Gli indirizzi 351
13.8 Qestione di stile 359
PARTE SECONDA ~ IL LINGUAGGIO C++ 261
13.9 Gli operatori di allocazione dinamica del C++ 360

Capitolo 11 Panoramica del linguaggio C++ 263 Capitolo 14 Overloading di funzioni, costruttori di copie
11.1 Le origini del C++ 263 e argomenti standard 371
11.2 Che cos' la programmazione a oggetti 265 14.1 Overloading delle funzioni 371
11.3 Elementi di base del linguaggio C++ 268 14.2 Overloading delle funzioni costruttore 373
11.4 C++ vecchio stile e C++ moderno 275 14.3 I costruttori di copie 377
11.5 Introduzione alle classi C++ 279 14.4 Ricerca dell'indirizzo di una funzione
11.6 L'overloading delle funzioni 284 modificata tramite overloading 381
11. 7 L' overloading degli operatori 287 14.5 L'anacronismo della parola riservata overload 383
11. 8 L'ereditariet 288 14.6 Gli argomenti standard delle funzioni 383
11.9 I costruttori e i distruttori 293 14.7 Overloading di funzioni e ambiguit 390
11.10 Le parole riservate del C+-i:_ 297
11.11 La forma generale di un p~ogramma C++ 297
Capitolo 15 Overloading degli operatori 395
15.1 Creazione di una funzione operator membro 396
Capitolo 12 Le classi e gli oggetti 299 15.2 Overloading di operatori tramite funzioni friend 403
12. l Le classi 299 15.3 Overloading di new e delete 409
12.2 Le strutture e le classi 303 15.4 Overloading di alcuni operatori particolari 418
12.3 Le unioni e le classi 305 15.5 Overloading dell'operatore virgola 425
12.4 Le funzioni friend 307
12.5 Le classi friend 312
12.6 Le funzioni inline 313 Capitolo 16 l'ereditariet 429
12.7 Definizione di funzioni inline all'interno 16.1 Controllo dell'accesso alla classe base 429
di una classe 316 16.2 Ereditariet dei membri protected 432
12.8 I costruttori parametrizzati 317 16.3 Ereditariet da pi classi base 436
12.9 I membri static di una classe 320 16.4 Costruttori, distruttori ed ereditariet 437
12.10 -Quaru:lo-verigono eseguiti i costruttori e 16.5 Accesso alle classi 445
i distruttori? 327 16.6 Classi base virtuali 448
12.11 L'operatore di risoluzione del campo d'azione 329
I 2.12 La nidificazione delle classi 330
12.13 Le classi locali 330 Capitolo 17 Funzioni virtuali e polimorfismo 453
I 2.14 Il passaggio di oggetti a funzioni 331 17.l Le funzioni virtuali 453
12.15 La restituzione di oggetti 334 17.2 L'attributo virtual viene ereditato 458
12.16 L'assegnamento di oggetti 335 17.3 Le funzioni virtuali sono gerarchiche 459
17 .4 Le funzioni virtuali pure 462
17.5 Uso delle funzioni virtuali 464
Capitolo 13 . Gli array, i puntatori, gli indirizzi 17 .6 Il binding anticipato e il binding ritardato 467
e gli operatori di allocazione dinamica 337
13.1 Gli array di oggetti 337
13.2 I puntatori a oggetti 341 Capitolo 18 I template 469
13.3 Verifiche di tipo sui puntatori C++ 343 l _ I&-1--Funzioni generiche 469
13.4 Il puntatore this_-=- ___ __ 343
1-- -- _J8:2 Uso delle funzioni generiche 4~~-

-T
I
- - - - -------- -

'
X INDICE INDICE Xl

18.3 Classi generiche 482 Capitolo23 Namespace, funzioni di conversione


18.4 Le parole riservate typename ed export 493 e altri argomenti avanzati 599
18.5 La potenza dei template 494 23.1 I namespace 599
23.2 Lo spazio dei nomi std 609
23.3 Creazione di funzioni di conversione 611
Capitolo 19 Gestione delle eccezioni 497 23.4 Funzioni membro const e mutable 614
19. I Principi di gestione delle eccezioni 497 23.5 Funzioni membro volatile 617
19.2 Gestione delle eccezioni per classi derivate 506 23.6 Costruttori espliciti 617
19.3 Opzioni della gestione delle eccezioni 507 23.7 Uso della parola riservata asm 619
19.4 Le funzioni terminate() e unexpected() 513 23.8 Specifiche di linking 620
19.5 La funzione uncaught_exception() 515 23.9 Operazioni di I/O su array 621
19.6 Le classi exception e bad_exception 515 23.10 Uso di array dinamici 626
19.7 Applicazioni della gestione delle eccezioni 516 23.11 Uso di I/O binario con stream basati su array 628
23.12 Riepilogo delle differenze esistenti fra Ce C++ 628

Capitolo 20 Il sistema di 1/0 C++: le basi 519


20.1 Operazioni di I/OC++ vecchie e nuove 520 Capitolo 24 Introduzione alla libreria STL 631
20.2 Gli stream del C++ 520 24.l Introduzione all'uso della libreria STL 632
20.3 Le classi per stream C++ 520 24.2 Le classi container 635
20.4 Operazioni di I/O formattato 522 24.3 Funzionamento generale 636
20.5 Overloading di e 535 24.4 I vettori 637
20.6 Creazione di funzioni di manipolazione 544 24.5 Le liste 647
24.6 Le mappe 658
24.7 Gli algoritmi 664
Capitolo 21 Operazioni di 110 su file in C++ 549 24.8 Uso degli oggetti funzione 674
21.1 L'header <fstream> e le classi per i file 549 24.9 La classe string 682
21.2 L'apertura e la chiusura di un file 550 24.10 Commenti finali sulla libreria STL 694
21.3 La lettura e la scrittura di un file di testo 553
21.4 Le operazioni di I/O binarie e non formattate 555
21.5 Altre forme della funzione get() 561 PARTE TERZA '~ LA LIBRERIA DI FUNZIONI STANDARD 695
21.6 La funzione getline() 561
21.7 Rilevamento della fine del file - - - ----- 563
21.8 La funzione ignore() 565 Capitolo 25 Le funzioni di I/O basate sul C 697
21.9 Le funzioni peek() e putback() 566
21.10 La funzione f!ush() 566
21.11 L'accesso diretto ai file 566 Capitolo 26 Le funzioni_per stringhe e caratteri 721
21.12 Lo stato delle operazioni di I/O 571
21.13 Personalizzazione delle operazioni di I/O sui file 573
Capitolo 27 Le funzioni matematiche 733

Capitolo 22 L'identificazione run-time dei tipi


e gli operatori cast ._ 577 Capitolo 28 Le funzioni per le date, le ore
22. l L'identificazione run-tim dei tipi (RTTI) 577 e la localizzazione 741
22.2 Gli operatori di conversione cast 587
22.3 L'operatore dynamic_cast 587
CaP-it9lo 29-J,.e fu.11zioni di allocazione dinamica
J_ della memoria ---- ------ - 749
- - - - ---- ~----
Xli IN O ICE I N-9-1-G-E -:Xlii-

Capitolo30 le funzioni di servizio 753 Capitolo 38 le classi per la gestione delle eccezioni 899
38.1 Le eccezioni 899
38.2 La classe auto_ptr 901
Capitolo 31 l..e funzioni per caratteri estesi 767 38.3 La classe pair 903
31.1 Le funzioni di classificazione per caratteri estesi 768 38:4 La localizzazione 904
31.2 Le funzioni di I/O per caratteri estesi 770 38.5 Altre classi interessanti 905
31.3 Funzioni per stringhe di caratteri estesi 772
31.4 Funzioni di conversione per stringhe
di caratteri estesi 773 . PARTE QUINTA ~ APPLICAZIONI C++ 907
31.5 Funzioni per array di caratteri estesi 773
31. 6 Funzioni per la conversione di caratteri
multibyte ed estesi 774 Capitolo 39 Integrazione delle nuove classi: una classe
personalizzata per le stringhe 909
39 .1 La classe StrType 910
PARTE QUARTA l..A LIBRERIA DI CLASSI STANDARD DEI.. C++ 775 39.2 Le funzioni costruttore e distruttore 912
39.3 Operazioni di I/O di stringhe 913
39.4 Le funzioni di assegnamento 914
Capitolo32 le classi di I/O del C++ standard 777 39.5 Il concatenamento 916
32.1 Le classi di I/O 777 39.6 Sottrazione di sottostringhe 918
32.2 Gli header di I/O 780 39.7 Gli operatori relazionali 920
32.3 I flag di formattazione e i manipolatori di I/O 780 39.8 Funzioni varie 921
32.4 I tipi del sistema di I/O del C++ standard 782 39.9 L'intera classe StrType 922
32.5 Overloading degli operatori < e > 784 39.1 O Uso della classe StrType 931
32.6 Le funzioni di I/O di utilizzo generale 784 39.11 Creazione e integrazione di nuovi tipi 933
39.12 Un esercizio 933

Capitolo 33 le classi container STl 799


Capitolo40 Un analizzatore di espressioni realizzato
con tecniche a oggetti 935
Capitolo 34 Gli algoritmi STl 823 40.1 Le espressioni 936
40.2 L'elaborazione delle espressioni: il problema 937
40.3 Analisi di un'espressione 938
_ Capitolo 35 lteratori, allocatori e oggetti funzione STl 843 40.4 La classe parser 939
35.1 Gli iteratori 843 40.5 Sezionamento di un'espressione 940
35.2 Gli oggetti funzione 854 40.6 Un semplice parser di espressioni 943
35 .3 Gli allocatori 860 40.7 Aggiunta delle variabili 949
40.8 Controllo della sintassi in un parser
a discesa ricorsiva 959
Capitolo 36 la classe string 863 40.9 Realizzazione di un parser generico 960
36.1 La classe basic_string 863 40.1 O Alcune estensioni da provare 967
36.2 La classe char_traits 872

Indice analitico 969


Capitolo 37 le classi per numeri 875
37.1 La classe complex___ _ 875
37 .2 La classe valarray 879
37.3 Gli algoritmi numerici 893

- - - - -----
~
-
: Prefazione

Cuesta la seconda edizione della Guida completa


C++. Negli anni trascorsi dalla realizzazione della prima edizione, il linguaggio
C++ stato sottoposto a numerose modifiche. Forse la modifica pi importante
stata la standardizzazione del linguaggio. Nel novembre del 1997, il comitato
ANSI/ISO, incaricato del compito di standardizzare il linguaggio C++, ha prodot-
to Io stndard internazionale per il linguaggio. Questo evento ha concluso un pro-
cesso lungo e talvolta controverso. Coie membro del comitato ANSI/ISO per la
standardizzazione del linguaggio C++, l'autore ha seguito tutti i progressi di que-
sto processo di standardizzazione, partecipando a ogni dibattito e discussione.
Alle battute finali del processo di sviluppo dello standard, vi era un serrato dialo-
go quotidiano via e-mail a livello mondiale in cui sono stati esaminati i pro e i
contro di ogni singolo argomento per giungere a una soluzione finale. Anche se
questo processo stato pi lungo e stressante di quanto chiunque potesse immagi-
nare, i risultati sono decisamente all'altezza delle aspettative. Ora esiste uno
standard per quello che senza ombra di dubbio il linguaggio di programmazione
pi importante del mondo.
Durante la fase di standardizzazione sono state aggiunte nuove funzionalit al
C++.Alcune sono relativamente piccole mentre altre, come l'introduzione della
libreria STL (Standard Template Library) hanno implicazioni che influenzeranno
il corso della programmazione negli anni a venire. Il risultato di queste aggiunte
stato una notevole estensione delle possibilit del linguaggio. Ad esempio, grazie
all'aggiunta della libreria per l'elaborazione numerica, ora il C++ pu essere utiliz-
zato pi comodamente nei programmi che svolgono una grande quantit di calco-
li matematici. Natu~almente, le inform~ioni contenute in questa seconda edizio-
ne riflettono lo standard internazionale del linguaggio C++ cos come stato de-
finito dal comitato ANSI/ISO, includendo tutte le nuove funzionalitrintrodotte.

---~-----------
XVI PREFAZIONE P R i:t=Az-1 ON E XVII

Le novit di questa seconda edizione programmi, dei puntatori e delle funzioni. Poich molti lettori conoscono gi il
linguaggio Ce hanno raggiunto un'elevata produttivit in tale linguaggio, la scel-
La seconda edizione della Guida completa C+-i- stata notevolmente estesa ri- ta di discutere il sottoinsieme C in una parte a s stante ha Io scopo di evitare al
spetto all'edizione precedente;-Questo si nota anche nella lunghezza del volume programmatore c di dover incontrare ripetutamente informazioni che conosce
che praticamente raddoppiata! Il motivo principale di ci che la seconda edi- gi. Dunque il programmatore esperto in C potr semplicemente consultare quel-
zione analizza in modo pi esteso la libreria delle funzioni standard e la libreria le sezioni del volume che discutono le funzionalit specifiche del linguaggio C++.
delle classi standard. Quando stata realizzata la prima edizione, nessuna di que- La Parte seconda descrive in dettaglio le estensioni che il C++ ha apportato al
ste due librerie era sufficientemente definita da consigliarne l'introduzione nel C. Fra di esse vi sono le funzionalit a oggetti come le classi, i costruttori, i di-
volume. Ora che la fase di standardizzazione del linguaggio C++ terminata, struttori e i template. In pratica la Parte seconda descrive tutti quei costrutti speci-
stato finalmente possibile aggiungere una descrizione di questi argomenti. fici del linguaggio C++ ovvero assenti in C.
Oltre a queste aggiunte, la seconda edizione include anche una grande quanti- La Parte terza descrive la libreria delle funzioni standard e la Parte quarta
t di materiale nuovo un po' in tutto il volume. La maggior parte delle aggiunte esamina la libreria delle classi standard, inclusa la libreria STL (Standard Template
il risultato delle funzionalit introdotte nel linguaggio C++ fin dalla preparazione Library). La Parte quinta mostra due esempi pratici di applicazione del linguag-
dell'edizione precedente. Sono stati particolarmente estesi i seguenti argomenti: gio C++ e della programmazione a oggetti.
la libreria STL (Standard Template Library), l'identificazione run-time dei tipi
(RTTI), i nuovi operatori di conversione cast, le nuove funzionalit dei template,
i namespace, il nuovo stile degli header e il nuovo sistema di 1/0. Inoltre stata
sostanzialmente modificata la parte riguardante l'implementazione di new e delete Un libro per tutti i programmatori
e sono state discusse molte nuove parole riservate.
Onestamente, chi non abbia seguito attentamente l'evoluzione del linguaggio Questa Guida completa C++ dedicata a tutti i programmatori C++, indipenden-
C++ negli ultimi anni, rimarr sorpreso della sua crescita e delle funzionalit che temente dalla loro esperienza. Naturalmente il lettore deve essere quanto meno in
gli sono state aggiunte; non pi lo stesso buon vecchio C++ che si usava solo grado di creare un semplice programma. Per tutti coloro che si trovano ad appren-
qualche anno fa. dere l'uso del linguaggio C++, questo volume potr affiancare efficacemente qual-
siasi Guida di apprendimento e costituire un'utile fonte di risposte. I programma-
tori C++ pi esperti troveranno particolarmente utili le parti che si occupano delle
funzionalit aggiunte in fase di standardizzazione.
Il contenuto della guida

Questo volume descrive in dettaglio tutti gli aspetti del linguaggio C++, a partire
dal linguaggio che ne costituisce la base: il linguaggio C. II volume suddiviso in Programmazione in Windows - - - -
cinque part:
11 Le basi del C++: il linguaggio C Il C++ il linguaggio perfetto per Windows ed completamente a suo agio nella
programmazione in tale ambiente operativo. Ciononostante, nessuno dei programmi
11 II linguaggio C++
contenuti in questo volume un programma per Windows. Si tratta in tutti i casi
11 La libreria di funzioni standard di programmi a console. II motivo facile da spiegare: i programmi per Windows
11 La libreria di classi standard del C++ sono, per loro stessa natura, estesi e complessi. La quantit di codice necessario
11 Applicazioni C++ per creare anche solo la semplice struttura di un programma per Windows occupa
dalle 50 alle 70 righe. Per scrivere un programma per Windows che sia utile per
_ La Parte prima fornisce una trattf!Zione completa del sottoinsieme del lin-
illustrare le funzionalit del C++ sono necessarie centinaia di righe di codice. In
guaggio C++, costituito dal linguaggio C. Come molti lettori sanno, il linguaggio
poche parole, Windows non l'ambiente pi appropriato per descrivere le funzio-
C++ si basa sul C. proprio il C che definisce le caratteristiche di base del C++,
nalit di un linguaggio di programmazione. Naturalmente possibile utilizzare
fin dai suoi elementi pi semplici come i cicli for e le istruzioni if. Inoltre il C
un compilatore Windows per compilare i programmi contenuti in questo volume
definisce la natura stessa del C++-eome nel-caso-della struttura a blocchi dei------ -
XVIII PRJ:FAZIONE

poich il compilatore creer automaticamente una sessione a console nella quale : Parte prima
eseguire il programma.
" LE BASI DEL C++:
: IL LINGUAGGIO C
Il codice sorgente nel Web
Il codice sorgente di tutti i programmi di questo volume disponibile gratuita-
mente nel Web all'indirizzo http://www.osborne.com. Prelevando questo codice
si eviter di dover digitare manualmente gli esempi.

Ulteriori studi
~ n questo volume la descrizione del linguaggio C++ vie-
La Guida completa C++ solo uno dei volumi scritti da Herbert Schildt. Ecco un ne suddivisa in due parti. La Parte prima si occupa delle funzionalit che il C++
elenco parziale dei volumi realizzati da questo autore, tutti editi da McGraw-Hill ha in comune con il suo progenitore, il C. Infatti il linguaggio C rappresenta un
Libri Italia. sottoinsieme del C++. La Parte seconda descrive le funzionalit specifiche del
Chi volesse sapere qual~osa di pi sul linguaggio C++, trover particolarmen- C++. Insieme, queste due parti, descrivono dunque il linguaggio C++. Come for-
te utili i seguenti volumi. se molti sanno, il C++ si basa sul linguaggio C. In pratica si pu dire che il C++
include l'intero linguaggio e e (tranne lievi eccezioni), tutti i programmi e sono
88 386 0351-0 H. Shildt, Guida completa C++ anche programmi C++. Quando fu inventato il linguaggio C++, venne impiegato
88 386 0332-4 H. Shildt, Windows 95 Programmazione in Ce C++ come base il linguaggio C al quale vennero aggiunte molte nuove funzionalit ed
88 386 3407-6 H. Shildt, Guida al linguaggio C++ estensioni con lo scopo di garantire il supporto della programmazione orientata
agli oggetti (OOP). Questo non significa che gli aspetti che il C++ ha in comune
Per quanto riguarda il linguaggio C, che sta alla base del C++, si consiglia la con il C siano stati abbandonati ma, a maggior ragione, il C standard ANSI/ISO
lettura dei seguenti volumi. costituisce il documento di partenza per lo Standard Internazionale per il C++.
Pertanto, la conoscenza del linguaggio C++ implica una conoscenza del linguag-
88 386 0340-5 H. Shildt, Guida completa C 2 ed. gio C.
88 38? 0175-5 H. Shildt, Arte della programmazione in C --- - ------ In un volume come questa Guida completa, il fatto di suddividere il linguag-
gio C++ in due parti (le basi C e le funzionalit specifiche del C++) consente di
Per sviluppare programmi per il Web utile consultare: ottenere tre vantaggi.
1. Si delinea con chiarezza la linea di demarcazione esistente fra C e C++.
88 386 0416-9 P. Naughton, H. Shildt, Guida completa lava 2. I lettori che gi conoscono il linguaggio C potranno facilmente trovare infor-
mazioni specifiche sul linguaggio C++.
Infine, per la programmazione per Windows, si rimanda a: 3. Viene fornito un modo per discutere quelle funzionalit del linguaggio C++ che
sono pi legate al sottoinsieme costituito dal linguaggio C.
88 386 0455-X H. Shildt, Programmazione Windows NT4 Comprenderne la linea di divisione esistente fra C e C++ importante poich
88 386 0397-9 k Shildt, MFC Programmazione Windows si tratta in entrambi i casi di linguaggi molto utilizzati e dunque molto probabile
che prima o poi venga richiesto di scrivere o eseguire la manutenzione di codice C
e C++. Quando si ,lavora in C si deve sapere esattamente dove finisce il C e dove
inizi~2l,_5~~: _Molti programmatori C++ si troveranno talvoltaasrivere codice
2 PARTE PRIMA

che deve rientrare nei limiti stabiliti dal "sottoinsieme C". Questo accade partico- Capitolo 1
larmente nel campo della programmazione di sistemi e della manutenzione di
applicazioni preesistenti. Conoscere la differenza fra C e C++ parte integrante Una panoramica
della propria esperienza di programmatore C++ professionale. sul linguaggio e
Una buona comprensione del linguaggio C insostituibile anche quando si
deve convertire del codice C in C++. Per svolgere l'operazione in modo profes-
1.1 Le origini del linguaggio C
sionale, necessario conoscere in modo approfondito anche il linguaggio C. Ad
esempio, senza una conoscenza approfondita del sistema di I/O del C impossibi- 1.2 Il e un linguaggio di medio livello
le convertire in modo efficiente dal C al C++ un programma che esegua notevoli 1.3 Il e un linguaggio strutturato
operazioni di I/O. 1.4 Il e un linguaggio per programmatori
Molti lettori conoscono gi il linguaggio C. Il fatto di discutere le funzionalit
e in apposite sezioni pu aiutare un programmatore e a ricercare con facilit e 1.5 L:aspetto di un programma e
rapidit le informazioni riguardanti il e senza perdere tempo a leggere informa- 1.6 La libreria e il linker
zioni gi note. Naturalmente in questa Parte prima sono state elencate anche alcu- 1.7 Compilazione separata
ne differenze marginali fra il C e il C++. Inoltre il fatto di separare le basi C dalle 1.8 Le estensioni di file: .e e .cpp
funzionalit pi avanzate e orientate agli oggetti del linguaggio C++ consentir di
concentrarsi sulle funzionalit avanzate perch tutti gli elementi di base saranno
stati trattati in precedenza.
Anche se il linguaggio C++ contiene l'intero linguaggio C, quando si scrivo- -. onoscere il C++ significa conoscere le forze che han-
no programmi C++ non vengono utilizzate molte delle funzionalit fomite dal no portato alla sua creazione, le idee che gli hanno dato il suo as~etto e i "caratte-
linguaggio C. Ad esempio, il sistema di I/O del C disponibile anche in C++ ma ri" che ha ereditato. Pertanto la storia del C++ non pu che partire dal C. Questo
quest'ultimo linguaggio definisce nuove versioni a oggetti. Un altro esempio capitolo presenta una panoramica del linguaggio di programmazione e, le s~e
rappresentato dal preprocessore. Il preprocessore molto importante in C; molto origini, il suo utilizzo e la sua filosofia. Poich il C++ si basa ~u~ C, questo capi:
meno in C++.Il fatto di discutere le funzionalit C nella Parte prima evita dunque tolo presenta anche un'importante prospettiva storica sulle rad1c1 del C++. M~l~
di congestionare di dettagli la parte rimanente di questo volume. degli elementi che hanno reso il linguaggio C++ quello che hanno la loro ong1-
S.U::~~l;lltfi!i;NIQ';_: Il sottoinsieme C descritto nella Parte prima costituisce la base ne nel linguaggio C.
del linguaggio C++ e il nucleo fondamentale su cui sono costruite le funzionalit
a oggetti del linguaggio C++.Tutte le funzionalit descritte in questa Parte pri-
ma fanno parte del linguaggio C++ e dunque sono disponibili all'uso. 1.1 Le origini del linguaggio C
}l!JJA~:;~J:J.:::~,j
La Parte prima di questo volume stata adattata da La guida Il C fu inventato e implementato per la prima volta da Dennis Ritchie su un siste-
completa C (McGraw-Hill Libri Italia - 1995 - ISBN 0340-5). Chi fosse partico-
ma DEC PDP-11 che impiegava il sisteina operativo Unix. Il C il risultato di un
lamiente interessato al linguaggio e trover tale volume molto interessante.
processo di sviluppo che partito da un linguaggio ~hia~ato BCP~. Il BCPL,
sviluppato da Martin Richards, influenz un linguaggio chiamato B, mventato da
Ken Thompson. Il B port allo sviluppo del C negli anni '70. . . .
Per molti anni, lo standard de facto del C fu la versione formta con 11 sistema
ffperativo Unix versione 5. Il linguaggio fu descritto per la prima volta nel volume
The C Programming Language di Brian Kernighan e Dennis Ritchie. Nell'estate
del 1983 venne nominato un comitato con lo scopo di crear.e uno standard ANSI
(American National Standards Institute) che definisse il linguaggio~ una volta

---- -----~--
UNA PANORAM+CA SUL LINGUAGGIO C 5
4 CAPITOLO

per tutte. Il processo di standardizzazione richiese sei anni (molto pi del previ- I tipi di dati pi comuni sono gli interi, i caratteri e i numeri reali. Anche se
sto): L~ standar~ ANSI C fu infine adottato nel dicembre del 1989 e le prime il C prevede cinque tipi di dati di base, non si tratta di un linguaggio fortemente
copte s1 resero disponibili all'inizio del 1990. Lo standard venne anche adottato tipizzato,.cnme il Pascal o l'Ada. Il C consente quasi ogni conversione di tipo.
dall'ISO (lntemational Standards Organization) ed ora chiamato Standard C Ad esempio, possibile utilizzare liberamente in un'espressione i tipi carattere
ANSI/I~O._ ~e~ semplicit si user semplicemente il termine Standard C. Oggi, e intero.
~utt1 t pnnc1pal1 compilatori C/C++ seguono lo Standard C. Inoltre, lo Standard C
A differenza dei linguaggi ad alto livello, il C non esegue verifiche di errore al
e anche alla base dello Standard C++. momento dell'esecuzione (verifiche run-time).
Ad esempio, nulla vieta di andare per errore a leggere oltre i limiti di un array.
Questo tipo di controlli deve pertanto essere previsto dal programmatore. Analo-
gamente, il C non richiede una compatibilit stretta di tipo fra un parametro e un
1.2 Il C un linguaggio di medio livello argomento.
Come il lettore pu pensare sulla base di precedenti esperienze di program-
Il c . co~siderato da molti un linguaggio di medio livello. Questo non significa mazione, un linguaggio di alto livello richiede normalmnte che il tipo di un argo-
~he il C ~ia meno p~tente, pi d~fficile da utilizzare o meno evoluto rispetto a un mento sia (in forma pi o meno forte) lo stesso del tipo del parametro che ricever
lmgua?g10 ad al~o livello come 11 BASIC o il Pascal, n che il C abbia la natura l'argomento. Questo non il caso del C. In C un argomento pu essere di qualsi-
c?mphcata d~l lmguaggio Assembler (con tutti i problemi derivanti). Piuttosto, asi tipo che possa essere ragionevolmente convertito nel tipo del parametro. La
s1gm~ca che Il c un linguaggio che riunisce i migliori elementi dei linguacrgi ad conversione di tipo viene eseguita automaticamente dal C.
alto hvello con le possibilit ~i controllo e la flessibilit del linguaggio Asse~bler. La peculiarit del C consiste nella possibilit di manipolare direttamente i bit,
La Tabella 1.1 m?stra la ~os1.zione. de~ C nell.o spettro dei linguaggi per computer. i byte, le word e i puntatori. Questo lo rende adatto alla programmazione di software
Es~en~~ u~ lm~uagg10 d1 medio livello, Il C consente la manipolazione di bit, di sistema, in cui queste operazioni sono molto comuni.
byte e mdmzz1, ~~1 ~leme~ti su :ui si basa il funzionamento di un computer. Un altro aspetto importante del C la presenza di solo 32 parole chiave (27
. Nonostan.t~ c10: Il codice C e anche molto trasportabile. Con trasportabilit si derivanti dallo standard "de facto" Kemighan e Ritchie e 5 aggiunte dal comitato
mtend~ la facilit di.adattare su un.sistema un software scritto per un altro compu- di standardizzazione ANSI), che sono i comandi che fonnano il linguaggio C.
ter o s1stem~ operat1:0. Ad ~semp10, se possibile convertire con facilit un pro- Normalmente i linguaggi di alto livello hanno molte pi parole chiave. Come
g.ran_ima scntto per Il DOS m modo che possa essere utilizzato sotto Windows, confronto, si pu ricordare che la maggior parte delle versioni di BASIC conta pi
significa che tale programma trasportabile. di 100 parole chiave!
. T~tti i _Hngu~ggi di programmazione di alto livello prevedono il concetto di
~po di ~at1. Un ~1po di. da.ti definisce una gamma di valori che una variabile in
or~do di memoi:zzare ms1eme a un gruppo di operazioni che possono-essere ese-.
gu1te su tale vanabile. 1.3 Il e un linguaggio strutturato
Tabella 1.1 Come si posiziona il Cnel mondo dei linguaggi. In altre esperienze di programmazione, il lettore pu aver sentito parlare di
strutturazione a blocchi applicata a un linguaggio per computer. Anche se il ter-
Alto livello Ada mine non si applica in modo stretto al C, si parla normalmente del C come di un
Modula2
Pascal
linguaggio strutturato. In effetti il C ha molte analogie con altri linguaggi strut-
COBOL turati, come l' ALGOL, il Pascal e il Modula-2.
FORTRAN
BASIC
-NOTA_" -~ - _. _ _ Il motivo per cui il C (e il C++) non , tecnicamente, un lin-
Medioli'.-ello Java guaggio strutturato a blocchi, il seguente: i linguaggi strutturati a bloc-chi con-
C++ sentono la dichiarazione di procedure o funzioni all'intemo di altre procedure o
e fim::.ioni. Tuttavia, poich in C questo non consentito non pu. formalmente,
FORTH
essere chiamato lingzmggio--strutturato a blocchi,___ _ ___ _
Macro assembler
____ _-_
Assembler --_
- -=::..=::.:;:-=-:::.:--=--.:_--_ _ _ _-----
uNA eA N Q_R. li. M.I _A suL L I N G uA G G I o e-
6 CA Pl-TO LO

NOTA Le nuove versioni di molti vecchi linguaggi di programmazio-


La caratteristica che distingue un linguaggio strutturato l'isolabilit del co- ne ha~no tentato di introdurre elementi di strutturazione. Un esempio rappre-
dice e dei dati. Questa la capacit del linguaggio di suddividere e nascondere dal sentato dal BASIC. Tuttavia le caratteristiche di base di tali linguaggi non posso-
resto del programma tutte le informazioni e le istruzioni necessarie per eseguire no essere completamente dissimulate poich si tratta di linguaggi sviluppati fin
una determinata operazione. Un modo per ottenere ci consiste nell'uso di dall'inizio senza avere tenere in considerazione le funzionalit della programma-
subroutine che impiegano variabili locali (temporanee). Utilizzando variabili lo- zione strutturata.
cali, possibile scrivere subroutine realizzate in modo tale che gli eventi che
avvengono al loro interno non provochino effetti collaterali in altre parti del pro- Il principale componente strutturale del C lafunzione: una subroutine a s
gramma. Questa possibilit semplifica la condivisione di sezioni del codice fra stante. In C, le funzioni sono i mattoni su cui si basa tutta l'attivit di un program-
pi programmi C. Se si sviluppa una funzione ben isolata, tutto quello che si deve ma. Esse consentono di definire e codificare in modo distinto le varie operazioni
sapere sulla funzione cosa essa faccia e non come Io faccia. Occorre ricordarsi svolte da un programma e quindi consentono di creare programmi modulari. Dopo
che un uso eccessivo di variabili globali (variabili note all'intero programma) pu aver creato una funzione, possibile utilizzarla in varie situazioni senza temere di
dare origine a bug (errori) provocati da effetti collaterali. Chiunque abbia pro- veder sorgere effetti collaterali in altre parti del programma. La possibilit di
grammato in BASIC conosce bene questo problema. creare funzioni a s stanti estremamente critica specialmente nei grandi progetti
in cui il codice realizzato da un programmatore non deve interferire accidental-
~QJA##L~ Il concetto di isolamento notevolmente impiegato in C++. mente con quello prodotto da un altro programmatore.
In particolare, in C++ possibile controllare esattamente quali parti del pro- Un altro modo per strutturare e isolare il codice C prevede l'uso di blocchi di
gramma debbano avere accesso a quali altre parti. codice. Un blocco di codice formato da un gruppo di istruzioni connesse logica-
mente che viene considerato come una singola unit. In C, possibile creare un
Un linguaggio strutturato d molte possibilit. In particolare accetta diretta- bloccq di codice inserendo una sequenza di istruzioni fra una coppia di parentesi
mente numerosi costrutti di ciclo, come while, do-while e tor. In un linguaggio
graffe. In questo esempio,
strutturato, l'uso del goto proibito o sconsigliato e non costituisce di certo la
forma pi comune di controllo del programma (come nel caso del BASIC standard
if (x < 10)
e del FORTRAN tradizionale). Un linguaggio stnitturato consente di inserire le printf("troppo basso, riprova\n");
istruzioni in qualunque punto di una riga e non prevede un forte concetto di cam- scanf("%d", &x);
po (come alcune vecchie implementazioni del FORTRAN).
Ecco alcuni esempi di linguaggi strutturati e non strutturati:
se x minore di 1O vengono eseguite entrambe le istruzioni che si trovano dopo
NON STRUTIURATI STRUTIURATI l'if e fra parentesi graffe. Queste due istruzioni, insieme alle parente~i ~raffe, r~p
presentano un blocco di codice. Si tratta di unit logiche: non poss1b1le esegmre - - - -
.FORTRAN . - - - f?ascal-
un'istruzione senza eseguire anche l'altra. I blocchi di codice consentono di im-
BASIC Ada plementare molti algoritmi con chiarezza, eleganza ed efficienza. Inoltre, aiutano
il programmatore a concettualizzare meglio la vera natura dell'algoritmo imple-
COBOL Java
C++ mentato.
e
Modula2

I linguaggi strutturati sono in genere pi moderni. Infatti, una caratteristica 1.4 Il e un linguaggio per programmatori
tipica dei vecchi linguaggi di programmazione l'assenza di strutture. Oggi, po-
chi __programmatori penserebbero di-utilizzare un linguaggio non strutturato per . Sorprendentemente;non tutti i linguaggi di programmazione sono comodi per un
realizzare programmi professionali. programmatore. Basta considerare i classici esem_Pi..di linguaggi per non pro~ra?1-
matori, come il COBOL e il BASIC. Il COBOL non stato progettato per m1gho-
__ rare il lavom.A~Lpi:ogrammatori, n per aumentare l'affidabilit del codice pro-
8 CAPITOLO 1 --

dotto e neppure per incrementare la velocit di realizzazione del codice. Piuttosto, so in avanti nel campo dei linguaggi di programmazione. Naturalmente anche il
il COBOL stato progettato, in parte, per consentire ai non programmatori di linguaggio C++ ha tenuto fede a questa tradizione.
leggere e presumibilmente (anche se difficilmente) comprendere il programma. Il Con la nascita del linguaggio C++, molti pensarono che l'esperienza del C
BASIC fu creato essenzialmente per consentire ai non programmatori di program- come linguaggio a s stante si sarebbe conclusa. Tale previsione si rivelata erra-
mare un computer pei: risolvere problemi relativamente semplici. ta. Innanzitutto non tutti i programmi richiedono l'applicazione delle tecniche di
Al contrario, il C stato creato, influenzato e testato sul campo da program- programmazione a oggetti fomite dal C++. Ad esempio i programmi di sistema
matori professionisti. Il risultato finale che il e d al programmatore quello che vengono in genere sviluppati in C. In secondo luogo, in molte situazioni viene
il programmatore desidera: poche restrizioni, pochi motivi di critiche, strutture a ancora utilizzato codice C e dunque vi un notevole lavoro di estensione e ma-
blocchi, funzioni isolabili e un gruppo compatto di parole chiave. Utilizzando il nutenzione di questi programmi. Anche se il C ricordato soprattutto per il fatto
C, si raggiunge quasi lefficienza del codice assembler ma utilizzando una strut- di aver dato origine al C++, rimane pur sempre un linguaggio molto potente che
tura simile a quella dell' ALGOL o del Modula-2. Non quindi una sorpresa che verr ampiamente utilizzato negli anni a venire.
il C e il C++ siano con facilit diventati i linguaggi pi popolari fra- i migliori
programmatori professionisti.
Il fatto che sia possibile utilizzare il C al posto del linguaggio Assembler
uno dei fattori principali della sua popolarit fra i programmatori. Il linguaggio 1.5 L'aspetto di un programma e
Assembler utilizza una rappresentazione simbolica del codice binario effettivo
che il computer esegue direttamente. Ogni operazione del linguaggio Assembler La Tabella 1.2 elenca le 32 parole chiave che insieme alla sintassi formale del C,
corrisponde a una singola operazione che il computer deve eseguire. Anche se il formano il linguaggio di programmazione C. Di queste, 27 sono state definite
linguaggio Assembler fornisce ai programniatori tutto il potenziale per eseguire dalla versione originale del C. Le altre cinque sono state aggiunte dal comitato
questi compiti con la massima flessibilit ed efficenza, si tratta di un linguaggio ANSI e sono: enum, const, signed, void e volatile. Naturalmente tutte queste paro-
notoriamente difficile per quanto riguarda lo sviluppo e il debugging di un pro- le riservate fanno parte anche del linguaggio C++.
gramma. Inoltre, poich il linguaggio Assembler non strutturato, il programma Inoltre, molti compilatori hanno aggiunto numerose parole chiave che con-
finale tende a essere molto ingarbugliato: una complessa sequenza di salti, chia- sentono di sfruttare al meglio un determinato ambiente operativo. Ad esempio,
mate e indici. Questa mancanza di strutturazione rende i programmi in linguaggio molti compilatori comprendono parole chiave che consentono di gestire l'orga-
Assemblerdifficili da leggere, migliorare e mantenere. Ma c' di peggio: le routine nizzazione della memoria tipica della famiglia di microprocessori 8086, di pro-
in linguaggio Assembler non sono trasportabili fra macchine dotate di unit di grammare con pi linguaggi contemporaneamente e di accedere agli interrupt.
elaborazione (CPU) diverse. Ecco un elenco delle parole chiave estese pi comunemente utilizzate:
Inizialmente, il C fu utilizzato per la programmazione di software di sistema.
Un programma di sistema un programma che fa parte del sistema operativo del asm _es _ds _es
computer o dei suoi programmi di supporto. Ad esempio, sono considerati pro- _ss cdecl ___ f?f __ _ huge
gramm~ di sistema i seguenti software: interrupt near pascal
11 sistemi operativi
Il compilatore pu inoltre prevedere altre estensioni che aiutano a sfruttare
11 interpreti
tutti i vantaggi di un determinato ambiente operativo.
11 editor Tutte le parole chiave del linguaggio C (e C++) devono essere scritte in lettere
11 compilatori minuscole. Inoltre le lettere maiuscole e le lettere minuscole sono considerate
11 programmi di servizio per la gestione di file differenti: else una parola chiave mentre ELSE non lo . In un programma non
11 ottimizzatori prestazionali possibile utilizzare una parola chiave per altri scopi (ovvero come una variabile o
un nome di funzione).
programmi per la gestione di eventi in tempo reale Tutti i programmi C sono formati da una o pifunzioni. L'unica funzione che
Mano a mano che crebbe la popolarit del C, molti programmatori iniziarono . ____ deve essere obbligatoriamente presente si chiama main(). la prima funzione che
a usarlo per realizzare tutti i loro programmi, sfruttandone la trasportabilit e viene richiamata quando inizia l'esecuzione del programma. In un programma C
lefficienza. quando venne-creato, il linguaggio C.-rappresentava-un notevole pas- ben realizzato, main() contiene uno schema dell'intero funzionamento del pro-
10 CAPITOLO .1 UNA PANORAMICA SUL LINGUAGGIO-e 11

gramma. Questo schema formato da una serie di chiamate a funzioni. Anche se


main() non una parola chiave, deve essere trattata come se lo fosse. Ad esempio,
non si pu cercare di usare main() come nome di variabile poich con ogni proba- Dichiarazioni globali
tipo restituito main(elenco parametri)
bilit si confonderebbe il compilatore.
{
L'aspetto generale di un programma C illustrato nella Figura 1.1, in cui le
sequenza istruzioni
indicazioni da f1 () a fN() rappresentano le funzioni definite dall'utente.
tipo restituito fl(elenco parametri)
{
sequenza istruzioni
1.6 La libreria e il linker
tipo restituito f2(elenco parametri)
In senso tecnico, possibile creare un utile e funzionale programma C o C++ {
costituito unicamente dalle istruzioni create dal programmatore. Tuttavia, questo sequenza istruzioni
molto raro in quanto n il C n il C++ forniscono metodi per eseguire operazioni
di input e output (1/0), per svolgere operazioni matematiche complesse o per
manipolare i caratteri. Il risultato che molti programmi includono chiamate alle
varie funzioni contenute nella libreria standard.
Tutti i compilatori C++ sono dotati di una libreria di funzioni standard che tipo restituito fN(elenco parametri)
eseguono le operazioni pi comuni. Lo Standard C++ specifica un gruppo mini- {
mo di funzioni che devono essere supportate da tutti i compilatori. Tuttavia, un sequenza istruzioni
determinato compilatore pu contenere molte altre funzioni. Ad esempio, la li-
breria standard non definisce alcuna funzione grafica ma il comi;iilatore ne inclu-
der probabilmente pi di una.
La libreria standard C++ pu essere suddivisa in due parti: la libreria delle
funzioni standard e la libreria delle classi. La libreria delle funzioni standard Figura 1.1 La forma generale di un programma C
ereditata dal linguaggio C. Il linguaggio C++ supporta l'intera libreria di funzioni
definita dallo Standard C. Pertanto nei programmi C++ sono disponibili tutte le
funzioni standard C. Oltre alla libreria di funzioni standard, il linguaggio C++ definisce anche una
propria libreria di classi. La libreria di classi offre delle routine a oggetti utilizzabili
Tabella 1.2_ !:elenco delle parole chiave del C ANSI. dai programmi. Inoltre definisce la libreria STL (Standard Template Library) che
offre soluzioni pronte all'uso per un'ampia variet di problemi di programmazio-
auto double int struct ne. La libreria di classi e la libreria STL verranno discusse pi avanti in questo
break else long switch
volume. Nella Parte prima verr utilizzata solo la libreria delle funzioni standard
poich l'unica definita anche in C.
case enum register typedef Gli implementatori del compilatore e hanno gi scritto la maggior parte delle
char extern retum uni on funzioni di utilizzo generale che il programmatore si trover a utilizzare. Quando
si richiama una funzione che non fa parte del programma, il compilatore C prende
const float short u_nsigned
nota del suo nome. In seguito, il linker riunisce al codic scritto dal programmato-
oiiinue lor signed void . -re il codice oggetto che si trova nella 'libreria standard. Questo processo. ,chiama-
to linking. Alcuni compilatori C sono dotati di un proprio linker mentre altri uti-
dfault goto sizeof volatile
lizzano il linker standard fornito insieme al sistema operativo.
--do -- - Ji------ .static while Le funzioni contenute nella libreria sono in formato rilocabile. Questo signi-
- - _____ fica che gli indirizzi di memoria-delle varie istruzioni in codice macchina J!On ___ _
- -__-----
___ , "

-------
12 CA P I T O LO 1

devono essere definiti in modo assoluto: devono essere conservate solo le infor- stinzione dunque importante poich il compilatore suppone che ogni program-
mazioni di offset (scostamento). Quand9 il programma esegue il linking con le ma che usa l'estensione .e sia un programma Ce che ogni programma che usa
funzioni contenute nella libreria standard, questi offset di memoria consentono di l'estensione .cpp sia un programma C++.Se non viene indicato esplicitamente,
creare gli indirizzi che verranno effettivamente utilizzati. Vi sono molti manuali -per i programmi della Parte prima possono essere utilizzate entrambe le estensio-
tecnici che descrivono questo processo in dettaglio. In questa fase, non vi per ni. Al contrario i programmi contenuti nelle parti successive devono avere I' esten-
alcun bisogno di conoscere in profondit leffettivo processo di rilocazione per sione .cpp.
iniziare a programmare in C o in C++. Un'ultima annotazione: anche se il C un sottoinsieme del C++, i due lin-
Molte delle funzioni di cui il programmatore avr bisogno nella scrittura dei guaggi presentano alcune lievi differenze e in alcuni casi necessario compilare
programmi sono gi contenute nella libreria standard. Queste funzioni possono un programma C come un programma C (usando l'estensione .e). Nel volume
essere considerate i mattoni che il programmatore pu unire per fonnare un pro- tutte queste situazioni verranno indicate in modo esplicito.
gramma. Se il programmatore si trover a scrivere una funzione che pensa di
utilizzare pi volte, potr inserire anch'essa nella libreria. Alcuni compilatori con-
sentono infatti di inserire nuove funzioni nella libreria standard; altri consentono
invece di creare nuove librerie aggiuntive. In ogni caso, il codice di queste funzio-
ni potr essere utilizzato pi volte.

1.7 Compilazione separata


La maggior parte dei programmi C pi piccoli si trova contenuta in un unico file
sorgente. Tuttavia, mano a mano che cresce la lunghezza del programma, cresce
anche il tempo richiesto dalla compilazione. Pertanto, il C/C++ consente di sud-
dividere un programma su pi file che possono essere compilati separatamente.
Dopo aver compilato tutti i file, ne viene eseguito il Jinking, includendo anche
tutte le routine della libreria, per formare un unico file di codice oggetto comple-
to. Grazie alla compilazione separata, possibile fare modifiche a un file sorgente
del programma senza dover ricompilare l'intero programma. Su progetti non pro-
prio banali, questo consente di risparmiare una_ gral)d~u@.ti! di tempo. Per
informazioni ~Ile strategie per la compilazione separata, consultare la documen-
tazione del compilatore C/C++.

1.8 Le estensioni di file: .e e .cpp


I programmi della Parte prima di questo volume sono, naturalmente, programmi
C++ e possono essere compilati utilizzando un moderno compilatore C++.Tutta-
via s tratta anche di programmi C compilabili con un- compilatore C. Pertanto se
si devono scrivere programmi C, quelli illustrati nella Parte prima possono essere
considerati buoni esempi.Tradizionalmente, i programmi Cusano l'estensione .c
e i programmi C++ usano l'estensione .cpp. Un compilatore C++ utilizza tale
estensione pe.r_di:_termin_ar~quale tipo di programma sta compilan<!_o_._q_~~-ta~i-
--~-
-----
Capitolo 2

Le espressioni

2.1 I cinque tipi di dati principali


2.2 Modificare i tipi principali
2.3 Nomi degli identificatori
2.4 Le variabili
2.5 I modificatori di accesso
2.6 Specificatori di classe
di memorizzazione
2.7 Inizializzazione delle variabili
2.8 Le costanti
2.9 Gli operatori
2.10 Le espressioni

uesto capitolo esamina l'elemento di base del lin-


guaggio Ce del C++: l'espressione. Le espressioni C/C++ sono sostanzialmente
pi generali e pi potenti rispetto a quelle della maggior parte degli altri linguaggi
di programmazione. Esse sono costituite dagli elementi atomici" del C: i dati e
gli operatori. I dati possono essere rappresentati da variabili o costanti. Come la
maggior parte dei linguaggi di programmazione il C/C++. consente di utilizzare
vari tipi di dati ed dotato di un'ampia variet di operatori.

2.1 I cinque tipi di dati principali


In Cvi sono cinque tipi di dati principali: caratteri, numeri interi, numeri in virgo-
la mobile, numeri in virgola mobile doppi e non-valori (rispettivamente char, int,
float, double e void). Tutti gli altri tipi di dati utilizzati in C si basano su questi
cinque tipi. Le dimensioni e i valori memorizzabili in questi tipi di dati possono
variare a seconda del microprocessore e del compilatore impiegati. Tuttavia, nella
maggior parte dei casi, un carattere contenuto in un byte. Le dimensioni di un
intero equivalgono in genere alle dimensioni di una word nell'ambiente di esecu-
zione del programma. Per la maggior parte degli ambienti a 16 bit, come il DOS
o Windows 3.1, un intero occupaTohit: Negli ambienti a 32 bit, come Windows
----~T. in genere un intero occupa 32 bit. In ogni caso sconsigliabile basarsi su

----- ------- ..
16 CAPITOLO LE ESPRESSIONI 17

queste semplici indicazioni, specialmente se si vuole fare in modo che i propri long
programmi poss-ano essere trasportabili da un ambiente a un altro. importante short
comprendere che sia gli standard C e C++ indicano solamente una gamma di
valori minimi che un determinato tipo di dati deve contenere e non le dimensioni possibile applicare i modificatori signed, short, long e unsigned al tipo inte-
in byte. ro e i modificatori signed e unsigned al tipo carattere. Inoltre, il modificatore long
pu essere applicato anche al tipo double.
:aoIA""r~~i}TJS?; Il C++ aggiunge ai cinque tipi di dati principali del Ci tipi
La Tabella 2.1 mostra tutte le combinazioni di tipi di dati validi, indicando le
boot e wchar_t che verranno discussi nella Parte seconda di questa guida. dimensioni approssimative in bit e l'intervallo minimo richiesto (questi valori
Il formato esatto dei valori in virgola mobile dipende dall'implementazione. sono validi anche per le implementazioni di C++). Si ricordi che la tabella mostra
Gli interi corrispondono generalmente alle dimensioni naturali di una word nel solo l'intervallo minimo che questi tipi devono avere secondo quanto specificato
computer. I valori di tipo char sono normalmente utilizzati per contenere i valori dallo Standard CIC++ e non un intervallo tipico. Ad esempio, nel caso di compu-
-- definiti dal set di caratteri ASCII. I valori che non rientrano in questo intervallo ter che impiegano laritmetica con complemento a 2 (praticamente tutti i compu-
possono essere gestiti in modo diverso dalle varie implementazioni di C. ter), un intero avr un intervallo compreso almeno fra 32767 e -32768.
Gli intervalli di valori utilizzabili nei tipi float e double dipendono dal metodo L'uso del modificatore signed sugli interi consentito ma ridondante in
utilizzato per rappresentare i numeri in virgola mobile. Qualunque sia il metodo, quanto la dichiarazione standard dell'intero prevede un numero dotato di segno.
questo intervallo piuttosto esteso. Lo Standard C specifica che l'intervallo mini- L'uso pi importante del modificatore signed in congiunzione con il tipo char in
mo perun valore in virgola mobile vada da lE-37 a 1E+37. Il numero minimo di implementazioni in cui char sia considerato senza segno.
cifre di precisione per ognuno dei tipi in virgola mobile si trova elencato nella
Tabella 2.1. Tabella 2.1- Tutti i tipi di dati definiti dal CANSI.

TIPO DIMENSIONI INTERVALLO MINIMO


~OTA_:-.::"~--"~~=;:_; Lo Standard C++ non specifica valori di estensione o di in- APPROSSIMATIVO IN BIT
tervallo minimi per i tipi predefiniti ma stabilisce solo alcuni requisiti minimi. Ad
esempio, lo Standard C++ dice che un int deve avere dimensioni naturali rispetto char da 127 a 127
all'architettura dell'ambiente di esecuzione. In ogni caso l'intervallo di valori unsigned char da0a255
deve essere uguale o maggiore rispetto a quanto indicato dallo Standard C. Ogni
signed char da -127 a 127
compilatore C++ specifica l'estensione e l'intervallo di valori consentiti da un
tipo nel file header <climits>. int 16 da 32767 a 32767

unsigned int 16 da Oa 65535


Il tipo void dichiara esplicitamente che una funzione non restituisce alcun tipo
signed int 16 come int
di valore o-consente di creare puntatori generici. L'uso di questi oggetti verr
discusso nei prossimi Capitoli. short int 16 come int

unsigned short lnt 16 da Oa 65535

signed short int 16 come short i nt

2.2 Modificare i tipi principali long int 32 da 2.147.483.647 a 2.147.483.647

signed long int 32 come 1ong i nt


Se si esclude il tipo void, i tipi di dati principali possono essere dotati di vari
unsigned long lnt 32 da Oa 4.294.967.295
modi_ficatori. Il modificatore, che riceve la dichiarazione di tipo, altera il signifi-
cato del tipo base per adattarlo con pi precisione alle varie-situazioni. L'elenco float 32 sei cifre di precisione
dei modificatori il seguente: double 64 dieci cifre di precisione

long double 80 dieci cifre di precisione


--signe-- --
unsigned
18 CAPITOLO LE ESPRESSIONI 19

La differenza fra interi con segno (signed) e senza segno (unsigned) il modo lunghezza di un identificatore e sono significativi almeno I 024 caratteri. Questa
in cui viene interpretato il bit alto dell'intero. Se si specifica un intero signed, il differenza importante se si deve convertire un programma dal C al C++.
compilatore C genera codice che assume che il bit alto di un intero venga utilizza- In un identificatore le lettere maiuscole e minuscole sono considerate diverse.
to come bit di segno. Se questo bit O, il numero positivo mentre se questo bit Pertanto, numero, Numero e NUMERO sono considerati tre identificatori distinti.
1, il numero negativo. Un identificatore non pu avere lo stesso nome di una parola chiave del Ce
In generale, i numeri negativi sono rappresentati utilizzando un approccio di non dovrebbe avere lo stesso nome delle funzioni contenute nella libreria del C.
tipo complemento a due, che inverte tutti i bit del numero (tranne il bit del segno),
aggiunge I al numero e imposta il bit del segno a I.
Gli interi con segno sono importanti per un gran numero di algoritmi ma han-
no un'ampiezza assoluta pari alla met dei loro fratelli unsigned. Ad esempio, 2.4 le variabili
questa la rappresentazione del numero 32767:
Come probabilmente il lettore gi sa, una variabile corrisponde a una determinata
0111111111111111 cella di memoria utilizzata per contenere un valore che pu essere modificato dal
programma. Tutte le variabili C prima di essere utilizzate devono essere dichiara-
Se il bit alto fosse impostato a I, il numero sarebbe stato interpretato come -1. te. La forma generale di una dichiarazione :
Se invece si dichiara questo numero unsigned int, impostando il bit alto a l, il
numero verr interpretato come 65535. tipo elenco_variabili;

dove tipo deve essere un tipo di dati valido in C comprendendo eventuali modifi-
catori mentre elenco_variabili pu essere formato da uno o pi nomi di
2.3 Nomi degli identificatori identificatori separati da virgole. Ecco alcune dichiarazioni:

In C e in C++ i nomi delle variabili, delle funzioni, delle etichette e degli altri int i ,j, 1;
oggetti definiti dall'utente sono chiamati identificatori. Questi identificatori pos- short i nt si;
sono essere costituiti da un minimo di un carattere. II primo carattere deve essere unsigned int ui;
una lettera o un carattere di sottolineatura e i caratteri successivi possono essere double balance, profit, loss;
lettere, cifre o caratteri di sottolineatura. Ecco alcuni esempi di nomi di identificatori
corretti ed errati: Occorre ricordare che in C il nome della variabile riciii liii nulla a che fare con
il suo tipo.
Corretto Errato
Numero I numero
test23 ciao! Dove vengono dichiarate le variabili?
val ori_bilancio valori ... bilancio
Le variabili possono essere dichiarate in tre luoghi: all'interno d~lle funzioni,
nella definizione dei parametri delle funzioni e ali' esterno di tutte le funzioni.
In C gli identificatori possano essere di qualsiasi lunghezza. Tuttavia. non
Queste tre posizioni corrispondono a tre diversi tipi di variabili: le variabili locali,
tutti i caratteri saranno necessariamente significativi. Se l'identificatore verr
i parametri formali e le variabili globali:
. _impiegato in un processo di linking esterno, saranno significativi alme!!~ _i primi
sei caratteri. Questi identificatori, chiamati nomi esterni, includono i nomi di fun- le variabili locali
zioni e le variabili globali condivise fra i vari file. Se l'identificatore non utiliz- Le variabili dichiarate all'interno di una funzione sono chiamate variabili locali.
zato in un procsso di linking esterno. saranno significativi almeno i primi 3 l In alcuni testi si parla di queste variabili come di variabili automatiche. Questo
caratteri. Questo tipo di identificatore cl'!iITITatoho1i1e in temo; ad ese-rtrpio sono______ - libro utilizza.il termine.pi comune di "variabile locale". Le variabili locali pos-
---=.= --- --nomi interni i nomi delle variabili localh In C++ invece non vi alcun Ii111ite-a!Ia sono essere utilizzate-solo tla:Ile istruziOni :~he si trovano all'interno del blocco in
- -- ----
~ -_.
20 CAPITOLO 2 LE ESPRESSIONI 21

cui sono dichiarate. In altre parole, le variabili locali non sono note all'esterno del void f(void)
proprio blocco di codice. Occorre ricordare che un blocco di codice inizia con una {
parentesi graffa aperta e termina con una parentesi graffa chiusa. int t;
La vita delle variabili locali legata all'esecuzione del blocco di codice in cui
sono dichiarate: questo significa che una variabile locale viene creata nel momen- scanf("%d" ,&t);
to in cui si entra nel blocco e vierie distrutta all'uscita.
if(t==l) {
Il blocco di codice pi comune in cui sono dichiarate le variabili locali la
char s[SO]; /* questa variabile viene creata
funzione. Ad esempio, si considerino le due funzioni seguenti: solo dopo l'ingresso in questo blocco*/
printf("illlllettere un nome:");
voi d funcl (voi d) gets (s);
{ /* operazioni vari e */
int x;

X = 10;
Qui, la variabile locale s viene creata all'interno del blocco di codice del-
l'istruzione ife distrutta alla sua uscita. Inoltre, s nota solo all'interno del blocco
appartenente all'if e non pu essere utilizzata in altri punti, anche in altre parti
void func2(void) della funzione che la contiene.
{
Un vantaggio della dichiarazione di una variabile locale all'interno di un blocco
int x;
condizionale consiste nel fatto che la variabile verr allocata solo se necessario.
X = -199; Questo avviene perch le variabili locali non vengono create se non nel momento
in cui si accede al blocco in cui sono dichiarate. Questo pu essere un fattore
importante quando ad esempio si produce codice per controller dedicati (ad esempio
La variabile intera x viene dichiarata due volte, una in func1 ()e una in func2(). un sistema di apertura di una porta di un garage che risponde a un codice di sicu-
La x in func1() non vede e non ha alcuna relazione con la x dichiarata in func2(). rezza digitale) in cui la RAM disponibile molto scarsa.
Questo avviene perch ogni x nota solo al codice che si trova all'interno dello La dichiarazione di variabili all'interno del blocco di codice che ne fa uso
stesso blocco in cui la variabile stata dichiarata. evita anche l'insorgere di effetti collaterali indesiderati. Poich la variabile non
Il linguaggio C contiene anche la parola chiave auto utilizzabile per dichiara- ___ .esiste.all'esterno del blocco in cui viene dichiarata, non potr essere modificata
re variabili locali. Tuttavia, poich tutte le variabili non globali sono, normalmen- nemmeno accidentalmente.
te, di tipo automatico, questa parola chiave non viene praticamente mai utilizzata. Vi un'importante differenza fra il Ce il C++ che consiste nella posizione in
Questo il motivo per cui negli esempi di questo libro questa parola non verr cui possibile dichiarare le variabili locali. In C si devono dichiarare tutte le
utilizzata (si dice che la parola chiave auto sia stata inclusa nel C per consentire variabili locali all'inizio del blocco in cui sono definite e prima di ogni istruzione
una compatibilit a livello di sorgente con il suo predecessore B).Di conseguen- che esegua un'azione. Ad esempio, la seguente funzione produrr un errore se
za, la parola chiave auto stata inclusa anche nel C++ per garantire la compatibi- compilata con un compilatore C.
lit con il C.
Per comodit e p~r abitudine la maggior parte dei programmatori dichiara /* Questa funzione errata in e
tut~e le vari~bili locali utilizzate da una funzione immediatamente dopo la paren- ma accettata da qua 1si asi
compilatore ++. */
tesi graffa d1 apertura della funzione e prima di ogni altra istruzione. Tuttavia,
void f(void)
possibile dichiarare variabili l.ocali in qualunque altro punto del blocc-01:li-codice. {
. Il blocco definito da ~na funzione no~ che un caso specifico. Ad esempio, int i;
1;_ - - --

------ ---
----- - - - -
f
22 CAPITOLO LE ESPRESSIONI 23

10; printf("%d 11
, j);

int j; /*._g_uesta riga provoca un errore */ j++; /* questa riga non ha effetti duraturi */
j = 20;
}

In C++ questa funzione perfettamente corretta poich possibile definire Parametri formali
variabili locali in qualsiasi punto del programma (la dichiarazione di variabili in
C++ viene discussa nella Parte seconda di questa Guida completa). Se una funzione deve utilizzare argomenti, deve anche dichiarare variabili che
Poich le variabili locali vengono create e distrutte ad ogni ingresso e uscita accoglieranno i valori passati come argomenti. Queste variabili sono chiamate
dal bloc:o in cui sono state dichiarate, il loro contenuto viene perso all'uscita dal parametri formali della funzione. Essi si comportano come una qualunque altra
blocco. E fondamentale ricordare ci quando si richiama una funzione. Nel mo- variabile locale all'interno della funzione. Come si pu vedere nel seguente fram-
mento in cui la funzioI)e viene chiamata, vengono create tutte le sue variabili mento di programma, la loro dichiarazione si trova dopo il nome della funzione e
locali e alla sua uscita, tutte queste variabili vengono distrutte. Questo significa fra parentesi:
che le variabili locali non possono conservare il proprio valore da una chiamata
della funzione alla successiva (anche se possibile chiedere al compilatore di /*Restituisce l se c si trova nella stringa s; O in caso contrario*/
int is_in(char *s, char e)
conservarne il valore utilizzando il modificatore static). {
Se non si specifica altrimenti, le variabili locali vengono memorizzate nello while(*s)
stack. Il fatto che lo stack sia una regione di memoria dinamica e continuamente i f{*s=:=c) return 1;
alterata spiega il motivo per cui le variabili locali non.possono, in generale, con- else s++;
servare il proprio valore fra due chiamate di funzioni. return O;
possibile inizializzare una variabile locale a un determinato valore noto.
Questo valore verr assegnato alla variabile ogni volta che si accede al blocco di
codice in cui la variabile stessa dichiarata. Ad esempio, il seguente programma La funzione is_in() ha due parametri: s e c. Questa funzione restituisce il valo-
visualizza per dieci volte il numero 1O: re 1 se il carattere specificato in c si trova all'interno della stringa s; in caso
contrario restituisce O.
#include <stdio.h> Si deve specificare il tipo dei parametri formali utilizzando la dichiarazione
mostrata nell'esempio. I parametri formali potranno quindi essere utilizzati al-
void f(void); 1' interno della funzione come se fossero <:omuni variabili locali. Occorre ricorda-
re che, come le variabili locali, anche i parametri formali sono dinamici e vengo-
int main(void)
{
no distrutti all'uscita dalla funzione.
int i; Come nel caso delle variabili locali, possibile utilizzare i parametri formali
per qualunque operazione di assegnamento e in qualunque espressine. Anche se
for(i=O; i<lO; i++) f(); queste variabili ricevono il loro valore dagli argomenti passati alla funzione,
possibile utilizzarle come qualsiasi altra variabile locale.
return O;

Le variabili globl!IU
void f(void)
. _A__djfferenza della variabili locali, le variabili globali sono note all'intero pro-
gramma e possono essere utilizzate in ogni punto del codice; Inoltre esse conser-
int j = 10; vano il proprio valore durante l'intera esecuzione del programma. Le variabili
- - -globali d~v~~-esse~_Qichiarate all'esterno di.ogni.funzione. In seguito, ogni
------ --------
24 -C-AP I T O L O _ _ _ _ _ _ _L__E_E__s_P_R_E;:_s.....-.;._~_1O~N_J_ ___;c2:..;.5 ---
- - - - - - - --_-_-_-_-_._-_-_-_-

espressione ne potr fare uso, indipendentemente dal blocco di codice in cui si stesso nome, ogni riferimento alla variabile all'interno del blocco di codice in cui
trova. dichiarata la variabile locale, far riferimento alla variabile locale e non avr
Nel seguente programma, la variabile count stata dichiarata all'esterno di alcun effetto sulla variabile globale. Questo comportamento pu essere estrema-
tutte le funzioni. Anche se la sua dichiarazione sili:ova prima della funzione main(), mente utile ma dimenticandolo un programma, anche se pu sembrare corretto,
la si sarebbe potuta posizionare in qualsiasi altro punto precedente il suo primo potrebbe iniziare a comportarsi in modo incomprensibile.
uso ma non all'interno di una. funzione. Tuttavia sempre meglio dichiarare le Le variabili globali vengono conservate dal compilatore C in una regione fissa
variabili globali all'inizio del programma. della memoria che ha proprio questo scopo. Le variabili globali sono utili quando
molte funzioni del programma devono utilizzare gli stessi dati. In generale, si deve
#include <stdio.h> per evitare di utilizzare le variabili globali quando non sono necessarie. Infatti esse
int .count; /* count globale */ occupano memoria per l'intera esecuzione del programma e non solo quando ve ne
bisogno. Inoltre, utilizzando una variabile globale in punti in cui si sarebbe potuta
void funcl(void); utilizzare una variabile locale si rende una funzione meno generale, in quanto essa
void func2(void);
fa affidamento su qualcosa che deve essere definito al suo esterno. Infine, utilizzan-
do un gran numero di variabili globali, il programma pu essere soggetto a errori a
int main(void)
{
causa di effetti collaterali sconosciuti e indesiderati. Il problema principale nello
count = 100; sviluppo di grossi programmi il sorgere di modifiche accidentali al valore di una
funcl(); variabile utilizzata in altri punti di un programma. Questo pu avvenire in C e in
C++ se si fa uso di un numero eccessivo di variabili globali.

void funcl(void)
(
int temp; 2.5 I modificatori di accesso
temp count; Il C definisce due modificatori che controllano il modo in cui le variabili possono
func2(): essere lette e modificate. Questi qualificatori sono const e volatile. Essi devono
printf("count uguale a %d", count); /*visualizza 100 */ precedere il modificatore e il nome del tipo che qualificano.

______ Tvoid
___ _func2(void) const
tnt count; Le variabili di tipo const non possono essere modificate dal programma ma
1
l possibile assegnare loro un valore iniziale. Il compilatore libero di posizionare
j
queste variabili in qualsiasi punto della memoria. Ad esempio,
for(count=l; count<lO; count++)
i putchar('. '); const int a=lO;
i
j Osservando attentamente questo programma, si pu notare che sebbene la
variabile count non sia dichiarata n in main() n in func1 (), entrambe le funzioni
crea una variabile intera chiamata a con un valore iniziale pari a 10 che il pro-
gramma non pu modificare. Tuttavia, possibile usare la variabile a in altri tipi
' di espressioni. Una variabile const riceve il proprio valor_!: da un'inizializzazione
ne fanno uso. Al contrario, in func2() viene dichiarata la variabile locale count. esplicita o da strumenti hardware.
Quindi, quando func2{) utilizza count, accede alla propria variabile locale e non Il qualificatore const pu essere-utilizzato per evitare che una funzione modi-
alla variabile globale. Se una variabile globale e ~~ variabile locale hanno lo fichi gli oggetti puntati dagli argomenti della funzione stessa. Ovvero, quando un

----- - - - - -
------- --
26 CAPITOLO 2 -TrTs p RE s s I o NI 27

puntatore viene passato a una funzione, tale funzione pu modificare il valore Molte funzioni nella libreria standard del C utilizzano const nelle proprie dichia-
__ della variabile puntata dal puntatore. Tuttavia, se il puntatore specificato come razioni di parametri. Ad esempio, il prototipo della funzione strlen() il seguente:
const nella dichiarazione dei parametri, il codice della funzione non sar in grado
di modificare l'oggetto puntato. Ad esempio, la funzione sp_to_dash() del pro- size_t strlen(const char *str);
gramma seguente stampa un trattino al posto di ogni spazio di un argomento strin-
ga. In questo modo, la stringa "questa una prova" verr visualizzata come "que- Specificando str come const ci si assicura che strlen() non modifichi la stringa
sta--una-prova". L'uso di const nella dichiarazione del parametro assicura che il puntata da str. In generale, quando una funzione della libreria standard non deve
codice presente all'interno della funzione non possa modificare l'oggetto puntato modificare un oggetto puntato da un argomento, il parametro viene dichiarato
dal parametro. come const.
possibile utilizzare const per verificare che il programma non modifichi
#include <stdio.h> una variabile. Occorre ricordare che una variabile di tipo const pu essere modi-
ficata da qualcosa che si trova all'esterno del programma, ad esempio un disposi-
void sp_to_dash(const char *str); tivo hardware. Tuttavia, dichiarando una variabile come const, si pu stabilire che
ogni modifica a tale variabile avvenga in seguito a eventi esterni.
i nt mai n(voi d)
{
sp_to_dash("questa una prova"); volatile

Il modificatore volatile dice al compilatore che il valore di una variabile pu


return O; essere modificato in modi non specificati esplicitamente dal programma. Ad
esempio, l'indirizzo di una variabile globale pu essere passato alla routine del-
1' orologio di sistema e utilizzata per conservare l'ora. In questa situazione, il
void sp_to_dash(const char *str) contenuto della variabile viene modificato senza alcun assegnamento esplicito
{
da parte del programma.
while(*str) {
if(*str== ' ') printf("%c", ''); Questo un fatto importante poich la maggior parte dei compilatori C/C++
else printf("%c", *str); ottimizza automaticamente determinate espressioni assumendo che il contenu-
stl'++; to di una variabile non possa essere modificato se non sul lato sinistro di una
istruzione di assegnamento; pertanto la variabile non verr riesaminata ad ogni
accesso. Inoltre., fil_ynj_ c2mpilatori alterano l'ordine di valutazione di un' espres-
sione durante il processo di compilazione. Il modificatore volatile evita queste
Se si scrive sp_to_dash() in modo che la stringa possa essere modificata, la modifiche.
funzione non verr compilata. Ad esempio, la seguente funzione produrr un er- anche possibile utilizzare i modificatori const e volatile insieme. Ad esem-
rore di compilazione: pio, se si assume che Ox30 sia il valore di una porta che pu essere modificata solo
da eventi esterni, la seguente dichiarazine eviter ogni possibilit di effetti
/* errata */ collaterali accidentali.
void sp to dash(const char *str)
{ - - const volatile char *port = (const volatile char *)Ox30;
whil e(*str) {
if(*str==' ' ) *str = '-'; /* operazione non consentita */
printf("%c", *str); /* str const */
stl'il.;-- _ 2.6 Specificatori di classe
. di memorizzazione
. .
} --
L __ __
- - - -,.:__,:_:;_--::..:. _:.:_ __ -
_ __Il C consente l'uso di quattr9 ~p~ificatori di classe di memorizzazione: -
---- - - --- -- --- - -
28 CAPITOLO --LE ESPRESSIONI 29

extem
static File 1 File 2
register
auto int x,y; extern int x,y;
char eh; extern char eh;
Questi specificatori dicono al ompilatore il modo in cui memorizzare le va- int main(void) voi d func22 (voi d)
riabili. Si noti che lo specificatore precede la parte rimanente della dichiarazione { {
della variabile. La sua forma generica : /* ... */ x=y/10;
} }
specifictore_memoria tipo nome_var
void funcl() voi d func23 ()
{ {
NQ!Al~;J!;~i Il C++ introduce un nuovo specificatore di classe d'accesso x=l23 y=lO;
chiamato mutable che verr descritto nella Parte seconda. } {

extern Figura 2.1 Uso di variabili globali in moduli compilati separatamente.


Poich il C/C++ consente la compilazione separata dei vari moduli che formano
un grosso programma e il loro successivo collegamento con il linker, vi deve
essere un modo per comunicare a tutti i file informazioni sulle variabili globali La parola chiave extern ha la seguente forma generale:
richieste dal programma. Anche se il C consente, tecnicamente, di definire pi
volte una variabile globale, non si tratta di una pratica "elegante" (oltre a causare extern elenco-variabili;
potenziali problemi di linking). Ma soprattutto, in C++ possibile definire una
variabile globale una sola volta. Ma allora, come possibile fornire a tutti i file Vi anche un altro uso, opzionale, della parola extern.
che compongono il programma informazioni relative a tutte le variabili globali Quando si usa una variabile globale all'interno di una funzione, possibile
utilizzate? La soluzione di questo problema rappresentata dalla distinzione fra dichiararla come extern:
la dichiarazione e la definizione di una variabile. Una dichiarazione dichiara sem-
plicemente il nome e il tipo di una variabile. La definizione provaca-invece int first, last; /* definizione globale di first
l'allocazione della memoria per la variabile. Nella maggior parte dei casi, le di- e last */
chiarazioni delle variabili sono anche definizioni. Tuttavia, se si fa precedere al
nome della variabile lo specificatore extem, si pu dichiarare una variabile senza main(void)
{
definirla. Pertanto, in un programma composto da pi file, possibile dichiarare
tutte le variabili globali in un file e utilizzare delle dichiarazioni extem negli altri extern int first; /* uso opzionale della
di chi arazione extern */
file, come indicato nella Figura 2.1.
Nel File 2, l'elenco di variabili globali stato copiato dal File 1 e alle dichia-
razioni stato aggiunto lo specificatore extern. Lo specificatore extern dice al
compilatore che i nomi e i tipi di variabili che lo seguono sono stati definiti altro-
ve. In altre parole, extern fa conoscere al compilatore il tipo e il nome di queste
variabili globali senza in effetti occupare alcuno spazio di memoria. Quando il .Anche se le dichiarazioni di variabili extern mostrate in questo esempio sono
linker collegher i due mduli, verranno risolti tutti i riferimenti alle variabili consentite dal linguaggio, si tratta di dichiarazioni non necessarie. Se il compila-
----esterne. tore trova una variabile che non stata dichiarata all'interno del ~focc9 corrent~ .
CAPITOLO LE ESPRESSIONI 31

controlla se essa corrisponde a una -delle variabili dichiarate in un blocco pi return series_num;
esterno e in caso di risposta negativa, controlla le variabili globali. In caso la
variabile venga trovata, il compilatore assume che si debba far riferimento a tale
. variabile globale. In questo esempio, la variabile series_num conserva il proprio valore da una
, In C++ lo specificatore extern ha anche un altro uso che verr descritto nella chiamata alla funzione alla successiva, a differenza delle comuni variabili locali
Parte seconda. che vengono create e distrutte in continuazione. Questo significa che ogni chia-
mata alla funzione series() produce un nuovo valore basandosi sul numero prece-
dentemente fornito e senza dichiarare alcuna variabile globale.
static Inoltre, possibile assegnare a una variabile locale static anche un valore di
inizializzazione. Questo valore viene assegnato una sola volta all'avvio del pro-
Le variabili static sono variabili permanenti all'interno della propria funzione o gramma (e non ogni volta che si entra nel blocco di codice, come nel caso delle
' del proprio file. A differenza delle variabili globali, esse non sono note all'. esterno comuni variabili locali). Ad esempio, questa versione di series() inizializza la va-
. della propria funzione o del proprio file, ma mantengono il proprio valore fra una riabile series_num a 100:
chiamata e la successiva. Questa caratteristica le rende utilissime quando si scri-
vono funzioni o librerie generalizzate che possono essere utilizzate da altri pro- int series(void)
grammatori. La parola chiave static ha effetti diversi sulle variabili locali e le {
variabili globali. static int series_num = 100;
Le variabili locali static seri es_num = seri es_num+23;
Quando si applica il modificatore static a una variabile locale, il compilatore crea return seri es _num;
per la variabile una cella di memoria permanente, cos come avviene per le varia-
bili globali. La differenza principale fra una variabile locale static e una variabile
globale consiste nel fatto che la prima Iimane nota solo all'interno del blocco in Con questa funzione, la serie inizia sempre dal valore 123. Anche se questo
cui dichiarata. In altri termini, una variabile locale static una variabile locale accettabile per alcune applicazioni, la maggior parte dei generatori di serie richie-
che conserva il proprio valore fra due chiamate di funzione, de che sia l'utente a specificare il valore iniziale. Un modo per assegnare a
Le variabili locali static sono molto importanti per la creazione di funzioni series_num un valore specificato dal!' utente consiste nel rendere seres_num una
isolate in quanto molti tipi di routine devono conservare un valore da una chiama- variabile globale e di assegnarle il valore specificato. Tuttavia, il modificatore
ta alla successiva. Se non fosse consentito l'uso di variabili locali static, si sarebbe static serve proprio a non dichiarare series_num globale. Questo introduce il se-
costretti a usare varfal5ili globali, che possono provocare effetti collaterali. Un condo uso di static.
esempio dlfunzione che beneficia dell'uso di variabili locali static un generato-
re di serie di numeri che produce un nuovo valore sulla base del valore fornito in Le variabili globali static
precedenza. Per conservare questo valore si potrebbe usare una variabile globale. Applicando Io specificatore statica una variabile globale, si chiede al compilatore
Tutta\'ia, ogni volta che la funzione viene utilizzata in un programma, sarebbe di creare una variabile globale che sia noia solq all'interno del file in cui stata
necessario dichiarare tale variabile globale e assicurarsi che essa non entri in con- dichiarata. Questo significa che anche se la vadabile globale, le routine di altri
flitto Cl1n altre variabili globali. La soluzione migliore consiste nel dichiarare la file non saranno in grado di vederla n di modificarne direttamente il contenuto,
variabile che deve conservare il numero generato come static, come nel seguente evitando cos il sorgere di effetti collaterali. Per i pochi casi in cui una variabile
framm.:-nto di programma: locale static non adatta, possibile creare uri piccolo file che contenga solo le
funzioni che devono utmzzare la variabile globale static, compilare separatamente
int series(void) tale file e utilizzarlo senza temere effetti collaterali.
{ Per illustrare l'uso delle variabili globali static, spuoimmaginare che il ge-
static int series_num; neratore di serie dell'esempio precedente debba essere ricodificato in modo che la
seriesh il!izializzata da un valore series fornito .daJu1a.chiamata a una seconda
__ .=--::.--:-:-series_num = series_num+b;---
32 CAPITOLO 2 LE ESPRESSIONI 33

funzione chiamata series_start(). L'intero file contenente series(), series_start() e Variabili register
series_num illustrato di seguito:
Lo specificatore di memorizzazione register si applica tradizionalmente solo a
/* Queste funzoni devono trovarsi nello stesso file, possibilmente da sole. variabili di tipo int, char e puntatori. Lo Standard C ha esteso questa definizione
per consentire l'applicazione dello specificatore register a ogni tipo di variabile.
*/
Originariamente, lo specificatore register chiedeva che il compilatore conser-
static int series num; vasse il valore di una variabile in un registro della CPU e non in memoria insieme
void series_start(int seed); alle altre variabili. Questo significava che le operazioni su una variabile register
int series(void); potevano avvenire molto pi velocemente rispetto a una comune variabile in quanto
il valore della variabile register veniva sempre conservato nella CPU e non richie-
int series(void) deva alcun accesso alla memoria per determinarne o modificarne il valore.
{ Oggi, la definizione di register stata notevolmente espansa e pu essere ap-
seri es_num = seri es_num+23; plicata a qualsiasi tipo di variabile. Il c standard stabilisce semplicemente che
return seri es_num; "l'accesso all'oggetto sia il pi rapido possibile" (lo standard C++ stabilisce che
la parola register sia un "suggerimento per il compilatore che indica che l'oggetto
dichiarato come tale verr impiegato in modo massiccio") .. In pratica, i caratteri e
/* inizializza series_num */
void series_start(int seed) gli interi verranno ancora conservati nei registri della CPU. Gli oggetti di maggio-
{ ri dimensioni come ad esempio gli array non potranno per esser conservati in un
seri es_num = seed; registro, m~ riceveranno dal compilatore un trattamento preferenziale. A seconda
dell'implementazione del compilatore C/C++ e del suo ambiente operativo, le
variabili register possono essere gestite in molti modi sulla base delle decisioni
Richiamando series_start() con un valore intero noto, si inizializza il genera- dell'implementatore del compilatore. Ad esempio, tecnicamente corretto anche
tore di serie. Dopo questo, le chiamate a series() generano l'elemento successivo che un compilatore ignori lo specificatore register e gestisca questo tipo di varia-
della serie. bile come qualunque altra, ma in realt questa una pratica utilizzata raramente.
Per riassumere: i nomi delle variabili locali static sono noti solo all'interno possibile applicare lo specificatore register solo a variabili locali e ai para-
del blocco di codice in cui sno dichiarate; i nomi delle variabili globali static metri formali di una funzione. Pertanto, non consentito l'uso di variabili globali
sono note solo all'interno del file in cui si trovano. Se si inseriscono le funzioni register. Ecco un esempio che utilizza variabili register. Questa funzione calcola il
series() e series_start() in una libreria, sar possibile utilizzare le funzioni mentre risultato di me per due numeri interi:
sar impossibile fare accesso alla variabile series_num che lo specificatore static
nasconde al_resto del codice del programma. Questo significa che anche possi- int int_pwr(register int m, register int e)
bile dichiarare e utilizzare un'altra variabile anch'essa chiamata series_num (na- {
turalmente in un altro file). In sostanza, il modificatore static consente l'uso di register int temp;
variabili note solo all'interno delle funzioni che ne richiedono l'uso, senza effetti
temp = 1;
collaterali indesiderati.
Le variabili static consentono di nascondere parti del programma rispetto ad
altre parti. Questo pu essere un notevole vantaggio quando si deve gestire un
for(; e; e--) temp = temp * m;
return temp;
programma molto esteso e complesso.
WA~%i@ii1$ In e++ questo uso di static, pur essenao ancora supportato,
sconsigliato. Questo significa che opportuno non utilizzarlo quando si realiz:a __l.!!_9.!l~Sto esempio, e, m e temp sono dichiarate come variabili register in
nupvo codice. In questo caso si dovr invece far ricorso ai namespace, argomento quanto vengono utilizzate all'interno di un ciclo. Il fatto che le variabili register
discusso nella Parte second.,.,a,,,,.~-- siano ottimizzate per quanto riguarda la velocit, le rende ideali per il controllo o
----~-- -'"7.

34 CAPITOLO
35

l'uso all'interno di cicli. Generalmente, le variabili register vengono utilizzate nei Le variabili globali e static locali sono inizializzate solo all'inizio del pro-
punti in cui si dimostrano pi efficaci, che sono spesso i luoahi in cui si eseauono gramma. Le variabili locali (ad esclusione delle variabili locali static) sono

pi accessi alla stessa variabi1e. Questo importante poich possibile dichlarare inizializzate ogni volta che si accede al blocco in cui sono dichiarate. Le variabili
un qualsiasi numero di variabili di tipo register ma non tutte riceveranno la stessa locali che non sono inizializzate, prima del primo assegnamento hanno un valore
ottimizzazione nella velocit di accesso. indefinito. Le variabili globali e le variabili static locali non esplicitamente
Il numero di variabili register che possibile ottimizzare in un determinato blocco inizializzate, vengono automaticamente inizializzate a O.
di codice dipende sia dall'ambiente operativo che dalla specifica implementazione di
?C++.11: ogni c~o non ci si deve preoccupare di dichiarare troppe variabili register
m quanto il compilatore C trasforma automaticamente le variabili register in variabili
non register non appena si supera il limite consentit (questo assicura la trasportabilit 2.8 Le costanti
del codice in un'ampia gamma di microprocessori).
Normalmente, nei registri della CPU possono essere conservate almeno due Le costanti fanno riferimento a valori fissi che il programma non pu alterare.
variabili register di tipo char o int. Poich gli ambienti operativi possono essere Le costanti possono essere di uno qualsiasi dei tipi principali. Il modo in cui
molto diversi, per determinare se possibile applicare le opzioni di ottimizzazione ogni costante rappresentata dipende dal suo tipo. Le costanti vengono chiama-
ad altri tipi di variabili, occorre consultare la documentazione del compilatore. te anche letterali. Le costanti di tipo carattere devono essere racchiuse fra apici
In C non possibile conoscere l'indirizzo di una variabile register utilizzando (ad esempio 'a' e'%'). In C/C++ sono definiti anche i caratteri estesi (utilizzati
l'operatore & (introdotto pi avanti in questo stesso capitolo). Ci dovuto al principalmente per lingue non comuni) le quali occupano 16 bit. Per specificare
fatto che una variabile register deve essere memorizzata in un reaistro della CPU una costante carattere estesa, si deve far precedere al carattere la lettera "L". Ad
del quale in genere non possibile conoscere l'indirizzo. Quest: restrizione non esemplo:
si applica al linguaggio C++. Tuttavia, il fatto di richiedere l'indirizzo di una
variabile register in C++ pu evitare che essa venga ottimizzata al meglio. wchar_t wc;
Anche se lo standard C/C++ ha espanso la descrizione di register oltre il suo wc= L'A';
significato tradizionale, in pratica ha un effetto si anificativo solo con variabili di
tipo intero o carattere. Pertanto, non si dovr prev:dere un sostanziale aumento di Qui a wc viene assegnata la costante a caratteri estesi equivalente alla lettera
prestazioni con altri tipi di variabili. "A". Il tipo dei caratteri estesi si chiamawchar_t. In C questo tipo definito in un
file header, ovvero non uno dei tipi standard del linguaggio. In C++, wchar_t
un tipo standard.
Le costanti intere sono specificate come numeri senza componente decimale.
2.7 - Inizializzazione delle variabili Ad esempio 1O e -100 sono costanti intere. Le_ C.Q~n.t.U.!1 virgola mobile conten-
gono il punto decimale seguito da una componente decimale. Ad esempio, 11. 123
Alla maggior parte delle variabili possibile assegnare un valore direttamente una costante in virgola mobile. In C/C++ consentito anche l'uso della notazio-
nella dic~i~azione, inserendo un segno di uguaglianza e un valore dopo il nome ne scientifica per i numeri in virgola mobile.
della vanab1le. La forma generica di inizializzazione : Vi sono due tipi di numeri in virgola mobile: float e double. Vi sono anche
molte varianti dei tipi principali che possibile generare utilizzando i modificato-
tipo nome_variabile = valore; ri di tipo. Normalmente, il compilatore inserisce una costante numerica nel pi
piccolo tipo di dati compatibile in grado di contenerla. Pertanto, supponendo di
Ecco alcuni esempi di inizializzazione: utilizzare interi a 16 bit, 10 sar normalmente un int mentre 103000 sar un long.
Anche se il valore 1O potrebbe rientrare nel_ tipo carattere, il compilatore far
char eh = 'a'; normalmente uso di un int. L'unica"eccezione alla regola del "tipo Pi piccolo"
! sono le costanti-in-virgola mobile che vengono sempre memorizzate come valori
-J~-~- int first = O; double.
_ Per la maggior parte dei programmi, le assunzioni fatte dal compilatore sono
float balance = 123.23; ------ - --=-=--:.- :__ corrette.Tuttavia, po~s_!Qi}~nc~~ s.pecificare con precisione. il tipQ d~Jle costan-
1
!
36 CAPIT"OLO 2 LE E s p R (ssro NI 37

_ ti numeriche utilizzando un suffisso. Per i tipi in virgola mobile, se il numero Occorre fare attenzione a non confondere le stringhe e i caratteri. Una costan-
seguito dalla lettera F, verr trattato come un flat. Se il numero seguito da una te carattere racchiusa tra singoli apici, come ad esempio 'a'. Al contrl!!io "a"
L, verr memorizzato come un long double. Per i tipi interi, il suffisso U sta per una stringa formata da un'unica lettera.
unsigned mentre L sta per long. Ecco alcuni esempi:

Tipo Esempi di costanti Costanti carattere speciali

int 1 123 21000 -234 L'inclusione di costanti di tipo carattere in singoli apici consentita anche per la
long int 35000L-34L maggior parte dei caratteri stampabili. Alcuni, per, come ad esempio il carattere
short int di Carriage Retum, non possono essere immessi in una stringa utilizzando la ta-
10 -12 90
unsigned int IOOOOU 987U 40000 stiera. Per questo motivo, il C/C++ prevede l'uso di costanti carattere speciali
float 123.23F 4.34e-3F (elencate nella Tabella 2.2) che consentono di utilizzare questi caratteri come co-
double stanti. Tali elementi sono chiamati anche sequenze di escare. L'uso di questi codi-
123.23 12312333 -0.9876324
long double IOOl.2L ci al posto degli equivalenti codici ASCII aiuta a garantire la trasportabilit del
codice.

Costanti esadecimali e ottali Tabella 2.2 Codici speciali.

Talvolta pi comodo utilizzare un sistema numerico in base 8 o 16. Il sistema CODICE SIGNIFICATO
numerico che si basa sull'otto chiamato ottale e utilizza le cifre da O a 7. Il \b Backspace
numero I O ottale corrisponde quindi al numero 8 decimale. Il sistema numerico
\f Form feel
in base 16 chiamato esadecimale e utilizza le cifre da Oa 9 e le lettere da A a F
che sostituiscono rispettivamente i numeri 10, 11, 12, 13, 14 e 15. Ad esempio, il \n Newline
numero esadecimale 10 corrisponde al numero 16 decimale. Poich questi siste- \r Carriage retum
mi numerici sono utilizzati molto frequentemente, il C/C++ consente di specifi-
care costanti intere anche in formato esadecimale e ottale. Una costante esadecimale \t Tabulazione orizzontale
formata dal prefisso Ox seguito dalla costante in formato esadecimale. Una co- \" Doppi apici
stante ottale inizia con uno O. Ecco alcuni esempi:
\' Apici

int hex = OxBO; /* 128 in decimale */ \O Carattere nullo


int oct = 012; /* 10 in decimale */
\\ Backslash

\v Tabulazione verticale
Costanti stringa
\a Bip
Il C/C++ consente l'uso di un altro tipo di costanti: la stringa. Una stringa for- \? Punto interrogativo
mata da gruppi di caratteri racchiusi fra doppi apici. Ad esempio, "questo un
esempio" una stririga. Altri esempi di stringhe sono quelli presenti in alcune \N Costante ottale (dove N una coslante ottale)
istruzioni printfO di=-programmi di esempio. Anche se il C consente di definire \xN COStante esadecimale (dove N una costante esadecimale) -
costanti stringa, non prevede formalmente il tipo stringa. Al contrario, il C++
prevede una classe apposita per le stringhe. ---

----- ~:::.---=.:
---- --- ----TI-Es-H-E=sSIONI 39
38---Ei A-P-H-0 L O 2

Ad esempio, il programma seguente salta su una nuova riga, visualizza un


Conversione di tipo negli assegnamenti
carattere di tabulazione e poi stampa la stringa Questo un esempio. Quando si utilizzano insieme variabili di tipo diverso, avviene una convers~one d~
tipo automatica. In un'istruzione di assegnamento, la regola per la conversione d1
#include <stdio.h> tipo semplice: il valore che si trova al lato destr~ ?ell'assegn~1:mto (l'esp.res-
int main(void)
sione) viene convertito in ci che si trova al lato sm1stro (la vanabile) come illu-
{
printf("\n\tQuesto un esempio"); strato di seguito:

return O; int X;
ehar eh;
float f;

void fune(void)
2.9 Gli operatori {
eh = x; /* riga 1 */
Il C/C++ un linguaggio molto ricco di operatori. Infatti, fa molto pi affidamen- X = f; /* riga 2 */
to sugli operatori della maggior parte degli altri linguaggi di programmazione. Vi f = eh; /* riga 3 */
sono quattro classi principali di operatori: aritmetici, relazionali, logici e bit-a-bit. f = x; /* riga 4 */
Inoltre prevede alcuni operatori adibiti a compiti specifici.
Nella riga 1, i primi bit (superiori) della variabile intera x vengono eliminati,
l'operatore di assegnamento lasciando in eh solo gli 8 bit meno significativi. Quindi, se x co~pres~ fra O~
256 eh e x avranno valori identici. In tutti gli altri casi, il valore d1 eh nflettera
In C/C++ possibile utilizzare l'operatore di assegnamento all'interno di ogni ~ol~ gli 8 bit inferiori di x. Nella riga 2, x riceve la parte intera di f. Nell_a ri~a 3, f
espressione valida. In questo senso il C/C++ differisce dalla maggior parte dei converte il valore intero a 8 bit memorizzato in eh nello stesso valore m virgola
linguaggi di programmazione (inclusi il Pascal, il BASIC e il FORTRAN), che mobile. Questo avviene anche nella riga 4, tranne per il fatto che f converte un
considerano l'operatore di assegnamento come un'istruzione. Nel linguaggio C la valore intero in un valore in virgola mobile.
forma generale dell'operatore di assegnamento : Quando si eseguono conversioni da interi a caratteri e ~a ~nteri lon_g a i~teri
normali viene eliminato un numero appropriato di bit supenon. In molti ambien-
nome_variabile=espressione; ti opera~ivi a 16 bit, questo significa che passan?o da un intero ~ un carattere, _si
perdono 8 bit e passando da un intero longa un mtero normale si perdono 16 b.1t.
dove espressione pu essere una semplice costante o complessa a piacere. Il C/ In ambienti a 32 bit la conversione di un intero in un carattere provoca la perdita
C++ usa per l'assegnamento un singolo segno di uguaglianza (il Pascal e il Modu- di 24 bit mentre la c'onversione da un intero a un intero short provoca la perdita di
la-2 utilizzano il costrutto :=). Sul lato sinistro dell'assegnamento deve trovarsi 16 bit. .
una variabile o un puntatore, non pu invece trovarsi una funzione o una costante. La Tabella 2.3 riassume le conversioni di tipo eseguite dagli assegnamenti.
Frequentemente, nei volumi che trattano la programmazione CIC++ e nei bene sapere che una conversione di un int in un float o. di u~ float in. un double. e
messaggi di errore del compilatore si trovano i due termini lvalue e rvalue. Que- cos via non aggiunge precisione al numero. Questo tipo d1 conve~s1one ~ambi~
sti oggetti corrispondono a ci che si trova, rispettivamente, sul lato sinistro (left solo il formato in cui il valore viene rappresentato. Inoltre alcuni comp1laton,
value - lvalue) e sul lato destro (right value - rvalue) dell'operatore di assegna- nella conversione di una variabili char in un int o in un float, considerano la varia-
mento. In termini pratici, con lvalue si intende la variabile mentre con rvalue si bile ~empre positiva indipendentemente al valore che essa Contiene. Altri compi-
intende il valore dell'espressione che si trova sul lato destro dell'operatore di latori considerano negativi i valori char maggiori di 127. In generale, bene usare
assegnamento. variabili char solo per i caratteri e usare per i numeri solo i tipi int, short int e
signed char;-questo eviter problemi di tr~sportabilit. ___ - ..

---- __
-_;:.;:.:.__;;- ...
LE ESPReSSl'ElNI 41
40

Operatori aritmetici
Per utilizzare la Tabella 2.3 per eseguire una conversione non indicata, basta
convertire un tipo alla volta fino a giungere al tipo di destinazione-Ad esempio, La Tabella 2.4 elenca gli operatori aritmetici presenti in C/C++. Gli operatori+, -
per. convertire un double in un int, si deve prima convertire il double in un float e , * e I si comportano come i corrispondenti operatori presenti in altri linguaggi di
poi il float in un int. programmazione. Questi operatori possono essere applicati a quasi tutti i tipi di
dati consentiti. Se si applica l'operatore I a un intero o a un carattere, il resto verr
troncato. Ad esempio, in una divisione fra interi 5 I 2 sar uguale a 2.
Assegnamenti multipli
L'operatore di modulo, opera in C/C++ come in molti altri linguaggi, fornen-
Il C/C++ consente di specificare pi variabili in un'unica istruzione di assegna- do il resto di una divisione intera. Tuttavia, non possibile utilizzare l'operatore
mento; tutte le variabili assumeranno quindi lo stesso valore. Ad esempio, questo per i tipi in virgola mobile. Il seguente frammento di codice illustra l'uso del-
frammento di programma assegna alle variabili x, y e z il valore O: 1' operatore %:

X =y =z = O; int X, y;

Nei programmi professionali, si fa normalmente un grande uso di questo di X = 5;


questo metodo di assegnamento. y = 2;
pri ntf("%d", x/y); /*visualizza 2 */
Tabella 2.3 Conversioni di tipo (assumendo word di 16 bit). printf("%d", x%y); /*visualizza l, il resto della
divisione intera */
TIPO 01 DE~TINAZIONE TIPO DELL'ESPRESSIONE POSSIBILE PERDITA
DI INFORMAZIONI
X = l;
signed char char Se il valore > 127, la destinazione negativa
y = 2;
char short int 8 bit pi significativi
printf("%d %d", x/y, x%y); /* visualizza O l */
char int (16 bit} 8 bit pi significativi

char int (32 bit} 24 bit pi significativi


L'ultima riga stampa uno Oe un 1 poich 1 / 2 nella divisione intera uguale
aOeilresto 1.
char long int 24 bit pi significativi

short int int (16 bit} Nulla


--:....:e--""-=-------------------
short int - int (32 bit) 16 bit pi significativi Tabella 2.4 Operatori aritmetici.
int (16 bit) long int 16 bit pi significativi OPERATORE AZIONE

int (32 bit) long int Nulla Sottrazione, anche meno unario

int float la parte decimale e, in alcuni casi, parte Addizione


delrintero
Moltiplicazione
float double Precisione, il risultato viene arrotondato
Divisione
doubl e long double Precisione, il risultato viene arrotondato
% Modulo

Decremento

+ + Incremento

--- - - - - - -
42 CAPITOLO 2 LE ESPRESSIONI 43

Il meno unario moltiplica il suo .oper.ando per -1. Questo significa che un gue il proprio operando, il C/C++ prima fornisce il valore dell'operanda e-poi lo
numero preceduto dal segno meno cambia il proprio segno. incrementa o decrementa. Ad esempio,

X = 10;
incremento e decremento Y = ++ x;

Il C/C++ include due utili operatori che generalmente non sono presenti in altri
assegna a y il valore 11. Se lo stesso codice fosse stato scritto come
lingua_ggi di programmazione: gli operatori di incremento(++) e decremento (- -).
Questi operatori, rispettivamente, aggiungono e sottraggono una unit al proprio
X = 10;
operando. In altre parole:
Y = x++;
X = x+l;
a y sarebbe stato assegnato il valore IO. In entrambi i casi, a x sar stato assegnato
il valore 11; la differenza sta nel momento in cui cambia il valore di x.
equivale a:
La maggior parte dei compilatori C/C++ produce codice oggetto molto effi-
ciente per le operazioni di incremento e decremento, molto migliore rispetto a
++x;
quello generato utilizzando l'equivalent~ operatore di assegnamento. Per questo
motivo, sempre bene preferire, quando possibile, gli operatori di incremento e
e
decremento.
La prec;edenza degli operatori aritmetici la seguente:
X = X-1;
alta + +- -
equivale a:
- (meno unario)
*!%
X- -;
bassa t-
Gl~ operatori di incremento e decremento possono precedere (forma prefissa) Gli operatori con lo stesso livello di precedenza vengono valutati dal compila-
o segurre (forma postfissa) l'operando. Ad esempio tore da sinistra verso destra. Nituralmente, possibile utilizzare le parentesi per
modificare l'ordine di valutazione. Il C/C++ considera le parentesi nello stesso
X = X+l;
modo di ogni altrolin.gua-ggio di programmazione. Le parentesi forzano il calcolo
di un'operazione o di un gruppo di operazioni in modo che assumano un livello di
pu essere scritto come precedenza superiore.
++x;
Operatori relazionali e logici
o
Nel termine operatore relazionale, con relazionale si intende la relazione che un
x++; valore ha rispetto a un altro. Nel termine operatore logico, l'aggettivo "logico" fa
riferimento ai modi in cui queste rela~oni possono essere connesse. Questi .due
Tuttavi_a, vi una differenza fra le forme prefissa e postfissa quando si utiliz- tipi di operatori vengono discussi insieme in quanto spesso vengono utilizzati
zano questi operatori in un'espressione. Quando l'operatore di incremento 0 de- congiuntamente. II concetto di operatore relazionale e logico si basa sull'idea di
-~remento. prec~de il. pr~prio operando, H C/C++ eseg~ 1'.i!l~remento 0 il decre-
verit e falsit. vero (true) qualsiasi valore diverso da zero mentre falso (false)
lo zero. Le espressioni che utilizzano operatori relazionali o logici restituiscono O-- - - --
men~ .1:r~~a dt!_ormre 11 valore dell'ope~~@.all'espressione. Se l'operatore se- _ -- __
- __ pe'.::Xal~e_e l per true. --- --
44 CAPITOLO 2.

Il C++ supporta completamente il concetto di zero associato a false e "non- In un'espressione possibile riunire pi operazioni come nell'esempio seguente:
zero" associato a true. Tuttavia viene definito anhe un nuovo tipo di dati chiama-
to bool e le costanti booleane true e false. In C++ il valore O viene automatica- 10 > 5 && !(10 < 9) li 3 <= 4
mente convertito in false e un valore diverso da zero viene automaticamente con-
vertito in true. Vale anche il contrario: true viene convertito in I e false viene In questo caso, il risultato vero.
convertito in O. In C++ il risultato di un'operazione relazionale o logica true o Anche se n il C n il C++ contengono l'operatore logico di OR esclusivo
false. Ma poich questi valori vengono automaticamente convertiti in 1 e O, la (XOR), facile creare una funzione che esegua questa operazione utilizzando gli
distinzione fra C e C++ in questo campo perlopi accademica. altri operatori logici. TI risultato di uno XOR true se e solo se un solo operando
La Tabella 2.5 elenca gli operatori relazionali e logici. La tabella di verit per vero ma non entrambi.
gli operatori logici mostrata in termini di I e O. Il seguente programma contiene la funzione xor() che restituisce il risultato di
un'operazione di OR esclusivo eseguita su due argomenti:
p q p&&q pllq !p
o o o o I #i nel ude <stdi o. h>
o 1 o 1 I
1 1 I 1 o int xor(int a, int b);
I o o 1 o
int main(void)
(
Gli operatori relazionali e logici hanno una precedenza inferiore rispetto agli
printf("%d", xor(l, O));
operatori aritmetici. Perci un'espressione come 10 >I+ 12 viene valutata come printf("i6d'', xor(l, 1));
10 > (1 + 12) e il risultato ovviamente falso. printf("%d", xor(O, 1));
printf("%d", xor(O, O));

Tabella 2.5 Operatori relazionali e logici. return O;

OPERATORI RELAZIONALI
OPERATORE AZIONE
/* Esegue uno XOR logico utilizzando
i due argomenti. *I
Maggiore di int xor(int a, int b)
{
Maggiore o uguale
return (a 11 b) && ! (a && b);
< Minore di

Minore o uguale
La tabella seguente mostra i livelli di precedenza relativa esistenti fra gli opera-
Uguale tori relazionali e logici:
!= Diverso
Alta
OPERATORI LOGICI >>=<<=
OPERATORE AZIONE ==!=
&&
&& ANO
Bassa Il
NOT -- - - - -
Come nel caso delle espressioni-aritmetiche, possibile.utilizzar.e le_paremesi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--~- -
per modificare l'ordine di-valutazione naturale. Ad esm_R~.".!.'..~spressione:
---- _..:...::.::.:.-_:_, __
-4&--C-A P 1-T O LO 2 --- --~~-~-

Gli operatori bit-a-bitAND, OR e NOT (compiemenio a uno) sono govemati__::___ -


!O&&OllO
dalla stessa tabella di verit dei corrispondenti operatori logici, tranne per il fatto
falsa. Se invece si aggiungono le parentesi alla stessa espressione, come mostra- che operano sui bit. La tabella dell'operatore di OR esclusivo (XOR) la seguente:
to di seguito, il risultato sar vero:
p q
!(0&&0) 110 o o
1 o
1 1
Occorre ricordare che tutte le espressioni relazionali e logiche producono un
risultato pari a Oo a 1. Pertanto, il seguente frammento di programma non solo o 1
corretto, ma visualizza il numero 1.
Come indica la tabella, il risultato di uno XOR vero se uno solo degli operandi
int x;
vero; in caso contrario il risultato falso.
Le operazioni bit-a-bit trovano applicazione molto spesso nel caso della rea-
X = 100; lizzazione di driver per dispositivi (programmi per modem, routine per la gestio-
printf("%d", x>lO); ne dei file e routine di stampa) in quanto consentono di mascherare determinati
bit, ad esempio quello di parit (il bit di parit conferma che la parte rimanente dei
bit del byte non sia modificata; normalmente si tratta del bit pi alto di ogni byte).
Operatori bit-a-bit Si pu pensare all'operatore di AND bit-a-bit come a un modo per cancellare
un bit. Infatti, ogni bit per il quale almeno uno degli operandi uguale a Oassume-
A differenza di molti altri linguaggi, il C/C++ dotato di una dotazione completa r il valore O. Ad esempio, la funzione seguente legge un carattere dalla porta del
di operatori bit-a-bit. Poich il C stato progettato per prendere il posto del lin- modem e riporta a Oil bit di parit:
guaggio Assembler nella maggior parte dei campi di programmazione, doveva
essere in grado di eseguire molte operazioni che normalmente potevano essere char get_char_from_modem(void)
svolte solo in Assembler, incluse le operazioni sui bit. Le operazioni bit-a-bit si {
occupano di controllare, impostare o spostare i bit che compongono un byte o una char eh;
word, che corrispondono ai tipi ehar e int e relative varianti. Quindi non possibi-
le utilizzare le operazioni bit-a-bit sui tipi float, double, long double, void, bool e eh = read_modem(); f* preleva un carattere
sui tipi pi complessi. La Tabella 2.6 elenca tutti gli operatori bit-a-bit. Queste da 11 a porta de 1 modem */
operazioni si applicano ai singoli bit degli operandi. return(ch & 127);
. ---------
Tabella 2.6 Operatori bit-a-bit.
La parit costituita normalmente dall'ottavo bit che viene posto uguale a O
OPERATORE AZIONE eseguendo un'operazione di AND bit-a-bit con un byte in cui i sette bit pi bassi
ANO sono uguali a 1 e il bit pi alto uguale a O. L'espressione eh & 127 esegue un
AND dei bit contenuti in eh con i bit che compongono il numero 127. Il risultato
OR che l'ottavo bit di eh diviene uguale a O. Nell'esempio seguente, si assume che
OR esclusivo (XOR) in eh sia stato ricevuto il carattere "A" e che il bit di parit sia uguale a 1:
Complemento a uno

Scorrimento a destra

Scorrimento a sinistra

---_-::. __ -:..__ - .

---~
48 CAPITOLO .2 L-E E S P R E S S I O N I 49

Bit di parit In queste operazioni, si perde un bit a un'estremit mentre il nuovo bit che si

l
11000001 eh contenente una "A" e bit di parit impostato a 1
crea all'altra estremit sempre impostato a O (nel caso di un numero intero
signed negativo, lo scorrimento a destra fa in modo che all'inizio del numero
venga conservato il bit a 1 che corrisponde al segno negativo). Occorre ricordare
01111111 127 in binario che uno scorrimento non una rotazione. Questo significa che i bit che fuoriescono
& -------- AND bit-a-bit da un'estremit non riappaiono all'altra estremit e vengono pertanto persi.
01000001 ''A" senza parit Le operazioni di scorrimento dei bit possono essere molto utili per decodificare
l'input di un dispositivo esterno, come ad esempio un convertitore digitale/analo-
L'operatore di OR bit-a-bit il contrario dell'operatore AND e pu essere gico e per leggere informazioni di stato. Gli operatori di scorrimento bit-a-bit
utilizzato per impostare a 1 un bit. Infatti, il bit risultante sar uguale a 1 se alme- consentono inoltre eseguire velocissime moltiplicazioni e divisioni di interi. Uno
no uno dei bit che fungono da operandi uguale a 1. Ad esempio, la seguente scorrimento a destra infatti divide un numero per due e uno scorrimento a sinistra
operazione corrisponde a 128 I 3: lo moltiplica per due, come si pu vedere dalla Tabella 2.7. Il programma seguen-
te illustra l'uso degli operatori di scorrimento:
10000000 128 in binario
00000011 3 in binario /* Un esempio di scorrimento di bit. */
OR bit-a-bit #include <stdio.h>
10000011 risultato
int main(void)
{
Un'operazione di OR esclusivo, normalmente abbreviato con XOR, imposta
unsigned"int i;
un bit a 1 se e solo se i bit confrontati sono diversi: Ad esempio, il risultato di int j;
127/\120 :
i = l;
01111111 127 in binario
01111000 120 in binario /* scorrimenti a sinistra */
I\
XOR bit-a-bit for(j=O; j<4; j++) {
00000111 risultato i = i 1; /* scorrimento a sinistra di i di 1 pos1z1one,
che corrisponde a una moltiplicazione per 2 */
printf("Scorrimento a sinistra di %d: %d\n", j, i);
Occorre ricordare che gli operatori relazionali e logici producono sempre un
risultato uguale a Oo I, mentre le analoghe operazioni bit-a-bit possono produrre
un valore diverso, sulla base dell'operazione eseguita. In altre parole, le operazio- /* scorrimenti a destra */
ni bit-a-bit possono produrre valori diversi da O o 1, mentre gli operatori logici for(j=O; j<4; j++) {
restituiscono sempre un valore pari a Oo a 1. i " i 1; /* scorrimento a destra di i di 1 posizione,
Gli operatori di scorrimento di bit, >>e<<, spostano tutti i bit di una variabile che corrisponde a una divisione per 2 */
rispettivamente verso destra o verso sinistra. La forma generale di un'istruzione printf("scorrimento a destra di %d: %d\n", j, i);
di scorrimento a destra :

variabile>> numero di posizioni return O;

La forma generale di un'istruzione di scorrimento a sinistra :


-.L'operatore di complemento a uno,-, inverte lo stato di ogni bit del SU() ope- .
variabile << 1111mero dLposizioni rando. In altre parole ogni 1 sar tramutato in uno Oe viceversa.
50 CAPITOLO 2 L E-:-E S PRESSI O NT- - 51

Tabella 2.7 Moltiplicazione e divisione con gli operatori di scorrimento. La forma generale dell'operatore ternario? la seguente:
UNSIGNED CHAR X; VALORE DI X DOPO L'ISTRUZIONE VALORE DIX
Espi? Esp2: Esp3;
X=7; 00000111

X=X1; 00001110 14 dove Espi, Esp2 e Esp3 sono espressioni. Si noti l'uso e la posizione dei due
punti.
X=X<<3; 01110000 112
L'operatore? esegue la seguente operazione: viene valutata l'espressione Espi;
X=X2; 11000000 192 se vera, viene valutata Esp2 che diverr il valore dell'espressione. Se Espi
falsa, viene valutata Esp3 e il suo valore diviene il valore dell'espressione. Ad
X=X>>1; 01100000 96
esempio, in:
X=X2; 00011000 24

Ogni sconimento a sinistra moltiplica il numero per due. Come si pu notare dopo l'operazione x<<2, si perde un bit a un'estremit. X = 10;
Ogni scorrimento a destra divide Il numero per due. Come si pu notare le successive divisioni non fanno riapparire i bit persi.
y = x>9 ? 100 : 200;

a y viene assegnato il valore 100. Sex fosse stata minore di 9, ad y sarebbe stato
Gli operatori bit-a-bit sono molto utilizzati nelle routine di cifratura. Se si assegnato il valore 200. Lo stesso codice scritto utilizzando un'espressione if-else
vuole fare in modo che un file appaia illeggibile, basta eseguire su di esso qualche
avrebbe il seguente aspetto:
operazione bit-a-bit. Uno dei metodi pi semplici consiste nell'eseguire il com-
plemento di ogni byte utilizzando il complemento a 1 per invertire tutti i bit del
= 10;

>
X
byte, come indicato dal seguente esempio:
if(x>9) y = 100;
Byte originale 00101100 else y = 200;
Dopo il primo complemento 110100 li Uguaii
Dopo il secondo complemento 00101100 L'operatore ? verr discusso dettagliatamente nel Capitolo 3 insieme alle altre
istruzioni condizionali.
Una sequenza di due complementi su un byte produce sempre il numero ori-
ginale. Pertanto, il primo complemento rappresenta la versione codificata del byte
e il secondo complemento decodifica il byte ritornando al valore originale. Gli operatori per i puntatori: & e *
Per co<!ificare un carattere si pu utilizzare la funzione encode() mostrata di
seguito: Un puntatore l'indirizzo in memoria di un oggetto. Una variabile puntatore
una variabile dichiarata in modo specifico per contenere un puntatore a un ogget-
/* Una semplice funzione di cifratura. */ to di un determinato tipo. La conoscenza dell'indirizzo di una variabile pu essere
char encode(char eh) molto importante in alcuni tipi di routine. Tuttavia, in C/C++ i puntatori hanno
{ principalmente tre funzioni. Possono essere un metodo rapido per far riferimento
return(-ch); /*complemento*/ agli elementi di un array. Essi consentono alle funzioni C di modificare i parame-
tri di chiamata. Infine, consentono la creazione di liste concatenate e di altre strut-
ture di dati dinamiche. I puntatori vengono discussi approfonditamente nel Capi-
tolo 5. In questo Capitolo si introducono semplicementei due operatori utilizzati
L'operatore ? per manipolare i puntatori.
Il primo operatore &;----n-peratore unario che restituisce l'indirizzo in me-
Il C/C++ contiene un operatore molto potente e comodo che sostituisce alcune moria del proprio operando (un operatore unario richiede un solo operando). Ad
istruzioni nel formato if-then-else. esempio, _ _ ___ __ _ ___ _
-- --- -- - - - - - -
52 -G-A-P-1-T 0-k.-0-2

m = &count;
-,
f In una stessa istruzione di dichiarazione possibile utilizzare variabili norma-
!
i li e variabili puntatore. Ad esempio,
inserisce nella variabile m l'indirizzo di memoria in cui si trova la variabile eount.
Questo indirizzo corrisponde alla posizione fisica della variabile nella memo- int x, *y, count;
ria del computer. Quindi l'indirizzo non ha nulla, a che vedere con il valore di
count. Si pu quindi pensare a & leggendolo come "indirizzo di". Pertanto, l'istru- dichiara x e eount come tipi interi e y come un puntatore a un tipo intero.
zione di assegnamento precedente sign,ifica "m riceve l'indirizzo di count". Il programma seguente utilizza gli operatori * e & per inserire i~ val~re 1?
Per meglio comprendere questo assegnamento, si pu assumere che la varia- nella variabile target. Come ci si pu attendere, questo programma v1sual1zza Il
bile eount si trovi nell'indirizzo di memoria 2000. Inoltre si pu assumere che valore 10.
eount contenga il valore 100. Quindi, dopo 1'9:Ssegnamento precedente, in m si
trover il valore 2000. #include <stdio.h>
Il secondo operatore per i puntatori *, che l'inverso di &. L'operatore * un
operatore unario che restituisce il valore della variabile che si trova all'indirizzo int main(void)
che segue l'asterisco. Ad esempio, se m contiene l'indirizzo di memoria della {
variabile count, i nt target, source;
int *m;
'
q = *m;
I source = 10;
m = &source;
inserisce il valore di eount in q. Ora in q si trover il valore 100, poich all'indiriz-
zo 2000 (l'indirizzo di memoria che memorizzato in m) si trova il valore 100. Si
I i
target = *m;

pu pensare a* leggendolo come "all'indirizzo". In questo caso, l'istruzione pre- printf("%d", target);
l
cedente si potrebbe leggere come "q riceve il valore che si trova all'indirizzo m".
Sfortunatamente, il simbolo di moltiplicazione e il simbolo "all'indirizzo" sono !
i
return O;
identici cos come i simboli per l'operatore AND bit-a-bit e il simbolo "indirizzo
di". Nonostante ci, questi operatori non hanno alcuna relazione gli uni con gli altri.
Gli operatori & e * hanno entrambi una precedenza pi elevata rispetto a tutti gli
operatori aritmetici tranne il meno unario il quale ha la loro stessa precedenza. L'operatore sizeof
Le variabili che devono contenere indirizzi di memoria (ad esempio i puntatori) sizeof un operatore unario che interviene al momento del!~ compilaz~o~e resti-
devono essere dichiarate inserendo un davanti al nome della variabile. Questo tuendo la lunghezza, in byte, di una variabile o di uno spec1ficatore d~ tipo rac-
comunica al compilatore che la variabile deve coriteneretilpi:mtatore. Ad esem- chiuso fra parentesi. Ad esempio, assumendo che gli interi siano formati da 4 byte
pio, per dichiarare eh come puntatore a un carattere, si deve utilizzare la forma: e i double da 8 byte, il frammento di codice
char *eh;
doubl e f;

In questo caso, eh non un carattere ma un puntatore a un carattere: la diffe- printf("%f ", sizeof f);
renza notevole. Il tipo di dati puntato dal puntatore, in questo caso char, detto printf("%d", sizeof(int));
tipo base del puntatore. Tuttavia, anche il puntatore una variabile che contiene
l'indirizzo di un oggetto del proprio tipo base. Pertanto, un puntatore a carattere visualizzer i numeri 8 e 4.
(o un qualsiasi puntatore) ha dimensioni sufficienti per contenere un iii.Oirizzo; Per calcolare le dimensioni di un tipo, qqest'ultimo deve ess~re racchiuso fra
tali dimensioni sono_definite dall'architettura del computer utilizzato. Tuttavia, parentesi. Questo non invece necessario nel caso di nom! di vari~bili.
occorre ricordare che un puntatore pu puntare solo a dati corrispondenti al pro- n C/C++ definisce (con typedef) un tipo particolare chiamato s1ze_t, _che cor-
prio tipo base. risponde a grandiJ.iM~_a un intero unsigne~!ecnicament~~ ~alor~ restituito da
--i~
54 C APl TG LO 2 L E E S P-R-E-8-S+G-N I - 55

sizeof di tipo size_t. Per ogni utilizzo pratico, tuttavia, si pu utilizzare un valore
intero unsigned. -
-1 Gli operatori punto(.) e freccia(->)

In C gli operatori . (punto) e-> (freccia) consentono di accedere ai singoli ele-


sizeof aiuta principalmente a generare codice tr;:i.portabile che dipende dalle
dimensioni dei tipi standard del C. Ad esempio, si immagini un programma di menti delle strutture e delle unioni. Le strutture le unioni sono tipi aggregati ai
quali possibile fare riferimento con un solo nome (vedere il Capitolo 7). In C++
database che debba memorizzare sei valori interi in ogni record. Se si vuole ren-
dere il programma trasportabile su pi piattaforme, non si deve fare alcuna assun- I gli operatori punto e freccia vengono utilizzati anche per accedere ai membri di
zione sulle dimensioni degli interi ma determinare la loro lunghezza effettiva con
sizeof. In questo modo, per scrivere un record sul file si potr usare una routine I
i
'\ma classe.
L'operatore punto utilizzato quando si opera direttamente sulla struttura o
sull'unione. L'operatore freccia utilizzato quando si opera con un puntatore a
simile alla seguente:
una struttura o a un'unione. Ad esempio, dato il frammento di codice,
/* Seri ve 6 interi su un fil e. I
void put rec(int rec[6], FILE fp) struct employee
{ - I {
int len; i char name[SO];
I i nt age;
i
len = fwrite(rec, siz!!of(int)*6, 1, fp); ! float wage;
if(len != 1) printf("Errore di scrittura"); emp;

struct employee *p = &emp; /* a p viene assegnato l'indirizzo di emp */


In questo modo, put_rec() viene compilata ed eseguita correttamente su qual-
siasi computer, indipendentemente dalle dimensioni in byte di un intero. Un 'ulti- per assegnare il valore 123.23 al membro wage della variabile strutturata emp si
ma annotazione: sizeof viene valutato al momento della compilazione e il valore deve utilizzare la seguente riga di codice:
prodotto viene trattato all'interno del programma come se fosse una costante.
emp.wage = 123.23;

L'operatore virgola Se invece si utilizza il puntatore alla variabile emp, si dovr utilizzare la riga:

L'operatore virgola consente di concatenare pi espressioni. II lato sinistro del- p->wage = 123.23;
.- Lo..12-er~tQre virgola viene sempre valutato come void. Questo significa che l'espres-
sione che si !rova al lato destro diviene il valore dell'intera espressione complessa
separata da virgole. Ad esempio, l'espressione Gli operatori ( ) e [ ]

x=(y=3, y+l); Le parentesi tonde sono operatori che aumentano la precedenza delle operazioni
che racchiudono.
assegna a y il valore 3 e quindi a x il valore 4. Le parentesi sono necessarie Le parentesi quadre consentono di accedere tramite indici agli elementi di un
poich l'operatore virgola ha una precedenza pi bassa rispetto all'operatore di array (gli array verranno discussi approfonditamente nel Capitolo 4 ). Dato un
assegnamento. array, lespressione fra parentesi quadre deve fornire un indice per accedere
_ Essenzialmente,_I~_virgola crea una sequenza di operazi9ni. Se utilizzata sul all'array. Ad esempio,
lato destro di un'istruzione di assegnamento, il valore assegnato quello dell'ul-
tima espressione dell'elenco separato da virgole. #include <stdio.h>
L'operatore \'irgola pu essere considerato come la conofonzione "e" del lin- char s [80);
guaggio naturale cos come viene utilizzata nella frase "fai ~uesto-e-questo''.
56 CAPITOLO 2 LE ESPRESSIONI .57

r Ordine di valutazione
s[3] = 'X';- I N il C n il C++ specificano l'ordine in cui devono essere v_~lutate le
printf("%c", s[J]};
I sottoespressioni di un'espressione. Questo lascia il compilatore libero di disporre
return O;
i un'espressione in modo da produrre codice il pi possibile ottimizzato. Tuttavia,
questo significa anche che il codice prodotto non deve mai fare affidamento sul-
!' ordine di valutazione delle sottoespressioni. Ad esempio nell'espressione
prima assegna il valore "X" al quarto elemento (in CIC++ gli array iniziano dal-
l'indice O) dell'array se quindi stampa tale elemento. fl(} + f2(};

I
X=

Riepilogo dell'ordine di precedenza non si pu essere certi che f1() venga richiamata prima di f2().
i
La Tabella 2.8 elenca la precedenza di tutti gli operatori C. Occorre notare che tutti
Conversioni di tipo nelle espressioni
gli operatori, ad eccezione degli operatori unari e del ? , sono associativi da sinistra
a destra. Gli operatori unari (*, &, -) e ? sono associativi da destra a sinistra. Quando in un'espressione si utilizzano costanti e variabili di tipi diversi, tutti i
'.NOt~:.;;:~J:a::::lil Il C++ de.finisce alcuni operatori aggiuntivi che verranno per
valori vengono convertiti nello stesso tipo. Il compilatore converte tutti gli operandi
discussi dettagliatamente nella Parte seconda di questa guida in modo che assumano lo stesso tipo dell'operando pi esteso; questa operazione
chiamata promozione di tipo. Questo significa che tutti i char e gli short int
vengono convertiti automaticamente in int. Questo processo chiamato promo-
zione intera. Terminata questa fase, tutte le altre conversioni vengono eseguite-
2.1 O le espressioni operazione per operazione secondo quanto descritto nel seguente algoritmo per la
conversione dei tipi:
Gli operatori, le costanti e le variabili sono gli elementi costitutivi delle espressio-
ni. Un'espressione CIC++ una combinazione valida di questi elementi. Poich SE un operando un long double
la maggior parte delle espressioni tende a seguire le regole generali dell'algebra, ALLORA il secondo convertito in long double
si d per nota la loro conoscenza. Tuttavia, vi sono alcuni aspetti delle espressioni ALTRIMENTI SE un operando un double
che sono specifici del Ce del C++. ALLORA il secondo convertito in double
ALTRIMENTI SE-un operando un float
Tabella 2.8 La precedenza degli operatori C. ALLORA il secondo convertito in float
ALTRIMENTI SE un operando un unsigned long
Alta OD->. ALLORA il secondo convertito in unsigned long
I - ++ (tipo) & sizeof
'/% ALTRIMENTI SE un operando un long
+. ALLORA il secondo convertito in long
<< >>
< <= > >= ALTRIMENTI SE un operando un unsigned int
= != ALLORA il secondo convertito in unsigned int
&

I Vi _IJ.nche un caso speciale: se un oper.ando long e l'altro unsigned intese


&&
Il il valore del unsigned int non pu essere rappresentato da un long, entrambi gli
?: operandi vengono convertiti in un unsigned long.
= += = '= I= ecc. Al termine di queste conversioni, tutte le coppie di operandi saranno dello
stesso tipo e il risultato di ogni operazione sar dello stesso tipo di entramblgli
_ -=-~pe_ran_qL_ _ _ --'---=-'-- - - --
- ----- ---==-=.---- ,_ - .
--
58 CAPITOLO 2
LE- EsTR-i=-s s 1oNI -~59 - -- -

Ad esempio, si considerino le conversioni di tipo che avvengono nella Figura ponga di voler usare un intero per controllare un ciclo e di dover eseguire un' ope-
2.2. Innanzi tutto, il carattere eh viene convertito in un intero .. Quindi, il risultato razione che richiede una parte frazionaria, come nel seguente programma:
di eh I i viene convertito in un double poich f*d un double. Il risultato di f+i
un float poich f un float. Il risultato finale double #include <stdio.h>

int main(void) /* stampa i e i/2 con frazioni */


Conversioni cast {
int i;
Questo tipo di conversione costringe un'espressione ad assumere un determinato
tipo. La forma generale di conversione cast : for(i=l; i<=lOO; ++i}
(tipo) espressione printf("%d / 2 uguale a: %f\n", i, (float) /2);
dove tipo un tipo valido per il C. Ad esempio, per essere sicuri che l'espressione
return O;
x I 2 fornisca un risultato di tipo float, si deve usare I' espressione

(float}x / 2
Senza la conversione (float), verrebbe eseguita una divisione intera. La con-
versione cast assicura che venga sempre visualizzata anche la parte frazionale
Le conversioni cast corrispondono tecnicamente ad operatori. Come operato-
della risposta.
re il cast unario ed ha la stessa precedenza di ogni altro operatore unario.
Anche se le conversioni cast non sono fra le operazioni pi utilizzate in pro- [RQT.A~::;;;.:;;::_-:_:Jj Il C++ definisce alcuni operatori di conversione cast aggiun-
grammazione, possono, in alcuni casi rivelarsi molto utili. Ad esempio, si sup- tivi (come ad esempio const_cast e static_cast) che verranno per discussi
dettagliatamente nella seconda parte di questa guida

charch; Spaziatura e parentesi


int i;
Per semplificare la lettura delle espressioni si possono utilizzare caratteri di
float f; tabulazione e spazi. Ad esempio, le due espressioni seguenti sono identiche:
double d;
result=(ch/i) + (f*d) (f+i); x=lO/y - (127 /x);

~ 00~ ~ x = 10 / y - (127/x);

Eventuali parentesi in eccesso non provocano alcun errore n rallentano I' ese-

ID~t
cuzione dell'espressione. Quindi si possono usare le parentesi per rendere pi
chiaro l'ordine di valutazione esatto di un'espressione, sia per se stessi che per gli
altri. Ad esempio, quale delle due espressioni seguenti pi facile da leggere?

x=y/2-34*temp&l27;
-----double x = (y/3) - ((34*temp) & 127);

Figura 2.2 Un esempio di conversionedi tipo-.- - -

::.::::------ - -- -
60 CAPITOLO

Abbrevi~zioni C : Capitolo 3
Una variante dell'istruzione di assegnamento semplifica la codifica di d~tennina Le istruzioni
te operazioni di assegnamento. Ad esempio,

X = x+lO; 3.1 La verit e la falsit in C e C++


3.2 Le istruzioni di selezione
pu essere scritta come 3.3 Le istruzioni di iterazione
3.4 La dichiarazione di variabili
X += 10; nelle istruzioni di selezione e iterazione
3.5 Le istruzioni di salto
L'operatore+= chiede al compilatore di assegnare a x il valore dix pi JO.
3.6 Le espressioni
Questa abbreviazione funziona con tutti gli operatori binari (gli operatori che .
richiedono due operandi). In generale, le istruzioni come: 3.7 I blocchi

var = var operatore _espressione

pu essere riscritta come Questo capitolo si occupa delle istruzioni. In senso


generale, un'istruzione un elemento eseguibile del programma; quindi un'istru-
zione specifica "un'azione. Il C/C++ raggruppa le istruzioni nei seguenti gruppi:
var operatore= espressione
istruzioni di selezione
Ad esempio, istruzioni di iterazione
istruzioni di salto
X = X-100; etichette
espressioni
pu essere scritta come
blocchi
X -= 100; Fra le istruzioni di selezione vi sono if e switch (al posto di "istruzione di
selezione" viene spesso utilizzato il termine "istruzione condizionale"). Le istru-
zioni di iterazione sono while, tor e do-while. Queste vengono normalmente chia-
La notazione abbreviata ampiamente utilizzata nei programmi C/C++ scritti
dai professionisti; quindi molto importante conoscerne bene l'uso.
mate istruzioni di ciclo. Le istruzioni di salto sono break, continue, goto e return.
Le istruzioni a etichette includono case e default (discusse insieme all'istruzione
switch) e l'istruzione di etichetta (discussa con goto). Le istruzioni di espressione
sono istruzioni formate di una espressione valida in C. Le istruzioni a blocco sono
formate semplicemente da blocchi di codice (come si ricorder un blocco inizia
con una { e termina con una }). Si fa riferimento ai blocchi come a istruzioni
composte.
[f!Qt~;;';;;,-r;;,_ff: Il C++ aggiunge anche due nuovi tipi di istru:.ioni: il blocco
try (per la gestione delle eccezioni) e l'istruzione di dichiarazione-che verranno
discussi nella Parte seconda.

- ------ --- - - -- -
---_ ~ _.::;..... --
--62- CAPITOLO 3

Poich molte espressioni Cusano il risultato di alcuni test condizionali, si pu In e, l'istruzione condizionale che controlla l'if deve produrre un risultato
iniziare parlando del concetto di ~erit e falsit. scalare. Uno scalare pu essere un intero, un carattere, un punt~tore ~un numero
in virgola mobile. In C++ pu anche essere di tipo bool. raro 1 uso di un numero
in virgola mobile per controllare un'istruzione condizionale ~n qu~to que~to r~
lenta considerevolmente il tempo di esecuzione (per esegmre _un operazione m
3.1 La verit e la falsit in ee C++ virgola mobile occorrono molte pi istruzioni rispetto alla comspondente opera-
zione con un intero o un carattere).
Molte istruzioni C si basano su un'espressione condizionale che determina l' azio- n seguente programma contiene un esempio di if. Il programma presen~a u.na
ne che deve essere intrapresa. Un'espressione condizionale pu fornire un risulta- versione molto semplice del gioco "indovina il numero magico". Quando il gi~
to vero o falso. In C il valore vero corrisponde a qualsiasi valore diverso da zero, catore indovina il numero, visualizza il messaggio ** .corrett~ . Pe~ generare li
inclusi i numeri negativi. Il valore falso corrisponde allo O. Questo approccio alla numero magico, il programma utilizza il generatore d1 numen casuali ran~(), che
verit e alla falsit consente di codificare un'ampia gamma di routine in modo restituisce un numero arbitrario compreso fra O e RAND_MAX ~eh: defi~1sce un
estremamente efficiente. Il linguaggio C++ supporta la definizione di true e false valore intero maggiore 0 uguale a 32767). La funzione rand() nchiede 1 uso del
(zero I non-zero) appena descritta. Ma il C++ definisce anche un tipo di dati file header stdlib.h. Un programma C++ pu anche usare il nuovo file header
booleano chiamato bool che pu assumere i soli valori true e false. Come si <CStdlib>.
detto nel Capitolo 2, in C++ il valore Oviene automaticamente convertito in false
e un valore diverso da zero viene automaticamente convertito in true. Vale anche /* Progra11111a del numero magico - Versione 1. */
il contrario: true viene convertito in 1 e false viene convertito in O. In C++, I' espres- #i nel ude <stdi o. h>
sione che controlla un'istruzione condizionale tecnicamente di tipo bool. Ma #include <stdlib.h>
poich ogni valore diverso da Oviene convertito in true e il valore Oviene conver-
tito in false, sotto questo punto di vista non esistono differenze pratiche fra C e i nt mai n(voi d)
C++. {
i nt magi e; /* numero magico *I
int guess; /* valore i11111esso dall'utente */

3.2 Le istruzioni di selezione magie = rand(); /* genera il numero magico */

11 C/C++ consente di utilizzare due istruzioni di selezione: ife switch. In alcune printf("Indovina il numero magico: ");
circostanze, in alternativa a if si pu utilizzare l'operatore?. scanf("%d", &guess);

if(guess == magie) printf("** Corretto-""'~};


L'istruzione if
return O;
La forma generale dell'istruzione if la seguente:
Sviluppando ulteriormente il program~a del numero magico, la .versione su~
if(espressione) istruzione;
cessiva illustra l'uso dell'istruzione else per stampare un messaggio nel caso m
else istruzione;
cui il numero sia errato.
dove istruzione pu essere una singola istruzione, un blocco di istruzioni o nulla
(in questo caso si parla di istruzioni vuote). La clausola else opzionale. /* Progra11111a de 1 numero .magico - V.ers ione 2. *I
#include <stdio.h>
Se espressione fornisce un risultato vero (diverso da zero), viene eseguita
#tm:lude <stdl ib.h>
l'istruzione o il blocco relativo all'if; in caso contrario, verr eseguita, se esiste,
l'istruzione (o il blocco) relati.va-all'else. Occorre ric_ordare che viene ese_gito int main(void)
solo il codice associato a!!'jf qppurejl codice associato all'else,.mai entrambi.
---- - -
- -- ~CAPTfOLO

/* Prograrrma de 1 numero magico - Versione 3. *I


int magie; /* numero magico */ #include <stdio.h>
int guess; /*valore irrmesso dall'utente*/ #include <stdlib.h>

magi e = rand O; /* genera il numero magico */


int main(void)
pri ntf("Indovina il numero magico: "); {
scanf("%d", &guess); int magie; /* numero magico */
int guess; /* valore irrmesso dall'utente */
if(guess == magie) printf("** corretto **");
else printf("Errato"); magi e = rand(); /* genera il numero magico */

return O; printf("Indovina il numero magico: ");


seanf("%d", &guess);

if (guess == magie) {
lf nidificati printf{"** Corretto**");
printf("Il numero magico : %d\n", magie);
Un if nidificato un if che si trova all'interno di un altro if o di un else. Gli if }
nidificati sono molto comuni in programmazione. In un if nidificato, un istruzione else {
else fa sempre riferimento all'istruzione if pi vicina che si trova all'interno dello printf("Errato, ");
stesso blocco e alla quale non sia associato nessun altro else. Ad esempio, if(guess > magie) printf("troppo alto\n");
else printf("troppo basso\n");
if(i)
{
if(j) istruzione 1; return O;
if(k) istruzione 2; /* questo if */
else istruzione 3; /* associato a questo else */

else istruzione 4; /*associato a if(i) */ Il costrutto if-else-if


Si tratta di un costrutto molto comune in programmazione; la sua forma generale :
Come indicato nel listato, I' else finale non associato a ifO) poich non si
trova nello stesso blocco. Infatti, !'else finale associato all'istruzione if(i). Inol-
tre, !'else interno associato a if(k), che l'if pi vicino. if (espressione)istruzione;
Lo Standard C specifica che debbano essere consentiti almeno 15 livelli di else
nidificazione. In pratica, la maggior parte dei compilatori consente un numero di if (espressione)istruzione;
livelli molto maggiore. Ma soprattutto, lo Standard C++ prevede l'uso in un pro- else
gramma C++ di un massimo di 256 livelli di if nidificati. In ogni caso, consigliabile if (espressione)istruzione;
contenere il pi possibile. il livello di nidificazione per evitare di confondere il
significato di un algoritmo.
L'uso di if nidificati consente di migliorare ulteriormente il programma -del
numero magico, fornendo al giocatore ulteriori informazioni in caso di errore. else istruzione;
L C. I .:::> I nu I- I .,:, l'f 1 ~ 01

Le condizioni vengono valutate dall'alto verso il basso. Quando viene trovata


else printf("Errato, troppo basso");
una condizione vera, viene eseguita l'istruzione associata e la parte rimanente del
costrutto viene ignorata. Se nessuna delle condizioni vera viene eseguita l'istru- return O;
zione associata all' else finale. Se I' else finale non presente, quando tutte le
condizioni sono false non viene eseguita alcuna istruzione.
Anche se lo schema di indentazione utilizzato precedentemente per il costrut-
to if-else-if tecnicamente corretto, pu portare a rientri troppo ingenti. Per que- L'alternativa: l'operatore?
sto motivo, il costrutto if-else-if viene normalmente indentato nel seguente modo:
L'operatore? pu sostituire le istruzioni if-else nella loro forma generale:
if (espressione)
istruzione; if (condizione) espressione
else if (espressione) else espressione
istruzione;
else if (espressione) Tuttavia, per poter utilizzare il punto interrogati~o necessari? che ~e esp~essio
istruzione; ni sia dell'if che dell'elsa siano una singola espressione e non un al~ 1struz10ne.
Il ? chiamato operatore ternario in quanto richiede tre operandi. La sua for-
ma generale :
else Espi ? Esp2: Esp3
istruzione;
dove Espi, Esp2 e Esp3 sono espressioni. Si noti l'uso e il posizionamento del
Utilizzando un costrutto if-else-if, il programma del numero magico diviene: segno di due punti. . .
Il valore di un'espressione ? determinato nel m~o se~~ente: mnan~~ tutto
/* Programma del numero magico - Versione 4. */ viene valutata Esp J. Se vera, viene valutata Esp2 che d1vei:a Il valore de~l .mter.a
#include <stdio.h>
espressione?. Se Espi falsa, allora viene v~utata.Es~J e Il suo valo.r~ d1v1~ne.~l
#include <stdlib.h>
valore dell'intera espressione. Ad esempio, s1 cons1dermo le seguenti 1struz1om.
int main(void)
{ X = 10;
int magie; /*numero magico *;-------.. y = x>9 ? 100 : 200;
int guess; /* valore immesso dall'utente */
In questo esempio, a y viene assegnato il valore 100. S~ x fos~e stata mi~~re di
magie = rand{); /* genera il numero magico */ 9, ad y sarebbe stato assegnato il valore 200. Lo stesso codice scntto con un istru-
zione if-else avrebbe avuto il seguente aspetto:
printf("Indovina il numero magico: ");
scanf("%d", &guess); X = 10;
if(x>9) y = 100;
if(guess == magie) { else y = 200;
printf("** Corretto** ");
printf("Il numero magico : %d", magi e);
Il seguente programm~ utilizza l'operator~ ? per calcolare il quadrato ~i un
. effelf(guess > magie) numero intero immesso datl'utente. Tuttavia, questo programma conserva Il se-
printf("Errato, troppo alto"); gno (1 o al quadrato dar 100 e -1 Oal quadrato dar -100) .
L} I S T R U Z I O N t- 69

#include <stdio.h> 11
printf("%d , n);
return O;
int main(void)
{
int i sqrd, i;
f2 (voi d)
{
printf("Immettere un numero: 11 ) ;
printf(" il valore immesso");
return O;
scanf("%d", &i);

isqrd "'i>O ? i*i : -(i*i);


Se in questo esempio si immette uno O, verr richiamata la funzione printf()
printf("%d al quadrato uguale a %d", i, isqrd); che visualizza il messaggio stato immesso lo zero. Se invece si immette un altro
numero, verranno eseguite le funzionif1 ()e f2(). In questo esempio il valore del-
return O; l'espressione? viene ignorato.
Alcuni compilatori C++ cercano di ridisporre lordine di valutazione delle
espressioni per cercare di ottimizzare il codice oggetto. In questi casi, le funzioni
L'uso dell'operatore? per sostituire le istruzioni ifelse non si limita solo acrli che formano gli operandi dell'operatore? possono essere eseguite in una sequen-
a:se~amenti. Tutte le funzioni (tranne quelle dichiarate come void) possono ;e. za errata.
sttturre. un va.lor7. Pertanto, ~~ un espressione possibile utilizzare una 0 pi chia- Utilizzando l'operatore? si pu riscrivere il programma del numero magico
~ate d1 funz1om. Qu~d~ s1 mcontra il nome della funzione, essa viene eseguita nel seguente modo:
m mod~ che po~~a rest~tui:e .un valore. Pertanto, possibile eseguire con un ope-
ra~ore una o pm funz1om, inserendo le chiamate nelle espressioni che formano /* Programma del numero magico - Versione 5. */
gli operandi di?, come in: #include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
int main(void)
int fl(int n); {
int f2(void); int magie;
int guess;
1nt rnain(void) magi e = rand(); /* genera il numero magico */
{ printf("Indovina il numero magico: ");
1nt t; scanf("%d", &guess);

prfntf("Inserire un numero: "); if(Guess == magie) {


scanf("lsd", &t); printf("** Corretto ** "):
printf("Il numero magico : %d", magie);
/ stampa.un messaggio appropriato */
t ? n(t) + fZ() : printf(" stato immesso lo zero"); else
guess > magie ? printf("Alto") printf("Basso");
m~o;
return O;

.~ fl(inc.'. n).

' --:.:.::.=_..-:_ __ - - --- - -


- - - --- Qui, l'operatore ? visualizza il messaggio corretto sulla base del risultato del
test guess>magic;- - - - - _______ -- ____ ___ ....

.. -
70 CAPITO~O 3 - -

l'espressione condizionale
di costanti intere o caratteri. Quando viene trovata una corrispondenza, vengono
eseguite le istruzioni associate alla costante. La forma generale dell'istruzione
Talvolta.,.coloro che incontrano per la prima volta il C/C++ rimangono confusi dal
switch la seguente:
f~tto che~ possibile co~tro.llare gli operatori if o ? utilizzando una qualsiasi espres-
s~one vali~a. Quest~ significa che non si costretti a usare le sole espressioni
nguardann operaton relazionali o logici (come nel caso del BASIC o del Pascal) switch (espressione) {
L'espressione deve semplicemente restituire un valore false o true (uguale 0 di~ case costante]:
verso da zero). Ad esempio, il seguente programma legge due interi dalla tastiera sequenza istruzioni
e visualizza il quoziente. Il programma utilizza un'istruzione if, controllata dal break;
secondo numero per evitare lerrore di divisione per zero. case costante2:
sequenza istruzioni
/* Divide il primo numero per il secondo. */ break;
case costante3:
#include <stdio.h> sequenza istruzioni
break;
int main(void)
{
int a, b;
default
printf("Immettere due numeri: ");
sequenza istruzioni
scanf("%d%d", &a, &b);
}
if(b) printf("%d\n", a/b);
else printf("Non possibile dividere per zero.\n"); L'espressione deve fornire un valore costituito da un carattere o da un intero.
Ad esempio non consentito l'uso di espressioni in virgola mobile. Il valore di
return O; espressione viene confrontato, nell'ordine, con i valori delle costanti specificate
nelle istruzioni case. Quando viene trovata una corrispondenza, viene eseguita la
sequenza istruzioni associata al case e ci fino alla successiva istruzione break o
Questo approccio funziona poich se b uguale a O, la condizione che con- alla fine dello switch. L'istrzione default viene eseguita solo se non viene trovata
trolla l'if falsa e viene eseguita l'istruzione dell'elsa. In caso contrario la condi- alcuna corrispondenza. Il default opzionale e, se assente, fa in modo che, nel
~one sar v:_ra (diversa da zero) e avr quindi luogo la divisione. L'uso di un'istru- caso1n cufnon venga trovata alcuna corrispondenza, non venga eseguita alcuna
z10ne if come la seguente: operazione.
Il C standard specifica che uno switch possa contenere almeno 257 istruzioni
if(b != O) printf("%d\n", a/b); case. Il C++ standard suggerisce che il compilatore accetti almeno 16.384 istru-
zioni case. In pratica, per motivi di efficienza, preferibile limitare il pi possibi-
ri~ond~te ~ potenzialme~te ine~cie~te e inoltre considerata un esempio di le il numero di istruzioni case. Anche se case un'istruzione di etichetta, non pu
~~ttivo s~ile d1 programmaz10ne. P01ch il valore di b sufficiente per controllare esistere da sola, all'esterno di uno switch.
1 1f, non e necessario confrontarlo con Io zero. L'istruzione break una delle istruzioni di salto del C/C++. possibile utiliz-
zarla in cicli e in istruzioni switch (vedere la sezione "Le istruzioni di iterazione").
Quando viene raggiunto urrbreak in uno switch, l'esecuzione del programma "salta"
L'istruzione switch alla riga di codice che segue l'istruzione switch.
Vi sono tre cose importanti da sapere sull'istruzione switch:
--1LC!C++ dotato di uO:istruzione...dLselezione a pi opzioni, chiamata switch che Lo switch diverso dal if poich il primo esegue solo verifi.pe di .JJgu.aglianza
_ __ _ col!!!'olla in successione il_vajg_re-di un'espressione confrontandolocorr un-el~nco mentre il secondo_pq yalutare.ogni tipo di espressione relazionale _9J_og~ca. _
- - - - ------- --
72 CAPITOLO 3 LE ISTRUZIONI 73

Non possibile specificare due costanti case con valori identici neo stesso /* Elabora un va 1ore */
switch. Naturalmente, un'istruzione switch racchiusa in un'altra istruzione switch void inp handler(int-i)
esterna pu avere c'ostanti case uguali allo switch esterno.
{ -
-int flag;
Se in un'istruzione switch vengono utilizzate costanti di tipo carattere, esse
verranno automaticamente convertite in interi.
flag = -1;
L'istruzione switch .normalmente utilizzata per gestire i comandi alla tastie-
ra, come ad esempio la scelta di opzioni da un menu. Come si pu vedere nel- switch(i)
l'esempio seguente, la funzione menu() visualizza un menu per un programma di case 1: /* questi case hanno sequenze di istruzioni */
verifica ortografica e richiama le procedure appropriate: case 2: /* comuni */
case 3:
void menu(void) flag = O;
{ break;
char eh; case 4:
flag = 1;
printf("l. Verifica ortografica\n"); case 5:
printf("2. Correzione errori ortografici\n"); error(fl ag);
printf("3. Visualizza errori ortografici\n"); break;
printf("Premere un altro tasto per uscire\n"); default:
printf(" Immettere la scelta: "); process (i) ;

eh = getchar(); /* 1egge i 1 tasto premuto */


swi tch (eh) { Questo esempio illustra due aspetti dello switch. Innanzi tutto, possibile
case '1': utilizzare istruzioni case senza alcuna istruzione associata. In questo caso, l'ese-
check_spelling(): cuzione salta semplicemente al successivo case. In questo esempio, i primi tre
break; case eseguono le stesse istruzioni che sono
case '2':
correct_errors (); flag = O;
break; break;
case '3':
display_errors();
break; In secondo luogo, l'esecuzione di una sequenza di istruzioni continua nel case
default : successivo finch non viene trovata un'istruzione break. Se i uguale a 4, flag
printf("Non stata selezionata al cuna opzione"); impostato a 1 e, poich non vi alcuna istruzione break al termine di tale case
l'esecuzione continua e richiama la funzione error(flag). Se i uguale a 5, viene
richiamata la funzione error(flag) con un valore di flag uguale a -1.
Il fatto che i case possano essere eseguiti insieme quando non si specifica
Tecnicamente, le istruzioni break che si trovano all'interno di un'istruzione un'istruzione break evita inutili duplicazioni di istruzioni, consentendo di produr-
switch sono opzionali. Esse concludono la sequenza di istruzioni associata ad ogni re un codi_ce pi efficiente.
costante. Se si omettesse l'istruzione break, l'esecuzione continuerebbe nella suc-
cessiva istruzione case e ci fino a raggiungere il primo break o fino alla fine
dello switch. Ad esempio, la seguente funzione utilizza questa caratteristica di
Istruzioni switch nidificate
case per se~p~ficare il ~odice di un gestore di input:
In C possibile inserire uno switch come parte di una sequenza di istruzioni di
uno switch _P-T-estmQ.:Anche_ s!:]~~()~_nfr case deglj_switch contengono valori
74 CAPITOLO 3 --LE ISTRUZIONI 75

comuni, questo non provocher alcun conflitto. Ad esempio, il seguente fram- Nel seguente programma, viene utilizzato un ciclo tor per visualizzare i nu-
mento di codice perfettamente corretto: meri da 1a100: --

switch(x) { #include <stdio.h>


case 1:
switch(y) int main{void)
case O: printf("Errore di divisione per zero. \n"); {
break int X;
case 1: process{x,y):
for(x=l; x <= 100; x++) printf("%d ", x);
break;
case 2: return O;

Nel ciclo, alla variabile x viene inizialmente assegnato il valore 1 che viene
poi confrontato con il 100. Poich x minore di 100, viene richiamata la funzione
printf() e si continua nel ciclo. La variabile x viene aumentata di una unit e nuova-
mente verificata per determinare se ancora minore o uguale a 100. In caso affer-
3.3 Le istruzioni di iterazione mativo, viene nuovamente richiamata printf(). Questo processo si ripete finch x
non diviene maggiore di 100, condizione di uscita dal ciclo. In questo esempio, x
In C/C++ e in tutti gli altri linguaggi di programmazione moderni, le istruzioni di la variabile di controllo del ciclo, che viene modificata e verificata ogni volta
iterazione (chiamate anche istruzioni di ciclo) consentono la ripetizione di un che il ciclo viene ripetuto.
gruppo di istruzioni fino al verificarsi di una determinata condizione. Questa con- Il seguente esempio un ciclo forche ripete l'esecuzione di pi istruzioni:
dizione pu essere predefinita (come ad esempio nel ciclo for) o aperta (come nel
caso dei cicli while e do-while). for(x=lOO; x != 65; x-=5 ) (
z = x*x:
printf("Il quadrato di %d %f", x, z):
Il ciclo for

La forma generale del ciclo for presente, in una forma o nell'altra, in-tutti i
linguaggi di programmazione procedurali. Tuttavia, in C/C++, presenta una fles- Sia il quadrato di x che la chiamata a printf() vengono eseguiti fintantoch x
sibilit, e quindi una potenza, molto maggiore. non diviene uguale a 65. Come si pu notare, il ciclo procede in senso negativo: x
La forma generale dell'istruzione for la seguente: inizializzata a 100 e ad ogni ripetizione del ciclo viene sottratto il valore 5.
Nei cicli for, il test condizionale viene sempre eseguito all'inizio del ciclo.
Questo significa che il codice all'interno del ciclo potrebbe anche non venir mai
for (inizializzazione; condizione; incremento) istruzione;
eseguito se la condizione dovesse essere falsa fin dall'inizio. Ad esempio, nel
frammento di codice:
Il ciclo tor consente molte varianti ma la sua forma pi comune funziona nel
modo seguente. L'inizializzazione normalmente un'istruzione di assegnamento
for(y=lO; y!=x: ++y) printf("%d", y):
utilizzata per impostare la variabile di controllo del ciclo. La condizione un' espres- printf("%d", y); /*questa l'unica istruzione printf() che verr eseguita
sione relazionale che determina l'uscita dal ciclo. L'incremento definisce il valore */
di cui la variabile di controllo deve-variare ad ogni ripetizione del ciclo. Queste tre
sezioni devono essere separate da un punto e virgola. Il ciclo for continua a essere ~ -~~"f'
il ciclo non verr mai eseguito poich-all-' ingresso del ciclo-X e y s_ono uguali.. P.er- -
---ripetuto finch la condizione si mantiene vera. Quando la condi::,ione diviene fai~_ __
sa, l'esecuzione del programma riprende d_al8stnizione che s_egue il for. -- ---r
i questo motivo, l'espressione q>qdi_zjonale_ fornir il valore f~s~ E;:__nQn-verranno
- ---- _...:::..; - -- ----

st -
-76---G-A P 11 O LO 3 ------ LE ISTRUZIONI 77

eseguiti n il corpo del ciclo n la porzione di incremento. Pertanto, y avr ancora int main{void)
il valore 1Oe l'unico output prodotto da questo frammento di programma sar una {

I'
char target[SO] = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
sola visualizzazione del numero IO.
converge(target, "Prova dell'uso di converge() 11 ) ;
printf("Stringa finale: %s", target);
Le varianti del ciclo for

La discussione precedente, si occupava della forma pi comune del ciclo for. Tut- i return O;
tavia vi sono molte varianti che aumentano la potenza, la flessibilit e l'applicabilit
del ciclo for in alcune situazioni.
/* Questa funzione copia una stringa in un'altra
Una delle varianti pi comuni prevede l'uso dell'operatore virgola che con- copiando I caratteri da entrambe le estremi ta
sente di controllare il ciclo con due o pi variabili (come si ricorder, l'operatore e convergendo al centro. * /
virgola consente di concatenare una serie di espressioni assumendo il significato
di "fai questo e questo"; vedere il Capitolo 2). Ad esempio, il seguente ciclo void converge(char *targ, char *src)
controllato dalle variabili x e y ed entrambe vengono inizializzate all'interno del- {
l'istruzione tor: int i, j;

for(x=O, y=O; x+y<lO; ++x) { printf( 11 %s 11 , targ);


y = getchar(); for(i=O, j=strlen(src); i<j; i++, j--) <R targ[iJ = src[i];
y = y- 1 0 1 ; /* sottrae a y targ[j] ~ src[j];
il codice ASCII di O */ printf( 11 %s 11 , targ);

Ecco l'output prodotto dal programma.

Le due istruzioni di inizializzazione sono separate da una virgola. Ad ogni


I' xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
1
ripetizione del ciclo, viene incrementata x e il valore di y determinato da ci che ; PXXXXXXXXXXXXXXXXXXXXXXXXXXXX
viene immesso alla tastiera. Il ciclo ha termine quando sia x che y hanno raggiun- PrXXXXXXXXXXXXXXXXXXXXXXXXXX.
to il valore corretto: Anche se il valore di y viene impostato tramite una lettura ProXXXXXXXXXXXXXXXXXXXXXXXX).
della tastiera, tale variabile deve essere inizializzata a Oin modo che il suo valore ProvXXXXXXXXXXXXXXXXXXXXXX().
Provaxxxxxxxxxxxxxxxxxxxxe().
sia definito prima della prima valutazione dell'espressione condizionale (se y non
Prova XXXXXXXXXXXXXXXXXXge().
fosse definita, potrebbe anche contenere il valore 1O, rendendo il test condiziona-
Prova dXXXXXXXXXXXXXXXXrge().
le falso fin dall'inizio e saltando cos l'esecuzione del ciclo). Prova deXXXXXXXXXXXXXXerge().
La funzione converge() mostrata di seguito mostra l'uso di pi variabili di Prova de 1XXXXXXXXXXXXverge ()
controllo in un solo ciclo. La funzione converge() copia una stringa in un'altra Prova de 11 XXXXXXXXXXnverge ()
iniziando a copiare i caratteri dalle estremit e convergendo verso il centro. Prova dell 'XXXXXXXXonverge().
Prova de 11 'uXXXXXXconverge ()
/'!, Uso di pi-a variabili di control-lo nei ci cl i. * / Prova del_l 'usXXXX converge(t.
#include <stdio.h> Prova defl 'usoXXi converge().
#include <string.h> Prova dell'uso di converge(),
Stringa finale: Prova dell'uso di converge().
void converge(char *targ, char *src);
78 CAPITOLO 3

In converge(), il ciclo for utilizza due variabili di controllo, i e j, che fungono for(prompt(); t=readnum(); prompt{))
da indici della stringa alle sue estremit opposte. Durante l'iterazione del ciclo, i sqrnum(t);
aumenta e j de~ementa. Il ciclo termina quando i maggiore di j, ovvero quando
sono stati copiati tutti i caratteri. return O;
L'espressione condizionale non deve quindi confrontare il valore della varia-
bile di controllo del ciclo con un altro valore. Infatti, la condizione pu essere int prompt(void)
un'istruzione relazionale o logica.
Questo significa che possibile utilizzare vari tipi di condizioni di uscita. Ad pri ntf ("Immettere un numero ");
esempio, per collegare un utente a un sistema remoto si potrebbe utilizzare la return O;
seguente funzione. L'utente ha tre tentativi per immettere la password. Il ciclo
termina quando si esauriscono i tre tentativi oppure quando l'utente immette la
password corretta. i nt readnum( voi d)
{
void sign_on(void) int t;
{
char str [20] ;
scanf("%d", &t);
int x; return t;

for(x=O; x<3 && strcmp(str, "password"); ++x) {


printf("Immettere 1a password:"); int sqrnum(int num)
gets(str); {
printf("%d\n", num*num);
re tu rn num*num;
if(x==3) return;
/* altrimenti collega l'utente .. */
Osservando attentamente il ciclo for in rnain(), si noter che ogni parte del ciclo
formata da chiamate a funzioni che visualizzano un messaggio per l'utente e leg-
Questa funzione utilizza strcrnp(), la funzione della libreria standard che con- gono un numero immesso alla tastiera. Se il numero immesso O, il ciclo termina in
fronta due stringhe e restituisce zero se le stringhe sono uguali. quanto lespressione condizionalesarfalsa. In caso contrario, il numero viene ele-
Occorre ricordare che ognuna delle tre sezioni del ciclo for pu essere formata vato al quadrato. Pertanto, questo ciclo for utilizza le porzioni di inizializzazione e
da una qualsiasi espressione valida. Non necessariamente le espressioni devono di incremento in senso non tradizionale ma assolutamente corretto.
avere a che fare con l'uso che generalmente si fa delle relative sezioni. Ad esem- Un altra caratteristica interessante del ciclo for il fatto che non richiede la
pio, si consideri il seguente listato: presenza di tutte le sue componenti. Addirittura, potrebbe non esservi alcuna espres-
sione in alcuna delle sue componenti, ovvero le espressioni sono opzionali. Ad
#include <stdio.h> esempio, questo ciclo continua finch l'utente non immette il valore 123:
i nt sqrnum( i nt num);
for(x=O; x!=123; ) scanf("%d", &x);
int readnum(void);
int prompt(void);
Come si pu notare, la porzione di incremento nella definizione del for vuo-
int main(void) ta. Questo-significa che ogni volta che il ciclo viene ripetuto, viene controllato il
{ valore dix per vedere se uguale a 123, ma non viene eseguita nessun'altra ope-
int t;
80 CAPITOLO J 81

razione. Se si immette 123 alla tastiera, la condizione del ciclo diviene falsa e il printf(" stata inmessa una A");
ciclo ha quindi tennine.
L'inizializzazione della variabile di controllo del ciclo pu verificarsi all'esterno Questo ciclo continuer ad essere eseguito finch l'utente non immetter una
del ciclo tor. Questo avviene frequentemente quando la condizione iniziale della A alla tastiera.
variabile di controllo del ciclo deve essere calcolata in modo piuttosto complesso,
come nell'esempio seguente:
I cicli for senza corpo
if(*s) x = strlen(s); /*calcola la lunghezza della stringa */
else x = 10; Le istruzioni possono anche essere .vuote. Questo significa che anche il corpo di
un ciclo tor (o di un qualunque altro ciclo) pu essere vuoto. possibile utilizzare
far( ; x<lO; ) { questo fatto per migliorare I' efficienza di alcuni algoritmi e per creare cicli di
printf("%d", x); ritardo.
++x; La rimozione di spazi da un canale di input un'operazione piuttosto comu-
ne. Ad esempio, un programma di database potrebbe consentire la generazione di
interrogazioni come: "mostra tutti i valori minori di 400". Il database richiede che
{-a sezione di inizializzazione stata lasciata vuota e x viene inizializzata ogni parola venga inviata separatamente, senza spazi. Questo significa che il ge-
prima dell'ingresso nel ciclo. store dell'input del database riconosce "mostra'~ ma non " mostra". Il ciclo se-
guente mostra come ottenere questo risultato oltrepassando gli spazi contenuti
nella stringa puntata da str.
I cicli infiniti

Anche se possibile creare un ciclo infinito con qualsiasi istruzione di ciclo, far( ; *str == ' '; str++)
normalmente si utilizza un ciclo tor. Poich nessuna delle tre espressioni che for-
mano il ciclo tor assolutamente necessaria, possibile creare un ciclo infinito Come si pu vedere, in questo ciclo il corpo assente, semplicemente perch
lasciando vuota l'espressione condizionale, come nell'esempio seguente: inutile.
I cicli per l'introduzione di ritardi sono molto utilizzati nei programmi. II
far( ; ; ) printf(" Ciclo infinito.\n"); seguente codice mostra la creazione di un ciclo di ritardo utilizzando un for:
l
Quando l'istruzione condizionale assente, si assume che questa sia vera. Si
pu specificare un'espressione di inizializzazione e una di incremento, ma fre- --+----- far(t=O; t<VALORE; t++) ;

quentemente i programmatori C creano cicli infiniti utilizzando il costrutto tor(;;).


In effetti, il costrutto for(;;) non garantisce che il ciclo sia veramente infinito, a
Il ciclo while
causa dell'istruzione break che, se si trova all'interno di un corpo di un ciclo, ne
causa immediatamente l'uscita (l'istruzione break verr discussa in seguito in II secondo tipo di ciclo disponibile in C il ciclo while. La sua forma generale :
questo capitolo). Il controllo del programma riprender dal codice che segue il
ciclo, come nell'esempio seguente:
while (condizione) istru:.ione;
eh = I \0 I ;
dove istruzione pu essere un'istruzione vuota, una singola istruzione oppure un,
far( ; ; ) { blocco di istruzioni. La condizione pu essere una qualsiasi espressione e, come
eh = getchar(); /* legge un carattere */ si ricorder, la verit dell'espressione data da un valore diverso da zero. Il ciclo
if(ch=='A') break; /*esce dal ciclo*/ itera mentre la condizione permane vera. Quando la condi_2;_ione diviene fal~JL.
i---. ------ - - ~011trollo del programma passa alla riga dicodic:_e~~~ segt~ il ciclo.
82 CAPIT()LO 3
L___ LE ISTRUZIONI 83

Il seguente esempio mostra una routine di input da tastiera che continua il


ciclo finch l'utente non immette una A: int 1:

wait for char(void) 1 = strlen(s); /* determina la lunghezza della stFnga */


{ - -
char eh; whil e(l <l ength)
s[l] ' '; /* inserisce uno spazio */
eh= ' \O'; /*inizializza eh*/ 1++;
while(ch != 'A') eh = getehar(); l
return eh; s [1] = '\O'; /* 1e stringhe sono terminate
da un carattere nullo */

Innanzi tutto, a eh viene assegnata la stringa nulla. Come variabile locale, il


suo valore non noto quando inizia l'esecuzione di wait_for_char(). Il ciclo while I due argomenti di pad() sono s, un puntatore alla stringa da allungare e length,
controlla che eh sia diversa da A. Poich eh stata inizializzata con la stringa il numero di caratteri che s dovr avere. Se la lunghezza della stringa s gi
nulla, il test vero e il ciclo ha inizio. La condizione viene nuovamente verificata uguale o maggiore di length, il codice del ciclo while non verr mai eseguito. Se s
ogni volta che si preme un tasto. Quando si immette una A, la condizione diviene pi breve di length, pad() aggiunge il numero necessario di spazi. Per conoscere
falsa poich eh uguale ad A e il ciclo ha termine. la lunghezza della stringa si utilizza la funzione strlen() che si trova nella libreria
Come nel caso dei cicli tor, anche il ciclo while verifica la condizione di test standard.
all'inizio; questo significa che il corpo del ciclo non vfone eseguito se la condizio- Se un ciclo while pu essere terminato da pi condizioni distinte, l'espressio-
ne iniziale falsa. Questa caratteristica elimina la necessit di eseguire un test ne condizionale sar formata da una singola variabile. Il valore di tale variabile
condizionale distinto prima del ciclo. La funzione pad() un ottimo esempio di verr impostato in vari punti del ciclo. In questo esempio:
ci.Essa aggiunge spazi alla fine di una stringa in modo da completare una strin-
ga di lunghezza predefinita. Se la stringa gi della lunghezza desiderata, non void funcl(void)
{
viene aggiunto alcuno spazio.
int working;
#include <stdio.h>
working = l; /* Vero */
#include <string.h>
--wnITT\wrking) {
void pad(char *s, int length);
working = processl();
if(working)
int main{void)
worki ng = proeess2 ():
{
if(working)
char str[BO];
worki ng = process3 ();

strepy(str, "stringa di prova");


pad(str, 40);
printf("%d", strl en(str));
!~uscita dal ciclo pu essere causata da una qualsiasi delle tre routine, se dovesse
return O; restituire il valore falso:-- - -
Non vi nemmeno nessuna necessit di inserire istruzioni nel corpo di un
ciclo while. Ad esempio,
---1* Aggiunge spaz1alla- fine derstrtnga. */
-void pad(char___:'~iE._t__length")" whil e( (ch=getchar(-)) - !=-!A')
- - - - ------- - -
'! -
~ .,,,
i--::=
84 CAPITOLO 3 LE ISTRUZIONI 85

continua il ciclo finch l'utente non immette la lettera A. Se l'utente trova scomo- do {
do l'inserimento dell'assegnamento all'interno dell'espressione condizionale del eh = getchar(); /*-1 egge la scelta
while, si ricordi che il segno di uguaglianza non che un operatore che valuta il dalla tastiera*/
valore dell'operando posto alla sua destra. -switch(ch) {
case '1':
check_spell ing();
Il ciclo do-while break;
case '2':
A differenza dei cicli tor e while, che controllano la condizione del ciclo all'inizio, correct_errors();
il costrutto do-while verifica tale condizione al termine del ciclo. Questo significa break;
case '3':
che un ciclo do-while viene sempre eseguito almeno una volta. La forma generale
display_errors();
di un ciel? do-while la seguente:
break;

do{ while(ch!='l' && ch!='2' && ch!='3');


istmzione;
} while(condizione);
Qui, il ciclo do-while un'ottima scelta, in quanto si desidera che la funzione
Anche se le parentesi graffe non sono necessarie quando si utilizza una sola del menu venga sempre eseguita almeno una volta. Dopo la visualizzazione delle
istruzione, sono normalmente utilizzate per evitare confusione (per il program- opzioni, il programma continua il ciclo finch non viene selezionata un'opzione
matore, non per il compilatore). Il ciclo do-while continua a ripetersi finch la valida.
condizione non diviene falsa.
Il seguente ciclo do-while legge numeri dalla tastiera fino a trovare un numero
minore o uguale a 100.
3.4 La dichiarazione di variabili nelle istruzioni
do { di selezione e iterazione
scanf("%d", &num);
} while(num > 100); In C++ (ma non in C) possibile dichiarare una variabile nell'espressione condi-
zionale di un if o di uno switch, nell'espressione condizionale di un ciclo while o
Probabilmente, l'uso pi comune dei cicli do~wlileniella selezione delle nella parte di inizializzazione di un ciclo tor. Una variabile dichiarata in uno di
opzioni di un-menu. Quando l'utente immette una risposta valida, essa viene re- questi luoghi ha un campo di visibilit limitato al blocco di codice controllato da
stituita come valore della funzione. Le risposte errate ripresentano il messa o-aio di tale istruzione. Ad esempio, una variabile dichiarata in un ciclo tor sar una varia-
richiesta. Il seguente codice mostra una versione migliorata del menu pe/'ii cor- bile locale di tale ciclo.
rettore ortografico sviluppato precedentemente in questo Capitolo: Ecco un esempio che dichiara una variabile nella porzione di inizializzazione
di un ciclo for:
void menu(void)
{ /* i visibile all'interno del ciclo; non visibile all'esterno del
char eh; ciclo. */
int j; _
printf("l. Veri fica ortografi ca\n"); for(int i = O; i<lO; i++)
printf("2. Correz,i one -errori ortografici \n 11 ) ; j = i * i;
printf("3. Visualizza errori ortografici\n");
printf(" Immettere la scelta: "); /* i = lQ; // *~Er:r..o.r.e._*_** -- non visibj}~! */ _ _ _ _ ..
86 CAPITOLO 3
LE ISTRUZIONI 87

Qui, i viene dichiarata nella porzione di inizializzazione del for e viene utiliz- L'istruzione return
zata per controllare il ciclo. All'esterno del ciclo, i sconosciuta.
Poich spesso la variabile di controllo di un ciclo for utilizzata solo ali' inter- L'istruzione return consente di uscire da una funzione. Si trova raggruppata insie-
no di tale ciclo, sta diventando pratica comune dichiarare la variabile nella por- me alle istruzioni di salto poich provoca un salto dell'esecuzione al punto in cui
zione di inizializzazione del for. Si deve soio ricordare che tale funzionalit non era stata eseguita la chiamata alla funzione. A un return pu essere associato o
supportata dal linguaggio C. meno un valore. Se a un return viene associato un valore, questo diverr il valore
restituito dalla funzione. In C, non tecnicamente necessario che una funzione
~j[@~M~ Il fatto che una variabile dichiarata nella porzione di
non void restituisca un valore. Al contrario, in C++ una funzione non void deve
inizializzazione di un ciclo for sia locale di tale ciclo un concetto che si modi-
restituire un valore. Ovvero, in C++, se una funzione dichiarata in modo da
ficato nel tempo. Originariamente la variabile risultava disponibile dal ciclo for
in avanti. Tuttavia lo standard C++ ha ristretto il campo di visibilit di tale va- restituire un valore, deve contenere un'istruzione return alla quale sia associato
riabile al solo ciclo tor. un valore (anche in C, se una funzione dichiarata in modo da restituire un valore,
bene che restituisca effettivamente un valore).
Se il compilatore aderisce completamente allo standard C++, si pu dichiara- La forma generale dell'istruzione return la seguente:
re una variabile all'interno di qualsiasi espressione condizionale, come quelle di
un if o di un while. Ad esempio, il seguente frammento di codice: return espressione;

if(int X = 20) { La parte espressione deve essere presente solo se la funzione dichiarata in
X = X - y; modo da restituire un valore. Se presente, espressione diverr il valore restituito
if(x>lO) y = O; dalla funzione.
In una funzione possono esservi tutte le istruzioni return desiderate ma la
funzione terminer l'esecuzione non appena incontra il primo return. La funzione
dichiara x e le assegna il valore 20. Poich questo un valore che viene valutato ha termine anche quando incontra la parentesi graffa che ne chiude la definizione.
true, la condizione dell'if d esito positivo. Le variabili dichiarate in un'istruzione In questo senso, la parentesi graffa chiusa corrisponde a un return senza associato
condizionale hanno il loro campo visibilit limitato al blocco di codice controlla- alcun valore. Se questo si verifica in una funzione non void, il valore restituito
to da tale istruzione. Pertanto, in questo caso la variabile x non risulta nota al- dalla funzione sar indefinito.
i' esterno dell'if. Onestamente, non tutti programmatori credono sia opportuno di- Una funzione dichiarata come void non pu contenere un'istruzione return
chiarare le variabili all'interno delle istruzioni condizionali e dunque questa tec- che specifica un valore (poich una funzione void non restituisce alcun valore,
-----nica non verr impiegata in questo volume. non ha alcun senso che un return al suo interno restituisca un valore). -- - -- ----
Per ulteriori informazioni sull'uso di return consultare il Capitolo 6.

3.5 Le istruzioni di salto L'istruzione goto


Il C/C++ dotato di quattro istruzioni che eseguono salti incondizionati: return, Poich il CIC++ dotato di un'ampia gamma di strutture di controllo e consente
goto, break e continue. Di queste, return e goto possono essere utilizzate in qual- un ulteriore controllo tramite break e continue, non vi in genere alcuna necessit
siasi punto del programma. Le istruzioni break e continue possono essere utiliz- di utilizzare goto. La maggior parte dei programmatori non gradisce utilizzare i
zate insieme a una qg_alsiasi istruzione di ciclo. Come si v_isto precedentemente goto poich tendono a rendere illeggibili i programmi. Ma, an~he se l'istruzione
in questo stesso Capitolo, si pu utilizzare break anche in uno switch. goto ha goduto di scarsa popolarit fino a pochi anni fa, stata recentemente
rispolverata. Non vi alcuna situazione di-programmazione che richieda un goto.
Si tratta piuttosto di un'istruzione comda ma solo in ambiti molto ristretti, come
--ad-esempio l'uscita da una serie di cicli molto nidificati.
-------
LE ISTRUZIONI 89

L'istruzione goto richiede l'uso di un'etichetta (un'etichetta un identificatore - stampa i numeri da Oa 10. Poi il ciclo termina poich un break causa l'immediata
'idido seguito da un due punti). Inoltre, l'etichetta deve trovarsi nella stessa fun- uscita dal ciclo, saltando il test condizionale t<100.
trone in cui si trova il goto (ovvero non possibile saltare d na funzione a I programmatori utilizzano spesso l'istruzione break nei cicli in cui una deter-
~n'altra). La forma generale dell'istruzione goto la seguente: minata condizione pu causare una terminazione immediata. Ad esempio, nel
programma seguente la pressione di un tasto pu concludere I' esecuzione della
goto etichetta; funzione look_up():

Void look_up{char *name)


{
etichetta: do {
/* ricerca dei nomi * /
if(kbhit()) break;
'Ji:Jve ~tic~tta ~ ~n'etichetta valida che pu trovarsi prima o dopo il goto. Ad
} while(!found);
~'<:mp10, e poss1b1le creare un ciclo che conta i numeri da 1 a 100 usando un goto
~ an' etichetta: /* process match */

I = l;
:.:ipl: Se non si preme alcun tasto, la funzione kbhit() restituisce O. Altrimenti, la
x++; funzione restituisce un valore diverso da O. A causa delle grandi differenze fra
if(x<IC>:) goto loopl; diversi ambienti di calcolo, n il C standard n il C++ standard definiscono la
funzione kbhit(), ma certamente il proprio compilatore ne prevede una (o una fun-
zione con un nome leggermente diverso).
L'istruzione break Un'istruzione break provoca l'uscita solo dal ciclo pi interno. Ad esempio,

L'istruzione break pu essere utilizzata in due casi. Pu concludere un case in for(t=O; t<IOO; ++t) {
m'istruzione switch (di cui si parla nella sezione dedicata all'istruzione switch count = I;
preceden~mente in questo stesso capitolo). Si pu utilizzare il break anche per for{;;) {
causare 1 immediata terminazione di un ciclo, saltando il test condizionale. printf("%d ", count};
Quru.id.Q.fil.iIJC_Qtltra un'istruzione break all'interno di un ciclo, il ciclo ha im- count++;
tnedia~nte tennine e il controllo del programma riprende dall'istruzione che if(count==lO) break;
~gue il ciclo. Ad esempio,

finclude <stdio.h>
stampa i numeri da 1a10per100 volte. Ogni volta che l'esecuzione raggiunge un
int main'.;:>id} break, il controllo torna al ciclo tor esterno.
i Un break utilizzato in un'istruzione switch riguarda solo tale switch e non ha
int t; alcun effetto sul ciclo in cui lo switch pu trovarsi.

for( t=':; t<lOO; t++)


prin:~( 11 %d 11 , t)'; La funzione exit() -
if(t==IO) break;
I Anche se exit() non un'istruzione per il controllo del programma, a questo punto
-necessaria.introdurla_ Come possibile uscire con break da un ciclo, anche
______ :::-_returr. '.i;
possibi~JJ.~cire da un programma utilizzando la funzione-standard di libreri~~i~).
i
---- --
r
90 CAPITOLO 3
-~ LE ISTRUZIONI 91
-~~
Questa funzione provoca l'immed~ta uscita dall'intero programma ed il ritorno
al sistema operativo. In effetti, la funzione exit() opera come un break relativo
all'intero programma.
La forma generale della funzione exit() la seguente:
I~ - printf("3. Visualizza errori ortografici\n");

:'.';::i: '":~::.,. ,. "'"" .);


ch=getchar(); /* legge la scelta dalla tastiera*/
void exit(int codice_di_uscita); switch(ch) {
case '1':
Il valore di codice_di_uscita restituito al processo chiamante, che normal- check_spel l ing();
mente il sistema operativo. Una terminazione normale del programma viene break;
normalmente segnalata dal codice di uscita O. Altri argomenti vengono general- case '2':
mente utilizzati per indicare una situazione di errore. Per il codice di uscita correct_errors();
possibile utilizzate-anche le macro EXIT_SUCCESS ed EXIT_FAILURE. L:;t fun- break;
zione exit() richiede l'impiego del file header stdlib.h. Un programma C++ pu case '3':
utilizzare anche il nuovo file header <Cstdlib>. display_errors();
break;
I programmatori utilizzano frequentemente la funzione exit() quando una del-
le condizioni di funzionamento del programma non soddisfatta. Ad esempio, si
provi a immaginare un gioco per computer di realt virtuale che richieda la pre-
iI -~- case '4':
exit(O); /* torna al sistema operativo */
senza di un determinato adattatore grafico. La funzione main() di tale gioco po-
trebbe avere il seguente aspetto: I while{ch!='l' && ch!='2' && ch!='3');
'
tinclude <stdlib.h>

I
l:istruzione continue
int main(void)
{ L'istruzione continue funziona come un'istruzione break. Ma invece di causare la
if(!virtual graphics()) exit(l); I _, terminazione del ciclo, continue causa lesecuzione della successiva iterazione
play(); - del ciclo, saltando tutto il codice seguente. Nel caso di cicli for, continue provoca
/* - */ l'esecuzione del test condizionale e della porzione di incremento del ciclo. Nel
}
caso di cicli while e do-while, il controllo del programma passa ai test condiziona-
/_* - */ li. Ad esempio, il programma seguente conta 1riiumero di spazi contenuti in una
stringa immessa dall'utente:
dove virtual_graphics() una funzione definita dall'utente che restituisce il valore
logico vero se sul computer presente l'adattatore grafico di realt virtuale. Se /* Conteggio degli spazi */
tale adattatore non installato, virtual_graphics() restituir il valore falso e il pro- #'include <stdio.h>
gramma avr termine.
Nell'esempio seguente menu() utilizza exit() per uscire dal programma e tor- int main(void)
nare al sistema operativo: {
char s[BO], *str;
void menu(void) int space;
i
char eh; prfot-f(.'cimmettere una stringa: ");
gets (s);
printf("l. Verifica ortogra'fea\n"h str = s;
printf("2. Correzione errori -ortografici\n");
92 CAPITOLO 3 LE ISTAUZIO-NI 93

for(space=O; *str; str++) { fune(); /* chiamata di una funzione */


if(*str != ' ') continue; a = b+c; /* istruzione di assegnamento */
space++; b+f(); /* istruzione corretta, anche se curiosa */
f* istruzione vuota */
pri ntf("%d spazi \n", space);
La prima istruzione di espressione esegue una chiamata a una funzione. La
seconda un assegnamento. La terza espressione, anche se pu sembrare curiosa,
In questo programma viene controllato ogni carattere per determinare se comunque valutata dal compilatore C in quanto la funzione f() pu eseguire
uno spazio. In caso negativo, l'istruzione continue provoca la successiva iterazione alcune operazioni necessarie. L'ultimo esempio dimostra la possibilit di usare
del ciclo for. Se invece il carattere uno spazio, viene incrementata la variabile istruzioni vuote (chiamate anche istruzioni nulle).
space,
Il seguente esempio mostra come possibile utilizzare continue per accelera-
re l'uscita da un ciclo causando la precoce esecuzione del test condizionale:
3.7 I blocchi
void code(void)
{ Le istruzioni di blocco non sono altro che gruppi di istruzioni correlate trattate
char done, eh; come una unit. t.e istruzioni che compongono un blocco sono connesse fra di
loro. I blocchi sono chiamati anche istruzioni composte. Un blocco inizia con una
done = O; { e termina cpn la corrispondente }. I programmatori utilizzano istruzioni di bloc-
while(!done) co soprattutto per creare gruppi di istruzioni per altre istruzioni, come ad esempio
eh = getchar(); un if. Tuttavia, possibile porre un'istruzione di blocco in qualunque punto in cui
i f (eh== I$ I ) { possa essere accettata una qualsiasi altra istruzione. Ad esempio, il seguente pro-
done = 1;
gramma perfettamente corretto (anche se insolito):
continue;

putchar(ch+l); /* passa alla posizione #include <stdio.h>


alfabetica successiva */
int main(void)
{.
int i;

Questa funzione codifica un messaggio spostando tutti i caratteri immessi alla /* inizio del blocco*/
lettera successiva. Ad esempio una A diverr una B. La funzione ha termine quan- i = 120;
do si immette il carattere $. Dopo l'immissione del $, non viene visualizzato printf("%d", i);
alcunch poich il test condizionale, richiamato da continue trover la variabile
done vera e questo provocher l'uscita dal ciclo.
return O;

3.6 ~ Le espressioni
Il Capitolo 2 ha parlato approfonditamente delle espressioni; Tuttavia, in questo
Capitolo occorre ricordare alcuni aspetti particolari. Occorre ricordare che un 'istru-
Zonedi espresSione non che un espressione valida in c seguita da un punto e
___ _ _v~~o~a_-come-in: - - - --- - - - - - ---
Capitolo 4

Gli array e le stringhe

4.1 Gli array monodimensionali


, 4.2 La generazione di un puntatore
a un array
4.3 Come passare un array
monodimensionale a una funzione
4.4 Le stringhe
4.5 Gli array bidimensionali
4.6 Gli array multidimensionali
4.7 L'indicizzazione dei puntatori
4.8 L'Inizializzazione degli array
4.9 L'esempio del tris {tic-tc-toe}

.. ,
L: n array formato da una serie di variabili dello stes-
so tipo cui si fa riferimento utilizzando un nome comune. Per accedere a un deter-
j minato elemento di un array si utilizza un indice. In C, tutti gli array sono memo-
rizzati in locazioni di memoria contigue. L'indirizzo pi basso corrisponde al
primo elemento e l'indirizzo pi alto all'ultimo elemento. Gli array non sono
necessariamente monodimensionali. Il tipo pi comune di array la stringa chiu-
sa dal carattere nullo, che semplicemente un array di caratteri al termine del
quale vi un carattere nullo.
--Gilarrye i puntatori sono strettamente correlati; quando si parla degli uni si
fa normalmente riferimento agli altri. Questo capitolo si concentra sugli array,
mentre il Capitolo 5 si occupa pi approfonditamente dei puntatori. Per compren-
dere appieno questi importanti costrutti del C necessario leggere entrambi i
capitoli.

4.1 Gli array monodimensionali


La forma generale di una dichiarazione di un array monodimensionale la
seguente:

tipo nome_vl!'I_@.!:_"!}.!__. __
- - - ------ --- -- -
- -----------
96 CAPITOLO 4 -GLI ARRAY E LE STRINGHE 97

Come ogni altra variabile, anche un array deve essere dichiarato esplicita- In C/C++ non pr~vista alcuna verifica di superamento dei limiti degli array.
mente in modo che il compilatore possa allocare lo spazio in memoria richiesto Questo significa che consentito scrivere oltre i limiti di un array, in un'altra
da tale array. Qui, tipo dichiara il tipo base dell'array, ovvero il tipo di ogni ele- variabile e perfino nel codice del programma. quindi compito del programma-
mento dell'array. dim definisce il numero di elementi che l'array deve contenere. tore verificare sempre di non superare i limiti degli array. Ad esempio, questo
Ad esempio, per dichiarare un array di 100 elementi chiamato bilancio di tipo codice verr compilato senza errori, ma errato poich il ciclo far consente di
double si deve utilizzare la seguente istruzione: superare le dimensioni dell'array count.

double balance[lOO]; i nt count [10]. i;

Per accedere a un elemento si associa un indice al nome dell'array. Si deve porre /*i limiti dell'array vengono superati */
l'indice dell'elemento fra parentesi quadre dopo .il nome dell'array. Ad esempio: for(i=O; i<lOO; i++) count[i] = i;

bil ance[3] =12 .23; Gli array monodimensionali sono essenzialmente elenchi di informazioni dello
stesso tipo conservate in locazioni di memoria contigue secondo un ordine ben
assegna ali' elemento numero 3 dell' array balance il valore 12.23. preciso. Ad esempio, la Figura 4.1 mostra l'aspetto in memoria dell'array A che
In C/C++, tutti gli array iniziano dall'indice O. Pertanto, quando si scrive inizia dalla locazione di memoria I 000 ed dichiarato nel modo seguente:

char p[lO]; char a[7];

si sta dichiarando un array di caratteri formato da 1Oelementi, da p[O] a p(9]. Ad


esempio, il seguente programma inserisce in un array di interi i numeri da Oa 99. 4.2 La generazione di un puntatore a un array
#include <stdio.h> Per generare un puntatore al primo elemento di un array basta specificare il nome
dell'array, senza alcun indice. Ad esempio, dato
int main(void)
{
int sampl e[lO];
int x[lOO]; /*dichiarazione di un array di 100 interi I
int t;
possibile generare un puntatore al primo elemento utilizzando il nome sample.
/* Inserisce in x i valori da O a 99 /* Ad esempio, il seguente frammento di programma assegna a p l'indirizzo del
for(t=O; t<lOO; ++t) x[t] = t; primo elemento di sample:

/* Vi sua lizza il contenuto di x /* int *p;


for(t=O; t<lOO; ++t) printf("%d ", x[t]); int sample[lO];

return O;

La quantit di memoria richiesta per contenere un array strettamente legata Elemento a[O] a[l] a[2) a[3] a[4] a[S] a[6]
al suo tipo e alle sue Omensioni. Per un array monodimensionale, le dimensioni Indirizzo 1000 1001 1002 1003 1004 1005 1006
totali in byte vengono calcolate nel modo seguente:

byte totali= sizeof(tipo) E-dimen-sMirr~y


98 CAPITOLO 4 GLI ARRAY E LE STRINGHE 99

p = sample;

Inoltre possibile specificare l'indirizzo del primo elemento di un array uti-


lizzando l'operatore &. Ad esempio, sia sample che &sample[O] producono lo stesso
risultato. Tuttavia, nel software professionale, sar difficile trovare la forma
&sample[O]. o infine come

void funcl(int x[]) /* array non dimensionato */


{
4.3 Come passare un array monodimensionale
a una funzione
In C non possibile passare un intero array come argomento a una funzione.
II
possibile, tuttavia, passare alla funzione un puntatore all' array, specificando il i
nome dell'array senza alcun indice. Ad esempio, il seguente frammento di pro- i! Queste tre dichiarazioni producono risultati simili in quanto ognuna di esse
gramma passa a func1() l'indirizzo di i: dice al compilatore che si ricever un puntatore a un intero. La prima dichiarazio-
ne utilizza a tutti gli effetti un puntatore. La seconda impiega la dichiarazione
int main(void)
{
int i (10];
,t
I standard di array. Nell'ultima versione, una versione modificata della dichiara-
zione di un array specifica semplicemente che si ricever un array di tipo int di
lunghezza non specificata. Come si pu vedere, la lunghezza dell'array non
importante per l'esecuzione della funzione in quanto il C/C++ non esegue alcuna
fune! (i); verifica di superamento dei limiti. Per quanto riguarda il compilatore, sar accet-
tabile anche la forma:

void funcl(int x[32])


{

Se una funzione riceve un array monodimensionale, si pu dichiarare il para-


--- -metro-formale in tre modi: come puntatore, come array dimensionato o come l'
array non dimensionato. Ad esempio, una funzione chiamata func1 () che riceve i -
i -
l'array i pu essere dichiarata come:
in quanto il compilatore C genera il codice che prepara func1 () a ricevere un
void funcl(int *x) /* puntatore */ puntatore e non crea un array di 32 elementi. _
{

4.4 Le stringhe
L'uso di gran lunga pi comune degli array monodimensionall'li vede nelle vesti
oppure come di stringhe di caratteri. Il linguaggio C.+-+- supporta due tipi di stringhe. Il primo
tipo rappresentato dalle stringhe chiuse dal carattere nullo. Si tratta di array di
void funcl(int x[lO]) /* array dimensionato */ -Garatteri che terminano con il carattere nullo (il carattere numero 0). Pertanto una
{ -------- -- -
--stringa chiusa dal carattere nullo-contiene tutti i ~ar:a1:1:rLc~e_formano la stringa
------- - - : ..
P-'5;""
100 G LI A R R AY . E L E S T R I N G H E 101
CAPITOLO

pi un carattere nullo. Questo :Punico tipo di stringa definito dal C ed tuttora #include <stdio.h>
molto utilizzato. Per questo motivo, le stringhe chiuse dal carattere nullo sono #include <string.h>
chiamate anlfe stringhe C. Il linguaggio C++ definisce anche una classe per le
stringhe, chiamata string, che fornisce un approccio a oggetti alla gestione delle int main(void}
stringhe. Tale classe verr descritta pi avanti in questo volume. Qui si parler (
char s1[80], s2[80];
unicamente delle stringhe chiuse dal carattere nullo.
Quando si dichiara un array che contiene una stringa chiusa dal carattere nul- gets(sl};
lo, occorre dunque indicare dimensioni pari al massimo contenuto che verr me- gets (s2};
morizzato nella stringa aumentato di una unit per il carattere nullo. Ad esempio,
per dichiarare un array str che pu contenere stringhe di 1O caratteri, si dovr printf("lunghezze: %d %d\n", strl en(sl), strlen(s2));
scrivere:
if(!strcmp(sl, s2)) printf("Le stringhe sono uguali\n");
char str[ll]; strcat(sl, s2);
printf("%s\n", sl);
Questa istruzione lascia uno spazio per il carattere nullo al termine della stringa.
strcpy(sl, "Questa una prova. \n");
Anche quando si crea una costante stringa quotata in realt viene creata una
printf(sl);
stringa chiusa dal carattere nullo. Una costante stringa un elenco di caratteri if(strchr("salve", 'e')} printf("e si trova in salve\n");
racchiuso tra doppi apici. Ad esempio, if(strstr("c.iao a tutti", "tu")) printf("trovato tu");

"ciao a tutti" return O;

Non necessario aggiungere il carattere nullo per terminare la stringa: questa


operazione viene svolta automaticamente dal compilatore C/C++. Se si esegue questo programma e si immettono le stringhe "salve" e "salve'',
Il C/C++ prevede un'ampia gamma di funzioni per la manipolazione delle l'output sar:
stringhe chiuse dal carattere nullo. Le pi comuni sono:
1unghezze: 5 5
Le stringhe sono ugual i
NOME FUNZIONE
salvesalve
strcpy(sl, ..:;2) Copia s2 in s!. Questa una prova.
e si trova in sa 1ve
streat(sl, s2) Concatena s2 alla fine di sl.
trovato tu
strlen(sl) Restituisce la lunghezza di s! .
da notare che quando le stringhe sono uguali, strcmp() restituisce il valore
strcmp(sl, s2) Restituisce Ose sl e s2 sono uguali; un valore minore di ose sl<s2; maggiore di ose sl>s2.
false. Se si sta controllando l'uguaglianza delle stringhe, occorre quindi ricordarsi
strchr(sl, eh) Restituisce un puntatore alla prima occorrenza di eh In s!. di utilizzare l'operatore logico! per invertire la condizione.
strstr(sl, s2) Restituisce un puntatore alla prima occorrenza di s2 in s!.
Anche se ora il linguaggio C++ definisce una classe per le stringhe, le strin-
ghe chiuse dal carattere nullo sono an~ora ampiamente utilizzate nei programmi.
Probabilmente questo tipo-ai stringhe rimarr in uso ancora a lungo in quanto
Queste funzioni utilizzano il file header standard string.h (i programmi C++ offre-un-elevato livello di efficienza e garantisce al programmatore il massimo
possono anche usare il file he"ader C++). Il seguente prooramma illustra l'uso di controllo sulle operazioni sul.le stringhe. Ma per le normali operazioni di manipo-
queste funzioni: __ "' lai:ione delle stringhe, molto pi comodo utilizzare la classe C++ string .

111-
..
-- -~-:: - --......-~- .

102 CAPITOLO 4
GLI ARRYE LE STRINGrtE-- W3-

4.5 Gli array bidimensionali Si pu visualizzare l'array num nel modo seguente:

num [t] (i]

l \-
Il C/C++ prevede l'uso di array multidimensionali. La forma pi semplice di array
multidimensionale l'array bidimensionale. Un array bidimensionale , essen-
zialmente, un array di array monodimensionali. Per dichiarare un array bidimen- I 2 3
sionale di interi d di dimensioni I 0,20, si dovr scrivere
o 1 2 3 4
i nt. d [10](20] ;

Occorre fare particolare attenzione a questa dichiarazione. Alcuni linguaggi 1 5 6 7 8


di programmazione utilizzano una virgola per separare le dimensioni dell'array;
in C/C++, ogni dimensione deve invece essere racchiusa fra parentesi quadre. 2 9 10 11 12
Analogamente, per accedere al punto 1,2 dell'array d si dovr usare la forma

d[l] [2] Gli array bidimensionali sono memorizzati in una matrice di righe e colonne,
dove il primo indice indica la riga e il secondo indica la colonna. Questo significa
Il seguente esempio inserisce in un array bidimensionale i numeri da I a 12 e che l'indice pi a destra cambia pi velocemente rispetto a quello pi a sinistra
poi li stampa riga per riga. quando si accede agli elementi dell'array nell'ordine in cui essi sono effettiva-
mente conseryati in memoria. Per una rappresentazione grafica del modo in cui
#include <stdio.h> un array bidimensionale viene conservato in memoria, consultare la Figura 4.2.
int main(void)
{
int t, i, num[3][4];

for(t=O; t<3; ++t)


for(i=O; i<4; ++i) Dato char ch[4][3]
num[t] [i] = (t*4)+i+l;
L'indice destro determina la colonna
/* stampa */
for(t=O; t<3; ++t)
.--- + ------.
for(i=O; i<4; ++i) /I'-eh_r:....:01-=-lo-=--1__.___I_eh_lo1_r1_1_._I_eh_[o_H2--'1J
~~~~~e~
printf("%3d ", num[t][i]);
printf{"\n"); eh_[1_1_r2-'1j
eh_[1_1_ro1_-'-I__:_ch_[1]_[_11___,_ _
1..-I

determina
return O; la riga Ieh (2] (O] eh [2] [1] eh [2] [2] I
~J,_eh-[~]-[O-]---'--ch-[-3]-[1-]-'---eh-[3-][-2]j
In questo esempio, num(O][O] ha il valore 1, num[0][1] ha il valore 2, num[0][2]
ha il valore 3 e cos via. Il valore di num[2][3] 12-.----

-=-:FJgura .4.2 .Memorizzazione di un array bidimensionale.


---- -------~.
104 CAPITOLCJ4-- -

Nel caso di un array bidimensionale, il numero di byte di memoria richiesti #include <stdlib.h>
per contenere l'array dato dalla seguente formula:
/*Un semplice database dei voti degli studenti. */
byte = dimensioni 1 indice * dimensioni 2 indice * sizeof(tipo base)
#defi ne CLASSES 3
lidefi ne GRAOES 30
Pertanto, se si considera che gli interi occupano 4 byte, un array bidimensio-
nale di interi di dimensioni 10,5 richieder: i nt grade [CLASSES] [GRADES] ;

10x5x4 voi d enter_grades (voi d);


i nt get grade (i nt num) ;
ovvero 200 byte. voi d di ~p _grades (i nt g [][GRADES]);
Quando si utilizza un array bidimensionale come argomento di una funzione,
int main(void)
viene in effetti passato solo il primo elemento dell'array. Tuttavia, il parametro
{
che riceve un array bidimensionale deve definire almeno le dimensioni che si
char eh, str[BO];
trovano pi a destra (si pu anche specificare la dimensione pi a sinistra ma
questo non strettamente necessario). La dimensione pi a destra necessaria in for{;;) {
quanto il compilatore C/C++ deve conoscere la lunghezza di ogni riga per poter do {
accedere correttamente ai vari elementi dell'array. Ad esempio, una funzione che printf("(I)mmissione voti\n");
riceve un array bidimensionale di interi di dimensioni 10,10 dovr essere dichia- printf("(?)tampa vot1\h'');
rata nel seguente modo: printf("(U)scita\n");
gets(str);
voi d funcl (i nt x [] [10]) eh = toupper{*str);
{ while{ch!='I' && ch!='S' && ch!='U');

switch(ch) {
case 'I':
enter_grades () ;
break;
Il compilatore- deve conoscFie dimensioni pi a destra per poter eseguire case 'S':
di sp_grades (grade);
correttamente espressioni come la seguente:
break;
case 'U':
x[2] [4]
exit(O);

Se la lunghezza delle righe non fosse nota, il compilatore non potrebbe determi-
nare l'inizio della terza riga.
Il seguente breve programma utilizza un array bidimensionale per memoriz- return O;
zare i voti numerici di ogni studente delle classi di un professore. Il programma
assume che il professore abbia tre classi e un massimo-di 30 studenti per classe. Si-
____E~ti il modo in cui si accede all'array grade da parte di ognuna delle funzioni. /* Immissione dei voti. */
voi d enter grades (voi d)
{ -
#include <stdio.h>
--'~n....
t_t~ i;
--- - -----1..i nc:l ude <ctype. h>
106 CA P I T O LO 4

for(t=O; t<CLASSES; t++) { gets(str_array[2]);


printf("Classe %d:\n", t+l);
for(i=O; i<GRADES; ++i) L'istruzione precedente funzionalmente equivalente a:
grade[t] [i] = get_grade(i);
gets{&str_array[2][0]);

ma la prima delle due forme molto pi comune nel codice professionale. Per
/* Lettura dei voti. */
i nt get grade (i nt num) meglio comprendere il funzionamento degli array di stringhe, si studi il seguente
{ - breve programma che utilizza un array di stringhe come base per un semplicissi-
char s[80]; mo editor di testi:

printf("Immettere -il voto dello studente %d:\n", num+l); /*Un semplicissimo editor di testi. */
gets (s); #include <stdio.h>
return(atoi (s));
#defi ne MAX 100
/* V-isualizzazione dei voti. */ #defi ne LEN 80
voi d di sp grades (i nt g [][GRADES])
[ - char text[MAX] (LEN];
int t, i;
int main(void)
for(t=O; t<CLASSES; ++t) { {
printf("Classe %d:\n", t+l); register int t, i, j;
for{i=O; i<GRADES; ++i)
printf("Studente %d %d\n", i+l, g[t] [i]); printf("Per uscire, immettere una riga vuota. \n");

for(t=O; t<MAX; t++)


printf("%d: 11 , t);
gets(text[t]);
Gli array di stringhe if(!*text[t]) break; /*riga vuota: uscita*/

In programmazione non difficile incontrare array di stringhe. Ad esempio, il


processore di input di un database potrebbe verificare la corrispondenza dei co- for(i=O; i<t; i++) {
mandi immessi dall'utente con un array di comandi validi. Per creare un array di for(j=O; text[i][j]; j++) putchar(text [i][j]);
stringhe chiuse dal carattere nullo, si deve usare un array di caratteri bidimensio- putchar(' \n');
nale. Le dimensioni dell'indice di sinistra determinano il numero di stringhe e le
dimensioni del!' indice di destra specifica la lunghezza massima di ogni stringa. Il
return O;
codice seguente dichiara un array di 30 stringhe ognuna delle quali pu essere
__]_unga al massimo 79 caratteri.
Questo programma legge righe di testo fino a incontrare una riga vuota. Quin-
char str_array(30] (80];
di visualizza ogni riga carattere per carattere..
In questo modo molto facile accedere alle singole stringhe: basta specificare
---- solo l'indice di sinistra. Ad esempio, l'istruzione che segue richiama-gets() con la___ -~=
terza stringa contenuta in str_array,_ ___-- -- - ---.:-_- - i'-~~
- - -- -.:..::e_-:-=:::~ - r,,..~c - =--:::::::--- - --
-- --~---~
------. r~~
l-~
-------- --
108 CA P I T O LO 4 ---- GLI ARRAY E LE STRINGHE-109

4.6 Gli array multidimensionali 4.7 l'indicizzazione dei puntatori

Il C/C++ consente di creare array a pi di due dimensioni. Il limite esatto, se In C/C++, i puntatori e gli array sono oggetti strettamente correlati. Come si gi
esiste, determinato dal compilatore utilizzato. La forma generica della dichiara- visto, il nome di un array senza indice il puntatore al primo.elemento dell'array.
zione di un array multidimensionale la seguente: Ad esempio, considerando il seguente array:

tipo nome[Diml][Dim2][Dim3] ... [DimN]; char p[lO];

Gli array di tre o pi dimensioni non vengono utilizzati molto spesso a causa Le seguenti due istruzioni hanno lo stesso significato:
della quantit di memoria che richiedono. Ad esempio, un array di caratteri
quadridimensionale di dimensioni 10,6,9,4 richiede: p
&p[O]
Hl*6*9*4
Detto in altri termini,
ovvero 2160 byte. Se l'array contenesse interi di 2 byte, occuperebbe 4320 byte.
Se contenesse valori double (assumendo che un double occupi 8 byte), occupereb- P == &p[O]
be 17280 byte. La quantit di memoria richiesta aumenta esponenzialmente con
l'aumentare delle dimensioni. Ad esempio, se all'array precedente viene aggiunta fornisce un risultato vero poich l'indirizzo del primo elemento di un array corri-
una quinta dimensione di 10 elementi, allora si raggiungerebbero i 172.800 byte. sponde all'indirizzo dell'array.
Negli array multidimensionali, il calcolo dell'indice richiede una grande quan- Come si detto, il nome dell' array senza un indice genera un puntatore. Ana-
tit di tempo di elaborazione. Questo significa che l'accesso a un elemento di un l9gamente, un puntatore pu essere indicizzato come se fosse dichiarato tramite
array multidimensionale pu essere pi lento rispetto all'accesso a un elemento di un array. Ad esempio, si consideri questo frammento di programma:
un array monodimensionale. Quando si passa a una funzione un array
multidimensionale, si devono dichiarare tutte le dimensioni tranne quella pi a int *p, i[lO];
sinistra. Ad esempio, se si dichiara l'array m come p =i;
p[S] = 100; /* assegnamento trami te indi ce */
*(p+S) = 100; /*uso dell'aritmetica dei puntatori */
i nt m[4] [3] [6] [5];

una funzione func1 () che riceva m dovr avere il seguente aspetto: Entrambe- le- istruzioni di assegnamento inseriscono il valore 100 nel sesto
elemento di i. La prima istruzione fa riferimento a p; la seconda utilizza l'aritme-
void funcl(int d[][3][6][5])
tica dei puntatori. In entrambi i casi, il risultato lo stesso (i puntatori e l' aritme-
tica dei puntatori sono l'argomento del Capitolo 5). -
Questo stesso concetto si applica anche agli array di due o pi dimensioni. Ad
esempio, assumendo che a sia un array di 10 per to interi, queste due istruzioni
sono equivalenti:

a
Naturalmente, conunque possibile fncludere anche la prima dimensione. &a [O] ~O]

Questo significa che possibile far riferimento all'elemento 0,4 di a in due

I
~
--- i
modi: utilizzando l'indice dell'array, a[0][4] o utilizzando il puntatore *((int *)a+4).

i
----------
110 CAPITOLO 4 --- G L LAJU{ A Y--1>--L t-:-;:rnr. ,; ~ ,-,-~

Analogamente, l'elemento 1,2 pu essere visto come a[1}[2} o come *((int *)a+12). for(t=O; t<row dimension; ++t)
In generale, per ogni array bidimensionale, printf("%d "~ (p+t)];

a[j)[k]

equivalente a
void f(void)
*((tipo-base*)a + (j * lunghezza-riga) + k) {
i nt num[lO] [10] ;
La conversione cast del puntatore all'array in un puntatore al suo tipo base pr_row (O, 10, (int) num); /*stampa la prima riga*/
necessaria perch l'aritmetica dei puntatori possa funzionare correttamente. Spesso
vengono utilizzati i puntatori per accedere agli array in quanto l'aritmetica dei
puntatori normalmente pi veloce rispetto all'indicizzazione degli array. Gli array di pi di due dimensioni possono essere ridotti in modo analogo. Ad
Un array bidimensionale pu essere ridotto a un puntatore a un array di array esempio, un array tridimensionale pu essere ridotto a un puntatore a un array
monodimensionali. Pertanto, l'uso di una variabile puntatore distinta un modo bidimensionale il quale pu essere ridotto a un puntatore a un array
semplice per utilizzare i puntatori per accedere agli elementi di una riga di un monodimensionale. Generalmente, un array n-dimensionale pu essere ridotto a
array bidimensionale. La seguente funzione illustra questa tecnica. La funzione un puntatore a un array (n-1)-dimensionale. Questo nuovo array pu essere ulte-
stampa il contenuto della riga specificata per l'array di interi globale num: riormente ridotto utilizzando lo stesso metodo. Il processo termina quando viene
prodotto un array monodimensionale.
i nt num[lO] [10];

4.8 L'inizializzazione degli array


void pr row(int j)
{
Il C(C++ consente di inizializzare un array nel momento della dichiarazione. La
int *p, t;
forma generica dell'inizializzazione di un array simile a quella delle altre varia-
p = (int *) &num[j][O]; /* determina l'indirizzo
bili, come si pu vedere nella riga seguente:
del primo elemento dena __:_i~i!J-~L __
specificatore_tipo nome_array[diml]. .. [dimN] = {elenco_valori};
for(t,;-0; t<lO; ++t) printf("%d ", *(p+t));
L'elenco_valori un elenco di valori separati da virgole il cui tipo deve essere
compatibile con specificatore_tipo. Il primo valore verr posizionato nella prima
possibile generalizzare questa routine facendo in modo che gli argomenti di posizione dell'array, il secondo nella seconda posizione e cos via. Si faccia parti-
chiamata siano la riga, la lunghezza della riga e un puntatore al primo elemento colare attenzione al punto e virgola che segue la parentesi graffa di chiusura.
dell'array: Nell'esempio seguente, viene inizializzato un array di dieci elementi interi
1 con i numeri da 1 a 10:
t void pr_row(int j, int row_dimension, int *p)
{ int i[lO] .= {l, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int t;
Questo significa che i[O] conterr il valore 1 e che i[9] conterr il valore 10.
p = p + (j * row_dimension);
Gli array di caratteri che .contengono stringhe consentono di utilizzare
un'inizializzazione-semplificata che ha 111_.egente for~a_:._ ___ _
__ ____ _
,

--~---
112 CAPITOLO t- - - - - --G-l: I ARRA Y E LE STRINGHE -:- 113

-:0: {2,4),
char nome_array[dim] ="stringa";
{3,9),
{4,16).
Ad esempio, questo frammento di codice inizializza la stringa str con la frase {5,25),
"Il Ctt bello". {6,36}.
{7,49}.
char str[15] = "Il C++ e bello"; {8,64},
{9,81},
Questo equivale a scrivere: {10,100)
};
char str[l5] = { 1 ! 1 , 1
11 , '
1 1
C1 , 1
+1 , 1
+1 , 1
'
1
1 , 1 1 1
b1 , 1
e1 , 1
11 ,
'l 1, 1 0 1 , 1 \0 1 } ; Quando si usa il raggruppamento dei sotto-aggregati, se non si fornisce un
numero sufficiente di inizializzatoci per il gruppo, a tutti i membri rimanenti verr
Poich in C tutte le stringhe terminano con il carattere nullo, necessario assegnato il valore O.
assicurarsi che l'array dichiarato sia lungo quanto basta per contenere il carattere
nullo. Questo il motivo per cui la stringa str lunga quindici caratteri, anche se
la frase "Il C ++ bell" lunga solo quattordici caratteri. Quando si utilizza una L'inizializzazione di array non dimensionati
costante stringa, il compilatore a inserire automaticamente il carattere finale
Si immagini di utilizzare l'inizializzazione di un array per costruire una tabella di
nullo.
messaggi di errore:
Gli array multidimensionali vengono inizializzati nello stesso modo degli array
monodimensionali. Ad esempio, il seguente array inizializza sqrs con i numeri da
char el [18] "Errore di 1ettura\n";
1 a 10 e con i rispettivi quadrati.
char e2 [20] "Errore di scrittura\n";
char e3 [27] "Impossibile apri re il fil e\n";
i nt sqrs [10] [2] = {
1,1,
Come si pu immaginare, non comodo contare manualmente i caratteri con-
2,4,
3,9, tenuti in ogni messaggio per determinare le dimensioni corrette dell'array. Fortu-
4,16, natamente si pu lasciare che il compilatore calcoli automaticamente le dimen-
_____?~?5! __ sioni degli array, ovvero si possono usare gli array non dimensionati. Se, nel-
6,36, l'istruzione di inizializzazione di un array, non si specificano le dime~sioni ___ _
7 ,49, dell'array, il comp,ilatore C/C++ crea automaticamente un array grande a suffi-
8,64, cienza per contenere tutti gli inizializzatori presenti. In questo caso si parla di
9,81, array non dimensionato. Utilizzando questo approccio, la tabella dei messaggi
10.100 diviene
};
char el [] "Errore di lettura\n";
Quando si inizializza un array multidimensionale, si possono aggiungere le char e2[] "Errore di scrittura\n";
parentesi graffe attorno agli inizializzatori di ciascuna dimensione. Questo detto char e3 [] "Impossibile aprire il file\n";
raggruppamento de-i-sotto-aggregati. Ad esempio, ecco un altro modo con cui si
pu scrivere la dichiarazione precedente. Date queste inizializzazione, l'istruzione

i nt sqrs [10] [2] = { II "':li'-' printf("La lunghezza di %s %d\n", e2, sizeof e2);

--- -~- li--~-- ---- -


{1, l},

~- ,--=-=-:.-.-::-----
---1:~
~~~;-----
--==-~''!~
114 CAPITOLO 4 GLl ARRAY E LE STRINGHE 115

\isualizzer: get_player_move() chiede di specificare la posizione in cui inserire una X. L'an-


golo superiore sinistro si trova alla posizione 1,1 e l'angolo inferiore destro alla
- La 1unghezza di Errore di seri ttura 20 posizione 3,3.
I:.' array a matrice viene inizializzato in modo da contenere spazi. Ogni mossa
Oltre a essere pi comoda, l'inizializzazione di array non dimensionati con- eseguita dal giocatore o dal computer trasforma uno spazio in una X o in una O.
sente di cambiare i messaggi senza temere di aver dimensionato in modo errato Questo semplifica anche la visualizzazione della matrice sullo schermo.
l'array. Ogni volta che viene eseguita una mossa, il programma richiama la funzione
Le inizializzazioni di array non dimensionati non si limitano agli array check(). Questa funzione restituisce uno spazio se non vi ancora un vincitore,
monodimensionali. Per gli array multidimensionali si deve specificare tutto tran- una X se ha vinto il giocatore o una O se ha vinto il computer.
ne la dimensione pi a sinistra (le altre dimensioni sono necessarie per consentire Per fare ci, scandisce le righe, le colonne e le diagonali, alla ricerca di file di
al compilatore di indicizzare correttamente l'array). In questo modo, possibile X o di O.
costruire tabelle di varie lunghezze e il compilatore allocher automaticamente lo La funzione disp_matrix() visualizza lo stato attuale del gioco. Si noti che il
spazio sufficiente. Ad esempio, ecco la dichiarazione di sqrs come array non fatto di aver inizializzato la matrice con spazi semplifica questa funzione.
dimensionato: Le routine di questo esempio accedono all' array matrix ognuna in un modo
diverso. Lo studio di queste funzioni aiuter a comprendere meglio il funziona-
i nt sqrs O[2] mento di ogni operazione che possibile svolgere su un array.
l, l,
2,4, /*Un semplice gioco del tris (Tic Tac Toe). */
3,9, #include <stdio.h>
4,16, #include <stdlib-.h>
5,25,
6,36, char matrix[3][3]; /*matrice del gioco*/
7,49,
8,64, char check(void);
9,81, void init_matrix(void);
10,100 void get_player_move(void);
}; void get computer move(void);
void disp_matrix(~oid);
Il vantaggio di questa dichiarazione rispetto alla versione dimensionata consi-
ste nella possibilit di allungare o accorciare la tabella senza cambiare le dimen- int main(void)
{
sioni dell'array.
char done;

printf("Questo il gioco del Tris.\n");


printf("Giocherai contro il computer. \n");
4.9 L'esempio del tris (tic-tac-toe)
done = ' ';
Il corposo esempio che segue illustra molti dei modi in cui possibile manipolare init_matrix();
gli af!ay in C/C++. Molto spesso, per simulare un gioco da scacchiera si utilizza do{
un array bidimensionale. Questa sezione=sviluppa un semplice programma di tris disp_matrix();
(tic-tac-toe). get_pl ayer_move();
Il computer svolge una strategia molto semplice. Quando il turno del com- done =check(); /*ha vinto?*/
puter, utili-iza la-funzione get_computer_moveQQi;;r_~~!l!re_ la scansione della if(done!= ' ') break; /* vincitore!*/
matrice.alla ricerca_ di una cella non occupata. Quando ne trova una, vi posiziona -- get_ computer_move ();
una -0.-Se non riesce-a -troYare_ una. q1seUilibera. esce dal gioco. La funzione - =-:-don e =-check() ; /* ha vinto? *i'--=- -- -
116 CAPITOLO 4 GLI ARRAY-fTE-s-TRJNGITT-
-----
-117
--

} while(done== ' '); printf("patta\n");


if(done=='X') printf("Hai vinto tu!\n"); exit(O);
else printf("Ho vinto io!!!!\n");
disp_matrix(); /*mostra le posizioni finali */ else
matrix[i] (j] 'O';
return O;

f* Visualizza la matrice. */
/* Inizializza la matrice. */ void disp_matrix(void)
void init_matrix(void) {
{ int t;
int i, j;
for(t=O; t<3; t++) {
for(i=O; i<3; i++)
for(j=O; j<3; j++) matrix[i](j] ' ';
printf(" %c I %c I %c ",matrix[t][O],
matrix[t](l], matrix (t][2));
if(t!=2) printf("\n--1--1--\n");
f* Legge 1a mossa del giocatore. I
void get_player_move(void) printf("\n");
{
int X, y;
/*Controlla se c' un vincitore. */
printf("Immettere le coordinate X, Y: "); char check(void)
scanf( %d%*c%d
11 11
, &x, &y); {
int i;
x--; y--;
for(i=O; i<3; i++) /* righe */
if(matrix[x][y] != ' '){ if(matrix[i] [O)==matrix[i] [l] &&
printf("Errore, riprovare. \n"); matrix[i] [O]==matrix[i] [2]) return matrix[i] [O];
get_player_move();
for(i=O; i<3; i++) /* colonne*/
e1se ma t ri x [x] [y] = 'X' ; if(matrix[O] [i]==matrix[l] [i] &&
matrix[O] [i]==matrix[2] [i]) return matrix[O] [i];

/* Mossa del computer. */ /*diagonali */


voi d get_computer_move (voi d) if(matrix[O] [O]==matrix[l] [l] &&
{ matrix[l] [l]==matrix[2] [2])
int i, j; return matrix[O] [O];
for(i=O; i<3; i++){
for(j=O; j<3; j++) if(matrix[O] [2]==matrix[l] [l] &&
if(matrix[i][j]==' ') break; matrix[l] [l]==matrix[2] [O])
if(matrix[i][j]==' ') break;---- return matrix[O] [2];

return ' ';


if(i*j==9)
< Capitolo 5
- 1

Il I puntatori

i 5.1 Che cosa sono i puntatori?


I 5.2 Variabili puntatore

l 5.3
5.4
5.5
Gli operatori per i puntatori
Espresf!Jloni con puntatori
Puntatori e array
5.6 Indirizzamento multilivello
5.7 Inizializzazione di puntatori
5.8 Puntatori a funzioni
5.9 Le funzioni di allocazione dinamica del C
5.10 Problemi con i puntatori

-; er poter scrivere programmi CIC++ fondamentale


comprendere appieno l'uso dei puntatori. Questo per tre motivi: innanzi tutto, i
puntatori costituiscono un metodo con il quale le funzioni possono modificare i
propri argomenti. In secondo lu.ogo, i puntatori consentono di utilizzare le routine
di allocazione dinamica. In terzo luogo, i puntatori possono aumentare l'efficien-
za di determinate routine. Inoltre, come si vedr nella seconda parte della guida, i
puntatori giocano nuovi e importanti ruoli anche in C++.
I puntatori sono una delle funzionalit pi potenti e pi pericolose del C/C++.
Ad esempio, un puntatore non inizializzato o un puntatore che contiene valori non
corretti possono provocare il blocco del sistema. Ma c' di peggio: facile utiliz-
zare i puntatori in modo errato, inserendo nel codice bug difficilissimi da scovare.
Per l'importanza e i potenziali abusi dei puntatori, questo capitolo si occupa
dei puntatori in modo dettagliato.

5.1 Che cosa sono i puntatori?


Un puntatore- una variabile che contiene un indirizzo di memoria. Questo indi-
rizzo corrisponde alla posizione di un altro oggetto (normalmente un'altra varia-
bile) in memoria. Ad esempio, se una variabile contiene l'indirizzo di un'altra
variabile;-st dice che-tirpri-mavriabilepunta alla seco.nda. La.Eigura 5.1 illustra
questa situ~ic_me.----- - - - -::-
~- ---==---- .
120 CAPITOnJ-o--- -- ---- -- I r' UN I r-.. V n 1

Il tipo base del puntatore definisce il tipo delle variabili a cui pu puntare il
Indirizzo Variabile puntatore. Tecnicamente, un qualsiasi tipo di puntatore pu puntare a un qualun-
di memoria in memoria que indirizzo della memoria. Tuttavia, tutta l'aritmetica dei puntatori si basa sul
tipo della variabile puntata e quindi importante dichiarare correttamente il
puntatore (l'aritmetica dei puntatori verr discussa pi avanti in questo stesso
1000 1003 capitolo).

1001
5.3 Gli operatori per i puntatori
1002
Gli operatori per i puntatori sono stati descritti nel Capitolo 2. Qui l'argomento
1003 verr approfondito, a partire dalle loro funzionalit di base. Vi sono due speciali
operatori per i puntatori: * e &. Il secondo un operatore unario che restituisce
1004 l'indirizzo di memoria del proprio operando (un operatore unario richiede un solo
operando). Ad esempio:
1005
m = &count;

1006 ,., assegna a m l'lndirizzo di memoria della variabile count. Questo indirizzo corri-
sponde all'indirizzo della variabile nella memoria fisica del computer. Quindi
l'indirizzo non ha nulla a che fare con il valore di count. Si pu pensare all'opera-
tore & traducendolo in italiano come "indirizzo di". Pertanto, l'istruzione di asse-
Memoria gnamento precedente si pu leggere come "m riceve l'indirizzo di count".
Per meglio comprendere lassegnamento precedente, si immagini che la va-
riabile count conservi il proprio valore nell'indirizzo di memoria 2000. Inoltre si
Figura 5.1 Una variabile punta a un'altra. immagini che count abbia il valore 100. Quindi, dopo l'assegnamento precedente,
m conterr il valore 2000.
--- ---~
Il secondo operatore sui puntatori, *, complementare rispetto a&. Si tratta di
un operatore unario che restituisce il valore che si trova nell'indirizzo di memoria
5.2 Variabili puntatore che lo segue.
Ad esempio, se m contiene l'indirizzo di memoria della variabile count,
Se una variabile deve contenere un puntatore, deve essere dichiarata come tale. La
dichiarazione di un puntatore formata da un tipo base, un asterisco e un nome di
q = *m;
variabile. La forma generale di dichiarazione di una variabile puntatore la se-
guente:

tipo *nome;

- -dove tipo il tipo base del puntatore e pu essere un qualsiasi tipo valido. nome
definisce il nome della variabile puntatore. .
-1 PUNTATORI __ 123
122 C A P I T O LO 5

Assegnamento di puntatori
Occorre fare attenzione che le variabili puntatore puntino sempre al tipo di
dati corretto. Ad esempio, quando si dichiara un puntatore a una variabile di tipo Un puntatore, come ogni altra variabile, pu essere util_izzato sul lato destro di
int, il compilatore assume che ogni indirizzo che il puntatore si trover a contene- un'istruzione di assegnamento in modo da assegnare il suo valore a un altro
re far riferimento a una variabite-intera. Poich il C consente di assegnare qual- puntatore come ad esempio in:
siasi indirizzo a una variabile puntatore, il seguente frammento di codice verr
compilato senza alcun messaggio di errore (o solo qualche avvertimento, a secon-
#include <stdio.h>
da del compilatore usato) ma non produrr il risultato atteso:
int main(void)
#include <stdio.h> {
int x;
int main(void) int *pl, *p2;
{
double x=l00.1, y; pl = &x;
int *p; p2 = pl;

/* La prossima istruzione fa in modo che p (un puntatore printf(" %p", p2); /* stampa l'indirizzo di x,
-a un intero) punti a un valore double. */ non il suo valore! */
p = &x;
return O;
/* La prossima istruzione non funziona
nel modo atteso. */
y = *p; Ora, sia p1 che p2 puntano a x. L'indirizzo dix viene visual~zzato ?tili~za~do
lo specificatore di formato %p di printf(), che fa in modo che pnntf() v1sual1zz1 un
printf("%f", y); /* non visualizza 100.1 */
return O;
indirizzo nel formato utilizzato dal computer.

Aritmetica dei puntatori


Questo frammento di codice non assegna il valore di x a y. Poich p dichia-
rato come un puntatore a un intero, solo 2 o 4 byte di informazioni verranno Sui puntatori si possono utilizzare solo due oper~zion~ aritx:ietiche: a?d~z.ione e
trasferiti da x a y e non gli 8 byte che normalmente compongono un numero sottrazione. Per comprendere cosa avviene nell'antmeticadeLpuntaton, s11mma:
double. gini che p1 sia un puntatore a un intero il cui valore at:uale 2000. Inoltre, s1
assumi che gli interi siano lunghi 2 byte. Dopo l'espressione
NOTA~"":.---=- In C++ non consentito convertire un tipo di puntatore in 1111
altro senza utili:.z.are uno specifico operatore di cast. Per questo motivo, il pro-
gramma precedente non potr essere compilato come programma C++ ma solo I ~- pl++;
come programma C. Il tipo di errore descritto pu in ogni caso verificarsi anche
in C++ ma in 1111 modo pi sottile. I. p1 conterr il valore 2002 e non 2001. Il motivo di ci risiede nel fatto che ogni
volta che si incrementa p1, esso deve puntare all'intero successivo. L? stesso

5.4 Espressioni con puntatori


lI ':- avviene per i decrementi. Ad esempio, se si immagina che p1 contenga il valore
2000, l'espressione

.l pl--;
In generale. le espressioni contenenti puntatori seguono le stesse regole delle altre _ _ L_
--'- ~p~ss~oni C. Questa sezione esamina.alcuni aspetti p.eculiari delle espressoni _ i ~-:::; fa in modo che p.1 c.oQt.enga iJ valore 1998.
con puntatori. - - -- - ___ __ i ~:.
- - - - - ---------
- --- --; ~~
'--~.
----i-;-u-
;t :;~,{.:-.
t -;::;:::-
! ~-.-
-- ~-=-- ----- - .~

124 CAPI T CH: O 5

Generalizzando l'esempio precedepte, l'aritmetica dei puntatori governata fa in modo che p1 punti al dodicesimo elemento dello stesso tipo di p1 oltre a
dalle seguenti regole. Ogni volta che si incrementa un puntatqre, esso punter alla quello a cui attualmente punta.
locazione di memoria dell'elemento successivo considerando il tipo base. Ogni Oltre all'addizione e alla sottrazione di un puntatore e di un intero, consen-
volta che il puntatore viene decrementato, punter ali' indirizzo dell'elemento pre- tita solo un'altra operazione aritmetica: possibile sottrarre un puntatore a un
cedente. Se l'aritmetica dei puntatori viene applicata a puntatori a caratteri, si altro puntatore per conoscere il numero degli oggetti deitipo base che separano i
ottiene la comune aritmetica in quanto i caratteri sono sempre lunghi un byte. due puntatori. Tutte le altre operazioni aritmetiche sono proibite. In particolare,
Tutti gli altri puntatori verranno invece incrementati o decrementati della lun- non si pu moltiplicare o dividere puntatori; non possibile sommare due puntatori;
ghezza del tipo di dati a cui essi puntano. Questo approccio assicura che un non possibile applicare loro operatori bit-a-bit e non possibile sommare o
puntatore punti sempre a un elemento appropriato nel proprio tipo base. Questo sottrarre valori di tipo float o double a o da puntatori.
concetto illustrato dalla Figura 5.2.
La manipolazione dei puntatori non limitata agli operatori di incremento e Confronti fra puntatori
decremento. Ad esempio possibile sommare o sottrarre interi a un puntatore.
L'espressione: I puntatori possono essere confrontati in un'espressione relazionale. Ad esempio,
dati i due puntatori p e q, la seguente istruzione perfettamente corretta:
pl = pl + 12;
if(p<q) printf("p punta a una cella di memoria inferiore rispetto a q\n");

Generalmente, il confronto dei puntatori utilizzato quando due o pi puntatori


puntano a un oggetto comune, come ad esempio un array. Come esempio, sono
state presentate due routine per la manipolazione dello stack che memorizzano e
char *ch=3000; leggono valori interi. Uno stack una struttura di elementi che utilizza l'accesso
int *i=3000; "first in - last out". Si pu pensare a uno stack come a una pila di piatti su un
tavolo: il primo piatto poggiato sul tavolo sar l'ultimo a essere tolto. Gli stack
eh 3000 sono molto impiegati nei compilatori, negli interpreti, nei fogli elettronici e in
molto altro software di sistema. Per creare uno stack, occorre utilizzare due fun-
zioni: push() e pop(). La funzione push() inserisce un valore nello stack e la fun-
ch+l 3001
zione pop{) estrae un valore. Queste routine sono illustrate nel listato seguente
con una semplice funzione main(). Il programma inserisce in uno stack i valori
ch+2 3002 imnressi dall'utente. Se si immette un O, verr estratto un valore dallo stack. Il
programma termina quando si immette il numero -1.
ch+3 3003
#include <stdio.h>
ch+4 3004 llinclude <stdlib.h>

#define SIZE 50
ch+S 3005
void push(int i);
Memoria in.t pop(void);

int *tos, *pl, stack[SIZE];

int main(void)
__figura 5.2 Tutta l'arit~iic-;-d~i puQtatori fa.riferimeniO af tipo base.

j-- - ~-- - -
-~---
126 -crp lTO LO .5

In pop() nell'istruzione return sono necessarie le parentesi. Senza di esse, l' istru-
int value; zione avrebbe il seguente aspetto:
tos = stack; /* tos punta alla cima dello stack */
pl = stack; /* inizializza pl */ return *pl +l;

do { In questa forma, l'istruzione restituisce il valore contenuto all'indirizzo p1 pi 1 e


printf("Inmettere un valore: "); non il valore contenuto nell'indirizzo p1+1.
scanf("%d", &value);
if{value!=O) push(value);
else printf("Il valore in cima allo stack %d\n", pop());
while(value!=-1);
5.5 Puntatori e array
Vi una stretta relazione fra puntatori e array. Si consideri il seguente frammento
void push(int i)
{
di programma:
pl++;
if(pl==(tos+SIZE)) char str[SO], *pl;
printf("Superato il 1imite superiore dello stack"); pl = str;
exit(l);
Qui, p1 conterr l'indirizzo del primo elemento dell'array str. Per accedere al
*pl " i; quinto element~ di str si pu scrivere:

str[4]
pop(void)
{
if(pl==tos) oppure
printf("Superato il 1imi te inferiore del 1o stack");
exit(l); *(pl+4)

pl--; Entrambe queste istruzioni restituiranno il quinto elemento (occo?'e ricor~~e


retum *(pl+l); che gli array iniziano sempre da 0). Per accedere al quinto elemento st deve ut1ltz-
zare 4 per indicizzare str. Ma si pu anche aggiungere 4 al puntatore.p1 in qu3:to
p1 punta attualmente al primo elemento di str (occorre ricordare che 11 no~e dt un
Come si pu vedere, la memoria che costituisce lo stack formata dall' array array senza alcun indice restituisce l'indirizzo iniziale dell'array, che comsponde
stael<. Il puntatore p1 punta al primo byte contenuto in stack. La variabile p1 all'indirizzo del primo elemento).
consente di accedere allo stack. La variabile tos contiene l'indirizzo di memoria L'esempio precedente pu essere generalizzato. In pratica il C/C+~ consente:
della cima dello stack. Il valore di tos consente di evitare di oltrepassare i limiti di usare due metodi per accedere agli elementi di un array: 1' aritmetica dei puntaton
superiore e inferiore dello stack. Dopo l'inizializzazione dello stack, sar possibi- e l'indicizzazione dell'array. Anche se la notazione a indici pi facile da com-
le usare push() e pop(). Entrambe queste funzioni eseguono un test relazionale sul prendere, l'aritmetica dei puntatori pu essere pi veloce. Poi~h la vel?ci.t
puntatore p1 per rilevare eventuali errori di superamento dei limiti. In push() si spesso molto importante in programmazione, per accedere ~glt eleme.nt1 dt un
confronta il valore di p1 con l'indirizzo della fine dello stack, aggiungendua tos il array i programmatori professionali utilizzan9 comun~m~~te i P~?ta:o:i. .
valore SIZE (le dimensioni dello stack). Questo evita di superare il limite massi- Queste due versioni di putstr(), una delle quali utilizza l md1c1zzaz10ne
mo dello stack. In poPQsi confronta con tos il valore di p1 per assicurarsi di non dell' array e I' altra i puntatori, illustrano l'uso dei puntatori al posto
superare i limiti inferiori dello stack. dell'inlicizzazione.__ _

1
l
128 CAPiTOLO I PJH'HALO.R I - 129

La funzione putstr() scrive una stringa sul dispositivo di output standard un


carattere per volta. int t;

/* Indicizza s come un array. */ for{t=O; t<lO; t++)


void putstr(char *s) printf{"%d ", *q[t]);
{
register int t;
for(t=O; s[t]; ++t) putchar(s[t]); Si ricordi che q non un puntatore a interi ma un puntatore a un array di
puntatori a interi. Pertanto si deve dichiarare il parametro q come un atray di
puntatori a interi, come si visto nell'esempio. Non possibile dichiarare q sem-
/* Accede a s come un puntatore. */
plicemente come un puntatore a interi poich ci falso.
void putstr(char *s)
{ I puntatori ad array sono utilizzati molto spesso per contenere puntatori a
while(*s) putchar(*s++); stringhe. possibile creare una funzione che visualizzi un messaggio di errore
sulla base di un codice numerico:

La maggior parte dei programmatori professionisti trover la seconda versio- void syntax_error(int num)
{
ne pi facile da leggere e da comprendere. Infatti, la versione a puntatori il modo
static char *err(] = {
in cui questo tipo di routine viene comunemente scritta in C/C++.
"Impossibile aprire il file\n",
"Errore di Jettura\n",
"Errore di scrittura\n",
Array di puntatori
"Guasto al dispositivo\n"
};
Anche i puntatori possono essere disposti in un array come qualsiasi altro tipo di
dati. La dichiarazione di un array di puntatori a int di dimensione IO la seguente:
printf("%s", err[num]);

int *x[lO];
L'array err contiene i puntatori ad ogni stringa. Come si pu vedere, l'istru-
Per assegnare l'indirizzo di una variabile intera chiamata var al terzo elemen- zione printf() che si trova all'interno di syntax_error() viene richiamata con un
--- _ _io _<;i!!l!' array di puntatori, si deve utilizzare l'istruzione puntatore a caratteri che punta a uno dei vari messaggi di errore indicizzati sulla -- --
base del numero di errore passato alla funzione. Ad esempio, se num contiene il
x[2] = &var; valore 2, verr visualizzato il messaggio Errore di scrittura.
Pu essere interessante sapere che !'argomento della riga di comando argv
Per conoscere il valore di var si deve utilizzare: un array di puntatori a carattere (vedere il Capitolo 6).

*x[2]

5.6 Indirizzamento multilivello


Se si deve passare un array di puntatori a una funzione, si pu utilizzare lo
stesso metodo gi_yisto per il passaggio di altri tipi di array: semplicemente ri-
..
~' Esistono anche puntatori che puntano a un altro puntatore che pirnta al valore di
chiamare la funzione con il nome dell'array senza alcun indice. Ad esempio, una
funzione che riceve l'array x avr il seguente aspetto:

void display_array(int *q[])


I11
:1.~;.
_ - .-~
.--L:~:
destinazione. Una situazione di questo tipolc_4!~J:!1ata indirizzamento multilivello
e si parla quindi di puntatori a puntatori. L'uso di puntatori a puntatori pu essere
fonte..dLQofusione. La Figura 5.3 aiuta a chiarire il concetto di indirizzamento
(~;;~ . multilivello. Come si pu vedere, il-valore di un cmune puntatore l'indirizzo
~-C 11=-~~-~- _=-=-:---:
130 CAPITOLO ---=:r_.t_ \ot-"- I f"'\ '

dell'oggetto che contiene il valore desiderato. Nel caso di un puntatore a un I/i nel ude <s tdi o. h>
puntatore, il primo puntatore contiene l'indirizzo del secondo puntatore che punta
__ ali' oggetto che contiene il valore desiderato. int main(void)
L'indirizzamento multilivello pu essere replicato fino al livello desiderato, ma rara- {
mente necessario utilizzare pi di un puntatore a un puntatore. Infatti, livelli di int X, *p, **q;
indirizzamento eccessivi risultano difficili da seguire e possono portare a errori concettuali.
X = 10;
HQTA~~~ Non si deve confondere l'indirizz.amento multilivello con le P = &x;
strutture di dati ad alto livello, come ad esempio le liste concatenate che utilizza- q = &p;
no i puntatori. Si tratta di due concetti completamente diversi.
printf("%d", **q); /*stampa il valore di x */
Una variabile di tipo puntatore a puntatore deve essere dichiarata come tale.
Si pu fare ci inserendo un ulteriore asterisco di fronte al nome della variabile.
Ad esempio, la seguente dichiarazione dice al compilatore che newbalance un Qui, p dichiarato come puntatore a un intero e q come puntatore a un puntatore
puntatore a un puntatore a un oggetto di tipo float: a un intero. La chiamata a printf() stampa sullo schenno il numero 10.

f1 oa t **newba 1ance;

fondamentale comprendere che newbalance non un puntatore a un nume- 5.7 Inizializzazione di puntatori
ro in virgola mobile ma un puntatore a un puntatore a un numero float.
Dopo la dichiarazione di un puntatore locale e prima che gli sia stato assegnato un
Per accedere al valore di destinazione puntato in modo indiretto da un puntatore
valore, esso contiene un valore non noto (al contrario, i puntatori globali vengono
a un puntatore, si deve applicare per due volte l'operatore asterisco, come nel-
l'esempio seguente: automaticamente inizializzati a null).
NOTA Se si tenta di utilizzare il puntatore prima di avergli assegnato
un valore valido, si corre il rischio di bloccare il programma e, talvolta, persino il
sistema operativo del computer: un tipo di errore veramente grave!
Nell'utilizzo dei puntatori, la maggior parte dei programmatori C/C++ utiliz-
Puntatore Variabile za un'importante convenzione: un puntatore che attualmente non punta a un indi-
rizzo di memoria valiiliL_deve avere valore nullo (zero). Per convenzione, ogni
Indirizzo Valore puntatore nullo si intende eh~ ~on punti a nulla e non dovrebbe essere utilizzato.
Tuttavia, il fatto che un puntatore abbia un valore nullo non lo rende "sicuro". Il
nome di "puntatore nullo" non che una convenzione fra programmatori. Non si
tratta di una regola stabilita dal linguaggio C/C++. Ad esempio, se si utilizza un
Puntatore Puntatore puntatore nullo sul Iato sinistro di un'istruzione di assegnamento, si corre ancora
Variabile
il rischio di bloccare il programma o il sistema operativo.
Indirizzo Indirizzo Valore Poich si presume che un puntatore nullo sia inutilizzato, lo si pu utilizzare
per semplificare la codifica e aumentare l'efficienza delle routine che utilizzano
puntatori,_Ad esemRio, si pu utilizzare un puntatore nullo per indicare la fine di
un array di puntatori. Una routine che accoa a tale array sapr di averne incontra- -
to la fine quando incontrer il valore nullo. Questo tipo di approccio illustrato
dalla funzione search().
Figura 5;3 Indirizzamento semplice e multilivello.
----

- -- --~ ---
/* ricerca un nome */ return O;
int search(char *p (], char *name)
{
register int t; Nel C++ standard, il tipo di una stringa letterale tecnicamente const char *.
Ma il linguaggio C++.fornisce una conversione automatica in char *. Pertanto il
for(t=O; p [t]; ++t)
programma precedente rimane valido. Tuttavia, questa conversione automatica
if(!strcmp{p[t]. name)) return t;
una funzionalit su cui opportuno non fare affidamento quando si realizza nuo-
return -1; /* non trovato */ vo codice. Nei nuovi programmi si deve sempre presumere che le stringhe lettera-
li siano costanti e che la dichiarazione di p nel programma precedente debba esse-
re scritta nel seguente modo:
Il ciclo for all'interno di search() continua a essere ripetuto fino a che non
viene trovl!ta una corrispondenza o fino al raggiungimento del puntatore nullo. Se const char *p = "ciao a tutti";
si assume che la fine dell'array sia indicata da un puntatore nullo, la condizione
che controlla il ciclo diverr falsa non appena verr raggiunto il puntatore nullo.
I programmatori C/C++ inizializzano normalmente tutte le stringhe. Si visto 5.8 Puntatori a funzioni
un esempio di ci nella funzione syntax_error() nella sezione "Array di puntatori".
Un'altra variante del tema dell'inizializzazione il seguente tipo di dichiarazione Una funzionalit molto potente (ma anche fonte di confusione) del C++ il con-
di una stringa:
cetto di puntatore afanzione. Anche se una funzione non una variabile, essa ha
un indirizzo fisico in memoria che pu pertanto essere assegnato a un puntatore.
char *p = "ciao a tutti"; L'indirizzo della funzione il punto di accesso a tale funzione e dunque pu
essere utilizzato anche per richiamarla. Un puntatore che punta a una funzione
Come si pu vedere, il puntatore p non un array. Il motivo di questo tipo di pu essere utilizzato per richiamarla. I puntatori a funzioni possono anche essere
inizializzazione risiede nel modo in cui opera il compilatore. Tutti i compilatori utilizzati come parametri di altre funzioni.
CIC++ creano una tabella di stringhe, utilizzata internamente dal compilatore per Per comprendere il funzionamento dei puntatori a funzione, necessario co-
conservare le costanti di tipo stringa utilizzate nel programma. Pertanto, l'istru- noscere di pi sul modo in cui le funzioni vengono compilate e richiamate
zione di dichiarazione precedente inserisce nel puntatore p l'indirizzo della strin- Innanzitutto, durante la compilazione di una funzione, il codice sorgente viene
ga ciao a tutti che memorizzata nella tabella delle stringhe. All'interno di un trasformato in codice oggetto e viene definito un punto di ingresso nella funzione.
programma, p potr essere utilizzata come qualsiasi altra stringa. Ad esempio, il Quando viene eseguita una chiamata alla funzione durante l'esecuzione del pro-
seguente programma perfettamente corretto: gramma, viene eseguita una chiamata in linguaggio macchina a tale punto di in-
gresso. Pertanto, se un puntatore contiene lindirizzo del punto di ingresso di una
#include <stdio.h> funzione, potr anche essere utilizzato per richiamare tale funzione.
#include <string.h>
possibile conoscere l'indirizzo di una funzione utilizzando il nome della
funzione senza parentesi o argomenti (corrisponde al modo in cui si ottiene l'indi-
char *p = "ciao a tutti";
rizzo degli array: si usa solo il nome dell'array senza indici). Per vedere il funzio-
int main(void) namento di ci, si studi il seguente programma, ponendo particolare attenzione
{ alle dichiarazioni:
regi ster i nt t;
#include <stdio.h>
/* stampa la stringa in avanti e indietro*/ #include <string.h>
printf(p);
----~~~~=strl en{p)-1; t>-1; t--) printf ("%c", p[t]); void check(char. *a, char *b,
int (*cmp)(const char *, const char *));
134 --CAl'lT o Lo

int main(void) puntatore (ovvero che cmp un puntatore a funzione e non il nome di una funzio-
{ ne). A parte questo le due espressioni sono equivalenti.
char sl[BO], s2[80]; Si noti che si pu richiamare check() utilizzando direttamente strcmp(), come
int (*p)(const char *, const char *); si vede di seguito:

p = strcmp; check (sl, s2, strcmp);

gets(sl);
gets (s2);
Questo elimina la necessit di utilizzare un'ulteriore variabile puntatore.
Ci si potrebbe chiedere perch qualcuno dovrebbe scrivere un programma in
:heck(sl, s2, p); questo modo. Ovviamente non vi alcun vantaggio e nell'esempio precedente si
introdotta un bel po' di confusione. Tuttavia, talvolta pu essere vantaggioso
return O; passare le funzioni come parametri o creare un array di funzioni. Ad esempio,
quando si scrive un compilatore o un interprete, il parser (la parte del compilatore
che valuta le espressioni) richiama spesso varie funzioni di supporto, come ad
void check(char *a, char *b, esempio quelle che calcolano operazioni matematiche (seno, coseno, tangente e
int (*cmp)(const char *, const_char *)) cos via), quelle che eseguono operazioni di VO o quelle che accedono a risorse
del sistema. Invece di avere una grande istruzione switch contenente un elenco di
pri ntf("controllo di uguagli anza\n"); tutte queste funzioni, si pu creare un array di puntatori a funzione. In questo
if(!(*cmp)(a, b)) printf("uguali");
modo, si seleziona la funzione corretta in base a un indice. Si pu vedere l'uso di
else printf("di~ersi ");
questa tecnica studiando una versione espansa dell'esempio precedente. In questo
programma, check() pu controllare l'uguaglianza alfabetica o numerica richia-
mando semplicemente due funzioni di confronto diverse.-
Quando viene richiamata la funzione check(), come parametri vengono pas-
sati due puntatori a caratteri e un puntatore a funzione. All'interno della funzione
#include <stdio.h>
check(), gli argomenti sono dichiarati come puntatori a carattere e puntatore a #include <ctype.h>
funzione. Si noti la dichiarazione del puntatore a funzione. Si deve utilizzare una #include <stdlib.h>
forma simile quando si deve dichiarare ogni altro puntatore a funzione, anche se il #include <string.h>
tipo restituito dalla funzione pu essere diverso. Le parentesi attorno a *cmp sono
necessarie perch_iLcrunpila.tore interpreti correttamente questa istruzione. void check(char *a, char *b,
All'intero~ di check(), l'espressione: int (*cmp)(const char *, const char *));
int numcmp(const char *a, const char *b);
(*cmp) {a, b)
int main(void)
{
richiama strcmp(), puntata da cmp, con gli argomenti a e b. Anche in questo caso
char sl[BO], s2[80];
sono necessarie le parentesi attorno a *cmp. Questo esempio illustra anche il me-
todo generale per utilizzare un puntatore a funzione per richiamare la funzione gets(sl);
puntata. Si pu usare anche la seguente forma semplificata: gets (s2);

cmp{a, b); if(isalpha(*sl))


check(sl, s2, strcmp);
Il motivo per cui si trover con maggiore frequenza la prima fonna il fatto else
che rende palese per chiunque il fatto che la funzione viene richiamata tramireun check(s~. s2,numemp)-;- -
PUNTATORI 137
136 CA P I T O LO

area di memoria permanente) e lo stack. Anche se le dimensioni dello heap non


return O; sono note, si pu presumere che esso contenga una grande quantit di memoria
libera.
void check(char *a, char *b,
Il nucleo del sistema di allocazione dinamica del e formato dalle funzioni
int (*cmp)(const char *, const char *)) malloc() e free(). La maggior parte dei compilatori forniscono molte altre funzioni
di allocazione dinamica, ma queste due sono le pi importanti. Queste funzioni
pri ntf("controllo di uguagl ianza\n"); operano insieme utilizzando la regione di memoria libera per definire e gestire un
if(!(*cmp)(a, b)) printf("uguali"); elenco delle celle di memoria disponibili. La funzione malloc() alloca memoria
else printf("diversi"); mentre free() la rende nuovamente disponibile. Questo significa che ogni volta
che viene eseguita una richiesta di memoria con malloc(), viene allocata una por-
zione della memoria che precedentemente era libera. Ogni volta che viene chia-
int numcmp(const char *a, const char *b) mata la funzione free(), la memoria viene restituita al sistema. Ogni programma
{ che utilizza queste funzioni deve includere il file header stdlib.h (un programma
if(atoi (a)==atoi (b)) return O; C++ pu utilizzare il nuovo file header <cstdlib> ).
else return 1; Il prototipo della funzione malloc() il seguente:

In questo progrmma, se si immette una lettera,~ ch.eck() viene p.assata strcm~(), void *malloc(size_t numero_di_byte);
altrimenti viene usata numcmp(). Poich check() richiama la funzione <:he le v~e
ne passata, in questo modo si possono usare funzioni di confronto differenti a Qui, numero_di_byte il numero di byte di memoria che si intende allocare. Il
tipo size_t definito in stdlib.h come (pi o meno) un intero unsigned. La funzio-
seconda dei casi.
ne malloc() restituisce un puntatore di tipo void; quesfo significa che possibile
assegnarlo a ogni tipo di puntatore. Dopo una chiamata avvenuta con successo,
malloc() restituisce un puntatore al primo byte della regione di memoria allocata
5.9 Le funzioni di allocazione dinamica del C nello heap. Se la memoria disponibile non sufficiente per soddisfare le richieste
di malloc(), si verifica un errore di allocazione e la funzione malloc() restituisce un
I puntatori forniscono il supporto necessario per il ?ote~te sistema di alloc~zione puntatore nullo.
dinamica del C. L'allocazione dinamica il modo m cm un programma puo ?tte: Il frammento di codice mostrato di seguito alloca I000 byte contigui di memoria:
nere memoria durante l'esecuzione. Come si detto in precedenza, lo spazm di
memoria delle variabili globali viene allocato al momento della compilazione. Le char *p; ---- -----
variabili locali utilizzano invece lo stack. Tuttavia, durante l'esecuzione del pro- P = malloc(lOOO); /*chiede 1000 byte*/
gramma non possibile aggiungere n variabili globali n variabili locali. Vi son~
casi in cui le esigenze di memoria di un programma non possono essere detenm- Dopo l'assegnamento, p punta all'inizio dei 1000 byte di memoria libera.
nate prima della sua esecuzione. Ad esempio, un word processor o un. data~as~ Si noti che, per assegnare a p il valore restituito da malfoc() non richiesta
dovranno poter utilizzare tutta la RAM disponibile nel sistema. Tuttavia, poich~ alcuna conversione di tipo (cast). In C un puntatore void *viene automaticamente
la quantit di memoria RAM disponibile v~ria ?a ~?mpute~ a com?uter, non ~ convertito nel tipo di puntatore che si trova sul lato sinistro dell'assegnamento.
possibile utilizzare per questi scopi le comum vanabth. Questi ed altn programmi Questa conversione automatica non avviene invece in C++. In particolare, in C++,
dovranno quindi allocare memoria su richiesta. Il linguaggio C++.supporta due per assegnare un puntatore void a un altro tipo di puntatore necessario specifi-
sistemi di allocazione dinamica: quello definito dal C e quello specifico del C++. care una conversione di tipo esplicita, Pertanto, in C++, l'assegnamento prece-
Il sistema specifico del C++ ontiene varie estensioni rispetto a quello utiliz~ato dente si sarebbe dovuto scrivere come:
dal C ma questo approccio verr descritto nella Parte seconda.. In questa pnma
parte verranno invece descritte le funzioni di allocazione dinamica del C. p = (char *) malloc(lOOO);
La memoria allocata aalt funzioni di allocazione dina_mica.deLC ottenuta
dallo heap, la regione di-memoria libera che si trova ~a_iLprogramma (e la sua
138 CAPITOLO 5 . I P U N T A T O 'Rl 139

Come regola generale, quando in C++ si deve assegnare (o convertire) un tipo grammi. Allo stesso tempo, quando un puntatore contiene un valore errato, pu
di puntatore in un altro si deve sempre impiegareuna conversione cast. Questa dare origine a uno dei bug pi difficili da scovare.
una delle differenze pi importanti fra il C e il C++. Un puntatore errato difficile da scovare poich il problema non risiede, in
Il prossimo esempio alloca-uro spazio per 50 interi. Si noti l'uso di sizeof per effetti, nel puntatore. Il problema che quando si esegue un'operazione con un
garantire la trasportabilit. puntatore errato, si legge o scrive su una locazione di memoria. Se loperazione
di lettura, il peggio che pu capitare di leggere oggetti senza senso. Se invece
int *p; l'operazione di scrittura, si potrebbe iniziare a scrivere su parti del codice o su
p = (int *)malloc(SO*sizeof(int)); altri dati. Questo genere di problema potrebbe quindi palesarsi solo molto pi
avanti nell'esecuzione del programma e potrebbe quindi condurre il programma-
Poich lo heap non infinito, quando si alloca memoria, si deve controllare il tore a ricercare il bug nel luogo errato. Potrebbe non esservi alcun indizio a sug-
valore restituito da malloc() per assicurarsi che non sia un puntatore nullo, prima gerire che un puntatore sia la causa del problema. Questo tipo di bug il problema
di impiegarlo. L'uso di un puntatore nullo provoca quasi certamente un blocco del pi temuto dai programmatori.
programma. Il modo corretto per allocare memoria e verificare la validit del Poich gli errori che vedono protagonisti i puntatori sono cos problematici,
puntatore restituito illustrato dal seguente frammento di codice. occorre fare del proprio meglio per evitarli. Questa parte del capitolo si occupa di
alcuni dei pi comuni errori che vedono come protagonisti i puntatori. L'esempio
p = (int *) malloc(lOO); classico di errore dovuto a un puntatore riguarda i puntatori non inizializz.ati.
if( !p} { Si consideri il seguente programma:
printf("Memoria esaurita. \n");
exit(l);
/* Questo programma errato. */
int main(void)
{
Naturalmente, al posto della chiamata a exit() si pu utilizzare un qualsiasi nt X, *p;
altro gestore di errori. Occorre semplicemente assicurarsi di non utilizzare il
puntatore p nel caso in cui questo sia nullo. X = 10;
La funzione free() l'esatto opposto di malloc() in quanto restituisce al siste- *p = x;
ma la memoria precedentemente allocata. Dopo aver liberato un'area di memoria,
essa potr essere riutilizzata con una successiva chiamata a malloc(). Il prototipo return O;
della funzione free() il seguente:

void free(void *p); Questo programma assegna il valore 10 a una 1ocazi0i'ie di"nemoria scono-
sciuta. Ecco il motivo: poich il puntatore p non ha mai ricevuto alcun valore, nel
Qui, p un puntatore a memoria che era stata precedentemente allocata utiliz- momento in cui viene eseguito lassegnamento *p=x, conterr un valore ignoto. In
zando la funzione malloc(). fondamentale richiamare sempre free() con un argo- questo modo, il valore di x verr scritto in una locazione di memoria sconosciuta.
mento valido, in caso contrario, si distrugger l'elenco di gestione della memoria Questo tipo di problema passa spesso inosservato quando il programma piccolo
libera. poich le casualit sono a favore di p che potrebbe in ogni caso contenere un
indirizzo "sicuro" (ovvero un indirizzo che non si trovi nel codice, nell'area dati o

5.1 O Problemi con i puntatori


Nulla pu dare pi problemi di un puntatore a zonzo! I puntatori sono un'arma a
I ;__
nel sistema operativo). Tuttavia, quando il programma comincia ad essere piutto-
sto lungo, aumentano le probabilit che p punti a qualche infonnazione vitale.
Nel caso peggiore, il programma si bloccher. La soluzione consiste nell'assicu-
rarsi sempre che un puntatore punti a un indirizzo valido, prima di utilizzarlo.
Un secondo errore molto comune dovuto .a una semplice incomprensione
doppio taglio. Forniscono immense potenzialit-e- sono necessari per m~It! p~o:~_ :1:::-
1i
sull'uso dei puntatori. Si consideri il seguente programma:

- -- --- -- .
----.f.
r
? .
140 eA p I T e Lo 5 I PUNTATORI 141

r Questo progral!l1la errato. */ Un errore correlato sorge quando si assume che due array adiacenti possano
#include <stdio.h> essere indicizzati come se fonnassero un solo array, incrementando semplice-
mente un puntatore anche quando giunge ai limiti dell' array. Ad esempio,
int main(void)
{
int first[lO], second[lO];
int X, *p;
int *p, t;
X = 10; p = first;
P = x; for(t=O; t<20; ++t) *p++ = t;
pri ntf("%d", *p);
Questo non un buon modo per inizializzare gli array first e second con i
return O; numeri da Oa 19. Anche se potrebbe funzionare con alcuni compilatori e in alcuni
casi. Questo frammento di programma assume che gli array siano posizionati
fianco a fianco in memoria a partire da first. In qualche caso questo potrebbe non
La chiamata a printf() non visualizza il valore di x, che 10, ma un valore essere vero.
sconosciuto, in quanto l'assegnamento: . Il programma successivo illustra un tipo di bug molto pericoloso. Prima di
procedere nella lettura, si provi a ricercarlo da soli.
p = x;
/* Questo progral!l1la contiene un bug. */
errato. Questa istruzione assegna il valore 10 al puntatore p. Ma p deve contene- lii nel ude <string. h;
re un indirizzo, non un valore. Per correggere il programma, basta scrivere: #include <stdio.h>

int main(void)
P = &x; {
char *pl;
Un altro errore che pu verificarsi dovuto a un'assunzione errata a proposito char s [80];
della posizione delle variabili in memoria. Non si pu mai sapere dove i dati si
troveranno in memoria o se verranno nuovamente posizionati nello stesso luogo o pl = s;
se ogni compilatore li tratter nello stesso modo. Per questo motivo, un confronto do {
fra puntatori che non puntino a un oggetto comune pu portare a risultati impre- - i'ts ( s );- - p: 1egge una stringa */
vedibili. Ad esempio,
/* stampa l'equivalente decimale
char s[BO], y[BO]; di ogni carattere */
char *pl, *p2; while(*pl) printf(" %d", *pl++);

pl = s; while(strcmp(s, "fine"));
p2 = y;
if(pl < p2) return O;

normalmente un concetto errato. Si tratta di una situazione piuttosto insolita, si


potrebbe utilizzare qualcos;i. del genere per determinare la posizione relativaaelle Questo programma utilizza p1 per stampare i valori ASCII associati ai carat-
____ ".:~abili, ma que~-~ un caso rar~:...___ _ _ - teri contenuti in s. II problema consiste nel fatto che a p1 viene assegnato l'indi-
rizzo di s una sola volta. La prima volta che si entra nel ciclo, p1 punta-a-I-primo-

- --- ------------
carattere contenuto in s. La seconda volta, continua da dove era rimasto, poich : Capitolo 6
non viene riposizionato all'inizio di s. Questo successivo carattere pu appartenere
alla seconda stringa, a un'altra variabile o anche a una porzione del programma! Le funzioni
Il modo corretto per scrivere questo programma il seguente:

/* Questo il prograrrma corretto. */ 6.1 La forma generale di una funzione


#i nel ude <string.h> 6.2 Regole di visibilit delle funzioni
#include <stdio.h> 6.3 Gli argomenti delle funzioni
int main(void) 6.4 Gli argomenti di main(): argc e argv
{ 6.5 L'istruzione return
char *pl; 6.6 Ricorsione
char s[80];
6.7 Prototipi di funzioni
do { 6.8 Dichiarazione di elenchi di parametri
pl = s; di lunghezza vari!!bile
gets(s); /* legge una stringa */ 6.9 Dichiarazione di parametri con metodi
vecchi e nuovi
/*stampa l'equivalente decimale 6.10 Elementi implementativi
di ogni carattere */
while(*pl) printf(" %d", *pl++);

~"'e funzioni sono i blocchi fondamentali che costitui-


while(strcmp(s, "fine"));

return O; scono il C!C++ e sono il luogo in cui si svolgono tutte le attivit del programma.
Questo capitolo esamina le funzionalit delle funzioni e, incluso il passaggio di
parametri, la restituzione di un valore e la ricorsione. Nella Parte seconda verran-
no discusse tutte le funzionalit tipiche delle funzioni C++, come ad esempio
Qui, ad ogni iterazione del ciclo, a p1 viene assegnato l'inizio della stringa. In l'overloading e i parametri indirizzo.
generale, si deve ricordare di reinizializzare un puntatore ogni volta che lo si
riutilizza. _________ _
Il fatto che la gestione errata dei puntatori possa provocare bug difficili da
scovare non un buon motivo per evitare l'uso dei puntatori. Occorre semplice- 6.1 La forma generale di una funzione
mente fare attenzione e assicurarsi di sapere dove punta un puntatore prima di
utilizzarlo. La fonna generale di funzione la seguente:

tipo _restituito nome-funzione(elenco_parametri)


{
corpo_della_funzione
}

il tipo_restituito specifica il tipo di dati restituito dalla funzione. Una funzio-


ne pu restituire un qualsiasi tipo di dati tranne un array. L' elenco_parametri un
elenco.di nomi-di-variabili separati da virgole e_ seguiti-dai-rispettivi tipi che rice-
vono i valori degli argomenti nel momento in cui la-funzione viene richiamata.
-----
144 CAPITOLO 6
LE FUNZIONI 145

Una funzione pu anche essere senza parametri, in tal caso l'elenco dei parametri In C (e in C++) non possibile definire una funzione all'interno di un'altra
sar vuoto. Tuttavia, anche se non vi alcun parametro, richiesta la presenza funzione. Questo il motivo per cui n il C n il C++ sono tecnicamente linguaggi
delle parentesi. strutturati a blocchi.
Nella dichiarazione di variabili, possibile dichiarare pi variabili di un tipo
comune, utilizzando un elenco di nomi di variabili separati da virgole. Al contra-
rio, tutti i parametri della funzione devono essere dichiarati singolarmente e per
ognuno si deve specificare sia il tipo che il nome. Questo significa che l'elenco di 6.3 Gli argomenti delle funzioni
dichiarazioni di parametri per una funzione ha la seguente forma generale:
Se una funzione deve utilizzare degli argomenti, deve dichiarare delle variabili
fttipo nomevarl, tipo nomevar2, ... , tipo nomevarN) che accettino i valori degli argomenti. Queste variabili sono chiamate i parametri
formali della funzione. Essi si comportano come ogni altra variabile locale della
Ad esempio, ecco un modo corretto ed uno errato per dichiarare i parametri funzione e vengono creati all'ingresso nella funzione e distrutti all'uscita. Come
delle funzioni: si pu vedere nella seguente funzione, la dichiarazione dei parametri si trova subi-
to dopo il nome della funzione:
f(int i, int k, int j) /* corretta *I
f(int i, k, float j) /* errata */ /* Restituisce 1 se c si trova nella stringa s; altrimenti restituisce O. */
int is_in(char *s, char c)
{
whil e(*s)
6.2 Regole di visibilit delle funzioni if(*s=c) return 1;
else s++;
Le regole di visibilit di un linguaggio sono le regole che governano ci che una return O;
parte del codice conosce e le possibilit di accesso che ha su un'altra parte di
codice o dati.
Ogni funzione forma un blocco unitario di codice. Il codice della funzione La funzione is_in() ha due parametri: s e c. Questa funzione restituisce I se il
privato e appartiene a tale funzione; non possibile quindi accedere a un'istruzio- carattere e si trova nella stringa s; in caso contrario, la funzione restituisce O.
ne in nessun 'altra funzione se non tramite una chiamata a tale funzione (ad esem- Come nel caso delle variabili locali, possibile eseguire assegnamenti ai pa-
pio, non possibile usare un goto per saltare all'interno di un'altra funzione). IL. ___ _ rametri formali di una funzione oppure utilizzarli in qualsiasi espressione. Anche
codice che forma il corpo di una funzione nascosto al resto del programma e, se se queste variabili eseguono il compito speciale di ricevere il valore degli argo-
non si usano variabili o dati globali, non pu influire n subire influssi da altri menti passati alle funzioni, comunque possibile utilizzarle come ogni altra va-
parti del programma. In altre parole, il codice e i dati che sono definiti all'interno riabile locale.
di una funzione non possono interagire con il codice o i dati definiti in un'altra
funzione in quanto le due funzioni hanno diverse aree di visibilit. Chiamata per valore e chiamata per indirizzo
Le variabili che sono definite all'interno di una funzione sono chiamate va-
riabili locali. Una variabile locale nasce nel momento in cui si entra nella funzio- In un linguaggio di programmazione possibile passare gli argomenti alle
ne e viene distrutta alla sua uscita. Questo significa che le variabili locali non subroutine in due modi. Il primo detto chiamata per valore. Questo metodo
possono conservare il proprio valore fra due chiamate di funzione. L'unica ecce- copia il valore di un argomento nel parametr9 formale della subroutine. In questi:i
?ione a questa regola si verifica quando la variabile dichiarata con lo spcificatore - caso~ogni modifica eseguita sul parametro non avr alcun effetto sull'argoment
di classe di memorizzazione static. In questo caso, il compilatore tratter la varia- passato. .
bile come se fosse una variabile globale per quanto riguarda la memorizzazione. . II secondo metodo per passare argomenti a una subroutine la chiamata per
ma continuer a limitare la sua visibilit solo all'interno della funzione (questo -. indirizzo. Con questo metodo, nel parametro viene-topiato I' indiri::;:.o dell'-argo-
-ru-go~ento e stato descritto approfonditamente nel Capitolo~- ___ _ ,-~ento. All'interno della subroutine, possibile.utilizzare l'indirizzo per accede_re
146 CAPITOLO 6
LE FUNZIONI 147

all'effettivo argomento utilizzato nella chiamata. Questo significa che le modifi- I puntatori possono essere passati a una funzione come ogni altro valore. Na-
che eseguite sul parametro avranno effetto anche sull'argomento. turalmente, necessario dichiarare -i parametri di tipo puntatore. Ad esempio, la
Normalmente il C/C++ utilizza la chiamata per valore per il passaggio degli funzione swap().L che scambia i valori delle due variabili intere puntate dai suoi
argomenti. In generale, questo significa che il codice all'interno di una funzione argomenti potrebbe avere il s~guente aspetto:
non pu modificare gli argomenti utilizzati per richiamare la funzione. Si consi-
deri il seguente programma: void swap(int *x, int *y)
{
#include <stdio.h> int temp;

i nt sqr( i nt x); temp = *x; /* salva il valore all'indirizzo x */


*x = *y; /* copia y in x */
int main(void) *y = temp; /* copi a x in y *I
{
int t=lO;
swap() in grado di scambiare i valori delle due variabili puntate da x e y perch
printf("%d %d", sqr(t), t);
le vengono passati gli indirizzi delle variabili e non i valori. Pertanto, all'interno
return O;
della funzione, sar possibile accedere al contenuto delle variabili utilizzando le
comuni operazioni sui puntatori. Questo il motivo per cui possibile scambiare
il contenuto delle variabili utilizzate per chiamare la funzione.
int sqr(int x) Si ricordi che swap() (e ogni altra funzione che utilizza parametri puntatore)
deve essere richiamata utilizzando gli indirizzi degli argomenti. II seguente pro-
x = x*x; gramma mostra il modo corretto per richiamare swap():
return (x);
void swap{int *x, int *y);

In questo esempio, il valore dell'argomento di sqrt(), 10, viene copiato nel int main(void)
parametro x. Quando viene eseguito l'assegnamento x=x*x, solo la variabile loca- {
le x viene modificata. La variabile t utilizzata per richiamare sqr() continuer a int i, j;
contenere il valore 10. Pertanto l'output sar 100 10. ----- ----
= 10;
!'.IOTA ....... .;;.!
Alla funzione viene passata solo una copia del valore dell'ar- = 20;
gomento. Ci che avviene all'interno della funzione non ha alcun effetto sulla
variabile utilizzata nella chiamata. swap(&i, &j); /*passa gli indirizzi di e j */

Creazione di una chiamata per indirizzo In questo esempio, alla variabile i viene assegnato il valore 10 e a j viene
assegnato il valore 20. Quindi viene chiamata swap() con gli indirizzi di i e j. Per
Anche se la convenzione di passaggio di parametri del C/C++ prevede la chiama- produrre gli indirizzi delle variabili viene utilizzato l'operatore unario &. Pertan-
ta per valore, possibile creare una chiamata per indirizzo passando alla funzione to, alla funzione swap() vengono passati gli indirizzi di i e j e non i rispettiv-i
un puntatore a un argomento '!!.E.osto _del!' argomento stesso. Poich alla funzione valori.
viene passato l'indirizzo dell'argomento, il codice che si trova all'interno della
_ _ _ _ funzione pu modificare il valore dell'argomento che si trova all'esterno della NOTA Il linguaggio C++ consente di automatizzare completamente
funzione stessa. - - --- - una chiamata per indirizzo tramite parametri indirizza. Qiiestafun::.torraUt verr
descrittd nella Parte seconda-:-
148 CAPITOLO 6
LE F uwz I o N I 149
Chiamate a funzione e array void print_upper(char *string);

Gli array sono stati trattati in dettaglio nel Capitolo 4. Questa sezione si occupa int main(void)
del passaggio degli array come argomenti a funzioni in quanto questa una ecce- {
zione alla regola di passaggio dei parametri. char s [80);
Quando si utilizza un array come argomento di una funzione, alla funzione ne
viene passato solo l'indirizzo. Questa un'eccezione alla convenzione di chiama- gets (s);
ta per valore degli argomenti. In questo caso, il codice che si trova all'interno print_upper(s);
della funzione opera e pu modificare il contenuto effettivo dell' array utilizzato printf("non viene modificata: %s", s);
per richiamare la funzione. Ad esempio, si consideri la funzione print_upper()
che stampa in lettere maiuscole la stringa passata come argomento: return O;

#include <stdio.h>
void print_upper(char *string)
#i nel ude <ctype.h> {
register int t;
void print_upper(char *string);
for(t=O; string[t]; ++t)
i nt mai n (voi d)
putchar(toupper(stri ng[t]));
{
char s [80);

gets (s); . In questa versione, il contenuto dell'array s non viene toccato in quanto
print_upper(s); pnnt_upper() non modifica i valori che esso contiene.
printf("in maiuscolo: %s", s); La ~unz.ione gets() contenuta nella libreria standard un classico esempio di
passaggio di array a una funzione. Anche se la funzione gets() della libreria standard
return O; molto, pi sofisticata, la seguente versione semplificata, chiamata xgets() pu
dare un idea del suo funzionamento.
/* Stampa una stringa in lettere maiuscole. */ /* Una versione semp 1i fica ta
void print_upper(char *string)
della funzione gets() della libreria standard*/--------
{
char *xgets(char *s)
register-int t; {
char eh, *p;
for(t=O; string[t]; ++t) { int t;
string[t] = toupper(string[t]);
putchar(string[t]);
P = s; /* gets() restituisce un puntatore a s */

for(t=O; t<BO; ++t) {


eh = getchar();
Dopo la chiamata a print_upper(), il contenuto dell'array s in main() sar la
stessa stringa ma in lettere maiuscole. Se questo non il comportamento- deside- swi tch (eh) {
rato, si potrebbe _riscrivere il programma nel modo seguente: case ' \n':
s[t] = ' \O'; /* conclude
#i nel ude <stdio.h> la stringa */
#i nl uae <ctype. h> return-p; - - - . --
~------~

L ~-.f.Y-N-Z-1-0 N1 151

case '\b': del programma viene considerato come primo argomento. Il parametro argv un
if(t>O) t--; puntatore a un array di puntatori a caratteri. Ogni elemento di questo array punta
break; a un argomento della riga di comando. Tutti gli argomenti della riga di comando
default: sono stringhe ed i numeri dovranno pertanto essere convertiti nel formato interno
s[t] = eh; corretto. Ad esempio, questo semplice programma stampa sullo schermo Salve e
il nome immesso come argomento.
}
S [79]: I \0 I ;
#include <stdio.h>
return p;
#include <stdl i b.h>

int main(int argc, char *argv[])


La funzione xgets() pu essere richiamata con un puntatore a carattere, che {
naturalmente pu essere anche il nome di un array di caratteri che, per definizio- if(argc!=2) {
ne, un puntatore a carattere. Subito dopo l'ingresso in xgets() si trova un ciclo for printf("Hai dimenticato di scrivere il nome.\n");
da O a 80. Questo impedisce l'immissione di stringhe troppo lunghe. Se si cerca di exit(l);
immettere pi di 80 caratteri, la funzione termina con un return. La vera funzione
getsO non ha questo limite. Poich il C/C++ non prevede verifiche dei limiti di un printf("Salve %s", argv[l]);
array, necessario assicurarsi che qualsiasi array utilizzato per richiamare xgets()
possa accettare almeno 80 caratteri. Mano a mano che si immetteranno caratteri return O;
alla tastiera, essi verranno sistemati nella stringa. Se si preme il tasto BACKSLASH, il
contatore t verr ridotto di 1, cancellando in effetti dall' array il carattere prece-
Se si chiama questo programma nome e si immette il nome Alice, si potr
dente. Quando si preme INVIO, al termine della stringa viene inserito un carattere
.richiamare il programma con il comando nome Alice. L'output del programma
nullo che ne indica la fine. Poich viene modificato l'array utilizzato per richia-
mare xgets(), all'uscita dalla funzione l'array conterr i caratteri immessi. sar quindi Salve Alice.
In molti ambienti operativi, i vari argomenti della riga di comando devono
essere separati da uno spazio o da un carattere di tabulazione. Le virgole, i punti e
virgola e gli altri segni di punteggiatura non sono considerati separatori.
6.4 Gli argomenti di main(): argc e argv Ad esempio,

Talvolta pu essere utile passare informazioni a un programma al momento del- run Spot, run
l'esecuzione. Generalmente, si passano informazioni alla funzione main() attra-
verso gli argomenti della riga di comando. Un argomento della riga di comando formato da tre stringhe mentre:
formato dalle informazioni che seguono il nome del programma sulla riga di co-
mando del sistema operativo. Ad esempio, quando si compilano programmi C, si Mari o, Luigi, Pietro
potrebbe utilizzare un comando simile al seguente:
una singola stringa in quanto le virgole non vengono normalmente considerate
cc nome_programma come separatori.
Alcuni ambienti consentono di racchiudere una stringa contenente spazi fra
dove nome_programma un argomento della riga di comando che specifica il doppi apici. In questo modo l'intera stringa verr trattata come un unico argom~n
nome del programma che si intende compilare. Il C prevede l'uso di due speciali to. Per informazioni sull'uso dei parametri sif'..lla riga di comando, si consulti la
argomenti: argv e argc, utilizzati per ricevere gli argomenti dalla riga di comando. documentazione del sistema operativo.
Il parametro argc un intero che contiene il numero di argomenti-che si troYano - - I I parametro argv deve essere dichiarato correttamente. Il metodo pi comune
nella tigadi-comando. II suo valore sempre almeno pari.a.Lin quanto il nome = .-- il seguente: - - -- ____ --
--==-1
---~-----~.
152 CA P I T O LO 6

char *argvO;
programma che utilizza gli argomenti della riga di comando, spesso visualizza
qualche riga di istruzioni se l'utente cerca di eseguire il programma senza aver
Le parentesi quadre vuote indicano che l'array di lunghezza non determina- immesso le informazioni corrette.
ta. In questo modo sar possibile accedere ai singoli argomenti indicizzando argv. Per accedere a un singolo carattere di uno degli argomenti del comando, basta
Ad esempio, argvfO] punta alla prima stringa che sempre il nome del program- aggiungere un secondo indice ad argv. Ad esempio, il programma segu~nte
ma; argv[1] punta al primo argomento e cos via. visualizza tutti gli argomenti con cui stato chiamato, un carattere per volta:
Un altro breve esempio che utilizza gli argomenti della riga di comando il
programma chiamato countdown mostrato di seguito. Il programma esegue il conto #include <stdio.h>
alla rovescia partendo da un valore iniziale (specificato nella riga di comando) ed
emette un segnale acustico quando raggiunge lo zero. Si noti che il primo argo- int main(int argc, char *argv[])
mento contenente il numero viene convertito in un intero dalla funzione standard {
atoi(). Se. come secondo argomento si utilizza la stringa "display", il conto alla int t, i;
rovescia verr anche visualizzato sullo schermo.
for(t=O; t<argc; ++t) {
/* Progranma Countdown. */ i = O;
#include <stdio.h>
linclude <stdlib.h> whil e(argv[t][i]) {
#i nel ude <ctype. h> putchar(argv[t) [i]);
l'include <string.h> ++i;

int main(int argc, char *argv[]) printf("\n"); .


{
int disp, count;
return O;
if(argc<2) {
printf("Irrmetti il valore di partenza\n");
printf("nella riga di comando. Riprova.\n"); Si ricordi che il primo indice accede alla stringa e il secondo accede ai singoli
exit{l); caratteri della stringa.
Normalmente, si utilizzano argc e argv per fornire dei comandi iniziali al
programma. In teoria, possibile specificare fino a 32767 argomenti, ma la mag-
if(argc==3 && !strcmp(argv[2], "display")) disp = l; gior parte dei sistemi operativi .ne prevede molti meno. Normalmente si utilizzano
else disp- = O;
questi argomenti per indicare il nome di un file o un'opzione. L'uso degli argo-
menti nella riga di comando pu dare a un programma un aspetto professionale e
for(count=atoi (argv[l]); count; --count)
if(disp) printf("%d\n", count);
ne facilita l'uso all'interno di file batch.
Quando un programma non richiede parametri sulla riga di comando, prati-
putchar( '\a'); /* questa istruzione ca comune dichiarare esplicitamente main() senza parametri. Nel caso dei pro-
emette un segnale acustico */ grammi e si deve specificare la parola chiave void nell'elenco dei parametri (que-
printf("Fi ne"); sto l'approccio utilizzato nei programmi della Parte prima di questo volume). In
C++ basta semplicemente specificare un elenco di parametri vuoto (in C++ l'uti-
return O; lizzo di void per indicare un elenco di par~.!!_letri vuoto ridondante).
I nomi argc e argv sono tradizionali ma arbitrari. In pratica, si possono utiliz-
zare altri nomi a scelta. Inoltre, alcuni compilatori sono in grado di gestire ulterio-
-. Sinoticfe -se non si specificano argomenti-della riga di comando, viene ri argomenti di main(), quindi sempre bene controllare sul manuale dell'utente .
.Y!s_i:_filIZzato il me~~~~g_io_ .i:PBrn~Ii<!!.o_,_ Quesro-- un comportamento classico: un -
154 CAPITOLO LE FUNZIONI 155

6.5 L'istruzione return Si ricordi, una funzione pu contenere pi istruzioni return. Ad esempio, la
funzione find_substr() contenuta nel seguente programma restituisce la posizione
L'istruzione return gi stata descritta nel Capitolo 3. Essa ha due importanti utiliz-
iniziale della sottostringa contenuta in una stringa principale o restituisce 1 se
zi. Innanzi tutto, provoca l'immediata uscita dalla funzione in cui si trova. In prati- non viene trovata alcuna corrispondenza.
ca, fa in modo che lesecuzione del programma ritorni al codice chiamante. In se-
condo luogo, return pu essere utilizzata per restituire il valore della funzione. #include <stdio.h>

int find_substr(char *sl, char *s2);


Uscita da una funzione
int main(void)
Vi sono due modi in cui una funzione pu terminare la propria esecuzione e torna- {
re al chiamante. La prima si verifica quando viene raggiunta l'ultima istruzione if(find_s.ubstr("Mi piace il C", "ac") != -1)
della funzione e viene incontrata la parentesi graffa di chiusura della funzione printf("sottostringa presente nella stringa");
(naturalmente, la parentesi graffa non in effetti presente all'interno del c?dice
0 aaetto ma pu essere conveniente ragionare in questo modo). Ad esempio, la return O;
fu':zione pr_reverse() contenuta in questo programma visualizza sullo schermo
invertendo le lettere la stringa "Mi piace il C" e poi semplicemente termina.
/*Restituisce l'indice della prima corrispondenza di s2 in sl. */
#i nel ude. <stri ng.h> find substr(char *sl, char *s2)
#include <stdio.h>
{ -
register int t;
voi d pr_reverse(char *s); char *p, *p2;

int main{void) for(t=O; sl[t]; t++) {


{ p = &sl[t];
pr_reverse("Mi piace il C++"); p2 = s2;

return O; while(*p2 && *p2 ==*p)


p++;
p2++;
void pr_reverse(char *s) )
{ if(!*p2) return t; /* Primo return */
register int t;
return -1; /* secondo return */
for(t=strlen(s)-1; t>=O; t--) putchar(s[t]);

Restituzione di valori
Dopo la visualizzazione della stringa, il codice di pr_reverse() termina e quin-
di l'esecuzione ritorna al punto in cui la funzione era stata chiamata. .
Tutte le funzioni, tranne quelle i tipo void, restituiscono un valore.'Questo valore
Nella pratica, sono poche le funzioni che utilizzano questo metodo per termi-
specificato tramite un'istruzione return. In C, se una funzione non-void non
nare al propria esecuzione. La m<rggior parte delle funzioni utilizza l'istruzione
restituisce esplicitamente un valore tramite un'istruzione return, verr restituito
return che consente di restituire un valore e di semplificare e di rendere pi effi-
un valore senza senso. In C++ una f.unzione.non-void deve come_o~re un 'istruzj_Q:. _
---iefit-il codice di uscita dal~a funzione.__
ne return la quale deve restituire un valore. Questo ~ig'nifica che in _+~....se una
funzione specifica la restituz.ioncCdfun vaIOre_tuite Jeim!!?i'oni return in _essa
LE FUNZIONI 157

contenute devono restituire un valore. Se l'esecuzione raggiunge la fine di una un valore, non necessariamente occorre utilizzare il valore da esse restituito. Una
funzione non-void, verr restituito un valore senza senso. Anche se questa non domanda molto omune riguardante i valori restituiti da una funzione la seguen-
una condizione di errore di sintassi, si tratta di una situazione che ~vrebbe essere te: "Il valore restituito dalla funzione deve essere necessariamente assegnato a
evitata. Se una funzione non dichiarata come void, sar possibile utilizzarla come qualche variabile?". La risposta no. Se non si specifica alcun assegnamento, il
un qualsiasi altro operando in ogni espressione valida. Pertanto, ognuna delle valore restituito viene semplicemente ignorato. Si consideri il seguente program-
seguenti espressioni perfettamente lecita: ma che utilizza la funzione mul():

power(y); #include <stdio.h>


if(max(x,y) > 100) printf("maggiore");
for(ch=getchar(); isdigit(ch); ) ; int mul(int a, int b);

Tuttavia, come regola generale, una funzione non pu essere la destinazione int main(void)
{
di un assegnamento. Un'istruzione come:
int x, y, z;

swap(x,y) = 100; /* istruzione errata */ X = 10; y = 20;


z = mul (x, y); /* 1 */
errata. Il compilatore C/C++ la evidenzier come un errore e non compiler un printf("%d", mul(x,y)); /* 2 */
programma che la contenga (come si vedr nella seconda parte di questa guida, il mul (x, y); /* 3 */
C++ presenta alcune interessanti eccezioni a questa regola generale che consento-
no di posizionare alcuni tipi di funzioni sul lato sinistro di un'istruzione di asse- return O;
gnamento).
Quando si scrivono programmi, le funzioni generalmente sono di tre tipi. Il
primo tipo di semplice calcolo. Queste funzioni sono progettate con lo scopo di mul(int a, int b)
{
eseguire operazioni sui propri argomenti e restituire un valore sulla base di tale
return a*b;
operazione. Una funzione di calcolo una funzione "pura". Esempi di questo tipo
sono le funzioni sqrt() e sin() della libreria standard, che calcolano rispettivamen-
te la radice quadrata e il seno dei propri argomenti.
Il secondo tipo di funzioni manipola le informazioni e restituisce un valore Nella riga I, il valore restituito da mul() viene assegnato a z. Nella riga 2, il
che indica sinpTiCeiiiente il successo o il fallimento di tale manipolazione. Un valore restituito non viene assegnato ma viene utilizzato dalla funzione printf().
esempio la funzione di libreria fclose() utilizzata per chiudere un file. Se I' ope- Infine, nella riga 3, il valore restituito viene perso poich non viene n assegnato
razione di chiusura ha successo, la funzione restituisce zero; se l'operazione non a un'altra variabile n utilizzato in un'espressione.
ha successo, la funzione restituisce EOF.
L'ultimo tipo di funzione non ha alcun valore esplicito da restituire. In prati- Restituzione di puntatori
ca, la funzione strettamente procedurale e non produce alcun valore. Un esem-
pio la funzione exit() che termina il programma. Tutte le funzioni che non resti- Anche se le funzioni che restituiscono puntatori vengono trattate come ogni altro
tuiscono alcun valore devono essere dichiarate di tipo void. Dichiarando una fun- tipo di funzione, necessario discutere alcuni importanti concetti.
zione come void si evita che possa essere utilizzata in un'espressione, evitando I puntatori a variabili non sono n interi n interi unsigned. Sono indirizzi di
cos errori aceidentali di utilizzo. -- - memoria 'Che puntano a un determinato tipo di dati. Il motivo di questa distinzione
Talvolta, le funzioni che non producono alcun risultato interessante restitui- dovuto alle caratteristiche specifiche dell'aritmetica dei-puntatori che si basa
scono comunque un valore. Ad esempio, la funzione printf() restituisce il numero sulle dimensioni del tipo di dati puntato. Ad esempio, se viene incrementato un
dei caratteri scritti. In realt difficile che un programma controlli ques!_o _va'v'-''-'-------- puntatore a _interi,..esso conterr un valore maggior di 2 unit rispetto al valore
- - - -In
_,_,altre
__ -
parole, anche se tutt_~_J~_ f~!J~iQni, tranne quello di tipo void, restituisc~n_<_? __ preced~~t_:_ (~ssmendo interi di 2 byte). In generale, o-gmvolta che si .incrementa --
158 CAPITO rn-o---
----=--- -- -- . - 159

(o decrementa) un puntatore, esso punta all'oggetto successivo (o precedente) Funzioni di tipo void
considerando il tipo puntato. Poich ogni tipo di dati pu avere lunghezza diversa,
il compilatore deve necessariamente sapere il tipo di dati cui il puntatore fa riferi- Uno degli usi di void consiste nella dichiarazione esplicita di funzioni che non
mento. Per questo motivo, una funzione che restituisca un puntatore deve dichia- restituiscono valori. Questo evita che vengano utilizzate espressioni e aiuta a
rare esplicitamente il tipo di tale puntatore. Ad esempio, per restituire un puntatore evidenziare errori di utilizzo accidentali. Ad esempio, la funzione print_vertical()
char * wn si deve utilizzare il tipo int *! stampa verticalmente la stringa fornita come argomento. Poich la funzione non
Per restituire un puntatore, una funzione deve essere esplicitamente dichiarata restituisce alcun valor.e, viene dichiarata come void.
in modo da restituire un oggetto di tipo puntatore. Ad esempio, la seguente funzione
restituL.-.::e un puntatore alla prima occorrenza del carattere e nella stringa s: void print_vertical (char *str)
{
while(*str)
,'* Rest~tuisce un puntatore alla prima occorrenza di e in s. */
printf("%c\n", *str++);
char *rretch(char e, char *s)
{
while'.c!=*s && *s) s++;
retun(s); Il seguente programma mostra un esempio che stampa verticalmente un argo-
mento della riga di comando:

Se non viene trovata alcuna corrispondenza, viene restituito un puntatore al #include <stdio.h>
carattere nullo di fine stringa. Ecco un breve programma che utilizza la funzione
match(1: void print_vertical(char *str); /*prototipo*/

int main(int argc, char *argv[])


fincl u:e <stdio.h>
{
if(argc > 1) print_vertical (argv[l]);
char *Tratch(char e, char *s); /* prototipo */
return O;
int ma"n(void)
{
char s [80], *p, eh;
void print_vertical {char *str)
{
gets'.s);
whil e{*str)
eh = getchar();
printf{"%c\n", *str++);
p = natch(ch, s);

if(T:) /* il carattere stato trovato */


p-'.ntf("%s ", p);" Un'ultima annotazione: le prime versioni di _C non definivano la parola riser-
el s: vata void. Pertanto, nei vecchi programmi C le funzioni che non restituivano alcun
p-intf("Carattere non presente."); valore venivano considerate di tipo int; pertanto non ci si deve sorprendere di
vedere molti esempi di ci nei programmi meno recenti.
re:.n O;

Che cosa restituisce-inain()?


Q1esto programma legge una stringa e un carattere. Se il carattere presente
nella ;tringa. il programma stampa la stringa a partire dal carattere ricercato. In La funzione main() restituisce al processo chiamante (che normalmente il siste:-
-- - - cso .:'.-~~___Q,_S~~P.a il messaggio Carattere_non presente. -operativo) un valore-inte~o. La-restituzione di un valore da main() equivale a
-chiamare e_xi_t() con lo stes~o~alore. Se main() non restituisce esplicitamente un
----

- -
- - .:..-_ --:
160 CAPITOLO 6 ~ L E F U N Z I O N-1- 161

valore, il valore passato al processo chiamante sar tecnicamente non definito. In sce 1. In ogni altro caso, restituisce il prodotto di factr(n1)*n. Per valutare questa
pratica, la maggior parte dei compilatori C/C++ restituir automaticamente uno espressione, viene nuovamente richiamata factr() con il parametro n-1. Questo
O, ma per motivi di trasportabilit del codice, bene non fare affidamento su avviene finch n non diviene uguale a 1 e la funzione inizia a restituire un valore.
questa abitudine. --- Ad esempio, per calcolare il fattoriale di 2, la prima chiamata a factr() provoca
una seconda chiamata ricorsiva con argomento uguale a 1. Questa chiamata resti-
tuisce il valore 1 che viene poi moltiplicato per 2 (il valore originale di n). Il
6.6 Ricorsione risultato sar quindi 2. Ora si provi a seguire il procedere del calcolo per il fattoriale
di 3 (ad esempio si potrebbero inserire istruzioni printf() in factr() per vedere il
In C/C++, una funzione pu richiamare se stessa. Quando un'istruzione all'inter- livello raggiunto da ogni chiamata ed i vari risultati intermedi).
no del corpo della funzione richiama la funzione stessa, tale funzione si dice Quando una funzione richiama se stessa, sullo stack viene allocata una nuova
ricorsiva. La ricorsione un processo che definisce qualcosa in termini di s serie di variabili locali e parametri e il codice della funzione viene eseguito nuo-
stesso e in alcuni casi viene chiamata definizione circolare. vamente dall'inizio utilizzando tali variabili. Una chiamata ricorsiva non crea una
Un semplice esempio di funzione riorsiva factr() che calcola il fattoriale di nuova copia della funzione. Solo i valori su cui opera sono nuovi. Quando ogni
un intero. Il fattoriale di un numero n il prodotto di tutti i numeri interi da 1 a n. _chiamata ricorsiva esegue un return, le vecchie variabili locali e i parametri ven-
A~ esempio, il fattoriale di 3 1 x 2 x 3 ovvero 6. Di seguito sono mostrate sia gono rimossi dallo stack e l'esecuzione riprende nel punto in cui la funzione ave-
factr() che la sua equivalente iterativa: va richiamato se stessa. Questo il motivo per cui le funzioni ricorsive sono chia-
mate anche funzione telescopiche.
int factr(int n) /* ricorsiva */ La maggior parte delle routine ricorsive non riducono in modo significativo le
{ dimensioni del codice n migliorano l'utilizzo della memoria. Inoltre, le versioni
int answer; ricorsive della maggior parte delle routine viene eseguita in modo leggermente
pi lento rispetto alle loro equivalenti iterative a causa del sovraccarico dovuto
if(n==l) return(l); alla continua ripetizione delle chiamate alle funzioni. Addirittura, un eccesso di
answer = factr(n-l)*n; /* chiamata ricorsiva */ chiamate ricorsive pu provocare la fuoriuscita dallo stack. Poich la
return(answer); memorizzazione dei parametri di funzione e delle variabili locali avviene sullo
stack e ogni nuova chiamata crea una nuova copia di queste variabili, lo stack
potrebbe andare a scrivere su altri dati o persino sulla memoria destinata al pro-
int fact(int n) /* non ricorsiva */
gramma. Tuttavia, in genere non ci si deve preoccupare di ci a meno che non si
{
i nt t, answer;
perda il controllo di una funzione ricorsiva.
Il vantaggio principale delle funzioni ricorsive consiskl)~lla possibilit di
answer = 1; creare versioni pi chiare e pi semplici di molti algoritmi. Ad esempio, l'algoritmo
QuickSort piuttosto difficile da implementare in modo iterativo. Inoltre, alcuni
for(t=l; t<=n; t++) problemi, specialmente quelli di intelligenza artificiale, sono pi adatti a soluzio-
answer=answer* (t); ni ricorsive. Infine, l'esecuzione ricorsiva viene considerata pi lineare.
Quando si scrivono funzioni_ ricorsive, da qualche parte si deve prevedere
return (answer); un'istruzione condizionale (ad esempio un if) che faccia in modo che la funzione
termini senza eseguire la chiamata ricorsiva Se si omette l'istruzione condiziona-
le, una volta entrati nel corpo della funzione non sar pi possibile uscirne. Du-
La versione non ricorsiva di fact() -dovrebbe risultare chiara. Utilizza un ciclo rante lo sviluppo del programma si consiglia di utilizzare abbondantemente istru-
che va da 1 a n e moltiplica progressivamente ogni numero per il prodotto accu- zioni printf() in modo da sapere sempre cosa sta avvenendo e annullare I' esecuziO-
mulato. . ne nel mom~nto in cui si rileva un errore.
L'operazione svolta dalla verslqr.ie ricorsiva faJr() un po' pi complessa.
----=--'"--- _Quand_o_ factr() viene richiamata con un ar_$~!Tl_c:_nto u~uale a 1, la funzione restit~i---_-~-
-------------

162 CAP 1-TO LO 6 LE FUNZIONI 163

6.7 Prototipi di funzioni


*i = *i * *i;
In C++ tutte le funzioni devono essere dichiarate prima dell'uso. A tale scopo si
utilizza un prototipo di funzione. I prototipi delle funzioni non erano presenti nel
linguaggio C originario. Si tratta, per, di una delle aggiunte eseguite nel momen- Anche la definizione della funzione, se si trova prima del primo uso della
to in cui stato prodotto lo standard C. Anche se lo Standard non richiede tecni- funzione nel programma, pu fungere da prototipo. Ad esempio, il seguente pro-
camente di utilizzare prototipi, si consiglia vivamente di utilizzarli. Il C++ ha gramma perfettamente valido.
invece sempre richiesto l'uso di prototipi. In questa guida pertanto, tutti gli
esempi includono prototipi di funzione completi. I prototipi consentono al Ce al
#include <stdio.h>
C++ di eseguire verifiche di tipo pi strette, analoghe a quelle svolte da linguaggi
come il Pascal. Quando si utilizzano i prototipi, il compilatore pu rilevare ogni
/* Questa definizione funge inoltre
conversione illecita fra il tipo degli argomenti utilizzati per chiamare una funzione
da prototipo al programma. */
e le definizioni di tipo dei suoi parametri. Il compilatore individuer inoltre le
void f(int a, int b)
differenze fra il numero degli argomenti utilizzati per richiamare una funzione e il
{
numero di parametri della funzione.
printf("%d ", a% b);
La fonna generale di un prototipo di funzione la seguente:

tipo nome_funz(tipo nome_parl. tipo nome_par2, ... ,tipo nome_parN); int main(void)
{
L'uso dei nomi dei parametri opzionale. Tuttavia, essi consentono al compi- f(l0,3);
latore di identificare ogni differenza di tipo utilizzando un nome e quindi sem-
pre consigliabile includerli. return O;
Il seguente programma illustra l'importanza dei prototipi di funzione. Il pro-
gramma produce un messaggio di errore in quanto contiene un tentativo di chia-
mare sqr_it() con un argomento intero al posto di un puntatore a interi (la conver- In questo esempio, poich f() viene definita prima del suo uso in main(), non
sione di un intero in un puntatore non consentita). necessario creare un prototipo distinto. Anche se possibile che la definizione di
una funzione funga anche da prototipo (specialmente nei programmi pi piccoli)
/*Questo programma utilizza un prototipo di funzione capita raramente di sfruttare questa possibilit nei programmi pi estesi, special-
per ottenere una pi forte verifica dei tipi. */ mente quando il programma costituito da pi file. I programmi contenuti in
questcrvolume includono un prototipo per ciascuna funzione poich questo il
void sqr_it(int *i); /*prototipo*/ modo in cui viene normalmente realizzato il codice C!C++.
L'unica funzione che non richiede un prototipo main(), dato che questa la
int main(void) prima funzione che viene richiamata all'avvio del programma.
Per motivi di compatibilit con la versione originale del C, vi una piccola
ma importante differenza nel modo in cui i linguaggi Ce C++ gestiscono i proto-
int X;
tipi di una funzione che non accetta parametri. In C++, un elenco vuoto di para-
metri viene indicato nel prototipo come una semplice mancanza di parametri. Ad
X = IO; esempio,
sqr_it(x); /*errore di tipo*/

return O ; int f(); /* Prototipo C++ per una funzione senza parametri */

------ In C questo prototipo significa qualcosa di legger.mente dif(erente..JY.mQtivi


void sqr_it(int *TJ-- storici, una lista diJl~rameJ~~ vuota dice che non vengono fornite in~E!!J.~ioni ~.!Ji
164 CA P I T O LO 6 LE FUNZIONI 165

parametri. Per quanto riguarda il compilatore, la funzione potrebbe.avere svariati 6.8 Dichiara_zione di elenchi di parametri
parametri o nessun parametro. In C, qmmdo una funzione non ha parametri, al- di lunghezza variabile
l'interno dell'elenco dei parametri del prototipo ~i deve specificare void. Ad esem-
pio, ecco l'aspetto del prototipo di f() un programma C. . ~ possibile specificare funzioni con un numero variabile di parametri. L'esempio
pi comune printf(). Per dire al compilatore che a una funzione pu essere passa-
f1 oat f(voi d); to un numero indefinito di argomenti, si deve terminare la dichiarazione dei suoi
parametri utilizzando tre punti. Ad esempio, questo prototipo specifica che fune()
Questa riga dice al compilatore che la funzione non ha parametri e che quan- riceve sicuramente almeno due param!=tri interi seguiti da un numero ignoto di
do la funzione viene richiamata con parametri, ci si trova in una condizione di parametri o anche da nessun parametr<_:>.
errore. In C++, ancora possibile inserire void per specificare un lenco di para-
metri vuoto ma tale precauzione ridondante. int func(int a, int b, );

jQ'tA'j;;-;:.\1;~~ In C++, f() e f(void) sono equivalenti. Questa forma di dichiarazione utilizzata anche per la definizione della
funzione.
I prototipi di funzioni aiutano a individuare i bug. Inoltre aiutano a verificare Ogni funzione che utilizza un numero variabile di parametri deve per avere
che il programma funzioni correttamente in quanto impediscono che le funzioni almeno un parametro dichiarato. Il seguente esempio quindi errato:
vengano richiamate con argomenti errati.
Un'ultima cosa: poich le prime versioni di C non supportavano completa- int fune( ... ); /*errato*/
mente la sintassi dei prototipi, in senso strettamente tecnico i prototipi sono
opzionali in C. Questo stato necessario per supportare il codice C sviluppato
prima della creazione dei prototipi. Se si deve convertire in C++ un programma C,
pu essere necessario aggiungere i prototipi di tutte le funzioni perch il program- 6.9 Dichiarazione di parametri con metodi vecchi
ma possa essere compilato. Occorre ricordare che i prototipi sono opzionali in C e nuovi
mentre sono obbligatori in C++. Questo significa che ogni funzione contenuta in
un programma C++ deve avere un prototipo. .,._ Le prime versioni di C utilizzava un metodo di dichiarazione dei parametri diffe-
rente da quanto stabilito dagli standard Ce C++.Questa guida utilizza un approc-
cio pi moderno. Lo Stndard C consente di utilizzare entrambe le forme ma
I prototipi di funzione della libreria standard consiglia vivamente di utilizzare la forma moderna. Lo Standard C++ prevede
invece solo il metodo di dichiarazione dei parametri moderno. Tuttavia, impor-
A ogni funzione della libreria standard utilizzata dal programma deve essere as- tante conoscere la forma classica in quanto molti programmi C meno recenti ne
sociato un prototipo. A tale scopo si deve includere il file header appropriato per fanno ancora uso.
ciascuna funzione di libreria. Il compilatore C/C++ fornisce tutti i file header La dichiarazione classica dei parametri delle funzioni formata da due parti:
necessari. In Ci file header hanno l'estensione .h. In C++ i file header possono un elenco di parametri, che si trova all'interno delle parentesi che seguono il nome
essere file distinti o possono essere contenuti nel compilatore stesso. In entrambi della funzione, e l'effettiva dichiarazione dei parametri, che sta fra la parentesi
i casi, un file header contiene due elementi: le definizioni utilizzate dalle funzioni chiusa e la parentesi graffa aperta della funzione. La forma generale della.defini-
della libreria e i prototipi delle funzioni della libreria. Ad esempio, stdio.h viene zione classica dei parametri la seguente:
incluso in quasi tutti i programmi contenuti in questa parte del volume in quanto
contiene il prototipo della funzione pr[~tf{). I file header per la libreria_standard tipo nomeJunz(parl ,par2 ,...parN)
sono descritti nella Parte terza. tipo parl;
.tipo par2;
166 CAPITOLO-O--__ _ ----- -
L'El' u N Z iQNI 167 - - -

tipo parN; L'efficienza


{
codice della funzione Le funzioni sono i mattoni costitutivi del C/C++ e sono fondamentali per qualsi-
} asi programma di complessit non elementare. Tuttavia, in alcune applicazioni
specializzate, pu essere conveniente eliminare una funzione e sostituirla con co-
Ad esempio, questa dichiarazione moderna: dice in linea. Il codice in linea esegue le stesse azioni di una funzione ma senza il
sovraccarico associato alla chiamata di funzione. Per questo motivo, quando si
float f(int a, int b, char eh) molto interessati ai tempi di esecuzione di un programma, si utilizza preferibil-
I mente codice in linea al posto delle funzioni.
/* ... */ Il codice in linea pi veloce rispetto a una chiamata a una funzione per due
motivi. Innanzi tutto, l'esecuzione dell'istruzione CALL richiede tempo. In secon-
do luogo, gli argomenti passati alla funzione devono essere posizionati sullo stack
corrisponde alla forma classica: e anche questo richiede tempo. Per la maggior parte delle applicazioni, si tratta di
una quantit di tempo molto limitata e di poco conto. Ma quando si interessati
float f(a, b, eh) alla velocit, si deve ricordare che ogni chiamata a funzione utilizza tempo che si
int a, b; potrebbe risparmiare posizionando il codice della funzione in linea. I due listati
char eh; seguenti presentano un programma che stampa il quadrato dei numeri da l a 10.
{
La versione in linea viene eseguita pi velocemente rispetto all'altra.
/* ... */
codice in linea - chiamata a funzione
Si noti che la forma classica consente la dichiarazione di pi di un parametro #include <stdio.h> #include <stdio.h>
dopo il nome del tipo. int sqr(int a);
int main(void) int main(void)
NOTA. la forma classica della dichiarazione dei parametri consi-
I
derata oggi obsoleta in Ce non consentita dal C++. int x; int x;

for(x=l; x<ll; ++x) for(x=l; x<ll; ++x)


printf("%d", x*x); printf("%d", sqr(x));
6.1 O Elementi implementativi

Vi sono alcune cose molto importanti da ricordare che riguardano lefficienza e int sqr(int a)
l'usabilit delle funzioni. I
return a*a;

Parametri e funzioni di utilizzo generale

Una funzione di utilizzo generale una funzione che pu essere utilizzata in pi ~9Tl\_ ... _ ...::C., In C++ il concetto di funzione in linea viene espanso e
situazioni, anche da programmatori diversi. In generale, si dovr fare in modo che formalizzato. Infatti, le funzioni in linea sono una componente importante del
le funzioni di utilizzo generale non si basino su dati gloffli. Tutte le informazioni linguaggio C++.
richieste dalla funzione dovranOQ...e.ss.ere passate attraverso i suoi parametri. Se
questo non fosse possibile si devono usare variabili statiche.
-----Oltre a consentire l'utilizzo generale delle funzioni, l'uso dei parametri ren-
der il codice pi leggibiie e-meno-soggetto ai bug goyu}!_a effetti collaterali.
- Capitolo 7

Strutture, unioni,
: enumerazioni
e tipi definiti dall'utente
7.1 Le strutture
7.2 Gli array di strutture
7.3 Il passaggio di strutture alle funzioni
7.4 I puntatori a strutture
7.5 Gli array e strutture all'interno
di altre strutture
7.6 I campi bit
7.7 Le unioni
7.8 Le enumerazioni
7.9 Uso di sizeof per assicurare
la trasportabilit del codice
7.10 La parola riservata typedef

f
I linguaggio C consente di creare tipi di dati persona-
lizzati in cinque modi diversi.
1. La struttura, un raggruppamento di variabili sotto un unico nome che anche
chiamato tipo di dati aggregato (o talvolta conglomerato).
2. Il campo bit che una variazione della struttura e consente l'accesso ai singoli
bit.
3. L'unione che consente di utilizzare la stessa area di memoria per definire due
o pi tipi di variabili diverse.
4. L'enumerazione: un elenco di costanti intere cui viene associato un nome.
5. L'ultimo tipo definibile dall'utente viene creato mediante typedef che defini-
sce un nuovo nome per un tipo preesistente.
Il linguaggio C++ supporta tutte le forme precedenti cui aggiunge le classi
che verranno descritte nella Parte seconda. Qui verranno descritti gli altri metodi
di creazione di dati personalizzati. In C++ le strutture e le unioni possono avere-_
attributi tipici della programmazione a oggetti. Questo capitolo si occuper uni-
camente delle funzionalit C, senza occuparsi degli elemeiilltP!c della program-
mazione-a oggetti,-dLcui.ci si occuper pi avanti.
-----
-===-:.-
-~ -- - - - -- ,._
170 CAPITOLO 7 TRUT'fURE, UNIONI, ENUM.E.B.Al!ON~ E TIPI DEFINlff DALL'UTENTE -17_1_

7.1 Le strutture Come si pu vedere, la parola riservata struct in C++ non necessaria. In
C++, quando si dichiara una struttura, si possono dichiarare variabili di tale tipo
Una struttura formata da una serie di variabili a cui si fa riferimento con un utilizzando semplicemente il nome, non preceduto dalla parola struct. Il motivo di
unico nome; grazie a questo, la struttura rappresenta un modo molto comodo per questa differenza che in C il solo nome della struttura non identifica un tipo. In
riunire insieme informazioni correlate. La dichiarazione della struttura opera da pratica, per il C Standard, il nome di una struttura un tag. In C, nella dichiarazio-
modello per creare le istanze della struttura. Le variabili che compongono la strut- ne di variabili il tag della struttura deve essere preceduto dalla parola riservata
tura sono chian1ate i membri di tale struttura (i membri delle strutture sono talvol- struct. Al contrario, in C++ il nome della struttura gi un nome di tipo completo
ta chiamati elementi o campi). e dunque pu essere utilizzato per definire variabili. Si deve notare che la dichia-
In genere, tutti i membri di una struttura sono logicamente correlati fra loro. razione C pu essere utilizzata anche in un programma C++. Poich i programmi
Ad esempio, le informazioni relative al nome e all'indirizzo in una mailing Iist della Parte prima di questo volume sono validi sia in C che in C++, verr utilizza-
vengono normalmente rappresentati con una struttura. Il seguente frammento di to il metodo di dichiarazione C. Si deve solo ricordare che il linguaggio C++
codice mostra la dichiarazione di una struttura che definisce i campi del nome e prevede anche una forma abbreviata.
dell'indirizzo. La parola chiave struct comunica al compilatore che si sta dichia- Quando si dichiara una variabile del tipo della struttura (come ad esempio
rando una struttura. addr_info), il compilatore alloca automaticamente una quantit di memoria suffi-
ciente per contenere tutti i membri della struttura. La Figura 7.1 mostra l'aspetto
struct addr in memoria della variabile addr_info assumendo che i caratteri occupino I byte e
{ che gli interi long occupino 4 byte.
char name[30];
char street[40];
char city[20];
char state[3];
unsigned long int zip;
};
Name 30byte 1
Si noti che la dichiarazione si chiude con un punto e virgola. Il punto e virgola
necessario in quanto la dichiarazione della struttura essa stessa un'istruzione.
Inoltre, il nome della struttura addr identifica questa determinata struttura di dati Street 40byte
-I
e diviene il suo specificatore di tipo.
A questo punto, non si ancora creata alcuna variabile. stata semplice-
mente definita la struttura dei dati. Quando si definisce una struttura, si definisce City 20byte .-.1
essenzialmente un tipo di variabile complesso e non la variabile stessa. Finch
non si dichiara una variabile di tale tipo, non esister quindi alcuna variabile. In C.
per dichiarare una variabile (ovvero un oggetto fisico) di tipo addr, occorre utiliz- State 3 byte
zare la riga:

struct addr addr_info;


ZIP 4byte

Questa riga dichiara una variabile di tipo struct addr e chiamata addr_info. In
C++ si pu utilizzare una forma abbreviata. = -

addr addr_info; Figura 7. 1 Aspetto in memoria della struttura addr.

----- ---------
172 CAPITOLO 7 _ ST_R UTTU RE. UN I ON I, E NUMERAZIONI E TIP I DEFINITI DALL. UTENTE- - -173--
.. --.~----

Quando si dichiara una struttura anche possibile dichiarare una o pi varia- Accesso ai membri delle strutture
bili. Ad esempio,
. Per accedere ai singoli membri di una struttura si utilizza l'operatore"." (chiamato
struct addr { spesso operatore punto). Ad esempio, il frammento di codice seguente assegna il
char name [30) ; codice 12345 al campo zip della variabile addr_info dichiarata precedentemente:
char street[40);
char city [20); addr_info.zip = 12345;
char state[3];
unsigned long int zip;
Per accedere ai singoli membri di una struttura si utilizza quindi il nome della
addr_info, binfo, cinfo;
variabile definita del tipo della struttura seguito da un.punto e dal nome del mem-
bro. La forma generale dell'accesso a un membro di una struttura la seguente:
definisce un tipo di struttura chiamato addr e dichiara le variabili addr_info, binfo
e cinfo che appartengono a tale tipo.
nome_struttura.nome_membro
Se si deve utilizzare una sola variabile del tipo definito dalla struttur~, il tag
della struttura non necessario.
Questo significa che: Pertanto, per stampare sullo schermo il codice ZIP, si deve utilizzare la riga:

struct { printf("%d", addr_info.zip);


char name[30];
char street[40]; Questa riga stampa il codice ZIP contenuto nel membro zip della variabile di
char city[20]; struttura addr_info.
char state[3]; Analogamente, l'array di caratteri addr_info.name pu essere utilizzato nel
unsigned long int zip; seguente modo:
addr_info;
gets (addr_i nfo. name);
dichiara una variabile chiamata addr_info definita dalla struttura che la precede.
La forma generale di una dichiarazione di una struttura : Questa riga passa un puntatore al carattere iniziale di name.
Poich name un array di caratteri, possibile accedere ai singoli caratteri di
struct nome_struttura { addr_info.name indicizzando name. Ad esempio, possibile stampare il contenu-
tipo nome_membro ; - to di addr_info.name un carattere per volta utilizzando il codice seguente:
tipo n-ome_membro ;
tipo nome_membro ; register int t;

for(t=O; addr_info.name[t]; ++t)

} variabili_struttura; putchar(addr_i nfo.name[t]);

d_?~~- le parti nome_st:_uttura o variabili_struttura (ma non entrambe) possono


AssegQ_amenti a una struttura
essere omesse.
Le informazioni contenute in una struttura possono essere assegnate a un'altra
struttura dello stesso tipo utilizzando un'unica istruzione di assegnamento. Que-
174 CAPITOLO 7
STRUTTURE, UNIONI, ENUMERAZIONI E TIPI DEFINITI DALL'UTENTE 175

sto significa che non necessario assegnare separatamente i valori di ogni mem- 7.'J- Il passaggio di strutture alle funzioni
bro della struttura. Il seguente programma illustra questo concetto:
Questa sezione si occupa del passaggio delle strutture e dei relativi membri a una
funzione.
#include <stdio.h>

int main(void)
Passaggio dei membri di una struttura a una funzione
{
struct {
Quando si passa a una funzione un membro di una struttura, la funzione riceve
int a;
int b;
solo il valore del membro. Pertanto, si passa semplicemente una variabile (a meno
x. y; che, ovviamente, tale elemento sia complesso, come ad esempio un array). Ad
esempio, si consideri questa struttura:
x.a = 10;
struct fred
x; /" assegna una struttura a un'altra struttura */ {
char x;
printf("%d", y .a); int y;
float z;
return O; char s [10);
) mike;

Dopo l'assegnamento, y.a conterr il valore 10. Ecco alcuni esempi di passaggio a una funzione dei membri di una struttura:

fune (mi ke. x) ; /* passa i 1 va 1ore carattere di x */


func2(mi ke.y); /* passa il valore intero di y */
7.2 Gli array di strutture func3(mike.z); /* passa il valore float di z */
func4(mi ke. s); /* passa l'indirizzo della stringa s */
Probabilmente, l'uso pi comune delle strutture negli array di strutture. Per func(mi ke. s (2)); /* passa il val ore carattere di s [2) */
dichiarare un array di struttur~,.Qccoi:r:~Jrmanzi tutto definire una struttura e quin-
di dichiarare una variabile come un array di tale tipo. Ad esempio, per dichiarare Se si desidera passare l'indirizzo di un determinato membro di una struttura,
un array di 100 elementi di strutture di tipo addr si pu scrivere: baster inserire l'operatore & prima del nome della struttura. Ad esempio, per
passare l'indirizzo dei membri della struttura mike, baster scrivere:
struct addr addr_i nfo [100);
func(&mi ke.x); /* passa l'indirizzo del carattere x */
Questa riga crea 100 gruppi di variabili organizzate nel modo definito nella func2(&mike.y); /*passa l'indirizzo dell'intero y */
struttura add r. func3(&mike.z); /*passa l'indirizzo del float z */
Per accedere a una determinata struttura basta indicizzare il nome della strut- func4(mike.s); /* passa l'indirizzo della stringa s */
tura. Ad esempio, ~~r stampar:.e il codice ZIP della ~truttura 3, basta scrivere: func(&mike.s[2]); /* passa _l ~i '!di rizzo del carattere s (2) */

_Q.ti_r1.tf("%d", addr_info[2] .zip); Si ricordi che l'operatore & precede il nome della struttura e non il nome-del --- -
membro. Si noti inoltre che s gi un indirizzo e quindi non si deve utilizzare
Come ogni array, anche gli array di strutture partono dall'indice O. operatore&. ----- -- - - - -
176 CAPITOLO 7
STRUTTURT,-uNIONl, ENUMERAZIONI E 'f-lp-f-fl-E-F-INITl DALL'UTENT_E_ 1i7

Passaggio a una funzione di un'inter~ struttura programma precedente errata e non verr compilata in quanto il nome del tipo
dell'argomento utilizzato per richiamare f1 () diverso dal nome del tipo del suo
Quando una struttura viene utilizzata come argomento di una funzione, l'intera parametro.
struttura viene passata utilizzando il metodo standard della chiamata per valore.
Naturalmente, questo significa che ogni modifica che la funzione apporta al con- /* Questo programma errato e non verr compilato. */
tenuto della struttura non modifica la struttura utilizzata come argomento. #include <stdio.h>
Quando si utilizza una struttura come parametro, occorre ricordare che il tipo
dell'argomento deve corrispondere al tipo del parametro. Ad esempio, nel se- /* Definisce un tipo di struttura. */
guente progrmma sia l'argomento arg che il parametro parm sono dichiarati del- struct struct_type {
lo stesso tipo della struttura. int a, b;
char eh;
#include <stdio.h>
/* Definisce una struttura simile a struct_type,
/* Definisce un tipo di struttura. */
ma con un nome di verso. */
struct struct_type {
struct struct_type2
int a, b;
int a, b;
char eh;
char eh;
} ;
void fl(struct struct_type parm);
void fl(struct struct_type2 parm);
int main(void)
int main(void)
{
{
struct struct_type arg;
struct struct_type arg;
arg. a = 1000;
arg.a = 1000;
f1 (arg);
fl(arg); /*differenza di tipo*/
return O;
return O;

void fl(st~uct struct_type parm)


voi d f1 (struct struct_type2 parm)
{
{
printf("%d'', parm.a);
printf("%d", parm.a);

Come si vede nel programma, se si dichiarano parametri che corrispondono a


strutture, occorre rendere globale la dichiarazione del tipo della struttura in modo
che possa essere utilizzata in ogni parte del programma. Ad esempio, se la struttu- 7.4 I puntatori a strutture
--ra struct_type viene dichiarata (ad esempio) all'interno di main(), non potr essere
visibile a f1 (). Il ClC++ consente di utilizzare un puntatori a struttura come un puntatore a qual-
Come si appena detto, quando si passano strutture alle funzioni, il tipo del- siasi altro tipo di variabile. Tuttavia, vi sono alcuni aspetti specifici dei puntatori
l'argomento deve corrispondere aLtipo del-parametro. Non sufficiente eh~ siano ~ strutture.
- fisicamente simili; devono corrisponder~j_lJQf!li. Aq_esempio, questa version_:_~er:--
178 CAPITOLO 7 STRUTTURE:-lJNIONI, ENUMERAZIONI E TIPI DEFINITI DALL'UTENTE 179

Dichiarazione di un puntatore a struttura la riga


Come ogni altro puntatore, un puntatore a struttura deve essere dichiarato inse- p = &person;
rendo un asterisco (*) davanti al nome di una variabile della struttura. Ad esem-
pio, assumendo l'uso della struttura addr definita precedentemente, la riga se-
inserisce nel puntatore p l'indirizzo della struttura person.
guente dichiara addr_pointer come un puntatore a dati di tipo addr:
Per accedere ai membri di una struttura utilizzando un puntatore a tale struttu-
ra, si deve utilizzare l'operatore->. Ad esempio, per accedere al campo balance si
struct addr *addr_pointer; utilizza la riga:
In C++ non necessario far precedere a questa dichiarazione la parola chiave p->bal ance
struct.
L'operatore ->viene normalmente chiamato operatore freccia ed formato
Uso dei puntatori a struttura dal segno meno seguito dal segno maggiore. L'operatore freccia viene utilizzato
al posto dell'operatore punto quando si accede a un membro di una struttura tra-
Vi sono due utilizzi principali dei puntatori a struttura: il passaggio di una struttu- mite un puntatore alla struttura stessa.
ra a una funzione tramite una chiamata per indirizzo e la creazione di liste P~r ve~ere l'uso dei puntatori a struttura, si esamini il seguente programma
conc_atenate e di altre strutture di dati dinamiche utilizzando il sistema di allocazione che v1suahzza sullo schermo le ore, i minuti e i secondi utilizzando un timer
dinamica. Questo capitolo si occupa del primo utilizzo. software.
Un aspetto da non sottovalutare nel passaggio di strutture complesse alle fun-
zioni il sovraccarico richiesto per inserire la struttura nello stack nel momento /* Visualizza un timer software. */
in cui viene richiamata la funzione (si ricordi che gli argomenti vengono passati #include <stdio.h>
alle funzioni sullo stack). Per semplici strutture costituite da pochi membri, que-
#def i ne DELA Y 128000
sto sovraccarico non cos significativo. Se la struttura contiene molti membri o
se alcuni dei suoi membri sono array, le prestazioni potrebbero degradare a livelli struct my_time
inaccettabili. La soluzione di questo problema consiste nel passaggio alla funzio- int. hours;
ne del solo puntatore. i nt mi nutes;
Quando alla funzione viene passato un puntatore alla struttura, nello stack i nt seconds;
viene copiato solo l'indirizzo della struttura. Questo consente di realizzare chia- } -;-- -- ----
mate a funzioni notevolmente veloci. Un secondo vantaggio, in alcunf casi, consi-
ste nella possibilit, da parte della funzione, di accedere all'effettiva struttura uti- void display(struct my_time *t);
lizzata come argomento e non a una sua copia. Passando un puntatore, la funzione void update(struct my_time *t);
pu modificare il contenuto della struttura utilizzata nella chiamata. void delay(void);
Per conoscere l'indirizzo di una variabile di tipo struttura, basta inserire l' ope-
ratore & prima del nome della struttura. Ad esempio, dato il seguente frammento int main(void)
{
di codice:
struct my_time systime;

struct bal systime.hours = O;


float balance; systime.minutes = O;
char name [80] ; systime.seconds = O;
person;
far{;;) {
-- struct bal *.p_;__/~dictiiaran puntatore a struttura */ update(&syst1me}_; -- -
180 CAPITOLO 7 STRUTIU"IIT-;lJNINI, ENUMERA-Z~ONI E TIPI DEFINITI DALL'UTENTE 181

display(&systime); Alle funzioni update() (che cambia l'ora) e display() (che visualizza l'ora)
viene passato l'indirizzo di systime. In entrambe le funzioni, gli argomenti sono
dichiarati come puntatori a una struttura di tipo my_time.
return O;
All'interno di update() e display() l'accesso ai membri di systime avviene
tramite un puntatore. Poich update() riceve un puntatore alla struttura systime,
void update(struct my_time *t) ne pu aggiornare anche il valore. Ad esempio, per riportare l'ora a zero quando si
{ raggiunge l'indicazione 24:00:00, update() contiene la riga di codice seguente:
t->seconds++;
i f(t->seconds==60) if(t->hours==24) t->hours = O;
t->seconds = O;
t->mi nutes++; che chiede al compilatore di prendere l'indirizzo di t (che punta a systime in
main()) e imposta hours a zero.
Si ricordi l'uso dell'operatore punto per accedere agli elementi della struttura
i f (t->mi nutes==60)
quando si opera sulla struttura stessa. Quando si utilizza un puntatore a una strut-
t->mi nutes = O;
tura si deve utilizzare loperatore freccia.
t->hours++;

if(t->hours==24) t->hours = O;
delay();
7.5 Gli array e strutture all'interno
di altrestrutture

void isplay(struct my_time *t)


Un membro di una struttura pu essere di tipo semplice o complesso. Un membro
{ di tipo semplice appartiene a uno dei tipi base, come ad esempio int o char. Al-
prir.tf("%02d:", t->hours); l'inizio di questo Capitolo si gi visto un tipo di elemento complesso: I'array di
prir.tf("%02d:", t->minutes); caratteri utilizzato in addr. Altri tipi di dati complessi sono gli array mono e multi
prirtf("%02d\n", t->seconds); dimensionali di altri tipi di dati e strutture.
Quando un membro di una struttura un array, esso viene trattato nel modo in
cui ci si pu attendere visti gli esempi precedenti. Ad esempio, si consideri la
void celay(void) struttura: - - - ---
{
long inr t;
struct x {
int a[lO] [10]; /* array di 10 x 10 int */
/* rodificare a piacere DELAY */
float b;
for(t=l; t<DELAY; ++t) ; } y;

Per accedere all'intero 3,7 nel membro a. della struttura y si deve scrivere:
La sincronizzazione di questo programma pu essere regolata modificando la
definizione di DELAY.
y.a[3] [7] -..
- Come si pu vedere, stata definita una struttura globale chiamata my_time
ma non stata dichiarata alcuna variabile. All'interno di main() stata dichiarata
Quando una struttura un membro di un'altra struttura, si parla di struttura
la struttura systime inizializzata a 00:00:00. Questo significa che systime utiliz-
nidificata. Ad esempio, nell'esempio seguente, la struttura address nidificata
~bik direttaJMote solo dalla fun&Q_n~_ main(L _ _ _
all'interno della struttura emp:
182 CAPITOLO STRUTTURE, UNIONI, ENUMERAZIONI E TIPI DEFINTTI DALL'UTENTE 183

struct emp {
struct addr address; /* struttura nidificata */ tipo nomeN : lunghezza;
float wage; } elenco_variabili; .
worker;

Qui, tipo specifica il tipo del campo bit .e lunghezza il numero di bit che
Qui, la struttura emp stata definita in modo da contenere due membri. II
compongono il campo. Un campo bit pu essere di un tipo intero o enumerativo.
primo una struttura di tipo addr che contiene l'indirizzo e l'altro il valore float
I campi bit di lunghezza 1 devono essere dichiarati come unsigned poich un
wage. Il seguente frammento di codice assegna il valore 93456 all'elemento zip di
singolo bit non pu avere un segno.
address.
I campi bit sono molto utilizzati per analizzare l'input proveniente da un di-
spositivo hardware. Ad esempio, un adattatore di comunicazione seriale pu resti-
worker.address.zip = 9345fi;
tufre un byte di stato organizzato nel seguente modo:

Come si pu vedere, l'accesso ai membri di ogni struttura avviene da quello


pi esterno a quello pi interno. Lo Standard C specifica che le strutture possono
essere nidificate fino a 15 livelli ma la maggior parte dei compilatore consente di BIT SIGNIFICATO
utilizzare un maggior numero di livelli. Lo Standard C++ suggerisce di consentire Cambia In cleartosend
almeno 256 livelli di nidificazione.
Cambia in datasetready
i
i Trailingedge

7.6 I campi bit Cambia in ricezione

Clearto-send

j
A differenza di altri linguaggi, il C!C++ prevede una funzionalit chiamata cam-
po bit che consente di accedere ai singoli bit dei dati. I campi bit sono utili in Dataset-ready
molte occasioni: Squillo del telefono
e ad esempio quando lo spazio di memoria limitato e si vogliano inserire pi
Segnale ricevuto
variabili booleane (con valori vero o falso) in un singolo byte;
alcuni dispositivi trasmettono informazioni sul loro stato codificate in bit;
---- -----
alcune routine di crittografia devono accedere ai bit di un byte.
possibile rappresentare queste informazioni in un byte di stato utilizzando i
Aneli.e se queste operazioni possono essere eseguite utilizzando operatori bit- seguenti campi bit:
a-bit, un campo bit pu aggiungere un livello di strutturazione ed efficienza mag-
giore al codice. struct status_type {
Per accedere ai singoli bit, il C utilizza un metodo che si basa sulla struttura. unsi gned delta_cts: 1;
Infatti, un campo bit non che un tipo speciale di membro di struttura che defini- unsigned delta_dsr: 1;
sce la lunghezza, in bit, del campo. La forma generale della definizione di un unsigned tr_edge: l;
campo bit la seguente: unsigned delta_rec: 1;
unsigned cts: 1;
struct nome { unsigned dsr: 1;
tipo nome] : lunghezza; unsigned ring: 1;
tipo nome2 : lunghezza; unsigned rec_l ine: 1;
status;
184 CAPITOLO 7 _s_r_R_u_r_T~U_R_E~._u_N_IO_N__;.1._E_N_U_M_E_RA_Z_l_O_N_l_E_T_IP_l_O_E_Fl_N_l_Tl_D_A_L_L'_U_T_E_N_T_E__1_85_--~~

Per consentire al programma di detenninare quando possibile inviare o ricevere deduzioni. Senza l'uso di campi bit, queste informazioni avrebbero occupato tre
dati, si deve utilizzare una routine simile alla seguente: byte.
I campi bit hanno per alcune restrizioni. Non possibile conoscere l'indiriz-
status = get port status() ; zo di un campo bit. Non possibile creare array di campi bit. Non possono essere
if(status.ct;) prlntf{"clear-to-send"); dichiarati static. Non possibile conoscere, su sistemi operativi diversi, se i cam-
if(status.dsr) printf("data-ready"); pi vanno da sinistra a destra o viceversa. In altre parole, il codice che utilizza
campi bit soggetto ad alcune dipendenze relative alla macchina su cui viene
Per assegnare un valore a un campo bit, si pu usare la forma gi vista per altri impiegato.
tipi di elementi. Ad esempio, il seguente frammento di codice cancella il contenu- Possono esservi anche altre restrizioni imposte dall'implementazione. A tale
to del campo ring: proposito si rimanda alla documentazione del compilatore.

status.ring = O;

Come si p~ vedere da questo esempio, per accedere ai campi bit si utilizza 7.7 Le unioni
l'operatore punto. Tuttavia, se l'accesso alla struttura avviene tramite un puntatore,
si deve utilizzare l'operatore->. In C, un'unione un indirizzo di memoria condiviso, in momenti diversi, da due
Non necessario assegnare un nome a: ogni campo bit. Il nome semplifica o pi variabili generalmente di tipo diverso. La dichiarazione di un'unione simi-
per l'accesso al bit desiderato, saltando quelli non utilizzati. Ad esempio, se le a quella di una struttura. La sua forma generale :
interessa solo il contenuto dei bit cts e dsr, si pu dichiarare una struttura status_type
nel modo seguente: union nome_imione {
tipo nome_membro;
struct status_type tipo nome_ membro;
unsigned : 4; tipo nome_ membro;
unsigned cts: 1;
unsigned dsr: 1;
status;
} variabili_unione;
Inoltre, si noti che i bit che seguono dsr non devono essere specificati se non
vengono utiziati. -- --- Ad esempio,
Inoltre consentito usare insieme nella stessa struttura membri standard e
campi bit. Ad esempio, uni on u_ type
int i:
struct emp { char eh;
struct addr address; }:
f1 oat pay;
unsigned lay_off: 1; /*a riposo o attivo*/ Questa dichiarazione non crea alcuna variabile. In C si pu dichiarare una
unsigned hourly: 1; /* paga oraria o mensile */ variabile specificandone il nome alla fine della dichiarazione o utilizzando una
unsi gned deduct i ons: 3; /* deduzi-0ni */ dichiarazioQe__distinta. Per dicb.iarare una variabile union di nome cnvt e di tipo
};
u_type utilizzando la definizione precedente, si deve scrivere:
definisce il record di un dipendente che utilizza solo un byte per contenere tre tipi uni on u_type cnvt:
___ ~ ~~ormazioni: lo stato de_l~&_~nde_n~e, il fatto che sia pagato a ore e il numero-rldct-i- - -------
- - - --=--- ---
186 CAPITOLO 7 -STRlJTTURE. UNIONI. E-N-!JM-G.R-AZIONI E TIPI Dt;F_INiTI DALL'UTENTE 187

Quando si dichiarano variabili union in C++, basta utilizzare il nome del tipo; Nell'esempio seguente, alla funzione viene passato un puntatore a envt:
dunque non necessario specificare la parola riservata union. Ad esempio, ecco
come cnvt v_i~ne dichiarata in C++: void funcl(union u type *un)
{ -
u_type cnvt; un->i = 10; /* assegna 10 a cnvt utilizzando
una funzione */
Anche in C++, comunque possibile specificare la parola riservata union che,
tuttavia, ridondante. In C++ infatti il nome dell'unione definisce da solo il nome
completo del tipo. In C il nome dell'unione un tag e deve essere necessariamen- L'uso di un'unione pu essere di grande aiuto nella produzione di codice indi-
te preceduto daila parola riservata union ( una situazione analoga a quella delle pendente dalla macchina su cui viene utilizzato. Poich il compilatore registra le
strutture; descritte in precedenza). Poich i programmi di questo capitolo sono effettive dimensioni dei membri dell'unione, non viene prodotta alcuna dipen-
validi sia in C che in C++, verr utilizzata la dichiarazione in stile C. denza dalla macchina. Questo significa che non ci si deve preoccupare delle di-
In cnvt l'intero i e il carattere eh condividono lo stesso indirizzo di memoria. mensioni di un int, di un long, di un float o di quant'altro.
Natu~almente i occupa 2 byte (nel caso di interi di 2 byte) e eh ne occupa uno solo.
Le unioni sono molto utilizzate quando si richiedono conversioni di tipo spe-
La Figura 7.2 mostra il modo in cui i e eh condividono lo stesso indirizzo. In un cializzate in quanto possibile far riferimento ai dati contenuti in un'unione in
determinato punto del programma possibile accedere ai dati memorizzati in modi completamente diversi. Ad esempio, si pu utilizzare un'unione per mani-
cnvt come a un intero o a un carattere. - polare i byte che formano un double in modo da modificarne la precisione o da
Quando si dichiara una variabile union, il compilatore alloca automaticamen- eseguire un qualche tipo insolito di arrotondamento.
te uno spazio di memoria sufficiente a contenere il membro pi esteso dell'unio- Per avere un'idea dell'utilit di un'unione quando occorre eseguire conver-
ne. Ad esempio, assumendo l'uso di interi di 2 byte, envt occuper 2 byte in modo sioni di tipo non standard, si consideri il problema della scrittura di un intero short
da poter contenere i, anche se eh richiede solo un byte. su un file. La libreria standard del C!C++ non contiene alcuna funzione scritta in
Per accedere a un membro di un'unione, si utilizza la stessa sintassi gi vista modo specifico. Anche se possibile scrivere dati di qualsiasi tipo su un file uti-
per le strutture: gli operatori punto e freccia. Se si opera direttamente su un'unio- lizzando fwrite(), il suo impiego sembra un po' eccessivo per un'operazione cos
ne si utilizza l'operatore punto. Se l'accesso all'unione avviene tramite un semplice. Utilizzando un'unione si pu facilmente creare una funzione chiamata
puntatore, si utilizza l'operatore freccia. Ad esempio, per assegnare l'intero IO putw() che scrive su file la rappresentazione binaria di uno s~ort int, un byte per
all'elemento i di envt, si deve scrivere volta (in questo esempio si suppone che un intero short occupi 2 byte). Per vedere
come ci sia possibile si inizi a creare un'unione formata da uno short int e un
cnvt.i = 10; array di caratteri di 2 byte:

union pw {
short int i;
char eh [2];
};

Ora si potrebbe utilizzare pw per creare versione di putw() mostrata nel se-
guente programma:

#include <stdio.h>
union pw {
shorj:_j_nt___j_;
char eh [2];
};
Figura 7.2 11 modo in cui e eh lltilizzano l'unione cnvt (assumendo l'uso dl i_r:ite~dl20ye).
---~ ---- ---..:..-:.__,_ -----
188 .CAPITOLO 7 STRUTTURE. UNIONI. ENUMERAZIONI E TI PI DEFINITI DALL 'UTENH' - 1sg--

putw(short int num, FILE *fp); enum nome_tipo_enumerativo { elenco enumerazioni } elenco_variabili;
int main(void) Qui, sia il nome del tipo enumerativo che l'elenco delle variabili sono ele-
{
menti opzionali (ma deve essere presente almeno uno di questi due elementi). Il
FILE *fp;
seguente frammento di codice definisce un'enumerazione chiamata coin:
fp = fopen("test.tmp","w+");
enum coin { penny, nickel, dirne, quarter,
putw(lOOO, fp); /* scrive il valore 1000 come un intero*/ half_dollar, dollar};
fclose(fp);
Il nome del tipo eriumerativo pu essere utilizzato per dichiarare variabili di
return O; tale tipo. In C, la seguente riga dichiara money come una variabile di tipo coir:i.

enum coin money;


int putw(short int num, FILE *fp)
{
union pw word;
In C++, la variabile money pu essere dichiarata con la seguente forma ab-
breviata:
word. i = num;
coin money;
putc(word.ch[O], fp); /*scrive la prima !llet */
return putc(word.ch[l], fp); /*scrive la seconda met*/ In C++, il nome di un'enumerazione specifica l'intero tipo. In C, il nome
dell'enumerazione un tag che richiede l'impiego della parola riservata enum.
Questa situazione simile a quella descritta nel caso delle strutture e delle unioni
Anche se putw() viene richiamata con uno short int, essa pu utilizzare la di cui si parlato in precedenza.
funzione standard putc() per scrivere su disco l'intero un byte per volta. Date tali dichiarazioni, le seguenti istruzioni sono perfettamente corrette:
~Qr.A::.:::,:;;::~1 Il C++ consente l'uso di un tipo particolare di unione chia- money = dirne;
mata unione anonima che verr discussa nella seconda parte di questa guida. if(money==quarter) printf("La moneta un quarter. \n");

Il fattore chiave per comprendere il funzionamento di un'enumerazione che


7.8 Le enumerazioni a ognuno dei simboli corrisponde un valore intero. Questi valori possono quindi
essere utilizzati in qualunque punto in cui si potrebbe utilizzare un intero. A ogni
Un'enumerazione formata da un gruppo di costanti intere dotate di un nome che simbolo viene assegnato un valore maggiore di una unit rispetto al simbolo pre-
specificano tutti i valori consentiti per una variabile di tale tipo. Le enumerazioni cedente. Il valore del primo simbolo dell'enumerazione zero. Pertanto,
sono molto comuni nella vita di tutti i giorni. Ad esempio, un'enumerazione delle
monete utilizzate negli Stati Uniti potrebbe essere: printf("%d %d", penny, dirne);

. =--penny, nickel, din::!e, quarter, half-dollar, dollar visualizza i valori O2.


Il valori di uno o pi simboli possono essere specificati esplicitamente tramite~
Le enumerazioni sono definite come le strutture;-Ia-parola chiave enum se- un inizializzatore. A questo scopo si deve far seguire al simbolo il segno di ugua-
gnala l'inizio di un tipo enumerativo. La forma generale delle enumerazioni la glianza e un valore intero. Ai simboli che appaiono dopo gli inizializzatori vengo-
-seguente:-- - - - - no assegnati valori maggiori rispetto all'in~ializzazione pre_c._edente. Ad esempio,
-J!~eg!,le_f!.~e c:~dice assegna a quarter il valore 100: - -
--=-=-- - -: :- - .
190 CAPITOLO 7 llll

enurn coin penny, nickel, dirne, quarter=lOO, case half_dollar: printf("half_dollar");


half_doll ar, doll ar}; -break;
case doll ar: printf("dollar");
I valori di questi simboli saranno quindi:

penny o Talvolta, possibile dichiarare un array di stringhe e utilizzare i valori


nickel 1 dell'enumerazione come indici per tradurre tali valori nella stringa corrisponden-
dime -2 te. Ad esempio, anche questo codice produce la stringa corretta:
quarter 100
half_dollar 101 char narne[][12] ={
dollar 102 "penny",
"nickel",
Un'assunzione piuttosto comune ma erronea riguardante le enumerazioni il "dirne",
"quarter",
fatto che i simboli possano essere utilizzati direttamente in operazioni di input e
"half_dollar",
output Ad esempio il seguente frammento di codice non funzioner nel modo "doll ar"
desiderato:
};
printf("%s", narne[rnoney]);
/* non funzionante */
rnoney = dollar;
printf( '%s", rnoney);
Naturalmente, questo funziona solo se non viene inizializzato alcun simbolo
in quanto l'ariay di stringhe deve essere indicizzato a partire da zero.
Poich i valori delle enumerazioni devono essere convertiti manualmente nei
Si ricordi che dollar il nome di un intero, non una stringa. Per lo stesso corrispondenti valori stringa per le operazioni di UO, la loro utilit risulta pi
motivo non possibile utilizzare questo codice per ottenere i risultati desiderati: evidente all'interno di routine che non eseguono tali conversioni. Un'enumerazione
viene spesso utilizzata per definire tabelle di simboli, ad esempio per un compila-
/* codice errato */
tore. Le enumerazioni sono spesso utilizzate anche per dimostrare la validit di un
strcpy(rnoney, "dirne");
programma fornendo una verifica di ridondanza al momento della compilazione
per confermare che a una variabile vengano assegnati solo valori validi.
Quindi, una stringa che C.QU.t~~_iLgome di un simbolo non viene convertita
automaticamente in tale simbolo.
La creazione di codice per l'input e l'output dei simboli delle enumerazioni
piuttosto noiosa (a meno che ci si voglia basare solo sui valori interi). Ad esem- 7.9 Uso di sizeof per assicurare la trasportabilit
pio, per visualizzare, in lettere, i valori contenuti in money, si deve utilizzare uno del codice
switch di questo tipo:
Si detto che le strutture e le unioni possono essere utilizzate per creare variabili
swi tch (rnoney) di dimensioni diverse e che le effettive dimensioni di tali variabili possono variare
case penny: printf("penny"); da macchina a macchina. L'operatore sizeof calcola le dimensioni di una variabile
break; o di un tipo e aiuta quindi a eliminare dal programma il codice dipendente dalla
case nickel: printf("nickel "); macchina. Questo operatore particolarmente utile quando si utilizzano strutture
---.break; o unioni.
case dirne: printf("dirne"); Per la discussione seguente, si assume un implementazione, molto comune
break;
nei compilatori C/C++, in cui i dati abbiano le seguenti dimensioni:
-- - - - -.. -Case quarter: printf("quarter");
bre~::-~-
192 CAPITOLO 7
UNIONI, ENUMERAZIONI E flf'l Ut:.t1l~1J-I UMcc uc ... c

Tipo Dimensioni in byte int i;


double f;
char 1 u_var;
int 4
double 8 Qui, sizeof(u_var) vale 8. Al momento dell'esecuzione, non importa cosa in
effetti u_var contenga. Tutto ci che interessa sapere sono le dimensioni del suo
Pertanto, il seguente frammento di codice visualizzer sullo schermo i numeri membro pi grande, in quanto ogni unione ha le dimensioni dell'elemento pi
1,4 e 8: grande dichiarato al suo interno.

char eh;
'.nt i;
double f; 7.1 O La parola riservata typedef
printf("%d", sizeof(ch}); Il C/C++ consente di definire esplicitamente nuovi tipi di dati utilizzando la paro-
la chiave typedef. In questo modo non si crea un nuovo tipo di dati ma si definisce
printf("%d", sizeof(i}}; un nuovo nome per un tipo preesistente. Questo processo pu aiutare a rendere
pi trasportabili i programmi dipendenti dalla macchina. Se si definisce un pro-
printf("%d", sizeof(f));
prio nome per ogni tipo di dati dipendente dalla macchina utilizzato dal program-
ma, quando il programma verr compilato in un nuovo ambiente baster cambia-
Le dimensioni di una struttura sono uguali o maggiori della somma delle di- re solo le istruzioni typedef. typedef pu anche essere utile per l'auto documenta-
mensioni dei suoi membri. Ad esempio, zione del codice; consentendo l'uso di nomi descrittivi per i tipi di dati standard.
La forma generale dell'istruzione typedef la seguente:
struct s {
char eh;
int i;
typedef tipo nuovonome;
double f;
s_var; dove tipo un qualunque tipo di dati valido e nuovonome il nuovo nome che si
intende assegnare a questo tipo. Questo nuovo nome si aggiunge al tipo preesistente
Qui, sizeof(s_var) uguale almeno a 13 (8 + 4 +l). Tuttavia, le dimensioni di ma non lo sostituisce.
s_var potrebbero essere maggiori in quanto il compilatore potrebbe sistemare Ad esempio, possibile creare un nuovo nome per il tipo float utilizzando:
diversamente una struttura per consentirne l'allineamento all'interno di una word
(2 byte) o di un paragrafo (16 byte). Poich le dimensioni di una struttura potreb- typedef fl oat ba 1ance;
bero essere maggiori rispetto alla somma delle dimensioni dei suoi membri, per
conoscere la dimensione della struttura si deve sempre utilizzare sizeof. Questa istruzione dice al compilatore di riconoscere balance come un altro
Poich sizeof un operatore che viene eseguito al momento della compilazio- nome di float. In seguito sar possibile creare una variabile float utilizzando il tipo
ne, tutte le informazioni necessarie per calcolare le dimensioni di una variabile balance:
saranno note al momento della compilazione. Questo particolarmente impor-
tante nel caso delle union in quanto le dimensioni di una union sono sempre uguali ba 1ance over_due;
all~ dimensioni del suo membro pi grande. Ad esempio si consideri la segl!:ente
unione - Qui, over_due una variabile in virgola mobile di tipo balance che non che
un modo diverso di chiamare il tipo float.
union u { Ora che si definitOTitipo balance, esso potr anche essere utilizzato in un
char eh; .. ajtro typedef. Ad esempio,
---~----
- -
194 CAPIFOLO 7

typedef balance overdraft;


Capitolo 8
chiede al compilatore di riconoscere overdraft come un altro nome di balance che Operazioni di I/O
non altro che un float.
L'uso di typedef pu rendere il codice pi facile da leggere e da trasportare su da console
una nuova macchina. Si ricordi per sempre che con typedef non si sta creando un
nuovo tipo di dati. 8.1 Un'importante nota applicativa
8.2 La lettura e la scrittura di caratteri
8.3 La lettura e la scrittura di stringhe
8.4 Le operazioni di I/O formattato
da console
8.5 La funzione printf()
8.6 La funzione scanf()

I linguaggio C++ supporta due sistemi di I/O. Il primo


deriva dal C e il secondo il sistema di I/O a oggetti definito dal C++. Questo
capitolo, insi~me al prossimo, descrive il sistema di I/O del C (il sistema del ++
verr descritto nella Parte seconda). Anche se probabilmente si preferir utilizza-
re il sistema C++ per tutti i nuovi progetti, pu capitare frequentemente di trovare
codice che impiega il sistema C. In ogni caso la conoscenza del sistema di I/O C
fondamentale per comprendere appieno le funzionalit del sistema C++.
In C, tutte le operazioni di input e output vengono eseguite utilizzando le
funzioni della libreria. Le operazioni di I/O si possono svolgere da console e da
file. Tecnicamente, non esiste una grande distinzione fra I/O su console e I/O su
file bench, si tratti concettualmente di situazioni molto diverse. Questo capitolo
esamina in dettaglio le funzioni di I/O da console. Il prossimo capitolo si occupa
dclSlstema di I/O da file e descrive le relazioni fra questi due sistemi.
Con un'unica eccezione, questo capitolo si occupa solo delle funzioni di I/O
da console definite dallo Standard C++.Lo Standard C++ non definisce nessuna
funzione per il controllo dello schermo (ad esempio per il posizionamento del
cursore) o per la visualizzazione di oggetti grafici, poich queste operazioni pos-
sono essere molto diverse da una macchina a un'altra. Inoltre non definisce alcu-
na funzione per la scrittura in una finestra di Windows. Quindi, le funzioni standard
di I/O da console eseguono solo operazioni di output di puro testo in formato
"teletype". Tuttavia, la maggior parte dei compilatori include nelle proprie libre-
-rie numerose funzioni-per il controllo dello schermo e funzioni grafiche che si
applicano solo all'ambiente per il quale il compilatore stato progettato. Natural-
mente si pu scrivere un programma Windows in C++ ma occorre tenere presente
che il linguaggio non fornisce funzioni in grado di eseguire queste operazioni.
------ --- --- -
196 C A PI T O LO 8 197

Le funzioni di I/O Standard C usano tutte il file header stdio.h. I programmi getchar() attende la pressione di un tasto e restituisce il valore corrispondente. Il
C++ possono anche utilizzare il nuovo file header <CStdio>. tasto premuto viene inoltre automaticamente visualizzato sullo schermo. La fun-
Il capitolo si occupa delle funzioni di I/O da console che accettano input dalla zione putchar() scrive un carattere sullo schermo alla posizione corrente del cursore.
tastiera e producono output sullo schermo. Tuttavia, queste funzioni hanno come I protOi:ipi delle funzioni getchar() e putchar() sono i seguenti:
origine e/o destinazione delle proprie operazioni i canali di input e output standard
del sistema. Inoltre, i canali di input e output standard possono essere diretti verso int getchar(void);
altri dispositivi. Questi concetti saranno chiariti nel Capitolo 9. int putchar(int e);

Come si pu vedere dal prototipo, la funzione getchar() restituisce un intero.


Tuttavia, si pu assegnare questo valore a una variabile char, operazione in genere
8.1 Un'importante nota applicativa molto comune, in quanto il carattere immesso si trover nel byte di ordine inferio-
re (il byte di ordine superiore sar normalmente uguale a zero). In caso di errore,
La prima parte di questa guida utilizza il sistema di I/O C perch questo l'unico
getchar() restituisce EOF.
metodo di I/O definito per il sottoinsieme C del C++.Come si detto, il C++ defi- Nel caso di putchar(), anche se nel prototipo si indica che accetta un parame-
nisce anche un proprio sistema di I/O orientato agli oggetti. Dunque, per la maggior tro intero, sar possibile richiamare la funzione utilizzando un argomento di tipo
parte dei programmi a oggetti, sar preferibile impiegare il sistema di I/O specifico carattere. In effetti, sullo schermo viene visualizzato solo il byte di ordine inferio-
del C++ e non il sistema di I/O C descritto in questo capitolo. Tuttavia, una cono- re del parametro. La funzione putchar() restituisce il carattere che essa stessa scri-
scenza approfondita del sistema di I/O C importante per i seguenti motivi: ve oppure EOF in caso di errore (la macro EOF definita nel file stdio.h e gene-
Potrebbe capitare di dover scrivere codice che deve limitarsi al sottoinsieme ralmente uguale a -1).
C. In questo caso si dovr necessariamente impiegare le funzioni di I/O C. Il seguente pro.gramma illustra l'uso delle funzioni getchar() e putchar(). Que-
Nel prossimo futuro, vi sar una coesistenza fra Ce C++. Inoltre vi saranno sto breve programma legge un carattere dalla tastiera e lo trasforma in maiuscolo
molti programmi ibridi contenenti codice C e C++. Inoltre sar molto comune se minuscolo e viceversa. Per fermare il programma basta immettere un punto.
l'aggiornamento dei programmi Ce la loro trasformazione in programmi C++.
Pertanto sar necessario conoscere sia il sistema di JJO del C che quello del C++. #include <stdio.h>
Ad esempio, per trasformare le funzioni di I/O del C in funzioni di I/O a oggetti llinclude <ctype.h>
del C++ sar necessario conoscere il funzionamento delle operazioni di I/O sia in
C che in C++. int main{void)
{
Una conoscenza dei principi di base tipici del.sistema di I/O del C fonda- char eh;
mentale per omprendere il sistema di JJO a oggetti del C++ (entrambi condivido-
no la stessa filosofia). printf{"Immettere del testo (per uscire, premere il punto). \n");
In alcune situazioni (ad esempio nei programmi pi brevi), pi facile utiliz- do {
zare il sistema di I/O del C rispetto a quello orientato agli oggetti del C++. eh = getchar{);
Infine, vi una tacita regola che stabiiisce che ogni programmatore C++ deb-
if{islower{ch)) eh = toupper{eh);
ba essere anche un programmatore C. Non conoscendo il sistema di I/O del C il
else eh= tolower{ch);
lettore corre il rischio di limitare i propri orizzonti professionali.
putchar{eh);
} whil e {eh ! = '. ') ;

8.2 la lettura-e la scrittura di caratteri return O;


}
Le pi semplici funzioni di I/O da console sono getchar() che legge un carattere
dalla tastiera-e putchar() !i~~ampa_un carattere sullo schermo:--La-funzione
--o-n-R A z I O-N I DI I/ o DA e O_N_ so LE 199
198 CA P I T O LO 8

I problemi di getchar() #include <stdio.h>


#include <conio.h>
l\ell'uso di getchar() possono sorgere alcuni problemi. Normalmente getchar() lii ne 1ude <ctype. h>
implementata in modo da bufferizzare l'input fino alla pressione del tasto 1Nv10.
Questa tecnica di input chiamata bufferizzazione della riga: per inviare qualun- int main(void)
{
que cosa al programma necessario premere il tasto INVIO. Inoltre, poich getchar()
char eh;
legge un solo carattere per volta, la bufferizzazione della riga poteva lasciare uno
o pi caratteri in attesa nella coda di input e questo pu rappresentare un proble- printf ("Immettere del testo (per usci re, premere il punto). \n");
ma in ambienti interattivi. Anche se lo Standard C/C++ specifica che getchar() do I
possa essere implementata come funzione interattiva, ci avviene raramente. Que- eh = getch();
sto il motivo per cui il programma precedente potrebbe non comportarsi nel
modo atteso. if(i slower(ch)) eh = toupper(ch);
else eh = tolower(eh);

Le alternative a getchar() putchar(ch);


wh il e (eh ! = ' ' ) ;
La funzione getchar() potrebbe essere implementata in modo non utile in ambien-
ti interattivi. In questo caso, si vorr probabilmente eseguire la lettura di caratteri return O;
dalla tastiera utilizzando una funzione diversa. Lo Standard C++ non definisce
nessuna funzione in grado di eseguire input interattivo, ma praticamente tutti i
compilatori C++ ne offrono una. Anche se queste funzioni non sono definite dallo Quanto si esegue questa versione del programma, il tasto viene immediata-
Standard C++, si tratta di funzioni ampiamente utilizzate in quanto getchar() non mente trasmesso al programma e visualizzato in maiuscolo se era minuscolo e
risponde alle esigenze di molti programmatori. viceversa. L'input non risulta pi bufferizzato. Anche se nel codice di questo vo-
Due delle pi comuni funzioni alternative, getch() e getche() hanno i seguenti lume non utilizzer pi getch() o getche(), si tratta di funzioni molto utili nei
prototipi: programmi .

int getch(void); .NOTA Al momento attuale, leftmzioni _getche() e _getch() del com-
int getche(void); pilatore Microsoft Visual C++ non sono compatibili con le fun::.ioni di input
standard CIC++ come scanf() e gets(). Al loro posto si devono usare speciali
versioni di questeftmzioni standard chiamate cscanf() e cgets(). Per informazionL ----
Nella ml!ggior parte dei compilatori, i prototipi di queste funzioni si trovano
dettagliate, consultare la documentazione di Visua1 C++.
nel file conio.h. Per alcuni compilatori, queste funzioni sono precedute dal carat-
tere di sottolineatura. Ad esempio, in Microsoft Visual C++ queste funzioni si
chiamano _getch() e _getche(). La funzione getch{) attende la pressione di un
tasto e ne restituisce immediatamente il valore ma non visualizza il carattere sullo
schermo. La funzione getche() uguale a getch() ma visualizza sullo schermo il 8.3 La lettura e la scrittura di stringhe
carattere corrispondente al tasto premuto. In questa guida si utilizzano molto spesso
sia getche() che getch() al posto di getchar() ogni volta che un programma interatti\'o Il passo successivo nelle operazioni di I/O da console, in termini di complessit e
- richiede la lettura di-un carattere dalla tastiera. Se il compilatore non dovesse di potenza, formato dalle funzioni gets() e puts(). Queste funzioni consentono di
prevedere l'uso di queste funzioni alternative o se getchar() fosse implementat<t leggere e scrivere stringhe di caratteri.
come funzione interattiva, si potr sostituire, se necessario, getchar(). La funzione gets() legge una stringa di caratteri immessa alla tastiera e la
Ad esempio, si pu riscrivere il programma precedente in modo che usi getch() inserisce all'indirizzo puntato dal propri argomento. L'immissione dei caratteri
al posto di ~e~h~r(): ha...termine_quando si preme il tasto INVIO. A questo punto, nella stringa verr
_lnserito il carattere nullo di fine stringa e..gets() terminer con un return. Quindi
200 CAPITOLO 8 O eE A A-ZIO N I O I I I O O A CONSOLE 201

non si pu usru:_e gets() per restituire il codice del tasto di INVIO (per questo scopo zione puts() viene spesso utilizzata quando importante produrre codice perfetta-
si pu usare getchar()). Gli errori di digitazione possono essere corretti prima mente ottimizzato. In caso di errore, la funzione puts() restituisce EOF. In tutti gli
della pressione del tasto INVIO utilizzando il tasto BACKASPACE. Il prototipo della altri casi, restituisce un valore non negativo. Ma in genere, quando si scrive sulla
funzione gets() il seguente: console, si pu ragionevolmente supporre che non si verifichi alcun errore-e quin-
di difficilmente si controlla il valore restituito da puts(). La seguente istruzione
char *gets(char) *str); visualizza sullo schermo la parola ciao:

puts("ciao");
dove str un array di caratteri che riceve l'input dall'utente. La funzione gets()
restituisce la stringa letta in str. Il seguente programma legge una stringa nell' array
str e ne visualizza la lunghezza: La Tabella 8.1 riepiloga le principali funzioni di UO da console.
Il seguente programma, un semplice dizionario computerizzato, mostra l'uso
#include <stdio.h> di molte delle funzioni di UO da console. Il programma chiede all'utente di im-
#include <string.h> mettere una parola e quindi la confronta con un piccolo database interno. Se viene
trovata una corrispondenza, il programma visualizza il significato della parola.
int main(void) Occorre fare particolare attenzione all'uso dei puntatori in questo programma. Se
{ si ha qualche difficolt a comprenderne il funzionamento, si ricordi che l'array
char str[BO]; dic un array di puntatori a stringhe. Si noti inoltre che l'elenco deve essere
concluso da due stringhe nulle.
gets(str);
printf("Lunghezza stringa= %d", strlen(str)); /*Un semplic dizionario. */
#include <stdio.h>
return O; #include <string.h>
#include <ctype.h>

Occorre fare attenzione a utilizzare gets() poich essa non esegue alcuna veri- /* elenco delle parole e significato */
fica di fuoriuscita dall'array che riceve l'input. Pertanto possibile che l'utente char *dic[] [40] = {
immetta un numero di caratteri eccessivo rispetto alle dimensioni dell'array. An- "atlante", "Un libro di mappe",
che se gets() adatta per semplici programmi e semplici utility di utilizzo privato "auto", "Un veicolo motorizzato",
dello sviluppatore, opportuno evitarne l'uso nei programmi commerciali. Un'al- "telefono", "Un dispositivo di comunicazione",
ternativa ~ rappresentata dalla funzione fgets() che verr descritta nel prossimo "aereo", "Una machina. vofante",
capitolo. Tale funzione non consente di fuoriuscire dall'array. "",
1111
/* stringa nulla di terminazione dell'elenco*/
};
La funzione puts() scrive sullo schenno il contenuto del proprio argomento
stringa seguito dal codice di fine riga. Il suo prototipo : int main(void)
{
int puts(const char *str); char word[BO], eh;
char **p;
La funzione puts() riconosce gli stessi codici backslash di printf(), come ad
esempio "\t" per la tabulazione orizzq_nt~le. Una chiamata a puts() richiede un do {
sovraccarico inferiore (in termini di tempo e memoria) rispetto alla funzione printf() puts(i\nimmettere una parola: ");
in quanto la prima pu solo visualizzare stringhe di carattere e non pu visualizzare scanf( 11 %s", word);
n numeri n eseguire conversioni di formato. Pertanto, puts() richiede meno spa-
p = (char **)dic;
zfo e viene eseguita pi velocement-ifspett~ aprhtf(tPet questo motivo, la fun: ____ _
202 CAPITOLO 8
OPERAZIONI DI I/O DA CONSOLE 203

/*trova la parola corrispondente e ne visualizza il significato */ 8.5 La funzione printf()


do {
if(!strcmp(*p, word)) {
puts ("Significato:");
Il prototipo di printf() :
puts(*(p+l));
break; int printf(const char *stringa_controllo, ... );

if(!strcmp(*p, word)) break; La funzione printf() restituisce il numero di caratteri scritto o un valore nega-
P = p + 2; /*scorre l'elenco*/ tivo nel caso in cui si verifichi un errore.
while(*p); La stringa_controllo formata da due tipi di oggetti. Il primo tipo costituito
if(!*.p) puts("La parola .non si trova nel dizionario"); dai caratteri che verranno visualizzati sullo schermo. Il secondo tipQ contiene
printf("Altre parole? (s/n): ");
specificatore di formato che definiscono il modo in cui dovranno essere visualizzati
scanf( 11 %c%*c 11 , &eh);
gli argomenti successivi. Uno specificatore di formato inizia con il segno di per-
while(toupper(ch) != 'N');
centuale ed seguito dal codice del formato. Il numero degli specificatori di for-
return O; mato deve corrispondere esattamente al numero di argomenti e inoltre vi deve
essere una corrispondenza esatta da sinistra a destra. Ad esempio, questa chiama-
ta a printf()

printf("Il %cmi %s", 'C', "piace molto!");


8.4 Le operazioni di I/O formattato da console
visualizza la frase
Le funzioni printf() e scanf() eseguono rispettivamente operazioni di output e input
format~to; questo significa che sono in grado di scrivere e leggere i dati in vari Il e mi piace molto!
formati sotto il controllo del programmatore. La funzione printf() scrive i dati
sullo schermo. La funzione complementare scanf(), legge i dati dalla tastiera. La funzione printf() accetta un'ampia gamma di specificatori di formato, indi-
Entrambe le funzioni accettano qualsiasi tipo di dati interno del C, inclusi i carat- cati nella Tabella 8.2.
teri, le stringhe e i numeri.

La visualizzazione dei" caratteri -- - - -- -


Tabella 8.1 re principali funzioni di I/O da console.
Per visualizzare un singolo carattere, si usa lo specificatore %c. In questo modo,
FUNZIONE OPERAZIONE l'argomento corrispondente verr visualizzato senza alcuna modifica. Per
getchar(} visualizzare una stringa, si deve utilizzare lo specificatore di formato %s.
Legge un carattere dalla tastiera: attende la pressione di INVIO.
0etche() Legge un carattere dalla tastiera e lo visualizza; non attende la pressione di 1Nv10:
non definita dallo standard ANSI ma un'estensione molto comune. La visualizzazione dei numeri
;etch (}
Legge un carattere dalla tastiera senza visualizzarlo; non attende la pressione di 1Nv10:
non def[nita dallo standard ANSI ma un'estensione molto com~ne. Per visualizzare un numero deci_male con segno si pu usare sia.lo specificatore
Scrive un carattere sullo schermo. o/od sia %i. Questi specificatori di formato sono equivalenti e vengono conservati
:;ets () Legge una stringa dalla tastiera.
entrambi per motivi storici.
Per visualizzare un valore senza segno si utilizza lo specificatone o/ou, e lo
Scri~e. uoa stringa-SUllO-SC~ermo. -
-------~--
specificatore di formato %f visualizza numeri in virgola mobile.
OPERAZIONI O I I/ 0-A CONSOLE - -205
CAPITOLO 8

Il seguente programma mostra gli effetti dello specificatore di formato %g:


Gli specificatori %e e %E chiedono di visualizzare un argomento double in
notazione scientifica. I numeri in notazione scientifica hanno la seguente forma
generale: #include <stdio.h>

int main(void)
x.dddddE+/-yy
(
double f;
Per visualizzare la lettera "E" in maiuscolo, si dovr utilizzare Io specificatore
%E mentre per visualizzare la "e" minuscola si dovr utilizzare Io specificatore for{f=l.O; f<l.Oe+lO; f=f*lO)
%e. printf("%g ", f);
Si pu chiedere al compilatore di decidere se usare %f o %e utilizzando gli
specificatori di formato %g e %G. In questo modo, printf() sceglier lo specificatore return O;
di formato che produce I' output pi breve. Lo specificatore di formato %G
visualizza, se necessario, la lettera esponenziale "E" in lettere maiuscole; %g la
visualizza in minuscolo. Il programma produce il seguente output:

1 10 100 1000 10000 100000 le+006 le+007 le+OOB le+009

Tabella 8.2 Gli specificatori di formato di printf(). Gli interi senza segno possono essere visualizzati in formato ottale o
CODICE FORMATO esadecimale utilizzando rispettivamente gli specificatori %o e %x. Poich il for-
mato numerico esadecimale utilizza le lettere da A a F per rappresentare i numeri
%e Carattere da 10 a 15, si pu decidere di visualizzare queste lettere in maiuscolo o in minu-
%d Interi decimali con segno scolo. Lo specificatore %X visualizza le lettere esadecimali in maiuscolo mentre
lo specificatore %x le visualizza in lettere minuscole:
%i Interi decimali con segno

%e Notazione scientifica (e minuscola) #include <stdio.h>


%E Notazione scientifica (e maiuscola)
int main(void)
%f Numero decimale in virgola mobile {
%g Usa il pi breve Ira %e e %f
unsi gned num;

%G Usa il pi breve fra %E e %F for(num=O; num<255; num++)


printf("%o ", num);
%o Ottale senza segno
printf("%x ", num);
%s Stringa di caratteri printf("%X\n", num);

%u Interi decimali senza segno

%x Esadecimale senza segno (lettere minuscole) return O;

%X Esadecimale senza segno (lettere maiuscole)

%p Visualizza un puntatore
La visualizzazione di. un indirizzo di memoria
%n t:argome~to ~ssociato un puntatore a interi in cui viene inserito il numero di caratteri scritti
Per visualizzare un indirizzo si utilizza lo speeificatore-di-formato %p. In questQ_ --- - - __ _
Stampa il carattere "%'
-- __::::__".""'_~-:----:::::-:--::-------------==::::::== modo lafuni.ione-printf() visualizzer un indirizzo di memoria del computer in un -- -- -__ _
206 CAPITOLO
OPERAZIONI DI 1/0 DA CONSOLE 207

formato compatibile con il tipo di indirizzamento 'utilizzato. Il seguente program- I modificatori di formato
ma visualizza l'indirizzo della variabile sample:
Molti specificatori di formato prevedono l'uso di modificatori che ne alterano
#include <stdio.h> leggermente il significato. Ad esempio, si pu specificare un'ampiezza minima di
un campo, il numero di cifre esadecimali e l'allineamento a sinistra. Il modifica-
int sample;
tore di formato deve trovarsi fra il segno di percentuale e il codice di formattazione.
int main(void)
{
Lo specificatore di minima ampiezza di campo
printf{"%p", &sample);
Un intero posto fra il segno di percentuale e il codice di formattazione agisce
return O;
come specificatore di minima ampiezza del campo. Questo modificatore inserisce
una serie di spazi nell'output in modo da raggiungere sempre almeno la larghezza
minima desiderata. Stringhe e numeri pi lunghi di questa dimensione minima
Lo specificatore %n verranno comunque interamente visualizzati. Normalmente il raggiungimento della
larghezza minima specificata viene eseguito tramite spazi. Se invece si desidera
Lo specificatore di formato o/on differisce da ogni altro specificatore. Invece di utilizzare un carattere diverso, ad esempio lo zero, si dovr inserire tale carattere
chiedere a printf() di visualizzqre qualcosa, chiede di inserire nella variabile pun- prima dello specificatore dell'ampiezza. Ad esempio, Io specificatore %05d ag-
tata dal suo argomento un valore uguale al numero d,i caratteri visualizzati. In giunge a un numero composto da meno di cinque cifre una serie di zeri in modo
altre parole, il valore che corrisponde allo specificator di formato o/on deve essere che la sua lunghezza totale sia uguale a cinque. Il seguente programma mostra
un puntatore a una variabile. Al termine dell'esecuzione di printf(), questa variabi- l'uso dello specificatore di ampiezza minima del campo:
le conterr il numero dicaratteri visualizzati, fino al punto in cui stato inserito Io
specificatore %n. Per meglio comprendere questo particolare specificatore di for- #include <stdio.h>
mato, si esamini il seguente programma:
int main(void)
#include <stdio.h> {
double item;
int main(void}
{ item = 10.12304;
int count:-
printf("%f\n", item);
printf("questa%n una prova\n", &count); printf{"%10f\n", item);
printf("%d", count); printf("%012f\n", item);

return O; return O;

Questo programma visualizza una stringa seguita dal numero 4. Lo Questo programma produce ~l seguente output :
specificatore di formato -o/on viene normalmente utilizzato per consentire al pro-
gramma di eseguire una formattazione dinamica del proprio output. io: 123040
10.123040
00010....123040
OPERAZIONIDi 1/0 DA CONSffi- 209
208 CAPITOLO
Quando si applica lo specificatore di precisione a un numero in virgola mobi-
Lo specificatore di ampiezza minima del campo utilizzato principalmente le visualizzato con gli specificatoci %f, %e o %E, determina il numero di cifre
per produrre tabelle con colonne allineate. Ad esempio, il programma successivo decimali visualizzate. Ad esempio, % 10.4f visualizza un numero assegnandogli
produce una tabella di quadrati e cubi per i numeri compresi fra I e 19: almeno dieci caratteri e con quattro cifre decimali. Se non si specifica la precisio-
ne, verr utilizzato il valore standard di sei cifre.
#include <stdio.h> Quando lo specificatore di precisione si applica ai formati %g e %G, indica il
numero di cifre significative.
int main(void) Applicato alle stringhe, lo specificatore di precisione indica la lunghezza
{ massima del campo. Ad esempio, %5.7s visualizza una stringa di almeno cinque
int i; caratteri e senza superare la lunghezza di sette caratteri. Se la stringa pi lunga
rispetto all'ampiezza massima del campo, verranno troncati i caratteri finali.
/* visualizza una tabella di quadrati e cubi */ Se applicato ai tipi interi, lo specificatore di precisione determina il numero
for(i=l; i<20; i++)
printf("%8d %8d %8d\n", i, i*i, i*i*i);
minimo di cifre che devono apparire per ciascun valore. Per ottenere il numero
richiesto di cifre, verr inserita una serie di zeri iniziali.
return O; II seguente programma illustra l'uso dello specificatore di precisione:

#include <stdio.h>
Il programma produce il seguente output:
int main(void)
{
printf("%.4f\n", 123.1234567);
2 4 8
printf("%3.8d\n", 1000);
3 9 27
printf("%10.15s\n", "Questa solo una prova.");
4 16 64
5 25 125
6 return O;
36 216
7 49 343
8 64 512
9 81 729 Il programma produce il seguente output:
10 100. 1000 ---------
11 121 1331 123.1235
12 fll4 1728 00001000
13 169 2197 Questa sol o u
14 196 2744
15 225 3375
16 256 4096 L'allineamento dell'output
17 289 4913
18 324 5832 Normalmente, tutto l'output di printf() viene allineato a destra. Questo significa
19 361 6859 che se l'ampiezza riel campo maggiore rispetto ai dati da visualizzare, i dati
verranno posizionati sul margine destro del cam).:l_o. Si pu chiedere l~ allineamen-
. to a sinistra dell'output inserendo il segno meno- subito dopo il segno di percen-
Lo specificatore.di precisione tuale. Ad esempio, lo specificatore %-10.2f allinea a sinistra un numero in virgola
mobile con due cifre decimali e posizionato in un campo largo dieci caratteri.
Lo specificatore di precisione segue lo specificatore di ampiezza minima del campo
(se presente) ed -formato da un punto s_eguito da un intero. Il su.o.esatto significa-
to dipende dal tipo di datT acu viene applicato. ----~
-----
210 C_A PI T O LO 8 op E.RA LTU N I u I I I u u,... ~ - " - - ~ ~

Il seguente programma illustra l'uso dell'allineamento a sinistra:

#i nel ude <stdi o. h>


n
printf("%*.*f", 10 4, 123.3);
int main(void)
{
printf("allineato a destra:%8d\n", 100);
LbL_J
printf("all ineato a sini stra:%-Bd\n", 100);

return O;

Figura 8.1 Corrispondenza di valori nell'utilizzo di .

La gestione di altri tipi di dati

Vi sono due modificatori di formato che consentono a printf() di visualizzare valo- Il seguente programma illustra l'uso dei modificatori# e *:
ri interi short e long. Questi modificatori possono essere applicati agli specificatori
di tipo d, i, o, u e x. l modificatore I dice a printf() che i dati relativi sono di tipo #include <stdio.h>
fong. Ad esempio, %Id chiede la visualizzazione di un numero long int. II modifi-
int main(void)
catore h chiede a printf() di visualizzare un intero short. Ad esempio, %hu indica
{
che i dati sono di tipo short unsigned int. printf("%x %#x\n", 10, 10);
Il modificatore L pu precedere gli specificatori in virgola mobile e, f e g e printf( 11 %*.*f 11 , 10, 4, 1234.34);
chiede la visualizzazione di un valore fong double.
return O;

I modificatori * e #

La funzione printf() prevede altri due modificatori di alcuni dei suoi specificatori
di formato: * e #. 8.6 La funzione scanf()
____ ---~~si fa precedere il carattere# agli specificatori g, G, f, E oppure e, si
richiede la visualizzazione del punto decimale anche se non vi alcuna cifra La funzione scanf() la routine di input da console di utilizzo pi generale. _Que- __ . __
decimale.- Se invece si fa precedere allo specificatore di formato x o X il caratte- sta funzione pu leggere valori in tutti i dati interni e converte automaticamente i
re#, il numero esadecimale verr visualizzato con il prefisso Ox. Se si fa prece- numeri nel formato interno corretto. Quindi non solamente l'inverso di printf().
dere allo specificatore o il carattere#, il numero verr visualizzato con uno zero Il prototipo di scanf() il seguente:
iniziale. Questi sono gli unici specificatori di formato che possono impiegare il
modificatore #. int scanf(const char *stringa_controllo, ... );
Oltre che tramite costanti, gli specificatori di ampiezza minima del campo e
di precisione possono essere forniti anche dagli argomenti di printf(). Per sfruttare La funzione scanf() restituisce il numero di dati a cui stato assegnato un
questa possibilit, si deve utilizzare l'asterisco*. Quando viene letta la stringa di valore. Se si verifica un errore, scanf() restituisce EOF. La stringa_controllo de-
formattazione dlprintf(), all'asterisco verr-sostituito l'argomento che si trova termina il modo in cui i valori vengono inseriti nelle variabili pu11_tate dall'e_lenco
nella posizione corrispondente. Ad esempio, nella Figura 8.1, l'ampiezza minima di argomenti.
del campo pari a 1O, la precisione 4 e il valore che verr visualizzato sar
123.3.
212 CAPITOLO
O P E RAZ I O N I O I I /O DA C O N S O L E 213

La stringa di_controllo formata da tre tipi di caratteri:


Tabella 8.3 Gli specificatori di formato di scanf()
specificatori di formatq
spazi CODICE SIGNIFICATO
caratteri diversi da spazi %C Legge un singolo carattere

%d Legge un intero decimale

Gli specificatori di formato %i Legge un intero decimale

Gli specificatori del formato di input sono preceduti dal segno % e dicono a scanf() %e Legge un numero in virgola mobile

il tipo dei dati che devono essere letti. Questi codici sono elencati nella Tabella %f Legge un numero in virgola mobHe
8.3. Gli specificatori di formato corrispondono da sinistra a destra agli argomenti
%g Legge un numero in virgola mobile
presenti nell'elenco argomenti.
%o Legge un numero ottale

%s Legge una stringa


La lettura di numeri
%x Legge un numero esadecimale
Per leggere un numero intero si possono utilizzare gli specificatori "!od o %i. Per
%p Legge un puntatore
leggere un numero in virgola mobile rappresentato in notazione standard o scien-
tifica si possono utilizzare gli specificatori %e, %f o %g. %n Riceve un valore intero uguale al numero di caratteri letti
possibile utilizzare scanf() per leggere valori interi in formato ottale o %u Legge un intero senza segno
esadecimale utilizzando i comandi di formato %o e %x. Lo specificatore %x pu
essere indicato a piacere in lettere maiuscole o minuscole e consentir in ogni %[ ] Attende l'immissione di un detenminato gruppo di caratteri
caso l'immissione di numeri esadecimali sia in lettere maiuscole che minuscole. %% Legge un segno percentuale
Il seguente programma legge un numero ottale e un numero esadecimale:

#include <stdio.h>
la lettura di interi senza segno
int main(void)
Per leggere un intero s~~~s~gi1_q, si utilizza lo specificatore di formato %u. Ad
{
int i, j; esempio,

scanf("%o%x", &i, &j); unsi gned num;


printf("%o %x", i, j); scanf("%u", &num);

return O; legge un numero senza segno e inserisce il suo valore in num.

-La funzione scanf() termina la lettura di un numero nel momento in cui incon- la lettura di sing_?li caratteri con scanf()
tra il primo carattere non numerico. - -
.. Come si detto in precedenza in questo capitolo, possibile leggere singoli carat-
teri utilizzando getchar() o una delle sue funzioni derivate. Per ~seguire la stessa
operazione utilizzando scanf() si utilizza lo specificatore di formato %c. Tuttavia,
. come mol~e_im_plementazioni di getc;;~~J). anc~e scanf() con lo specificatore "!oc- -
O P E R A Z I O N I D I I /-0 DA C O N S O LE 215
214 CA P I T O LO 8
La lettura di un indirizzo
legge un input bufferizzato a riga. Questa caratteristica rende la funzione scanf()
inadatta ali' utilizzo in ambienti interattivi. Per immettere un indirizzo di memoria, si utilizza lo specificatore di fonnato %p.
Anche se gli spazi, i caratteri di tabulazione e i caratteri di fine riga sono Questo specificatore fa in modo che scanf() legga un indirizzo nel formato defini-
utilizzati come separatori di campo nella lettura di altri tipi di dati, quando si deve to dall'architettura della CPU. Ad esempio, questo programma legge un indirizzo
leggere un singolo carattere gli spazi vengono letti come un qualsiasi altro carat- e visualizza il contenuto della corrispondente cella di memoria:
tere. Ad esempio, se si immettono i caratteri "x y", questo frammento di codice:
#i nel ude <stdi o. h>
scanf("%c%c%c", &a, &b, &e);
int main(void)
{
restituisce il carattere x in a, uno spazio in b e il carattere y in c. char *p;

printf("Specificare un indirizzo: ");


La lettura di stringhe scanf("%p", &p);
printf("Nell 'indirizzo %p contenuto il valore %c\n", p, *p);
. La funzione scanf() pu essere utilizzata per leggere una stringa dal canale di
input utilizzando lo specificatore di formato %s. Con %s, scanf() legge una serie return O;
di caratteri fino a incontrare uno spazio vuoto. I caratteri letti vengono inseriti
nell'array di caratteri puntato dall'argomento corrispondente e il risultato viene
completato dal carattere nullo finale. Per quanto riguarda scanf(), con spazio vuo-
to si intende il carattere di spazio, un carattere di fine riga, una tabulazione oriz- Lo specificatore %n
zontale, una tabulazione verticale o un carattere di fine pagina. A differenza di
gets(), che legge una stringa fino alla pressione del tasto INVIO (codice di line- Lo specificatore %n chiede a scanf() di assegnare il numero di caratteri letti dal
feed/carriage-retum), scanf{) legge una stringa fino all'immissione del successi- canale di input nel punto in cui si incontrato Io specificatore, alla variabile pun-
vo spazio. Questo significa che non si pu utilizzare scanf() per leggere una strin- tata dall'argomento corrispondente.
ga come "questa una prova" in quanto il primo spazio conclude il processo di
lettura. Per osservare l'effetto dello specificatore %s, si provi a utilizzare questo
L'utilizzo di un gruppo di scansione
programma immettendo una stringa composta da pi di una parola.
La funzione scanf() consente l'uso di uno specificatore di formato generico chia-
#include -<stdio.h> mato gruppo di scansione. Un gruppo di scansione definisce un gruppo di caratte-
ri. Quando scanf() elabora il gruppo di scansione, immette solo i caratteri definiti
int main(void)
dal gruppo di scansione. I caratteri letti verranno assegnati all'array di caratteri
{
char str[80];
puntato dall'argomento che corrisponde al gruppo di scansione. Si definisce un
gruppo di scansione immettendo i caratteri desiderati fra parentesi quadre. La
printf("Immettere una stringa: "); parentesi quadra aperta deve per essere preceduta dal segno di percentuale. Ad
scanf("%s", str); esempio, il gruppo di scansione seguente chiede a scanf() di leggere solo i carat-
printf("Ecco la stringa: %s", str); teri X, YeZ.

return O; %[XYZ]

- Quando si utilizza un_gruppo.di-scansione, scanf() continua a JeggemJ:.ar.att~ri _


Il programma visualizzer. solo.la prima parola immessa. e li inserisce nel corrisponden~ l'!_rr_ay di cEratteri fino a che non in'2.2~_tr.a un carat-
- - --=----- ----
216 CAPITOLO 8
OPERAZIONI DI I/O DA CONSOLE
---~--- -
tere che non appartiene al gruppo di scansione. All'uscita da scanf(), questo array
conterr una stringa chiusa dal carattere nullo e formata da tutti i caratteri letti. codice di fine pagina. In pratica, un carattere di spazio nella stringa di controllo fa
Per vedere il funzionamento di questo specificatore di formato, si provi ad esegui- in modo che scanf() legga ma non memorizzi un qualsiasi numero (anche uguale
re il seguente programma: a zero) di spazi fino al successivo car:~tere diverso da uno spazio.

#include <stdio.h>
Caratteri diversi da spazi nella stringa di controllo
int main(void)
{ Un carattere diverso da uno spazio nella stringa di controllo fa in modo che scanf()
int i; legga e elimini tutti i corrispondenti caratteri nel canale di input. Ad esempio,
char str[80], str2[80]; "%d,%d" fa in modo che scanf() legga un intero, legga e ignori una virgola e poi
legga un altro intero. Se il carattere specificato non viene trovato, scanf() ha ter-
scanf("%d%[abcdefg]%s", &i, str, str2); mine. Per leggere e ignorare il carattere di %, si deve utilizzare la stringa di con-
printf("%d %s %s", i, str, str2); trollo%%.
return O;
I parametri di scanf{) devono essere indirizzi

Si provi a immettere 123abcdtye e al termine si prema INVIO. Il programma Tutte le variabili utilizzate per ricevere valori tramite scanf() devono essere passa-
visualizzer 123 abd tye. Poich "t" non appartiene al gruppo di scansione, scanf() te utilizzando i relativi indirizzi. Questo significa che tutti gli argomenti devono
termina la lettura dei caratteri in str nel momento n cui incontra la lettera "t". I essere puntatori alle variabili utilizzate come argomenti. Si ricordi che questo
caraEteri rimanenti verranno quindi inseriti nella stringa str2. uno dei modi utilizzabili per creare una chiamata per indirizzo e che consente a
E anche possibile specificare un gruppo invertito se il primo carattere del una funzione di modificare il contenuto di un argomento. Ad esempio, per leggere
gruppo l'accento circonflesso" Il carattere""" chiede a scanf() di accettare un intero nella variabile count si deve utilizzare la seguente chiamata a scanf():
tutti i caratteri non definiti nel gruppo di scansione.
In molte implementazioni possibile definire un intervallo utilizzando un scanf("%d", &count);
trattino. Ad esempio, lo specificatore seguente chiede a scanf() di accettare tutti i
caratteri compresi fra A e Z: Le stringhe verranno lette in array di caratteri e il nome dell' array, senza alcun
indice, corrisponde all'indirizzo del primo elemento dell'array. Quindi, per leg-
%[A-Z] gere una stringa nell' array di caratteri str si dovr utilizzare l'istruzione:

Un fatto molto importante da ricordare che il gruppo di scansione fa distin- scanf("%s", str);
-zione fra lettere maiuscole e minuscole. Per eseguire la scansione di lettere sia
maiuscole che minuscole, sar necessario specificarle singolarmente. In questo caso str gi una variabile puntatore e non dovr pertanto essere
preceduta dall'operatore&.
Eliminazione degli spazi non desiderati
I modificatori di formato
Uno spazio vuoto nella stringa di controllo fa in modo che scanf() salti li.no o pi
spazi presenti nel canale di input. Lo spazio vuoto pu essere uno spazio, una Corrieprintf(), anche scanf() consente di modificare alcuni dei suoi specificatori d_i
tabulazione orizzontale, una tabulazione verticale; un -codice di fine riga o un formato. -
Gli specificatori di formato possono includere un modificatore di lunghezza
massima del campo. Si tratta di un intero che deve trovarsi fra il segno % e lo
_-'5.pecifiatore di formato il quale limita ifiiumero di caratteri leggibili per un-de _
--<-___, -- -
218 CAPITOLO 8

tenninato campo. Ad esempio, per leggere non pi di venti caratteri ed inserirli Capitolo 9
nella stringa str, si deve scrivere:
Operazioni di 1/0 da file
scanf("%20s", str);
9.1 Operazioni di I/O C e C++
Se nel canale di input sono presenti pi di venti caratteri, la chiamata succes- Stream e file
9.2
siva alla funzione inizier dal punto in cui era arrivata questa chiamata. Ad esem-
pio, se si immette la stringa 9.3 Gli stream
9.4 I file
ABCDEFGHIJKLMNOPQRSTUVWXYZ 9.5 Principi di funzionamento
del file system
come risposta alla chiamata scanf() di questo esempio, nella stringa str verranno 9.6 fread() e fWrite()
immessi solo i pruru venti caratteri, fino alla lettera "T", a causa dello specificatore 9.7 fseek() e operazioni di I/O
di ampiezza massima del campo. Questo significa che i caratteri rimanenti, ad accesso diretto
UVWXYZ, rimarranno nel canale di input. Se viene eseguita un'altra chiamata a 9.8 fprint() e fscanf()
scanf(), come ad esempio: 9.9 Gli stream standard

scanf("%s", str):

nella stringa str verranno immesse le lettere UVWXYZ. L'immissione di dati in un -uesto capitolo descrive il sistema di I/O su ~le d~
campo pu avere tennine prima della lunghezza massima del campo nel caso in tipo c. Come si detto nel Capitolo 8, il linguaggio _C++ s~pporta due d1vers1
cui si incontri uno spazio vuoto. In questo caso, scanf() si posiziona sul campo sistemi di I/O: quello ereditato dal C e quello a oggetti defi~n? dal C++. Questo
successivo. Per leggere un intero long, si deve inserire una I davanti allo specificatore capitolo si occupa del file system C (il file. system c+:. verr~ discusso nella Parte
di formato. Per leggere un intero short, si deve inserire una h davanti allo seconda). Anche se il codice sviluppato d1 recente ~t~hzza 11 ~le .~y:t~m C++, ~a
specificatore di formato. Questi modificatori possono essere utilizzati con i codi- conoscenza del file system C importante per i mot1v1 elencati all 1mz10 del capi-
ci di formato d, i, o, u e x. tolo precedente.
Normalmente, gli specificatori f, re g chiedono a scanf() di assegnare dati ad
un float. Se si inserisce una I davanti-a-uno di questi specificatori, scanf() assegne-
r i dati a ug double. Se si utilizza una L si informa scanf() che la variabile che
riceve i dati un long double. 9.1 Operazioni di I/O C e C++
Talvolta si fa un po' di confusione sulle relazioni esistenti ~ra I/O C e C++.
Soppressione dell'input Innanzitutto occorre dire che il C++ supporta l'intero sistema d1 I/O su file del C.
Pertanto la trasformazione di codice C in C++ non richiede alcuna modifica alle
Si pu chiedere a scanf() di leggere un campo e di non assegnarlo ad alcuna varia- routine di I/O del programma. In secondo luogo, il C++ definisce un prop?o siste-
bile facendo precedere al codice di formato del campo il carattere*. Ad esempio. ma di IJO a 0 aaetti che include funzioni e operatori di I/O. Il sistema d1 I/O del
data la chiamata: C++ duplica ~;mpletamente le_ funzionalit del sistema di. I/O de!~ c?~ risult.a
pertanto ridondante. In generale, anche se probabilmente s1 prefenra ut1hzzare_ d _ _
_g~n.f_C,~d%*c%d ,
11
&xs &y); sistema C++, si sar comunque liberi di scegliere il file system C. naturalmente la
mag~ p_arte dei programmatori C++ pr~feris~~ impi~gare il_ sistema di yo C++
si potr immettere la coppia di coordinate 1O,1 O. La virgola verr letta corretta- per motivi che diverranno chiari 1eg_gnao la Parte seconda d1 questa gu__!da.:. ____ _
- -mente ma non verrl_a~segnata a nulla. La soppressione dell'assegnamento par-
ticolanneiiillfikqand-6 -si deve elaborare solo una parte dei dati immessi.
OPERAZIONI DI I/O DA FILE 221
220 CAPITOLO 9

Stream binari
9.2 Stream e file
Uno stream binario. formato da una sequenza di byte con una corrispondenza
Prima di iniziare la discussione sul file system del e, importante comprendere la uno a .uno con i byte presenti sul dispositivo fisico (questo significa che non viene
differenza esistente fra i termini stream e file. Il sistema di I/O del C presenta un esegmta alcuna traduzione dei caratteri). Inoltre, il numero di byte scritti (o letti)
interfaccia consistente per il programmatore, indipendente dal dispositivo effetti- corrisponde al numero di byte presenti nel dispositivo fisico. A uno stream bina-
vamente utilizzato. Questo significa che il sistema di I/O del C fornisce un livello rio pu per essere aggiunto un numero di byte nulli definito dall'implementazione.
di astrazione che si interpone fra il programmatore e il dispositivo. Questa astra- Questi byte nulli possono essere utilizzati per allineare le informazioni in modo,
zione chiamata stream e il dispositivo effettivo chiamato file. importante ad esempio, da riempire un settore di un disco.
comprendere le interazioni che si svolgono tra stream e file .
.N.QfA:.;:.~ }::;-;: :~: I concetti di stream e file sono importanti anche per il siema
di 110 del C++ descritto nella Parte seconda. 9.4 I file

In C/C++, unfile pu corrispondere a qualsiasi cosa, da un disco a un terminale a


9.3 Gli stream una stampante. Si associa uno stream a un determinato file eseguendo un'opera-
zione di apertura. Una volta che il file aperto, sar possibile scambiare informa-
Il file system del C progettato per funzionare con un'ampia variet di dispositivi, zioni fra il file e il programma.
come terminali, unit disco e unit nastro. Anche se ogni dispositivo molto Non tutti i file hanno le stesse funzionalit. Ad esempio, un file su disco pu
diverso da un altro, il file system bufferizzato trasforma ogni dispositivo fisico in consentire operazioni di accesso diretto mentre alcune stampanti no. Questo in-
un dispositivo logico chiamato stream. Tutti gli stream si comportano in modo troduce un fatto importante relativo al sistema di I/O del C: tutti gli stream sono
analogo. Poich gli stream sono in gran parte indipendenti dai dispositivi, le stes- uguali mentre i file no.
se funzioni saranno in grado di scrivere su un file su disco ma potranno anche Se il file consente operazioni di posizionamento, l'apertura di tale file inizializza
essere utilizzate per scrivere su un altro tipo di dispositivo, come ad esempio lo anche l'indicatore di posizione nel file assegnandogli la posizione iniziale del file.
schermo. Vi sono due tipi di stream: stream di testo e binari. Mano a mano che si leggono o scrivono caratteri sul file, l'indicatore di posizione
viene incrementato, seguendo le operazioni svolte dal programma.
Per eliminare l'associazione fra un file e un determinato stream si utilizza
Stream di testo l'operazione di chiusura. Se si chiude un file aperto in output, l'eventuale conte-
nuto-dello- stream ad esso associato viene scritto sul dispositivo interno. Questo
Uno strean:. di testo formato da una sequenza di caratteri. Lo standard C consen- processo viene chiamato di svuotamento dello stream e garantisce che nessuna
te (ma non .ichiede) che uno stream di testo sia organizzato in righe concluse da informazione venga accidentalmente lasciata nel buffer del disco. Tutti i file ven-
un carattere di fine riga. Tuttavia, il carattere di fine riga opzionale sull'ultima gono chiusi automaticamente, se il programma termina normalmente, da main()
riga (in effetti, molti compilatori C/C++ non concludono gli stream di testo con ~he ritorna al sistema operativo o da una chiamata a exit(). I file non vengono
un carattere di fine riga). In uno stream di testo, possono svolgersi determinate mvece chiusi se un programma termina in modo anormale, ad esempio quando
traduzioni di caratteri richieste dall'ambiente ospite. Ad esempio, un carattere di blocca il sistema o quando viene eseguita una chiamata ad abort().
fine riga pu essere convertito in una coppia carriage-retum/line-feed. Pertanto, Ogni stream a cui associato un file ha una propria struttura di controllo di
potrebbe non esservi una relazione uno-a-uno fra i caratteri scritti (o letti) e quelli tipo FILE. Si faccia attenzione a non modificare mai questo blocco di controllo
presenti sul dispositiyo fisico. Inoltre, a causa della possibilit di traduzioni. il del file. - -
numero di caratteri effettivamente scritti (o letti) potrebbe essere diverso dal nu- Se si alle prime armi nella programmazione, la distinzione fra gli stream e i
mero di caratteri presenti nel dispositivq fisico. ---- - file pu sembrare inutile e superflua. Ma si ricordi che lo scopo principale consi-
222 CAPITOLO 9 OPERAZIONllJllTO DA FILE 223

ste nel fornire un interfaccia uniforme. Baster quindi pensare in termini di stream I file header definiscono anche numerose macro. Quelle pi importanti per gli
e utilizzare un solo file system per eseguire tutte le operazioni di I/O. Il sistema di scopi di questo capitolo sono NULL, EOF, FOPEN_MAX, SEEK_SET, SEEK_CUR
I/O convertir quindi automaticamente le semplici operazioni di input o output e SEEK_END. La macro NULL definisce un puntatore nullo. La macro EOF
tipiche di ogni dispositivo nelle operazioni di alto livello dello stream. generalmente definita uguale a -I ed il valore restituito quando una funzione di
input cerca di leggere oltre la fine del file. FOPEN_MAX definisce un valore inte-
ro che determina il numero di file che possono essere contemporaneamente aper-
ti. Le altre macro sono utilizzate con fseek(), la funzione che consente di eseguire
9.5 Principi di funzionamento del file system accessi casuali a un file.
Il file system C formato da numerose funzioni correlate. Le funzioni pi comu-
nemente utilizzate sono elencate nella Tabella 9.1. Queste funzioni richiedono Il puntatore del file
l'inclusione nel programma del file header stdio.h. I programmi C++ possono
utilizzare il file header <cstdio>. I file header stdio.h e <cstdio> contengono i Il puntatore del file il filo conduttore che unifica il sistema di I/O C. Un puntatore
prototipi delle funzioni di I/O e definiscono tre tipi: size_t, fpos_t e FILE. Il tipo a file un puntatore a una struttura di tipo FILE. Esso punta a informazioni che
size_t in genere un intero unsigned, come fpos_t. Il tipo FILE verr discusso definiscono vari fattori relativi al file, incluso il nome, lo stato e la posizione
nella prossima sezione del capitolo. corrente del file. In pratica, il puntatore a file identifica un determinato file del
disco e viene utilizzato dallo stream ad esso associato per dirigere il funziona-
Tabella 9.1 Le funzioni pi utilizzate per il file system di tipo ANSI. mento delle funzioni di I/O. Per leggere o scrivere i file, il programma deve utiliz-
zare il puntatore a file. Per ottenere una variabile di tipo puntatore a file, si utilizza
NOME FUNZIONE
un'istruzione .simile alla seguente:
fopen () Apre un file

fclose{) Chiude un file FJLE *fp;


putc () Scrive un carattere su un file

fputc () Come putc() Apertura di un file


getc () Legge un carattere da un file
La funzione fopen() apre uno stream e vi collega un file; quindi restituisce il
fgetc () Come getc ()
puntatore associato a tale file. La maggior parte delle volte (e per la parte rima-
fgets () Legge una stringa da un file nente di questa discussione) il file si trova su disco. La funzione fopen() ha il
fputs {) Scrive una stringa su un file seguente prototipo: - - - -- --- -
fseek () Si posiziona su un determinato byte di un file
FILE *fopen(const char *nomefile, const char *modalit);
ftell () Restituisce la posizione del file

fpri ntf{) Esegue su un file ci che printf() esegue sulla console dove nomefile un puntatore a una stringa l.i caratteri che contiene un nome
valido per un file e pu includere l'indicazione di un percorso di directory. La
fscanf () Esegue su un file ci che scanf() esegue sulla console
stringa puntata da modalit determina il modo in cui il file verr aperto. La Tabel-
feof() Restituisce il valore vero quando viene raggiunta la fine del file la 9.2 mostra i valori consentiti per modalit. Le stringhe come "r+b" possono
ferror() Restituisce il valore vero se si verifica un errore essere anche rappresentati come "rb+".
rewi nd () Riporta l'indicatore di posizione'del file all'inizio del file
Come,si detto, la funzione fopen() rest-ituisce un puntatore a file. Il program- -
ma non dovr mai modificare il valore di questo puntatore. Se si verifica un errore
remove () Cancella un file
quando si cerca di aprire il file, fopen() restituir un puntatore nullo.
ffl ush () Scarica sul file il contenuto del bufter in memoria

- ---- -------
- - - 224--~-t-TO-LO
OPERAZIONI DI I/O DA FILE 225

Tabella 9.2 Valori consentiti per modalit.


Anche se la maggior parte delle modalit di apertura dei file autoesplicativa,
MODALIT SIGNIFICATO
opportuno commentare un attimo l'argomento. Se, quando si apre un file per
Apre un file di testo in lettura operazioni di sola lettura, il file non esiste, la funzione fopen() non ha successo.
Crea un file di testo in scrittura
Quando si apre un file iri modalit append, se il file non esiste, verr creato. Inol-
tre, quando un file viene aperto in modalit append, tutti i nuovi dati avverranno
Apre un file di testo in modalit append (aggiunta di dati) scritti alla fine del file. Il contenuto originale del file non verr modificato. Se,
rb Apre un file binario in lettura quando un file viene aperto in scrittura, il file non esiste, verr creato. Se esiste, il
contenuto del file verr distrutto e sostituito dai nuovi dati che vi verranno scritti.
wb Crea un file binario in scrittura
La differenza fra le modalit r+ e w+ il fatto che r+ non crea un file che non
ab Apre un file binario in modalit append (aggiunta di dati) esiste. Inoltre, se il file esiste, con w+ ne viene distrutto il contenuto, al contrario
di quanto avviene con r+.
r+ Apre un file di testo in lettura/scrittura
Come si pu vedere nella Tabella 9.2, un file pu essere aperto in modalit
w+ Crea un file di testo in lettura/scrittura testo o in modalit binaria. Nella maggior parte delle implementazioni, in moda-
a+ Crea o apre in modalit append (aggiunta di dati) un file di testo per operazioni di lettura/scrittura
lit testo le sequenze Carriage retum I Line feed vengono tradotte in un carattere
Newline. In output accade il contrario: i caratteri Newline vengono tradotti in
r+b Apre un file binario in lettura/scrittura Carriage retum I Line feed. Sui file binari tale traduzione non avviene.
w+b Crea un file binario In lettJra/scrittura Il numero di file che possono essere aperti contemporaneamente specificato
dalla macro FOPEN_MAX. Questo valore normalmente uguale almeno a 8 ma a
a+b Crea o apre in modalit append (aggiunta) un file binario per operazioni di lettura/scrittura
tale proposito sempre bene consultare il manuale del compilatore.

Il codice seguente utilizza fopen() per aprire in output un file chiamato TEST. Chiusura di un file

La funzione fclose() chiude uno stream che era stato precedentemente aperto con
FILE *fp;
una chiamata a fopen(). La funzione scrive sul file i dati eventualmente rimasti nel
buffer del disco e quindi esegue una richiesta al sistema operativo di chiusura del
fp = fopen("test", "w"); file. La mancata chiusura di uno stream pu provocare ogni genere di problemi,
inclusi la perdita di dati, la distruzione di file ed eventuali errori intermittenti nel
Anche se tecnicamente corretto, il codice precedente viene normalmente programma. La chiamata a fclose() libera anche il blocco di controllo del file
scritto in questo modo: associato allo stream, rendendolo nuovamente disponibile. Nella maggior parte
dei casi, vi un limite determinato dal sistema operativo al numero di file apribili
FILE *fp; contemporaneamente, e quindi si potrebbe essere costretti a chiudere un file pri-
ma di aprirne un altro. Il prototipo della funzione fclose() il seguente:
if ((fp = fopen("test","w"))==NULL) {
printf("Impossibile aprire il file. \n"); int fclose(FILE *pf);
exit(l);
dove pf il puntatore a file restituito dalla chiamata a fopen(). Se il valore restitu-
ito uguale a zero, significa che la chiusura del file avvenuta c:_on successo. In
Questo metodo ha il vantaggio di rilevare eventuali ~;;.ori di apertura di un caso di errore, la funzione restituisce il valore EOF. Per determinare eventuali
file, ad esempio in caso di disco protetto in scrittura o di disco pieno, prima che il problemi, si pu utilizzare la funzione standard ferrar() discussa fra breve. Gene-
programma tenti di scrivervi. In generale, prima di cercare di utilizzare il file ralmente, fclose(} entrer in errore solo quando un disco viene prematuramente
---oc-corre assicurarsi che la chiamata a fopen() sia stata eseguita con successo. estratto dal drive o quando-non v1 e pm spazio sul disco. ----
-- -- - --- --
- - -- -~ ___ ;': __
I.
226 CAPITOLO OPERAZIONI DI I/O DA FILE 227

Scrittura di un carattere Tuttavia, getc() restituisce EOF anche quando si verifica un errore. Per deter-
minare con precisione ci che avvenuto, si pu utilizzare ferror().
Il sistema di I/O C definisce due funzioni equivalenti che scrivono un carattere:
putc() e fputc(). In realt, putc() normalmente implementata come macro. Vi
sono due funzioni identiche solamente per conservare la compatibilit con le ver- Uso di fopen(), getc(), putc() e fclose()
sioni precedenti del C. In questa guida si utilizza putc() ma, se si preferisce, si pu
usare fputc(). Le funzioni fopen(), getc(), putc() e fclose() formano il gruppo minimo di routine
La funzione putc() scrive caratteri su un file precedentemente aperto in scrit- per le operazioni di 110 su file. li programma seguente, KTOD, un semplice
tura utilizzando la funzione fopen(). Il prototipo di questa funzione : esempio d'uso dlle funzioni putc(), fopen() e fclose(). Il programma legge sem-
plicemente caratteri dalla tastiera e li scrive su un file finch l'utente non immette
int putc(int car, FILE *pf); il segno di dollaro. Il nome del file deve essere specificato nella riga di comando.
Ad esempio, se si chiama questo programma KTOD, scrivendo KTOD TEST sar
dove p/ il puntatore a file restituito da fopen() e car il carattere che deve essere possibile immettere righe di testo in un file chiamato TEST.
scritto sul file. Il puntatore a file dice a putc() su quale file si deve scrivere il
carattere. Per motivi storici, la variabile car definita come int ma di essa verr /* KTOO: Un programma di scrittura su fil e */
#include <stdio.h>
scritto solo il byte di ordine inferiore. -
#i nel ude <stdl i b. h>
Se un'operazione putc() ha successo, restituir il carattere scritto. In caso contra-
rio, restituir EOF.
int main(int arge, ehar *argv(])
{
FILE *fp;
lettura di un carattere ehar eh;

Vi sono due funzioni equivalenti anche per la lettura di un carattere: getc() e fgetc(). if(arge!=2)
Sono definite entrambe per garantire la compatibilit con le versioni meno recenti printf("Immettere il nome del file.\n");
di C. In questa guida si utilizza getc() (che in effetti implementata come macro) exit (1);
ma, se si preferisce, si pu usare fgetc().
La funzione getc() legge caratteri da un file aperto in modalit di lettura tra-
mite fopenQ. Il p~ototi~~-~~_getc{) il seguente: if((fp=fopen(argv(l], "w"))==NULL) {
pri ntf (" Impossibile apri re il fil e. \n ") ;
int putc"{int car, FILE *pf); exit(l);

dove pf un puntatore a file di tipo FILE restituito da fopen(). Per motivi storici,
do
getc{) restituisce un intero, ma il carattere contenuto nel byte di ordine inferiore. eh = getchar();
Se non si verifica un errore.il byte di ordine superiore sar sempre uguale a zero. putc(ch, fp);
Quando viene raggiunta la fine del file, la funzione getc() restituisce EOF. ) while (eh!='$');
Pertanto, per leggere dalla fine di un file di testo si pu utilizzare il codice seguen-
te: fclose(fp);

do { return O;
eh = getc(fp);
) while(ch!=EOF);
-=-OPERA ZIO N 1---0 I I I O DA FIL-E-- 229
228 CAPITOLO 9
pensare di aver raggiunto la condizione di fine file anche quando non viene rag-
Il programma complementare DTOS legge un file di testo e ne visualizza il giunta la fine fisica del file. In secondo luogo, getc() restituisce EOF anche quan-
contenuto sullo schermo. do fallisce l'operazione di lettura (oltre che alla fine del file). Utilizzando il solo
valore restituito da getc() impossibile capire cosa capitato. Per risolvere que-
/* DTOS: Legge il cori't:enuto d-i un file e lo visualizza sullo schermo. */ sto problema, il e include la funzione feof(), che determina il raggiungimento
#include <stdio.h> della fine del file. Il prototipo della funzione feof() il seguente:
lii nel ude <stdl ib.h>
int feof(FILE *pf);
int main(int arge, char *argv[J)
{ FILE *fp;
char eh;
La funzione feof() restituisce il valore logico vero quando viene raggiunta la
fine del file e zero in tutti gli altri casi. Pertanto, la seguente routine legge dati da
if(argc!=2) { - - un file binario finch non viene raggiunta la fine del file:
printf("lmmettere il nome del file. \n");
exit(l); while(!feof(fp)) eh= getc(fp);

Naturalmente, si pu applicare questo metodo anche ai file di testo oltre che ai


if ((fp=fopen(argv [1], "r")) ==NULL) { file binari. ---
printf("lmpossibile aprire il file. \n");
Il seguente programma, che copia file di testo binari, contiene un esempio
exil(l);
d'uso di feof(). I file vengono aperti in modalit binaria e feof() controlla quando
viene raggiunta la fine del file.
eh = getc ( fp); /* 1egge un carattere *I
/* Copia un file. */
whi le (eh! =EOF) llinclude <stdio.h>
putchar(ch); /* lo visualizza*/ lii nel ude <stdl i b. h>
eh = getc(fp);
int main(int argc, char *argv[])
{
felose(fp); FILE *in, *out;
char eh;
return O;
if(arge!=3)
printf("lmmettere il nome del file. \n");
exit(l);
Si provi a usare questi due programmi. Prima si pu usare KTOD per creare
un file di testo e quindi si pu leggere il contenuto del file utilizzando DTOS.
if((in=fopen(argv[l], "rb"))==NULL) {
printf("lmpossibile aprire il file di origine. \n");
Uso di feof() exit(l);
Come si detto, quando si raggiunge la fine del file, getc() restituisce EOF. Tutta-
0
if((out=fopen(argv[2], "wb")) == NULL)
via, la verifica del valore restituito da getc() pu non essere il modo migliore per prin:t_((.".Jmpossibile aprire il file di destinazione. \n");
detenninare se si arrivati alla fine del file. Innanzitutto il sistema di I/O su file exit(l);
opera su file binarle di testo...Quando il file viene ap~!:!_Q_per operazioni di i~put_
binario, pu essere letto 1111 v_ajorejntero uguale al codice E~_f_.__Questo puo far
230 CAPITOLO 9
OPERAZIONI DI 1/0 DA FILE 231

/*Questa parte del codice copia il file. */ _


wbfle(lfeof(in)} {
eh" getc(in); char str[BO];
lf(!feof(in)) putc(ch, out); FILE *fp;

if((fp " fopen("TEST", "w"))""NULL) {


fclcse(in); printf("Impossibile aprire il file. \n");
fc:ose(out); exit(l);

return O;
do {
pri ntf ("Immettere una stringa (INVIO per usci re): \n");
gets(str);
strcat(str, 11 \n"); /*aggiunge un codice di fine riga*/
Le stringhe: fputs{) e fgets()
fputs(str, fp);
Oltre a getc() e putc(), il sistema di VO C dotato di due funzioni correlate, fgets() whil e(*strl"' \n');
e fputs(), che leggono e scrivono stringhe di caratteri da un file su disco. Queste
return O;
funzioni operano in modo analogo a putc() e getc() ma invece di leggere o scrive-
re un singolo carattere, operano su intere stringhe. I prototipi di queste funzioni
sono:
rewind()
int fputs( const char *str, FILE *pf);
char *fgets(char *str, int lunghezza, FILE *pf); La funzione rewind() riporta l'indicatore di posizione del file all'inizio del file
specificato come argomento. In pratica "riavvolge" il file. II suo prototipo :
La funzione fputs() scrive sullo stream specificato la stringa puntata da str e in
caso di errore restituisce il valore EOF. void rewind(FILE *pf);
La funzione fgets() legge una stringa dallo stream specificato fino al
raggiungimento di un carattere di fine riga o fino alla lettura di lunghezza-I carat- dove pf un puntatore a file valido.
teri. Se viene letto un codice di fine riga, questo entrer a far parte della stringa (a Per vedere un esempio di rewind(}, si pu modificare iLprogramma della se-
differenza di quanto avviene con la funzione gets()). La stringa risultante verr zione precedente in modo che visualizzi il contenuto del file appena crea~o. ~e~
conclusa-con un carattere nullo. La funzione restituisce str se ha successo o un ottenere ci, il programma deve riavvolgere il file al termine delle operaz1om di
puntatore nullo in caso di errore. input e quindi utilizza fgets() per rileggere il file. Si noti che ora il file deve e_ssere
Il programma seguente dimostra l'uso di fputs(). Il programma legge stringhe aperto in modalit di lettura e scrittura utilizzando "W+" come parametro d1 mo-
dalla tastiera e le scrive sul file TEST. Per uscire dal programma basta immettere dalit.
una riga vuota. Poich gets() non memorizza il carattere di fine riga, il program-
ma ne aggiunge manualmente uno prima della scrittura della stringa sul file, in #include <stdio.h>
modo che il file stesso possa essere letto con pi facilit. #include <stdl ib.h>
#include <string.h>
1foclude <stdio.h>
#include <stdl ib.h> int main(void)
#include <string.h;> {
----'1f"'i~ ___char str[BO];
int main(void) FILE *fp;
-- - ---- -
-~,...,, ..
232 CAPITO"lO 9
#i nel ude <stdio.h>
i f ( ( fp = fopen ("TEST", "w+")) ==NULL) { #include <stdl ib.h>
printf("Impossibile aprire il file. \n");
exit(l); #define TAB_SIZE 8
#define IN O
#defi ne OUT 1
do
printf("Immettere una stringa (INVI9 per uscire):\n"); void err(int e);
gets(str);
strcat(str, "\n"); /*aggiunge un codice di fine riga*/ int main(int argc, char *argv[])
fputs (str, fp);
whil e(*str! =' \n'); FILE *in, *out;
int tab, i;
/* ora, legge e visualizza il file */ char eh;
rewind(fp); /*riporta l'indicatore di posizione all'inizio del file.*/
while(!feof(fp)) { if(argc!=3)
fgets(str, 79, fp); printf("uso: detab <in> <out>\n");
printf(str); exit(l);

return O; if((in = fopen(argv[l]. "rb")}==NULL) {


printf("Impossibile aprire %s.\n", argv[l]);
exit(l);
}
ferror() if((out = fopen(argv[2]. "wb") )==NULL) {
printf("Impossibile aprire %s.\n", argv[l]);
La funzione ferror() determina se un'operazione svolta su un file ha prodotto un exit{l);
errore. Il prototipo della funzione ferror() il seguente:

int ferror(FILE *pf); tab = O;


do {
-ch-=-getc (in) ;
dove pf un puntatore a file valido. Nel caso in cui si sia verificato un errore if(ferror(in)) err{IN);
durante l'ultima operazione sul file, la funzione restituisce il valore vero; in caso
contrario, restituisce il valore logico falso. Poich lo stato di errore viene impo- seri ve un numero di spazi appropri a-
/* Se viene trovata una tabulazione,
stato da ogni operazione su file, necessario richiamare ferrar() subito dopo ogni to */
operazione svolta su file; in caso contrario, la condizione d'errore verr persa. if{ch==' \t I) {
Il seguente programma illustra l'uso di ferrar() eliminando le tabulazioni da for{i=tab; i<8; i++) {
un file di testo e sostituendole con un numero appropriato di spazi. Le dimensioni putc(' ', out);
delle tabulazioni sono definite da TAB_SIZE. Si noti come ferror() venga richia- if(ferror(out)) err(OUT);
mata dopo ogni operazione svolta sui file. Per utilizzare il programma, si devono
- tab = O;
specificare i nomi dei file di input e di output sulla riga di comando.
}
else {
/* Il programma sostituisce alle tabulazioni una serie di spazi putc (eh, out) ;
- - - - - - i n un file di testo.e contl'.'.olJ.a.....il.ver.ificarsi di errori. */ if(ferror(out)) err(OUT);
234 CAPITOLO

tab++; printf("Devo cancellare %s? (S/N): ", argv[l]);


if(tab==TAB_SIZE) tab = O; gets (str);
if(ch=='\n' Il ch=='\r') tab =O;
}
if(toupper(*str) ==' S')
} while(!feof(in)); if(remove(argv[l])) {
fclose(in); printf("Impossibile cancellare il file.\n");
fclose(out); exit(l);

return O; return O; /* ritorna con successo al sistema operativo */

void err(int e)
{
Svuotamento di uno stream
if(e==IN) printf("Errore di input. \n");
else printf("Errore di output.\n"); Per vuotare il contenuto di uno stream di output si utilizza la funzione fflush() il
exit(l);
cui prototipo il seguente:

int fflush(FILE *pf);


Cancellazione di file
Questa funzione scrive il contenuto di un buffer nel file associato a pf Se si
La funzione rernove() cancella il file specificato. Il su~ prototipo : richiama fflush() con pf nullo, verranno svuotati i buffer di tutti i file.
Se viene eseguita con successo, la funzione fflush() restituisce O; in caso con-
int remove(const char *nomefile); trario restituisce EOF.

Quando viene eseguita con successo, la funzione restituisce zero. In caso con-
trario restituisce un valore diverso da zero.
9.6 fread() e fwrite()
Il programma seguente cancella il file specificato nella riga di comando dan-
do la p~ssibilit di annullare l'operazione prima di eseguirla. Un programma di Per leggere e scrivere tipi di dati pi lunghi di un byte, il file system del C ANSI
questo tipo pu essere utile per gli utenti alle prime-armi. -- ---- fornisce le due funzioni fread() e fwrite(). Queste funzioni consentono di leggere e
scrivere blocchi di dati di qualsiasi dimensione. I loro prototipi sono i seguenti:
/* Doppia verifica prima della cancellazione. */
#include <stdio.h>
#include <stdlib.h> size_t fread(void *buffer, size_t num_byte, size_t numero, FILE *fp);
#include <ctype.h> size_t fwrite(const void *buffer, size_t num_byte, size_t numero, FILE *fp);

int main(int argc, char *argv[]) Per fread(), buffer un puntatore a una regione di memoria che ricever i dati
{ letti dal file. Per fwrite(), buffer un puntatore a una regione di memoria che
char s tr [80] ; conserva i dati da scrivere sul file. Il valore di num determina il numero di oggetti
if(argc!=2) { letti o scrittj, ognuno dei quali ha una lunghezza pari a num_byte byte (si ricordi
printf("uso: xerase <nomefile>\n"); che il tipo size_t definito come un intero unsigned). Infine, pf .un puntatore a
exit(l); -----
file corrispondente a uno stream precedentemente aperto.
La funzione fread() restituisce il numero di oggetti letti. Questo valore pu
essere minore di-fwmero quando viene raggiunta la fine del file o_quando si veri-
236 CA P 1-T O LO 9
OPERAZIONI DI 1/0 DA FILE 237

fica un errore. La funzione fwrite() restituisce il numero di oggetti scritti. Questo


valore sempre uguale a numero sempre che non si verifichi un errore. Come si pu vedere in questo programma, il buffer pu essere (e spesso )
semplicemente la memoria utilizzata per contenere una variabile. In questo sem-
plice programma, i valori restituiti da fread() e fwrite() vengono ignorati. Nel-
Uso di fread() e fwrite() l'utilizzo pratico invece questi valori devono essere sempre controllati per evitare
errori.
Se un file stato aperto per operazioni su dati binari, fread() e fwrite() possono Una delle applicazioni pi utili di fread() e fwrite() riguarda la lettura e la
leggere e scrivere ogni tipo di informazioni. Ad esempio, il seguente programma scrittura di tipi di dati definiti dall'utente, specialmente strutture. Ad esempio,
scrive su un file e poi rilegge un double, un int e un long. Si noti l'uso di sizeof per data la seguente struttura:
determinare la lunghezza di ogni tipo di dati.
struct struct_type
/*Scrive e poi rilegge una serie di valori diversi da caratteri. */ float balance;
#include <stdio.h> char name[SO];
#include <stdlib.h> cust;

int main(void}
la seguente istruzione scrive il contenuto di cust sul file puntato da fp.
{
FILE *fp;
double d = 12.23; fwrite(&cust, sizeof(struct struc_type), 1, fp;
int r= 101;
1ong 1 = 123023L;

if( (fp=fopen("test", "wb+") )==NULL) { 9.7 fseek() e operazioni di I/O ad accesso diretto
printf("Impossibile aprire il file.\n");
exit(l); Per eseguire operazioni di lettura e scrittura diretta con il sistema di VO C, si
utilizza la funzione fseek() che imposta la posizione dell'indicatore di file. Il suo
prototipo il seguente:
fwrite(&d, sizeof(double), 1, fp);
fwrite(&i, sizeof(int), 1, fp); int fseek(FILE *pf, long num_byte, int origine);
____ f_"!_~~~_e(&l, sizeof(long), 1, fp);
Qui, pf un puntatore a file restituito da una chiamata a fopen(). 1ium..:__oy1e it ----
rewind(fp);
numero di byte a partire da origine in cui si intende portare l'indicatore di posi-
zione del file mentre origine pu essere una delle seguenti macro:
fread(&d, sizeof(double), 1, fp);
fread(&i, sizeof(int), 1, fp);
fread(&l, sizeof(long), 1, fp); ORIGINE NOME MACRO
Inizio del file SEEK_SET
printf("%f %d %ld", d, i, l);
Posizione corrente SEEK_CUR
fclose(fp);
Fine del file SEEK_END
return O;
Pertanto, per posizionare l'indicatore di poSl.zione del file a num_byte rispetto
all'in~~- ~el f!le, si dovr utilizzare come origine SEEK_SET. Pr eseguire il
__ posizionamento sulla base della posizione attuale nel file. si dovr utilizzare
238 :APITOLO

SEEK_CUR e per eseguire il posizionamento sulla base della fi~e del file, si dovr Per determinare la posizione corrente all'interno di un file si usa la funzione
usare SEEK_END. La funzione fseek() restituisce O quando viene eseguita con Il suo prototipo :
ftell().
successo e un valore diverso da zero in caso di errore.
Il seguente programma illustra l'uso di fseek(). Il frammento si posiziona su long ftell(FILE *fp)
un de~~n~to by~e di un file e ne visualizza il contenuto. Il nome del file e il byte
su cui pos1Zlonars1 devono essere indicati nella riga di comando. Questa funzione restituisce la posizione corrente all'interno del file associato
a fp. In caso di errore viene restituito il valore -1.
#include <stdio.h> In generale, l'accesso diretto dovrebbe essere utilizzato solo su file binari. Il
#include <stdlib.h> motivo di ci semplice. Poich ai file di testo pu essere applicata una traduzio-
ne dei caratteri, potrebbe non esservi una corrispondenza diretta fra il contenuto
int main(int argc, char *argv[J) del file e il byte in cui viene eseguito il posizionamento. L'unico caso in cui si
{
dovrebbe usare fseek() con un file di testo si verifica quando ci si deve portare su
FILE ~fp;
una porzione precedentemente determinata da ftell(), utilizzando come origine
SEEK_SET.
if{argc!=3)
printf("Uso: SEEK nomefile byte\n");
Un ultimo elemento importante: anche un file che contiene testo pu essere
exit{l); aperto come un file binario. Non esistono divieti all'uso di operazioni accesso
diretto su file contenenti testo. Questa restrizione si applica solo ai file di testo
aperti come file di testo.
if{(fp = fopen(argv[l], "r"))==NULL) {
printf("Impossibile aprire il file. \n");
exit(l);
9.8 fprint() e fscanf()
if(fseek(fp, atol{argv[2]), SEEK SET)) { Oltre alle funzioni di I/O di base discusse precedentemente, il sistema di I/O ANSI
printf("Errore di posizionamento. \n"); include le funzioni fprint() e fscanf(). Queste funzioni si comportano esattamente
exit(l); come printf() e scanf() tranne per il fatto che operano su file. I prototipi di fprint()
e fscanf() sono i seguenti:

printf("Il byteaTFfriffrizzo %ld contiene %c.\n", atol(argv[2]), int fprintf(FILE *pf, const char *stringa_controllo, . ..);
getc ( fp) );_
fclose(fp);
int fscanf(FILE *pf, const char *stringa_controllo, . .. );

return O; dove pf un puntatore a file restituito da una chiamata a fopen(). Le funzioni


fprint()e fscanf() operano sul file puntato da pf
Come esempio viene presentato il seguente programma che legge dalla tastie-
~i ~u usare fseek() per posizionarsi a un multiplo di qualsiasi tipo di dati ra una stringa e un intero e li scrive sul file TEST. Il programma quindi legge il file
molt1phcando semplicemente le dimensioni dei dati per il numero di oggetti da e ne visualizza il contenuto sullo schermo. Dopo aver eseguito questo program-
saltarr Ad esempio, si immagini di avere un file contenente un elenco di indirizzi ma, si provi ad esaminare il contenuto del file TEST. Come si pu vedere, contie-
costituito da strutture di tipo list_type. Il seguente frammento di codice si posizio- ne testo leggibile.
ner sul decimo indirizzo.
/*esempio d'uso di fscanf() e fprintf() */
#include ~tdio.h>
#include <io.h>
240 CA P t.T O LO 9 0 PER-A Z I 0 N I U I 11 y_ Li" r , ~e -""..:C........_

#include <stdlib.h> fanno riferimento alla console, ma possono essere rediretti dal sistema operativo
in modo da connettersi ad altri dispositivi. La redirezione delle operazioni di 110
int main(void) gestita, per fare qualche esempio, dai sistemi operativi Windows, DOS, UNIX e
{
OS/2.
FILE *fp; Poich gli stream standard sono puntatori a file, essi possono essere utilizzati
char s[SO];
dal sistema di I/O C che esegue operazioni di I/O su console. Ad esempio, putchar()
int t;
pu ess~re definita nel seguente modo:
if((fp=fopen("test", "w")) == NULL) {
printf("Impossibile aprire il file. \n"); int putchar(char c)
exit(l); {
return putc(c, stdout);

printf("Immettere una stringa e un numero: ");


fscanf(stdin, "%s%d", s, &t); /*lettura dalla tastiera */ In generale, lo stream stdin viene utilizzato per leggere dalla console, mentre
stdout e stderr sono utilizzati per scrivere sulla console.
fprintf(fp, "%s %d", s, t); f* scrittura sul file*/ possibile utilizzare stdin, stdout e stderr come puntatori a file in qualsiasi
fclose(fp); funzione che utilizzi una variabile di tipo FILE *.Ad esempio, possibile usare
fgets() per leggere una stringa dalla console utilizzando una chiamata come la
if{ {fp=fopen{"test", "r")) == NULL) { seguente:
printf{"Impossibile aprire il file. \n");
exit{l);
char str[255];
fgets(str, 89, stdin);
fscanf(fp, "%s%d", s, &t); f* lettura dal file*/
fprintf(stdout, "%s %d", s, t); /*visualizzazione*/ Infatti pu essere molto utile usare fgets() in questo modo. Come si detto in
precedenza in questo capitolo, con gets() possibile fuoriuscire dall'array utiliz-
return O; zato per ricevere i caratteri immessi dall'utente in quanto la funzione gets() non
offre alcuna verifica del superamento dei limiti dell'array. Quando viene usata
con stdin, la funzione fgets() rappresenta un 'utile alternativa in quanto consente di
specificare il numero di caratteri da leggere e pertanto evita il problema appena
J.iQTJ'":~~.;,:;:p~-'] Anche se fprint() e fscanf() sono normalmente il modo pi faci- descritto. L'unico problema il fatto che fgets{t (a differenza di gets()) non ri-
le per scrivere e leggere dati di vario genere, normalmente non sono il modo pi muove il carattere di fine riga; pertanto sar necessario rimuovere manualmente
efficiente di operare. Poich i dati vengono scritti cos come appaiono sullo schenno tale carattere, come illustrato nel seguente programma.
(e non in binario), og11i chiamata alle funzioni richiede un sovraccarico di tempo.
Quindi, se si interessati alla velocit e alle dimensioni dei file prodotti, si dovr #include <stdio.h>
in genere preferire l'uso di fread() e fwrite(). #include <string.h>

int main(void)
{
9.9 Gli stream standard char str[SO];
int i;
Quando inizia )'esecuzione di un programma C, vengono automaticamente aperti
tre stream. I loro no~Hsono stdin (str~am di input standard), stdout (stream di printf("Immettere una stringa: ");
output standard) e stderr (st~~-~i err~re standard). Normlment~, quest1stream fgets(str, 10, stdin);
CAPITOLO 9 OPERAZIONI DI 110 DA FILE 243

/*eliminare, se presente, il carattere newline */ Si supponga che questo programma si chiami TEST. Se yiene eseguito nor-
i = strlen(str)-1; malmente, il programma visualizzer il messaggio sullo schermo, legger la stringa
if(str[i]==") str[i] = '\O'; dalla tastiera e visualizzer tale stringa sullo. schermo. Tuttavia, in un ambiente
che consente la redirezione delle operazioni di 1/0, possibile redirigere sia stdin
printf("Questa la stringa immessa: %s", str); che stdout che entrambi su un file. Ad esempio, in ambiente DOS o Windows, si
pu eseguire TEST nel modo seguente:
return O;

TEST > OUTPUT

stdin, stdout e sterr non sono variabili nel senso comune del termine e non pos- in questo modo, l'output di TEST verr scritto sul file OUTPUT. Se invece si
sibile assegnare loro un valore utilizzando fopen(). Inoltre, poich questi puntatori esegue TEST in questo modo:
a file vengono creati automaticamente all'inizio del programma, vengono chiusi
automaticamente al suo termine e non si.dovr quindi cercare di chiuderli. TEST < INPUT > OUTPUT

si redirige stdin da un file chiamato INPUT e si invia l'output su un file chiamato


Collegamenti per operazioni di I/O da console OUTPUT.

Nel Capitolo 8 si detto che il C/C++ fa poche distinzioni fra I/O da console e Il NOTA Al tennine del programma C, gli stream rediretti vengono ri-
O da file. Le funzioni di I/O da console descritte nel Capitolo 8 dirigono le proprie portati al loro stato originario.
operazioni di I/O sugli stream stdin o stdout. In pratica, le funzioni di I/O da
console sono solamente versioni speciali delle corrispondenti funzioni che opera-
no sui file. Si tratta di funzioni diverse solo per comodit del programmatore. Uso di freopen() per redirigere gli stream standard
Come si detto nella sezione precedente, si possono eseguire operazioni di Il
Per redirigere gli stream standard si pu utilizzare la funzione freopen(). Questa
O da console utilizzando una qualsiasi delle funzioni del file system. Tuttavia,
funzione associa uno stream esistente a un nuovo file. Pertanto la si pu utilizzare
potr sorprendere che possibile anche eseguire operazioni di I/O su disco utiliz- anche per associare uno stream standard a un altro file. Il suo prototipo :
zando le funzioni per_ la console come ad esempio printf() ! Infatti tutte le funzioni
di I/O da console operano sugli stream stdin e stdout. In ambienti che consentono
la redirezione.delle operazioni di 1/0, questo significa che stdin e stdout possono FILE *freopen(const char *nomefile,
const char *modalit, FILE *stream);
far riferimento a un dispositivo diverso dalla tastiera e dallo schermo. Ad esem-
pio, si consideri il seguente programma:
dove nomefile un puntatore a un file da associare allo stream puntato da stream.
#include <stdio.h>
Il file viene aperto utilizzando il valore di modalit che co1Tisponde ai valori uti-
lizzati con fopen(). In caso di successo, freopen() restituisce stream e in caso di
int main(void) insuccesso restituisce NULL.
{ Il seguente programma utilizza freopen() per redirigere lo stream stdout sul
char str[80]; file OUTPUT:

printf("Immettere una stringa: "); #include <sfaio.h>


gets(str);
printf(str); int main(void)
{
retufh.O; ------ char str[BO];
__ _
_}
244 CAPITOLO

freopen ("OUTPUT", "w", s tdout) ;


' Capitolo 1O
printf("Immettere una stringa: ");
gets(str); Il preprocessore
pri ntf(str); : e i commenti
return O;
10.1 Il preprocessore
10.2 La direttiva #define
In generale, la redirezione degli stream standard utilizzando treopen() utile 10.3 La direttiva #error
in casi particolari, come ad esempio per il debugging. t.:uso di operazioni di I/O su
10.4 La direttiva #Include
disco con gli stream stdin e stdout rediretti non cosi efficiente come l'uso delle
funzioni fread() e fwrite() .. 10.5 Le direttive per compilazioni
condizionali
10.6 La direttiva #undef
10.7 Uso di defined
10.8 La direttiva #line
10.9 La direttiva #pragma
10.10 Gli operatori del preprocessore# e##
10.11 Le macro predefinite
10.12 I commenti

el codice sorgente di un programma C/C++ possi-


bile includere una serie di istruzioni per il compilatore. Queste istruzioni sono
chiamate direttive per il preprocessore e, anche se non fanno parte del linguaggio
Co C++, ne espandono notevolmente le possibilit. Oltre a trattare tali direttive,
questo capitolo si occuper anche dei commenti.

10.1 11 preprocessore
Prima di iniziare importante riportare il preprocessore alla sua prospettiva stori-
ca. Per quanto riguarda il linguaggio C++, il preprocessore si pu considerare in
larga misura un retaggio derivante dal C. Inoltre il preprocessore C++ pratica-
mente identico a quello definito dal C. La differenza principale fra C e C++
l'affidamento che il linguaggio fa sul preprocessore. In C ogni direttiva del---
preprocessore necessaria. In C++ alcune funzionalit sono state rese ridondanti
-grazie-a11uovi elementi introdott! nel linguaggio. In realt, uno degli obiettivi a
- lungo termine del linguaggio C+--Ja-completa eliminazione del preproessore.-
246 CAPITOLO 10 ___________! L_P_R_E...;;e..B;....._o_c_E_s_s_o_R_E_E_l_C_O_M_M_E_N_T_l__2_47 - - - - - -

Ma per il momento e anche nel prossimo futuro il preprocessore continuer ad Dopo la definizione del nome della macro, essa pu essere utilizzata anche
essere ampiamente utilizzato. all'interno delle definizioni di altre macro. Ad esempio, le tre righe seguenti defi-
Il preprocessore e accetta le seguenti direttive: niscono i valori di UNO, DUE e TRE:

#define #elif #else #endif #define UNO


#error #if #ifdef #ifndef #defi ne DUE UNO+UNO
#include #line #pragma #undef #defi ne TRE UNO+DUE

Come si pu vedere, tutte le direttive iniziano con il segno #. Inoltre, ogni La sostituzione delle macro semplicemente la sostituzione di un identificatore
direttiva deve trovarsi su una propria f!.ga. Ad esempio, la riga seguente: con la sequenza di caratteri ad esso associata. Pertanto, per definire un messaggio
di errore standard, si pu procedere nel seguente modo:
#include <stdio.h> #include <stdlib.h>
lldefine E_MS "errore di input\n"
errata.
printf(E_MS);

Il compilatore, ogni volta che incontra l'identificatore E_MS sostituir la stringa


10.2 La direttiva #define "errore di input\n". Quindi, per il compilatore, l'istruzione printf() avr il seguente
aspetto:
La direttiva #define definisce un identificatore e Una sequenza di caratteri che
verranno sostituiti all'identificatore ogni volta che questo si presenta all'interno printf("errore di input\n");
del file sorgente. Questo identificatore chiamato nome della macro e il processo
di sostituzione viene chiamato sostituzione della macro. La forma generica della . Se all'interno del listato appare l'identificatore racchiuso fra virgolette, non
direttiva la seguente: viene eseguita alcuna sostituzione. Ad esempio,
#define nome_macro sequenza_car #define XYZ questa una prova

Si noti l'assenza del punto e virgola al termine di questa istruzione. Fra printf("XYZ");
l'identificatore e la sequenza di caratteri pu esseremsetro un numero arbitrario
di spazi ma una volta che la sequenza di caratteri ha inizio, viene conclusa solo non visualizza questa una prova ma XYZ.
dal codice di fine riga. Se la sequenza si estende su pi di una riga la si pu continuare sulla riga
Ad esempio, se si vuole usare la parola SINISTRA per il valore I e la parola seguente inserendo il carattere \ al termine della riga nel modo seguente:
DESTRA per il valore O, si possono creare le due macro seguenti:
#define LONG_STRING "questa una stringa molto \
#define SINISTRA lunga utilizzata a titolo di esempio"
#defi ne DESTRA O
Normalmente, i programmatori C/C++ definiscono gli identificatori utiliz-
In questo modo, ogni volta che il compilatore trover nel file sorgente le paro- zando lettere maiuscole. Questa convenzione aiuta nella lettura del_programma in
le SINISTRA o DESTRA, sostituir i valori 1 e O. Ad esempio, la riga seguente quanto si troveranno a colpo d'occhio i punti in cui avverr la sostituzione di
visualizza sulio schermo i numeri O 1 2: macro. Inoltre, sempre bene inserire tutti i #define all'inizio del file o in un file
header distinto evitando quindi di disperderli all'interno del programma.
printf('~d -%d-%d!!., ._DESTRA, SINISTRA, SINISTRA+l); Molto spesso le macro sono utilizzate pe:lefinire numeri-chiave. che appaio-
no in pi__p~E_trnrun pro~ran~:a:_~~e&empio, -se:iln-_~r~gramnia definisce un
248 CAPITOLO 10
IL PREPROCESSORE E I CO-M-ME-N-'.F-1- 249

array e ha numerose routine che accedono a tale array, invece di inserir~ nel pro-
Al momento della compilazione del programma, al parametro a della defini-
gramma le dimensioni dell'array utilizzando una costante, si pu definire la di-
zione della macro verranno sostituiti prima il valore -1 e poi il valore 1. Le paren-
mensione utilizzando un 'istruzione #define e quindi utilizzare il nome della macro
tesi che racchiudono la a garantiscono la corretta sostituzione in ogni caso. Ad
ogni volta che si deve specificare al dimensione dell'array. In questo modo, se
esempio, se le parentesi attorno alla a venissero rimosse, dopo la sostituzione
necessario cambiare le dimensioni dell'array, baster modificare l'istruzione
della macro questa espressione:
#define e ricompilare il programma. Ad esempio,

#defi ne MAX SIZE 100 ABS(l0-20)


/* ... */ -
float balance[MAX SIZE]; verrebbe convertita in:
/* ... */ -
for(i=O; i<MAX SIZE; i+.+) printf("%f",. balance[i]): 10-20<0 ? -10-20 : 10-20
/* ... *I -
for(i=O; i<MAX_SIZE; i++) x =+ balance[i]; e ci porterebbe a risultati errati.
L'uso di macro funzioni al posto di funzioni vere ha un vantaggio principa-
Poich MAX_SIXE definisce le dimensioni dell'array balance se necessario le: aumenta la velocit di esecuzione del codice in quanto elimina il sovraccari-
cambiare le dimensioni di balance baster modificare la definizione di MAX_SIZE. co di tempo e memoria dovuto alla chiamata alla funzione. Tuttavia, se le di-
Tutti i successivi riferimenti alla macro verranno quindi aggiornati automatica- mensioni della macro funzione sono molto estese, l'aumento di velocit si paga
mente alla successiva ricompilazione del programma. in termini di aumento delle dimensioni del programma a causa della duplicazione
del codice.
NOi'A:~~_-;:,;:,'.~:'. - !~linguaggio C++ fornisce un modo migliore per definire le
costanti, ovvero utilizzando la parola riservata const che verr descritta nella 'NOTA Anche se le macro parametrizzate sono unafun:.ionalit mol-
Parte seconda. to importante, il C++ ha un modo migliore per creare codice in linea, ovvero
tramite la parola riservata inline.
Macro che operano come funzioni

La direttiva #define ha per altre possibilit: il nome della macro pu avere asso-
-- J:iati degli argomenti. Ogni volta che nel listato il compilatore incontra il nome 10.3 La direttiva #error
della macro, gli argomenti utilizzati nella definizione della macro venaono sosti-
tuiti dagli ffettivi argomenti trovati nel programma. Questa forma di macro La direttiva #error chiede al compilatore di concludere la compilazione. Qusta ------
chiamata macro funzione. Ad esempio, direttiva utilizzata principalmente per il debugging. La forma generale della
direttiva #error la seguente:
#include <stdio.h>
#error messaggio_errore
#define ABS{a) {a)<O ? -(a) {a)

int main(void)
Il messaggio_errore non deve essere posto fra doppi apici. Quando il compi-
{ latore incontra la direttiva #error, visualizza il messaggio di errore ad essa asso-
ciato, insieme_ad altre informazioni eventualmente definite dal c~~pilatore:
printf("Valore assoluto di -1 e 1: %d %d", ABS(-1), ABS(l));

return O;
IL PREPROCESSORE E I COMMENTI 251
250 CAPITOLO 10

10.4 La direttiva #include pilazione condizionale ed ampiamente utilizzato da tutte le software house che
forniscono programmi ed eseguono la manutenzione di pi versioni personalizzate
La direttiva #include chiede al compilatore di leggere un altro file sorgente oltre a di un programma.
quello che contiene la direttiva #include. Il nome del file sorgente deve essere
racchiuso fra doppi apici o fra parentesi angolari. Ad esempio,
Le direttive #if, #else, #elif e #endif
#include "stdio.h"
l!incl ude <stdio.h>
#if: #else, #elif e #endif sono le direttive di compilazione c~ndizionale probabil-
mente pi utilizzate. Esse consentono di includere in modo condizionale alcune
porzioni di codice sulla base del risultato di un'espressione costante.
chiedono al compilatore di leggere e compilare il file header delle funzioni di
libreria dedicate ai file. La forma generale di #if la seguente:
- I file inclusi possono contenere altre direttive #include. In questo caso si parla
di include nidificati. Il numero di livelli di nidificazione varia da compilatore a #if espressione_costante
compilatore. Il C standard stabilisce che debbano essere consentiti almeno otto sequenza istruzioni
livelli di inclusione. Lo standard C++ raccomanda di concedere almeno 256 livel- #endif
li di nidificazione.
L'inclusione del nome del file fra doppi apici o fra parentesi angolari determi- Se l'espressione costante che segue la direttiva #if vera, verr compilato il
na il modo in cui deve essere condotta la ricerca del file specificato. Se il nome del codice che si trova fra #ife #endif. In caso contrario, tale codice viene saltato. La
file racchiuso fra parentesi angolari, il file viene ricercato in un modo definito direttiva #endif_ segnala la fine di un blocco #if. Ad esempio,
dal creatore del compilatore. Spesso, la ricerca viene eseguita in alcune speciali
directory dedicate ai file di inclusione. Se il nome del file racchiuso fra doppi f* Esempio d'uso di #if. */
apici, il file viene ricercato utilizzando un altro metodo definito #include <stdio.h>
dall'implementazione. Per molti compilatori, i doppi apici consentono di esegui-
re la ricerca all'interno della directory corrente. Se il file non viene trovato. la #defi ne MAX 100
ricerca viene ripetuta come se il file fosse racchiuso fra parentesi angolari.
int main(void)
Normalmente, la maggior parte dei programmatori utilizza le parentesi ango- {
lari per includere i file header standard. L'uso dei doppi apici normalme;te #i f MAX>99
riservato all'inclusione di file che hanno una relazione stretta con il programma. printf("compil11zioQ_~__ill !l_r_i:_~y con pi di 99 elementi\n");
In ogni caso, non vi nessuna regola che governi questo tipo di comportamenti. #endif
Un programma C++ pu utilizzare la direttiva #include anche per includere
un header C++. II linguaggio C++ definisce una serie di header standard che for- return O;
niscono tutte le informazioni. necessarie alle varie librerie C++. Un header un
identificatore standard che non fa riferimento necessariamente al nome di un file.
Pertanto un header semplicemente un'astrazione che garantisce che nel pro- Questo programma visualizza il messaggio sullo schermo poich MAX mag-
gramma vengano incluse tutte le informazioni richieste. L'uso degli header verr giore di 99. L'esempio illustra un fatto molto importante. L'espressione che segue
descritto nella Parte seconda. la direttiva #if viene valutata al momento della compilazione. Pertanto deve con-
tenere solo identificatori e costanti precedentemente definiti e non consentito
luso di variabili. -
La direttiva #else analoga all'istruzione #else del linguaggio C++: in prati-
10.5 Le direttive per compilazioni condizionali ca stabilisce un'alternativa nel caso in cui non sia verificata l'e~pressione costante
associata alla direttiva #if. L'esempio precedente pu essere espanso nel seguente
m
Vi sono alcune direttive che consentono-di COQ!Qilare modo selettivo alcune modo: --
poi:_zi~!!rdercodic-s.Qrge!Jte cli uitpr~gramma~ Qu.~s!_o processo chiamato com - -
252 CAPITOLO 10 I L p R Ep R o e Esso R CE I e o MM ENTI 253

/* Esempio d'uso di #if/#else. */ Ad esempio, il seguente frammento di codice utilizza il valore di


#include <stdio.h> ACTIVE_COUNTAY per definire il simbolo monetario:

#defi ne MAX 10 #defi ne FRANCIA O


#define INGHILTERRA 1
int main(void) #define ITALIA 2
{
#if MAX>99 #define ACTIVE_COUNTRY FRANCIA
printf("compilazione per array con pi di 99 elementi\n");
#else #i f ACTIVE COUNTRY == FRANCIA
printf("compilato con un array breve\n"); char cur;ency[] = "franco";
#endif #elif ACTIVE_COUNTRY == INGHILTERRA
char currency[] "sterlina";
return O; #else
char currency[] "lira";
#endif
In questo caso, MAX minore di 99 e quindi la porzione #if del codice non
viene compilata. Viene invece compilata l'alternativa indicata da #else e pertanto Il C standard stabilisce che le direttive #ife #elif possano essere nidificate fino
verr visualizzato il messaggio compilato con un array breve. a otto livelli. Lo standard C++ suggerisce di consentire almeno 256 livelli di
Si noti che la direttiva #else indica sia la fine del blocco #if che l'inizio del nidificazione: In caso di nidificazione, ogni #endif, #else e #elif si associa al pi
blocco #else. Questa precisazione necessaria in qanto vi pu essere una sola vicino #if o #elif. Ad esempio, il listato seguente perfettamente corretto:
direttiva #endif associata a una detenninata #if.
La direttiva #elif significa "else if' e definisce una catena if-else-if che presen- #if MAX>lOO
ta pi opzioni di compilazione. La direttiva #elif deve essere seguita da un' espres- #if SERIAL_VERSION
sione costante. Se lespressione vera, viene compilato il blocco di codice ad essa int port=l98;
associato e verranno saltate tutte le altre eventuali espressioni #elif. In caso con- #elif
trario, viene controllata l'espressione del blocco successivo. La fonna generale di i nt port=200;
#elif la seguente: #endif
#else
char out_buffer[lOO];
#if espressione
#endif
sequenza istruzioni
#e 1i f espressione 1
sequenza istruzioni
Le direttive #ifdef e #ifndef
#elif espressione 2
sequenza istruzioni
Un altro metodo per la compilazione condizionale utilizza le direttive #ifdef e
#elif espressione 3
sequenza istruzioni
#ifndef che possono essere tradotte come "se definito" e "se non definito". La
#elif espressione 4 fonna generale di #ifdef la seguente:

#ifdef nome_macro
sequenza istruzioni
#elif espressione N #endif
---~equenza istruzioni
#endif
-----_-_------------=..~..:---

254 CAPITOLO 10 COMMENTI 255-


IL PREPROCESSORE E

Se nome_macro stata definita precedentemente in un'istruzione #define, il #undef nome_macro


blocco di codice corrispondente verr compilato.
La forma generale di #ifndef la seguente: Ad esempio,
#ifndef nome_macro #defi ne LEN 100
sequenza istruzioni #defi ne WIDTH 100
#endif
char array[LEN] [WIOTH];
Se nome_macro si trova attualmente non definito da un'istruzione #define, il
blocco di codice corrispondente verr compilato. l/undef LEN
Sia #ifdef che #ifndef possono utilizzare un'istruzione #else o #elif. l/undef WIDTH
Ad esempio, /* a questo punto sia LEN che WIDTH non sono pi definite */

#include <stdio.h> Sia LEN che WIDTH rimangono definite finch non vengono incontrate le
istruzioni #undef. La direttiva #undef utilizzata principalmente per fare in modo
#define TEO 10 che i nomi delle macro siano locali rispetto alla sezione di codice in cui sono
richieste.
int main(void)
{
#ifdef TEO
printf("Ciao Ted\n"); 10.7 Uso di defined
#else
printf("Ciao a tutti\n"); Oltre a #ifdef, per determinare se il nome di una macro definito, si pu utilizzare
#endif la direttiva #if insieme all'operatore di compilazione defined. La forma generale
#i fndef RALPH dell'operatore defined la seguente:
printf("RALPH non definito\n");
#endif
defined nome-macro
return O;
Se nome-macro attualmente definita, l'espressione vera. In caso contrario
l'espressione falsa. Ad esempio, per determinare se la macro MYFILE definita,
visualizzer i messaggi Ciao Ted e RALPH non definito. Se anche TEO non fosse si possono usare le due direttive seguenti:
definito, il programma visualizzerebbe Ciao a tutti seguito da RALPH non definito.
llif defined MYFILE
Le direttive #ifdef e #ifndef possono essere nidificate in C fino a otto livelli. Lo
standard C++ suggerisce di consentire almeno 256 livelli di nidificazione.
o

#i fdef MY FILE
10.6 La direttiva #undef
Per invertire la condizione, basta far precedere alla parola defined il punto""-
La direttiva #undef elimina una definizione precedente relativa al nome della macro esclamativo (!). Ad esempio, il seguente frammento di codice viene compilato
specificata. In pratica cancella la definizione di una macro. La forma generale di solo se DEBUG non definita.
--#un~ef la-Seguente:-

- --- ---
---
256 CAPITOLuTU--

#i f ! defi ned DEBUG


dere un'opzione che consenta di attivare l'opzione di Trace sull'esecuzione del
printf("Versione finale!\n");
#endif programma. Molto probabilmente questa opzione potr essere specificata con
un'istruzione #pragma. Per informazioni sulle opzioni disponibili necessario
consultare la documentazione del compilatore.
Un motivo che consiglia di usare defined rispetto a #ifdef la possibilit di
determinare l'esistenza di un nome di macro all'interno di un'istruzione #elif.

10.1 O Gli operatori del preprocessore # e ##


10.8 la direttiva #line Il preprocessore prevede due operatori: #e##. Questi operatori possono essere
utilizzati con l'istruzione #define.
La direttiva #line consente di alterare il contenuto di _UNE_ e _FILE_ che
L'operatore#, chiamato anche operatore di conversione in stringa, tramuta
sono identificatori predefiniti del compilatore. L'identificatore _UNE_ contie-
l'argomento seguente in una stringa fra doppi apici. Ad esempio, si consideri il
ne il numero di riga corrente nel codice compilato. L'identificatore ~FILE_
programma seguente:
una stringa che contiene il nome del file sorgente compilato. La forma generale di
#line la seguente:
#include <stdio.h>

#line numero "nomefile" #define mkstr(s) # s

dove numero un numero p~sitivo intero e diverr il nuovo valore di UNE e int main(void)
il parametro opzionale nomefile un qualunque identificatore valido di file clte {
diverr il nuovo valore di _FILE_. La direttiva #line utilizzata principalmente printf(mkstr(Il C++ bello));
per scopi di debugging e per particolari applicazioni.
. A~ esempio, il seguente codice specifica che il conteggio delle righe deve return O;
npartrre dal numero 100 e quindi l'istruzione printf() visualizzer il numero 102 in
quanto la terza riga nel programma sorgente dopo l'istruzione #line 100.
Il preprocessore e trasforma la riga
#include <stdio.h>
printf(mkstr(Il C++ bello));
llline 100 /* reinizializza il contatore di riga */
in
int main(void) /* riga 100 */
{ /* riga 101 */ printf("Il C++ bello");
printf("%d\n",_ )!NE__ ); /* riga 102 */
return O;
L'operatore## chiamato anche operatore di concatenamento. Ad esempio:

#i nel ude <s tdi o. h>

#define concat(a, b) a #1!_ p


--+0-:9 La direttiva #pragma
int main(void)
__ ---~~ragma una direttiva specifica dell'implementazione che consente l'invio di {
ffiT XY = 10;
vari ti_ei~nf_OrJ!l~~o!l} al compilatore. Ad-esempio, un compilatore pu preve-
258 CAPITOLO 10 IL PREPROCESSORE E COMMENTI 259

printf("%d", concat(x, y)); Lo standard C++ aggiunge alle macro precedenti una nuova macro chiamata
__cplusplus, che contiene almeno sei cifre. I compilatori non standard usano
return O; cinque o meno cifre.

Il preprocessore trasforma
10.12 I commenti
printf("%d", concat(x, y));
In C, tutti i commenti iniziano con la coppia di caratteri I* e terminano con */. Fra
in l'asterisco e la barra non devono essere inseriti spazi. Tutto ci che si trova fra
questi simboli di apertura e di chiusura verr ignorato dal compilatore. Ad esem-
printf("%d", .xy); pio, questo programma visualizza sullo schermo solo la parola ciao:

Se il funzionamento di questi operatori pu sembrare un po' curioso, si tenga #include <stdio.h>


a mente che non sono necessari e che in genere non vengono impiegati. La loro
int main(void)
esistenza consente al preprocessore di gestire casi particolari.
{
printf("ci ao");
/* printf("a tutti"}; */
10.11 Le macro predefinite
return O;

Il C++ specifica sei nomi di macro predefinite:

__UNE__ I commenti C sono chiamati anche commenti multiriga in quanto possono


__FILE__ anche estendersi su pi righe, come nel seguente esempio:
__DATE__
__TIME__ /* Questo
__STDC__ un commento
su pi righe */
__cplusplus
I commenti possono essere inseriti in qualunque punto di un programma, sem-
Il C ne definisce solo cinque. Le macro __ LINE__ e __ FILE__ sono state pre che non appaiano all'interno di una parola chiave o di un identificatore. Quin-
discusse precedentemente nella sezione che riguardava la direttiva #line. In breve di, questo commento valido:
esse contengono rispettivamente il numero di riga e il nome del file sottoposto a
compilazione. x = 10+ /* somma dei numeri */5;
La macro __ DATE__ contiene una stringa nel formato mese/giorno/a11110.
Questa s~ringa rappresenta la data della traduzione del codice sorgente in codice mentre
oggetto.
--- La macro __TIME__ contiene una stringa che riporta l'ora della traduzione
swi/*non funziona*/tch(c) { ..
del codice sorgente in codice oggetto. La forma di questa stringa :
ore:minuti:secondi ..
errato poich una parola chiave non pu contenere un commento. Tuttavia,
II significato della macro __ STDC__ definitQ_dall'implementazi().!1~. In _ _
genere se __STDC__ definita, il compilatore accetter unicamente codiceC/ __ -. sconsigliabile inserire commenti all'interno di espressioni in quanto ne_ C()_!}fon-
-'--~-'-- -e++ sfancfard, rifiutando le estensioni_non_sfilnaard.-= _. ___ =:-:- - ---
dono il significato:-Non possibile.nidifu:areh:omme_nti C: quindi un corru11ento
260 CAPITOLO 10

- non pu contenere un altro commento. Ad esempio, questo frammento di codice Parte seconda
provoca un errore di compilazione:
IL LINGUAGGia C++
/* questo un commento esterno
X = y/a;
/* questo un commento interno e prov,p<;a un errore * /
*/

Al momento attuale, lo Standard C definisce solo lo stile di commenti appena


descritto. Al contrario il linguaggio C++ supporta due tipi di commenti. Il primo
il commento multiriga C. Il secondo il commento su una sola riga. I commenti
su una sola riga iniziano con la sequenza// e terminano alla fine della riga. Ad
esempio:
:... a Parte prima ha esaminato il sottoinsieme C del lin-
/I Questo un commento su una so 1a riga guaggio C++. La Parte seconda si occupa delle funzionalit specifiche del C++,
ovvero di quelle funzionalit del C++ che non sono presenti nel linguaggio C.
Anche se lo Standard C attualmente non definisce questo stile di commenti, in Poich la maggior parte delle estensioni che il C++ apporta al C sono dedicate al
realt tale stile accettato dalla maggior parte dei compilatori e probabilmente in supporto della programmazione a oggetti (OOP), la seconda parte fornisce anche
futuro verr incorporato ufficialmente nello Standard C. L'argomento dei com- una discussione sulla teoria e i vantaggi di questa tecnica di programmazione.
menti su una sola riga verr ulteriormente sviluppato nella Parte seconda.
buona norma utilizzare sempre i commenti per descrivere il funzionamento
del codice. Tutte le funzioni di complessit non elementare, dovranno prevedere
un commento ali' inizio che indichi lo scopo della funzione, il modo in cui deve
essere chiamata e il valore da essa restituito.
----==-------

Capitolo 11

Panoramica del
linguaggio C++

11.1 Le origini del C++


11.2 Che cos' la programmazione a oggetti
11.3 Elementi di base del linguaggio C++
11.4 C++ vecchio stile e C++ moderno
11.5 Introduzione alle classi C++
11.6 L'overloading delle funzioni
11.7 L'overloading degli operatori
11.8 L'ereditariet
11.9 I costruttori e i distruttori
11.10 Le parole riservate del C++
11.11 La forma generale
di un programma C++

uesto capitolo presenta una panoramica dei concetti


di base che hanno condotto allo sviluppo del C++. Il C++ un linguaggio di
programmazione a oggetti le cui funzionalit sono strettamente correlate fra loro.
In molti casi, questa correlazione rende difficile descrivere una funzionalit del
C++ senza menzionarne nel contempo anche altre. In molte situazioni, le funzio-
nalit a oggetti del C++ sono cos correlate fra loro che per trattare una funziona-
lit necessario-che il ieffre sia a conoscenza di una o pi funzionalit correlate.
Per risolvere questo problema, questo capitolo presenta una rapida panoramica
degli aspetti pi importanti del C++, la sua storia, le sue funzionalit principali e
le differenze esistenti fra il C++ tradizionale e quello definito dallo standard. I
capitoli successivi di questa parte della guida esaminano pi in dettaglio il C++.

11.1 Le origini del C++


Il li;guaggio C++ nacque come est~;:;-sione del C. Le estensioni del C++ sono
state inizialmente sviluppate da Bjarne Stroustrup nel 1979 presso i laboratori
Bell di Murray Hill nel New Jersey. Inizialmente il nuovo linguaggio fu chiamato
semplicemente "C con classi". Nel 1983 questo nome venne cambiato in_C++. - - - -
- ~ ~--

264--C API TOl-0-++------ - ~~~~~~~~~~_:_~~AN:..:.:..:D~-~R~A~M~l~C~A..:..__:D:...:.E~i:--=-t-~IN:..:....:G~~~A-G_~=....:.G~l~O--c-+~+~;--~~26.:..:c5--

Anch se il lino-uao-gio e stato uno dei linguaggi di programmazione profes- standard per il C++ una realt. Il materiale contenuto in questo volume descrive
sionali pi apprez:ati ~ ampiamente utilizzati al mondo, l'invenzione del C++ fu lo Standard per il linguaggio C++ , c~mprendendo tutte le funzionalit pi recen-
dettata dalla necessit di raggiungere maggiori livelli di complessit. Nel trascor- ti. Questa la versione del linguaggio C++ creata dal comitato di standardizza-
rere degli anni, i programmi per computer sono dive~tati sempre pi estes! e com- zione ANSI/ISO _e___accettata da tutti i pi importanti compilatori.
plessi. Anche se il C un linguaggio di programmazione eccellente, anch :sso h~
i propri limiti. In C, quando un programma supera le 25.000 o le 100.000 ngh: d1
codice, diviene cos complesso che risulta difficile considerarlo nella sua tota~1t: 11.2 Che cos' la programmazione a oggetti
Il C++ consente di superare questa barriera. L'essenza del C++ stata qumd1
concepita con lo scopo di permettere ai programmatori di comprendere e gestire Poich la programmazione a oggetti (OOP) ha dato origine al C++, necessario
programmi pi estesi e complessi. . comprendere i suoi principi fondamentali. La programmazione a oggetti rappre-
La maggior parte delle funzionalit aggiunte da Stroustrup al C consente 11 senta un nuovo e potente metodo di programmazione. Le metodologie di pro-
supporto della programmazione a oggetti, chi~mata an:he OOP .(per un~ brev: grammazione sono cambiate notevolmente dall'invenzione del computer, soprat-
descrizione della programmazione a oggetti, si consulti la prossima sezione d1 tutto per consentire di aumentare la complessit dei programmi. Ad esempio, quan-
questo capitolo). Stroustrup asserisce che alcune delle funzionalit a oggetti del do furono inventati i computer, la programmazione veniva eseguita impostando
C++ sono state ispirate da un altro linguaggio di programmazione a oggetti il istruzioni binarie tramite il pannello frontale del computer. Finch i programmi
Simula67. Pertanto, il C++ rappresenta il punto di unione fra due dei metodi di erano composti da poche centinaia di istruzioni, questo approccio ha funzionato.
programmazione_ pi potenti. . Con la crescita .dei programmi stato sviluppato il linguaggio Assembler con il
Da quando stato inventato, ii linguaggio C++ stato sottoposto a tre grandi quale un programmatore poteva realizzare programmi pi estesi e complessi, uti-
revisioni, ognuna delle quali ha apportato aggiunte e modifiche al linguaggio. La lizzando rappresentazioni simboliche delle istruzioni in linguaggio macchina. Ma
prima revisione si svolta nel 1985 e la seconda nel 1998. La terza si verificata i programmi continuavano a crescere e furono perci introdotti linguaggi di alto
durante la fase di standardizzazione del C++. Il lavoro per la standardizzazione livello che davano al programmatore pi strumenti per gestire questa nuova ri-
del lino-uago-io C++ iniziato molti anni fa. A quell'epoca stato creato un comi- chiesta di complessit. II primo linguaggio di questo genere fu naturalmente il
tato c~ngi~nto fra ANSI (American National Standards Institute) e ISO FORTRAN. Anche se il FORTRAN stato un notevole passo in avanti rispetto al
(Intemational Standards Organization). La prima bozza di standard nacque il 2~ passato, si trattava di un linguaggio che non incoraggiava la realizzazione di pro-
gennaio 1994. In tale bozza, il comitato di standardizzazione C++ ANSI/ISO (d1 grammi chiari e facili da comprendere.
cui l'autore membro) ha mantenuto le funzionalit inizialmente definite da Il 1960 ha dato i natali alla programmazione strutturata. Questo il metodo
Stroustrup e ve ne ha aggiunte di nuove. In generale la bozza iniziale rifletteva lo seguito da linguaggi come il Ce il Pascal. L'impiego di linguaggi strutturati ha
stato del linguaggio C++ a quel tempo. reso possibile la realizzazione di programmi piuttosto complessi con una discreta
Poco dopo il completamento della prima bozza dello standarc:l. . fil.._y!;!J_ificato facilit. I linguaggi strutturati sono caratterizzati dal supporto di subroutine indi-
un evento che ha provocato una grande espansione del linguaggio: la creazione pendenti, variabili locali, costrutti di controllo avanzati e dal fatto di non impiega-
della libreria STL (Standard Template Library) da parte di Alexander Stepanov. re GOTO. Tuttavia, anche utilizzando metodi di programmazione strutturata, un
La libreria STL costituita da una serie di routine generiche per la manipolazione progetto pu diventare incontrollabile una volta che raggiunga determinate di-
dei dati. Si tratta di un oggetto potente ed elegante ma anche piuttosto esteso. _mensioni.
Successivamente alla prima bozza, il comitato ha deciso di includere la libreria Si consideri questo fatto: ad ogni punto di svolta nel campo della programma-
STL nelle specifiche del linguaggio C++.L'aggiunta della libreria STL ha esteso zione, sono stati creati strumenti e tecniche che consentivano al programmatore
notevolmente le capacit del linguaggio, molto oltre la definizione originale e di realizzare programmi pi complessi. Ogni passo in questo percorso consisteva
l'inclusione della libreria STL ha rallentato la standardizzazione del linguaggio. nell'utilizzo dei migliori elementi dei metodi precedenti e nel loro sviluppo. Pri-
Dunque si pu dire che la standardizzazione del linguaggio ++ ha richiesto ma dell'invenzione della programmazione a oggetti, molti prQgetti raggiungeva- -
molto pi tempo di quanto chiunque potesse attendersi Nel frattempo sono state no o superavano il punto in cui l'approccio strutturato non pu pi essere adotta-
apportate alcune aggiunte e molte piccole modifiche al linguaggio. In pratica la to. La programmazione a oggetti nata con Io scopo di superare questa barriera.
versione di C++ definita dal comitato di standardizzazione molto pi estesa e La programmazione a oggetti ha preso le migliori idee della programmazione
_ _ --eomplessa del progetto originale di Stroustrup, ma ora finalmente pronto Io strutturata~ le ha comb!nate con nuovi concetti. Il risultitO__un'or,gamz~aziOiie -
---- standard. La bozza finale stataprodot~a _!! !4 _no~_em~~e 1997 e finalmente-lo---'-
completamente nuova dei programmi. In generale un programma pu essere rea- La specifica azione selezionata determinata dalla natura della situazione. Un
lizzato in due modi: ponendo al centro il codice ("ci che accade") o ponendo al esempio di polimorfismo tratto dal mondo reale il termostato. Non importa il
centro i dati ("gli attori interessati"). Utilizzando le tecniche della programmazio- tipo di combustibile utilizzato (gas, petrolio, elettricit e cos via): il termostato
ne strutturata, i programmi vengono tipicamente organizzati attorno al codice. funziona sempre nello stesso modo. In questo caso, il termostato (che l'interfaccia)
Questo approccio prevede che il codice operi sui dati. Ad esempio, un programma sempre lo stesso qualsiasi sia il tipo di combustibile (metodo) utilizzato. Ad
scritto con un linguaggio di programmazione strutturato come il C definito dalle esempio, se si desidera raggiungere una temperatura di 20 gradi, si imposta il
sue funzioni, le quali operano sui dati usati dal programma. I programmi a oggetti termostato a 20 gradi. Non importa quale sia il tipo di combustibile che fornisce il
seguono l'altro approccio. Infatti sono organizzati attorno ai dati e si basano sul calore.
fatto che sono i dati a controllare l'accesso al codice. In un linguaggio a oggetti si Questo stesso principio si pu applicare anche in programmazione. Ad esem-
definiscono i dati e le routine che sono autorizzate ad agire su tali dati. Pertanto pio, un programma potrebbe definire tre diversi tipi di stack. Uno stack per i
sono i dati a stabilire quali sono le operazioni che possono essere eseguite. I lin- valori interi, uno per i caratteri e uno per valori in virgola mobile. Grazie al
guaggi che consentono di attuare i principi della programmazione a oggetti hanno polimorfismo, sar possibile creare un solo insieme di nomi (push() e pop()) uti-
tre fattori in comune: l'incapsulamento, il polimorfismo e l'ereditariet. lizzabile per i tre tipi di stack. Nel programma verranno create tre diverse versio-
ni di queste funzioni, una per ogni tipo di stack, ma il nome delle funzioni rimarr
Io stesso. Il compilatore selezioner automaticamente la funzione corretta sulla
L'incapsulamento base del tipo dei dati memorizzati. Pertanto, l'interfaccia dello stack (ovvero le
funzioni push() e pop()) non cambier indipendentemente dal tipo di stack utiliz-
L'incapsulamento il meccanismo che riunisce insieme il codice e i dati da esso zato. Naturalmente le singole versioni di queste funzioni definiscono
manipolati e che mette entrambi al sicuro da interferenze o errati utilizzi. In un implementazioni (metodi) specifiche per ciascun tipo di dati.
linguaggio a oggetti, il codice e i dati possono essere- raggruppati in modo da II polimorfismo aiuta a ridurre la complessit del programma consentendo di
creare una sorta di "scatola nera". Quando il codice e i dati vengono raggruppati utilizzare la stessa interfaccia per accedere a una classe generale di azioni. Sar
in questo modo, si crea un oggetto. In altre parole, un oggetto un "dispositivo" compito del compilatore selezionare lazione specifica (ovvero il metodo) da ap-
che supporta l'incapsulamento. plicare in una determinata situazione. II programmatore non dovr pi fare questa
All'interno di un oggetto, il codice, i dati o entrambi possono essere privati di tale selezione manualmente, ma dovr semplicemente ricordare e utilizzare linterfaccia
oggetto oppure pubblici. Il codice o i dati privati sono noti e accessibili solo da generale.
parte degli elementi dell'oggetto stesso. Questo significa che il codice e i dati I primi linguaggi di programmazione a oggetti erano interpretati e quindi il
privati non risultano accessibili da parte di elementi del programma che si trovano polimorfismo era, per forza di cose, supportato al momento dell'esecuzione (run-
all'esterno dell'oggetto. Se il codice o i dati sono pubblici, risulteranno accessibi- time). Ma il C++ un linguaggio compilato pertanto il polimorfismo supportato
li anche da altre.parti.del. programma che non sono definite ali' interno dell' ogget- al momento dell'esecuzione e al momento della compilazione (compile-time).
to. Generalmente le parti pubbliche di un oggetto sono utilizzate per fornire
un'interfaccia controllata agli elementi privati dell'oggetto stesso.
Un oggetto in tutto e per tutto una variabile di un tipo definito dall'utente. Pu L'ereditariet
sembrare strano pensare a un oggetto, che contiene codice e dati, come a una
variabile. Tuttavia nella programmazione a oggetti, avviene proprio questo. Ogni L'ereditariet il processo grazie al quale un oggetto acquisisce le propriet di un
volta che si definisce un nuovo tipo di oggetto, si crea implicitamente un nuo\'O altro oggetto. Questo un concetto fondamentale poich chiama in causa il con-
tipo di dati. Ogni specifica istanza di questo tipo una variabile composta. cetto di classificazione. Se si prova a riflettere, la maggior parte della conoscenza
resa pi gestibile da classificazioni gerarchiche. Ad esempio, una mela rossa
Delicious appartiene alla classificazione mela che a sua volta appartiene alla clas-
Il polmorfsmo se frutta che a sua volta si trova nella classe pi estesa cibo. Senza l'uso della -.-
classificazione, ogni oggetto dovrebbe essere definito esplicitamente con tutte le
I linguaggi di programmazione a oggetti supportano il polimorfismo che caratte- proprie caratteristiche. L'uso della classificazione consente di definire un oggetto
rizzato dalla frase "un'interfaccia, pi metodi". In altri termini, il polil!!Qrfism_P___ sulla base delle qualit-che lo.rendono unico all'interno della propria classe. Sar
____c~~nte a un'interfaccia di.~ntroll'!r~I'~~~~sso a una classe generale di azio~i.:_. il meccanismo _cli: ereditariet a rendere possibile per un oggetto di essere Ufl~ __
268 CAPITOLO 11 PANORAMICA DEL LINGUAGGIO C++ 269

specifica istanza di un caso pi generale. Come si vedr, l'ereditariet un impor- cin i;


tante aspetto della programmaiione a oggetti.
11 output di un numero con 1 'operatore
cout <<i << " al quadrato uguale a " i*i << "\n";

11.3 Elementi di base del linguaggio C++ return O;

Nella Parte prima, stato descritto il sottoinsieme C del linguaggio C++ e sono
stati presentati alcuni programmi C che avevano lo scopo di i!lustrare queste.fu~ Come si pu vedere, questo programma ha un aspetto molto diverso dai pro-
zionalit. Da qui in avanti, tutti gli esempi saranno programrm C++. Questo s1gm- grammi C presentati nella Parte prima. Pu essere utile commentarlo riga per
fica che tali progi:ammi faranno uso delle funzionalit specifiche del linguag~io riga. Per iniziare, viene incluso l'header <iostream>. Questo file utilizzato per
C++.Per semplificare la discussione, d'ora in poi si far riferimento alle funzio- consentire l'esecuzione di operazioni di I/O in stile C++ (<iostream> per il C++
nalit specifihe del linguaggio C++ parlando di "funzionalit C++". Chi avesse ci che stdio.h per il C). Si pu anche notare che il nome iostream non ha I' esten-
esperienza di programmazione in c o chi abbia.studiato i p:ogra~ d~l sottoin: sione .h. Questo dovuto al fatto che iostream un header definito dallo Standard
sieme C contenuti nella Parte prima, noter che i programmi C++ d1ffenscono dai C++.I nuovi header non usano l'estensione .h.
programmi C per alcuni aspetti importanti. La maggior parte delle differenze ri: La riga successiva del programma :
guarda l'utilizzo delle funzionalit a oggetti tipiche del linguaggio C++. Ma i
programmi C++ differiscono dai programmi C in molti altri sens_i, a? ~s.empi~ nel using namespace std;
modo in cui vengono eseguite le operazioni di I/O e nelle operaz1om d1 mclus10ne
dei file header. Inoltre la maggior parte dei programmi C++ ha una serie di tratti il Questa riga chiede al compilatore di utilizzare il namespace std. I namespace
comune che li identificano chiaramente come tali. Prima di affrontare l'argomen- sono una funzionalit che stata aggiunta solo recentemente al C++. Un namespace
to dei costrutti a oggetti del linguaggio C++, opportuno conoscere gli elementi crea una regione di dichiarazione in cui possono essere inseriti vari elementi del
fondamentali di un programma C++. programma. Il namespace aiuta a organizzare meglio i programmi pi estesi.
Questa sezione descrive vari elementi riferiti a quasi tutti i programmi C++. Nel L'istruzione using informa il compilatore che si vuole utilizzare il namespace std.
frattempo verranno evidenziate alcune delle differenze pi importanti fra il Ce le Questo il namespace in cui dichiarata l'intera libreria standard C++.Dunque
prime versioni di C++. utilizzando il namespace std si semplifica I' accesso alla libreria standard. I pro-
grammi della Parte prima che utilizzavano solo il sottoinsieme C non avevano
bisogno dell'istruzione namespace poich le funzioni della libreria C sono dispo-
Un programma C++ di esempio nibili anche nel namespace globale.----- -- ----
Si pu partire con il semplice programma C++ presentato di seguito. :;.,or.("J"'-- -::. - Poich gli header di questo tipo e i namespace sono funziona-
lit ggiunte recentemente al linguaggio C++, capiter con facilit di trovare
#include <iostream> vecchio codice che non le impiega. Inoltre i compilatori non recenti non prevedo-
using namespace std; no il supporto di queste funzionalit. Pi avanti in questo stesso capitolo si po-
tranno trovare informazioni utili per impiegare vecchi compilatori.
int main()
{ Ora si esamini la seguente riga.
int i;
int main()
cout "Stringa di output.\n"; Il commento su una sola riga
/* si pu utilizzare anche lo stile di commenti C *I
Si noti che l'elenco dei parametri di main() vuoto. In C++, questo significa
Il input di un-numeroco1r-'"Operatore che main() non ha parametri. In C, una funzione che non ha parametri deve speci-
cout << "Immettere un-numero: -'~;
-- -ficare-come e~~c.CL_di.p~ametri la dichiarazione vid::--. -- -
270 CAPITOLO 11 PANORAMICA DEL LINGUAGGIO-(;-++ 271

int main(void) In C++, l'operatore>> continua a eseguire l'operazione di scorrimento a de-


stra ma quando viene utilizzato in questo modo, assume il significato di operatore
Questo era il modo in cui main() veniva dichiarata nei programmi della Parte di input. Questa istruzione assegna a i il valore letto dalla tastiera. L'identificatore
prima. In C++, l'uso di void ridondante e inutile. Come regola generale, in C++ cin fa riferimento al dispositivo di input standard che normalmente la tastiera. In
quando una funzione non ha parametri, baster che il suo elenco di parametri sia generale, si utilizza cin >> per assegnare un valore a una variabile di uno qualsiasi
vuoto senza la necessit di utilizzare la parola chiave void. dei tipi di base pi le stringhe.
La riga successiva contiene due nuove funzionalit del C++:
;NOJA La. riga di codice appena descritta stampata correttamente.
cout "Stringa di output. \n"; 11 commento su una sol a riga In particolare, non si deve inserire il carattere & davanti alla l Quando si esegue
l'input di informazioni utilizzando unafu.nzione C come scanf(), necessario pas-
sare allafu.nzione un puntatore alla variabile che ricever le infonnazioni. Que-
Questa riga introduce due nuove funzionalit del C++.Innanzi tutto, l'istruzione:
sto significa che la variabile deve essere preceduta dall'operatore "indirizzo di"
ovvero&. Ma, grazie al modo in cui l'operatore>> implementato in C++, que-
cout "Stringa di output.\n"; sto non necessario. Il motivo verr descritto nel Capitolo I 3.

provoca la visualizzazione sullo schermo della frase Stringa di output seguita dal- Anche se questo non viene illustrato dall'esempio, rimane comunque possibi-
la combinazione Carriage Return - Line Feed. In C++, l'operatore assume le utilizzare funzioni di input C, come ad esempio scanf() al posto di cin .
nuovi significati. Continua a fungere da operatore di scorrimento a sinistra ma Tuttavia, come si detto nel caso di cout, la maggior parte dei programmatori
quando viene utilizzato nel modo illustrato dall'esempio, assume il significato di trova che cin sia pi nello spirito del C++.
operatore di output. La parola cout un identificatore che fa riferimento allo Di seguito viene presentata un'altra riga molto interessante del programma:
schermo (in realt anche il C++ come il C supporta la redirezione delle operazioni
di I/O, ma per quanto riguarda questa discussione, si suppone che cout faccia cout << i " al quadrato ugual e a " i*i << 11
\n";
riferimento solo allo schermo). Si pu utilizzare cout e l'operatore per
visualizzare ogni genere di dati predefiniti come pure stringhe di caratteri. Se si suppone che il valore di i sia IO, questa istruzione visualizza la frase 1O
In C++ comunque possibile utilizzare printf() o una qualsiasi delle altre fun- al quadrato uguale a 100, seguito dalla combinazione Carriage Retum-Line
zioni di I/O del C. Tuttavia la maggior parte dei programmatori trova che l'utiliz- Feed. Questa riga dimostra che possibile utilizzare di seguito pi operazioni di

I
zo di sia pi nello spirito del C++. Inoltre, anche se la visualizzazione di una output<<.
stringa con printf() praticamente equivalente all'utilizzo di, il sistema di I/O Il programma termina con l'istruzione:
del C++ pu essere espanso in modo da eseguire operazioni sugli oggetti definiti
dalrutente (un'operazione non eseguibile utilizzando printf()). return O;
Quello che segue l'espressione da visualizzare in output un commento C++
su una sola riga. Come si detto nel Capitolo 10, in C++ i commenti possono Questa riga fa in modo che al processo chiamante (normalmente il sistema
essere definiti in du~ modi. Si pu utilizzare un commento C, che funziona nello operativo) venga restituito il valore zero. Questa riga ha lo stesso significato gi
stesso modo anche in C++. Ma in C++ anche possibile definire un commento su visto per il C. La restituzione del valore zero indica che il programma terminato
una singola riga utilizzando la coppia di caratteri //; ci che segue viene ignorato normalmente. Una terminazione anormale del programma dovr essere segnalata
dal compilatore fino alla fine della riga. In generale, i programmatori C++ utiliz- restituendo un valore diverso da zero. In alternativa si possono usare i valori
zano commenti C per creare commenti multi riga e commenti C++ quando devo- EXIT_SUCCESS e EXIT_FAILURE.
no inserire un commento formato da un'unica riga. =
Quindi, il programma chiede all'utente un numero. Il numero viene letto dalla
tastiera dalla seguente istruzione: Il funzionamento-degli operatori di I/O

_______ cin>>i; - Come si detto, quando vengono utilizzati per operazioni di I/O, gli operatori
e sono in gradodgestire q~~a~LJ..ipodLd~ti predefinito del C++.--Ad esem-
272 C AEJ T O LO 11
PAN ORAtvfiC.A O EL LINGUAGGI c)---c:;:- - 273

pio, questo programma legge in input un valore fl9at, un double e una stringa e poi di un blocco devono essere dichiarate all'inizio di tale blocco. Quindi non pos-
li visualizza. sibile dichiarare una variabile in un blocco dopo un'istruzione di "azione". Ad
esempio, in C, il seguente frammento di codice errato:
#inclu:le <iostream>
usi ng namespace std; /* Errato in c. Accettato in C++, */
int f()
int main() {
{ int i;
float f; i = 10;
char str[BO];
double d; int j; /* istruzione non compilabile in e */
j = i*2;
cout "Immettere due numeri in virgola mobile: ";
cin f d; return j;

cout << "Immettere una stringa: ";


cin str; Poich la dichiarazione di j preceduta da un'istruzione eh.e esegue un'azio-
ne, il compilatore C individuer un errore e si rifiuter di compilare la funzione.
cout. << f << 11 11
<< d << 11 11
<< str; In C++ invece questo frammento di codice perfettamente corretto e verr com-
pilato senza alcun errore. In C++ le variabili locali possono essere dichiarate in
retJrn O; qualsiasi punto di un blocco e non solo all'inizio.
Ecco un'altra versione del programma contenuto nella sezione precedente, in
cui ogni variabile viene dichiarata nel momento in cui vi l'effettiva necessit.
Quando si esegue questo programma, si provi a immettere come stringa la
frase Questa una prova. Quando il programma visualizzer le informazioni
lii nel ude <i ostream>
immesse, presenter la sola parola "Questa". La parte rimanente della stringa non using namespace std;
verr visualizzata poich l'operatore termina la lettura della stringa nel mo-
mento in cui incontra il primo spazio. Pertanto, il resto della frase, ovvero " una int main()
prova.,, non verr mai letto dal programma. Questo programma illustra inoltre la {
possibilit di inserire pi operazioni di input in un'unica istruzione. __ float J;___ _
Gli operatori di I/O del C++ riconoscono tutte le costanti descritte nel Capito- doubl e d;
lo 2. Ad esempio perfettamente lecito scrivere: cout "Immettere due numeri in virgola mobile: ";
cin f d;
cout "A\tB\tC";
cout "Immettere una stringa: ";
char str[BO]; // str viene dichiarata appena prima del suo uso
Questa istruzione produce in output le lettere A, B e C separate da uno spazio
cin str;
di tabulazione.
cout << f << " " << d << " " << str;
La dichiarazione di variabm locali return O;
Chi proviene da un'esperienza di programmazione in C++ deve onoscere un'al-
tra importante differenza fra il codic~ ce.. C_++, ovvero la posizione in cui possi-
bile dichiarare le vaiiabililocali. In C, tutte le variabili localiutilizzate ctll'intemo La posizione in cui si dichiarano le variabili dipende quindi dal programmato-
re. Poich molta-della-teoria del C++ legata all'incapsulamento di codice e dati,
- ---- -~ __ :,. __ :._
274 CAPITOLO 11
p A N1JR AMI eA DEL LI N G u AGGI o e+ + 275

ha senso dichiarare le variabili il pi possibile vicino al luogo in cui vengono func(int i)


impiegate piuttosto che all'inizio del blocco. Nen~~e~p~o pre~edente, le_dichia: {
razioni sono state separate solo per semplificarne l md1v1duaz1one. facile pt;ro return i*i;
immaginare esempi pi calzanti in cui questa caratteristica del C++ risulta molto
pi importante. .. .
La dichiarazione di variabili vicino al luogo in cui verranno utilizzate aiuta a In C++ standard in questa funzione si deve specificare esplicitamente il tipo int.
evitare effetti collaterali indesiderati. In ogni caso, i maggiori benefici derivanti
dalla dichiarazione delle variabili nel luogo in cui vengono usate si ottengono int func(int i)
nelle funzioni pi estese. Francamente, nelle funzioni pi bre:i \come molti ~e~l~ {
esempi presenti in questa guida) non vi alcun motivo per d1~h1a:are le vana~1li return i*i;
in un luooo diverso dall'inizio della funzione. Per questo motivo, m questa gmda
le variablli verranno dichiarate nel luogo in cui vengono utilizzate per la prima
volta solo quando ci giustificato dalle dimensioni o dalla complessit di una In pratica, quasi tutti i compilatori C++ supportano ancora la regola della
funzione. trasformazione in int per motivi di compatibilit con il codice meno recente. Tut-
Vi un acceso dibattito sul luogo in cui sia pi saggio localizzare la dichiara- tavia si deve evitare di utilizzare questo automatismo nel nuovo codice poich in
zione delle variabili. Alcuni sostengono che spargendo le dichiarazioni all'inter- futuro tale trasformazione automatica non sar pi consentita.
no di un blocco, si complica e non si semplifica la lettura del codice poich pi
difficile trovare rapidamente le dichiarazioni di tutte le variabili utilizzate in tale
blocco, complicando inutilmente la manutenzione_ lel programma. Per que~to Il tipo di dati bool
motivo, alcuni programmatori C++ non sfruttano questa caratte~stica Questa gmda
non intende schierarsi in questo dibattito. Tuttavia, da considerare che quando Il linguaggio C++ definisce un tipo booleano chiamato bool. Al momento attuale,
viene applicata correttamente, specialmente nelle funzioni pi estese, la dichiara: Io standard per il C non prevede questo tipo. Gli oggetti di tipo bool possono
zione delle variabili nel punto in cui vengono utilizzate per la prima volta puo contenere i soli valori true e false che divengono parole riservate del linguaggio
aiutare nella realizzazione di programmi esenti da bug. C++. Come si detto nella Parte prima, il linguaggio esegue delle conversioni
automatiche che consentono di convertire i valori bool in valori interi e viceversa.
In particolare, ogni valore diverso da O viene convertito in true e Oviene converti-
Trasformazione automatica in int to in false. Si verifica anche la situazione opposta: true viene convertito in l e
false viene convertito in O. Pertanto, permane il concetto generale che prevede che
II linouaooio C++ stato recentemente sottoposto auna modifica che pu in- Oequivalga a false e che un valore diverso da O equivalga a true.
fluen;ar:-u vecchio codice C++ e anche la conversione del codice C in C++.Il
Iinouaooio Ce le specifiche oriainali del linguaggio C++ stabilivano che quando
in ~na dlchiarazione non era indicato esplicitamente un tipo, doveva essere impie-
gato il tipo int. Questa regola stata eliminata dal C++ un paio di anni _fa nella ~ase 11.4 C++ vecchio stile e C++ moderno
di standardizzazione. Probabilmente anche il prossimo standard del lmguagg10 C
eliminer questa regola che tuttavia attualmente in uso e viene impiegata da una Come si detto, il linguaggio C++ stato sottoposto a un processo evolutivo
grande quantit di programmi. Questa regola stata anche impiegata nel software piuttosto intenso durante la fase di sviluppo e standardizzazione. Questo ha porta-
C++ meno recente. . to all'esistenza di due versioni di C++.La prima la versione tradizionale che si
L'uso pi comune della regola di trasformazione in int riguarda il-tipo r~sri_tu basa sul progetto originale di Bjarne Stroustrup. Questa la versione di C++ che
ito dalle funzioni. Era infatti pratica comune non specificare esplicitamente 11 upo veniva utilizzata dai programmatori nel decennio scorso. La versione pi recente,
int quando farunzfone restituiva un risu~tato intero. Ad esem_pio, in C e nelle il C++ standard, stata creata da Stroustrup e dal comitato di standardizzazione
vecchie versioni di C++, la seguente funzione sarebbe stata val!da: ANSI/ISO. Anche se queste due versioni di C++ sono molto simili, il C++ standard
cont~ne numer.ose..estensioni che son<La.ssenti nell.a_y.er.sione precedente. Pertan-
to il C++ ~~'!da_rd rappresenta un "sovrainsi!!!e:':.ctel C++ tradizionale.
-=..:..:. ---~ -- - .
276 CAPITOLO 11 PAN OR AMI CA O ELTIN G\T'A'G G I OC-+_;--- 2iF--
Questo volume si occupa del linguaggio C++ standard, ovvero la versione di using namespace std;
C++ definita dal comitato di standardizzazione ANSI/ISO e implementata da tutti
i compilatori C++ recenti. Il codice contenuto in questo volume descrive lo stile int main()
{
di codifica e le pratiche di programmazione incoraggiate dallo standard. Tuttavia
return O;
se si usa un vecchio compilatore, potrebbe accadere che i programmi di questo
volume non vengano accettati. Ecco il motivo. Durante il processo di standardiz-
zazione, il comitato ANSI/ISO ha aggiunto al linguaggio molte nuove funzionali-
t. A mano a mano che queste funzionalit venivano definite, sono state imple- Questa versione utilizza il nuovo stile di header e specifica un namespace.
mentate dagli sviluppatori di compilatori. Naturalmente esiste sempre un inter- Entrambe queste funzionalit sono state accennate in precedenza e ora verranno
vallo di tempo fra l'aggiunta di una nuova funzionalit e la sua disponibilit nei descritte in modo pi approfondito.
compilatori commerciali. Poich tali funzionalit sono state aggiunte al C++ lun-
go un arco di alcuni anni, un compilatore non recente potrebbe non supportate una I nuovi header C++
o pi di esse. Questo particolarmente importante nel caso di due recenti aggiun-
te del linguaggio C++ che influenzano tutti i programmi, anche i pi semplici. Se Come si sa, quando si usa una funzione di libreria in un programma C, si deve
si usa un vecchio compilatore che non accetta queste nuove funzionalit, nessun includere il relativo file header. Tuie operazione viene eseguito tramite l'istruzio-
problema. Vi una soluzione rapida e agevole. ne #include. Ad esempio, in C, per includere il file header per le funzioni di I/O si
Le differenze principali fra codice "vecchio stile" e ~odice moderno riguarda- deve utilizzare la seguente istruzione:.
no due funzionalit: i nuovi header e l'istruzione namespace. Per comprendere
queste differenze, si partir da due versioni di un semplicissimo programma C++ #include <stdio.h>
che non fa assolutamente nulla. La prima versione riflette il modo in cui venivano
scritti i programmi C++ vecchio stile. Qui, stdio.h il nome del file utilizzato dalle funzioni di I/O e l'istruzione
precedente provoca l'inclusione fisica di tale file nel programma.
/* Quando venne inventato il linguaggio C++ e per molti anni, si usato lo stes-
Un progralll11a C++ vecchio stile.
so stile di inclusione dei file header ereditato dal C. Pertanto venivano utilizzati
*/ veri e propri file. Lo standard C++ supporta ancora l'inclusione di file header in
#include <iostream.h> stile C, ad esempio per tutti i file header creati dal programmatore oltre che per
motivi di compatibilit all'indietro.
int main() Ma lo standard per il C++ ha creato un nuovo tipo di header utilizzato dalla
{ libreria standard C++. I nuovi header C++ non specificano nomi di file ma
return O; identificatori standard che non necessariamente sono file. I nuovi header C++
sono un'astrazione che garantisce semplicemente la dichiarazione dei prototipi e
delle definizioni richiesti dalla libreria C++.
Si faccia attenzione all'istruzione #include. Tale istruzione include il file Poich i nuovi header non sono file, non. hanno l'estensione .h. Essi sono
iostream.h e non l'header <iostream>. Inoltre si noti che non vi alcuna istruzio- costituiti unicamente dal nome dell'header racchiuso fra parentesi angolari. Ad
ne namespace. Ecco la seconda versione di questo programma, riscritta in C++ esempio ecco alcuni dei nuovi header supportati dal C++ standard.
standard.
<iostream> <fstream> <vector> <string>
/*
Un moderno programma C++.che.utilizza I nuovi header sono inclusi utilizzando l'istruzione #include. L'unica diffe-
il nuovo stile header e un namespace. renza il fatto che i nuovi header non rappresentano necessariamente. file. Poich
------*/ il linguaggio C++ include l'intera libreria di funzioni C, supporta anche i file
#include <iostream> -- - - - - header C associati a tale li~i:_:ri~. Pertanto sono ancoraoispnibjll 1. file header
----- -------
- -
~-
_2Z_a__::_ _c A-:-P I T o L o 11 p AN o RAM I CA-Dt CL I rn3 u AGGI o e+ + -279---- -

stdio.h e ctype.h. Tuttavia lo standard del C++ definisce anche nuovi header da non fa altro che rendere visibile il namespace std (ovvero inserisce std nel
utilizzare in luogo di questi file. Le versioni C++ degli header aggiungono sem- _ namespace globale). Dopo la compilazione di tale istruzione, non vi alcuna
plicemente un prefisso "c" al nome del file e non usano il suffisso .h. Ad esempio, differenza fra lavorare con un file header vecchio stile o con un nuovo header.
il nuovo headerC++ di math.h <cmath>. Quello per string.h <string>. Anche se Un'ultima indicazione: per motivi di compatibilit, quando un programma
attualmente ancora possibile includere file header C quando si devono utilizzare C++ include un file header C come ad esempio stdio.h, il suo contenuto viene
delle funzioni della libreria C, tale approccio sconsigliato dal C++ standard. Per inserito nel namespace globale. Questo consente ai compilatori C++ di compilare
questo motivo, da questo punto in avanti si utilizzer l'istruzione #include unica- i programmi C.
mente con i nuovi header. Se il compilatore non dovesse supportare questi nuovi
header, baster sostituirli con le vecchie versioni in stile C.
Dato che i nuovi header sono un'aggiunta recente al linguaggio C++, si trove- Utilizzo di un vecchio compilatore
ranno moltissimi vecchi programmi che non li impiegano. Questi programmi uti-
lizzano i file header C. Ecco il metodo tradizionale di inclusione del file header Come si detto, sia i namespace che i nuovi header sono stati aggiunti piuttosto
per le funziolli di I/O. recentemente al linguaggio C++, durante la fase di standardizzazione. Dunque
non tutti i nuovi compilatori C++ potrebbero supportare queste funzionalit. In
#i nel ude <i ostream. h> questo caso il compilatore rilever uno o pi errori quando tenter di compilare le
prime due righe dei programmi presentati in questo volume. In questo caso vi
Questa istruzione provoca l'incfusione nel programma del file iostream.h. In una semplice soluzione: basta utilizzare i file header vecchio stile e cancellare
generale, un vecchio file header utilizza lo stesso nome del corrispondente nuovo l'istruzione namespace. In pratica basta sostituire:
header ma gli aggiunge il suffisso .h.
Al m~mento attuale, tutti i compilatori C++ supportano anche i vecchi file #include <iostream>
header. Tuttavia questa soluzione stata dichiarata obsoleta e dunque se ne scon- using namespace std;
siglia l'uso nei programmi di nuovo sviluppo. Questo il motivo per cui tale
approccio non verr impiegato in questo volume. con:

_SUGGERIMENTO Anche se i vecchi file header sono tuttora molto comuni, se ne #i nel ude <i ostream. h>
sconsiglia l'uso in quanto obsoleti.
Questa modifica trasforma un programma moderno in un programma vecchio
I namespace stile. Poich in questo caso il contenuto dei file header vecchio stile viene inserito
nel namespace globale, non vi alcuna necessit di impiegare l'istrzione
Quando si include un nuovo header in un programma, il contenuto di tale header namespace.
si trova nel namespace std. Un namespace semplicemente una regione di dichia- Un'ultima annotazione: per il momento e per i prossimi anni, si troveranno
razioni. Lo scopo di un namespace quello di localizzare i nomi degli identificatori molti programmi C++ che utilizzano i file header vecchio stile e non impiegano
per evitare conflitti. Gli elementi dichiarati in un namespace sono distinti rispetto l'istruzione namespace. Il compilatore non avr problemi a compilarli ma op-
agli elementi dichiarati in un altro namespace. Originariamente, i nomi delle fun- portuno realizzare i nuovi programmi in stile moderno per adeguarsi allo standard
zioni di libreria C++ venivano semplicemente inseriti nel namespace globale (si del linguaggio C++. Comunque le vecchie funzionalit continueranno ad essere
pensi ad esempio al C). Con la nascita dei nuovi header, il contenuto di questi supportate per anni.
header stato inserito nel namespace std. Si parler pi dettagliatamente dei
namespace-pi avanti in questo velume. Per il momento non occorre preoccupar-
sene troppo in quanto l'istruzione:
11.5 Introduzione alle classi C++
using namespace std;
Questa_~_ezione introduce. la funzionalit pi importante del C++: la classe. In
C++, per cre~un oggetto si deve innanzi tutto definire-la, sua forma ge~e!~~C:-
-----P7\NO-RAMICA D-E-l-LINGUAGGIO C++ 281
280 CAPITOLO 11

stack mystack;
utilizzando la parola chiave class. Una classe ha una sintassi simile a una struttu-
ra. Ecco un ~sempio. La seguente classe definisce un tipo chiamato stack utilizza-
to appunto per creare uno stack: Quando si dichiara un oggetto di una classe, si crea un'istanza di tale classe.
In questo caso, mystack un'istanza di stack. anche possibile creare oggetti nel
lu~go stesso ~n c~i viene definita la classe, specificandone il nome dopo la paren-
#defi ne SIZE 100
tesi graffa d1 chiusura, esattamente come avviene nel caso delle strutture. Per
Il Creazione della classe stack. ricapitolare: in C++ class crea un nuovo tipo di dati che pu essere utilizzato per
cl ass stack { creare oggetti di tale tipo. Pertanto, un oggetto un'istanza di una classe esatta-
i nt stck [S IZE] ; mente come altre variabili sono istanze, ad esempio del tipo int. In altre parole,
int tos; una classe un'astrazione logica, mentre un oggetto reale (ovvero esiste all'in-
public: terno della memoria del computer).
void init(); La forma generale di una dichiarazione di una semplice classe la seguente:
void push{int i);
int pop{); class nome-classe {
);
dati e }Unzioni private
public:
Una classe pu contenere parti private e pubbliche. In generale, tutti gli og-
dati e }Unzioni pubbliche
getti definiti all'interno di una classe sono privati. Ad esempio, le variabili stck e
} elenco oggetti;
tossono private. Questo significa che non sono visibili da nessun'altra funzione
che non sia un membro della classe. Questo uno dei modi in cui si ottiene
Naturalmente, l'elenco oggetti pu essere vuoto.
l'incapsulamento: l'accesso a determinati oggetti e dati pu essere controllato in
modo rigido mantenendoli privati. Anche se questo non viene illustrato dall' esem- All'interno della dichiarazione di stack, le funzioni membro sono identificate
dall'uso dei prototipi. In C++, tutte le funzioni devono avere un prototipo. In .altre
pio presentato, possibile definire funzioni private che possono essere richiamate
parole i prototipi non sono opzionali. Il prototipo di una funzione membro all'in-
solo dai membri della classe.
terno della definizione di una classe funge in generale da prototipo della funzione.
Per rendere pubblica (ovvero accessibile da altre parti del programma) una
parte della classe, necessario dichiararla esplicitamente come pubblica utiliz- Quando si dovr realizzare una funzione membro di una classe, si dovr dire
al compilatore la classe a cui appartiene la funzione, qualificandone il nome con il
zando la parola chiave public. Tutte le variabili e le funzioni definite dopo public
nome della classe di cui la funzione membro. Ad esempio, la funzione push()
possono essere utilizzate da tutte le altre funzioni del programma. Essenzialmen-
te, laparte rimanente del programma accede a un oggetto utilizzando le sue fun- pu essere codificata nel seguente _m~do: __ . __
zioni pubbliche. Anche se possibile avere variabili pubbliche, si deve in genera-
void stack: :push(int i)
le cercare di limitarne l'uso. Quindi si dovr cercare di rendere tutti i dati privati
{
e controllare l'accesso ai dati utilizzando funzioni pubbliche. Un'ultima annota-
if{tos==SIZE) {
zione: si noti che la parola chiave public seguita dal carattere di due punti. cout << "Stack esaurito.";
Le funzioni init(), push() e pop() sono chiamatefanzioni membro poich sono return;
membri della classe stack. Le variabili stck e tos sono chiamate variabili membro.
Si ricordi che un oggetto racchiude codice e dati. Solo le funzioni membro hanno stck[tos] i;
accesso ai membri privati della classe in cui sono dichiarate. Pertanto solo init(). tos++;
push{) e pop() potranno accedere a stck e tos. =- _
Dopo aver definito una classe, possibile creare un oggetto di tale tipo sem-
plicemente impiegando il nome della classe. In pratica, il nome della classe divie- . --L'operatore:: chiamato operatore di risoluzione del campo d'azione. Essen-
ne un nuovo sps;ci_ficatore di tig.0,_Ad esempio, la seguente istruzione crea l'ogget- zialmente, l'operatore dice al compilatore che questa versione di push() appartie-
to mystack di tipo st~~~ ___ ------- ----- - ne al!a Elasse stack o in altre parole, che push() nel campo d'azione di stack. In
--~--- - --~---~ ~-

~--
- - --==--- -- -
~

282 CAPITOLO 11 p A N o RA M I e A D E L L I N G uA G G I o e - -- 283

C++ lo stesso nome di funzione pu essere utilizzato da pi classi. Il compilatore void stack:: i nit()
pu detenninare la classe a cui appartiene una funzione grazie all'operatore di {
isoluzione del campo d'azione. tos = O;
Quando si fa riferimento a un membro di una classe da una parte di codice che
non si trova all'interno della classe stessa, l'operazione deve essere eseguita sem-
void stack::push(int i)
pre in congiunzione con un oggetto di tale classe. A tale scopo si deve utilizzare il
{
nome dell'oggetto seguito dall'operatore punto seguito a sua volta dal membro.
if(tos==SIZE) {
Questa regola si applica sia che si debba accedere a dati membro che a funzioni cout "Stack esaurito.";
membro. Ad esempio, il frammento di codice seguente richiama la funzione init() return;
per l'oggetto stack1.

stack stackl, stack2;


stck[tos]
tos++;
.
i

stackl. i nit():
int stack: :pop()
Questo frammento di codice crea i due oggetti stack1 e stack2 e poi inizializza {
stack1. molto importante comprendere che stack1 e stack2 sono due oggetti if(tos==O) {
distinti. Questo significa, ad esempio, che l'inizializzazione di stack1 non provo- cout << "Stack vuoto.";
return O;
ca l'inizializzazione anche di stack2. L'unica relazione che intercorre fra stack1 e
stack2 consiste nel fatto che sono oggetti dello stesso tipo. tos--;
All'interno di una classe, una funzione membro pu richiamare un'altra fun- return stck[tos];
zione membro oppure far riferimento direttamente ai dati membro senza utilizza-
re l'operatore punto. Il nome dell'oggetto e l'operatore punto devono essere uti-
lizzati solo quando l'accesso a un membro avviene da parte di codice che non int main()
appartiene alla classe. {
Il programma seguente raccoglie le parti illustrate finora e le parti mancanti e stack stackl, stack2; Il crea due oggetti della classe stack
mostra rutilizzo della classe stack:
stackl. i nit();
#include <iostream> stack2. i nit();
using namespace std;
stackl.push(l);
#defi ne SIZE 100 stack2. push (2);

Il Creazione della classe stack. stackl.push(3);


cl ass stack { stack2.push(4);
int s:ck[SIZE];
int tos; cout stackl.pop() " ";
public: cout s tackl. pop() " ";
foid i nit(); cout-..:< stack2.pop() " ";
void push(int i); cout << stack2.pop() "\n";
int pJp();
_}_;__ return O;

~----~ __ : . _
284 CAPITOLO 11 p A N o R A M I e A D E L L I N G u A s-s-1-e-- e-+- + 285

Ecco l'output del programma: cout abs(-10) "\n";

cout abs(-11.0) "\n";


3 1 4 2
cout abs (-9L) << "\n";
Un'annotazione: le parti private di un oggetto sono accessibili solo da parte
delle funzioni membro di tale oggetto. Ad esempio, l'istruzione: return O;

stackl. tos = O; 11 errore int abs(int i)


{
non pu trovarsi nella funzione main() del programma precedente poich tos cout "abs() per interi\n";
privata.
return i<O ? -i : i;

11.6 L'overloading delle funzioni double abs(double d)


{
Un modo utilizzato dal C++ per ottenere il polimorfismo consiste nell'uso di cout "abs() per double\n";
tecniche di overloading delle funzioni. In C++, due o pi funzioni possono condi-
return d<O.O ? -d : d;
videre lo stesso nome sempre che i loro parametri siano differenti. In questo caso,
si dice che le funzioni che condividono lo stesso nome sono state sovraccaricate
(o che hanno subito overloading) e il processo chiamato overloading delle fun- long abs(long 1)
zioni. {
Per vedere il motivo per cui l'overloading delle funzioni un fattore impor- cout "abs() per long\n";
tante, si considerino tre funzioni definite nel sottoinsieme C: abs(), labs() e fabs().
La funzione abs() restituisce il valore assoluto di un intero, labs() restituisce il return 1<O ? -1 : l ;
valore assoluto in un long e fabs() restituisce il valore assoluto di un double. An-
che se queste funzioni eseguono operazioni praticamente identiche, in e devono
essere rappresentate da tre nomi leggermente diversi. Questo complica la situa- Ecco l'output prodotto dal programma:
zione sia concettualmente che m pratica: Anche se il funzionamento di ogni fun-
zione identico, il programmatore dovr ricordare tre nomi al posto di uno. In abs() per interi
C++ invece possibile utilizzare lo stesso nome per le tre funzioni come si pu 10
vedere dall'esempio seguente: abs{) per double
11
fi nel ude <iostream> abs{) per long
using namespace std; 9

11abs assume tre significa ti grazie all 'overloading Questo programma crea tre funzioni simili ma differenti chiamate abs() ognuna
int abs(int i); -"- delle quali restituisce il valore assoluto del proprio argomento. II compilatore pu
double abs(double d); determinare la funzione d richiamare in un determinato momento sulla base del
--long abs(long 1); tipo dell'argomento. L'importanza delle funzioni modificate tramite overloading_
dovuta al fatto che consentono di accedere a un gruppo di funzioni correlate con
int rr.ain()
-_{--
un-unico nome. Pertanto,.il ~ome..abs().rappresenter I' azione generale che deve
286 CAPITOLO 11 p A N o R A M-re A;-1)-E-i:- eI N G u A G G l..0.. e + + 287

essere eseguita. Sar compito del compilatore scegliere il metodo specifico cor- 11 concatena una stringa con un intero convertito in stringa
retto per una determinata circostanza. Il programmatore dovr semplicemente void stradd(char *sl, int i)
ricordare l'azione generale d~ eseguire. Grazie al polimorfismo, al posto di tre {
oggetti baster ricordarsi di uno. Questo esempio molto semplice ma se si espande char temp [80] ;
il concetto, si pu immaginare come il polimorfismo possa aiutare a gestire pro-
sprintf(temp, "%d", i);
grammi molto complessi.
strcat (sl, temp);
In generale, per eseguire I' overloading di una funzione, basta dichiararne ver-
sioni diverse. Il resto rimarr a carico del compilatore. Quando si esegue
l'overloading di una funzione, si deve tenere conto di un'importante restrizione: il
In questo programma, la funzione stradd{) stata modificata tramite
tipo e/o il numero dei parametri di ogni funzione modificata tramite overloading
overloading. Una versione concatena due stringhe (proprio come strcat()). L'altra
deve essere diverso. Non sufficiente che le funzioni restituiscano semplicemen-
versione converte in stringa un intero e poi lo aggiunge a una stringa. In questo
te valori di tipo diverso. Devono essere diversi anche i tipi o il numero dei para-
caso l'overloading utilizzato per creare un'interfaccia che consenta di aggiun-
metri (non sempre il tipo restituito fornisce informazioni sufficienti che consenta-
gere a una stringa un'altra stringa oppure un valore intero.
no al compilatore di decidere la funzione da utilizzare). Naturalmente, le funzioni
possibile utilizzare lo stesso nome anche per eseguire I' overloading di fun-
modificate tramite overloading possono restituire valori di tipo diverso.
zioni non correlate ma questo utilizzo sconsigliato. Ad esempio, si potrebbe
- Ecco un altro esempio che impiega funzioni modificate tramite overloading:
utilizzare il nome sqr() per creare funzioni che restituiscano il quadrato di un
valore int e la radice quadrata di un valore double. Ma queste operazioni sono per
#include <iostream>
principio diverse. Questa applicazione dell'overloading delle funzioni contraria
#i nel ude <cstdi o>
ai suoi obiettivi (ed considerata un pessimo stile di programmazione). In pratica
#i nel ude <estri ng>
usi ng namespace std; si dovr eseguire l'overloading solo di funzioni strettamente correlate.

void stradd(char *sl, char *s2);


void stradd(char *sl, int i);