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);
11.7 l'overloading degli operatori
int main()
{ In C++ il polimorfismo pu essere ottenuto anche eseguendo l'overloading degli
char str[BO]; operatori. Come si sa, in C++ possibile utilizzare gli operatori e per ese-
guire operazioni di I/O dalla console. Questi operatori possono svolgere queste
strcpy(str, "Salve "); operazioni aggiuntive poich nell'header <iostream> questi operatori vengono
stradd(str, "a tutti"); modificati tramite overloading. Quando si esegue l' overloading di un operatore,
cout << str << "\n"; questo assume un significato aggiuntivo rispetto a una determinata classe. Nel
contempo continuer a conservare i suoi significati precedenti.
stradd(str, 100);
In generale, possibile eseguire l' overloading della maggior parte degli ope-
cout << str << "\n";
ratori C++ definendone il significato rispetto a una determinata classe. Ad esem-
return O;
pio, per tornare alla classe stack sviluppata precedentemente in questo capitolo,
possibile eseguire l'overloading dell'operatore+ rispetto a oggetti del tipo stack
in modo che l'operatore consenta di aggiungere il contenuto di uno stack al con-
-iI concatena due stringhe tenuto di un altro. In ogni caso, l'operatore +conserver il suo significato origina-
void stradd(char *sl, char *s2) rio rispetto ad altri tipi di dati.
{ Poich l'overlarungdegli operatori , nella pratica, un po' pi complesso
strcat(sl, s2); rispetto all'overloading delle funzioni, ne verranno presentati esempi solo a parti-
re dal Capitolo 14--- __ --- __ __ -
--- -------~
288 CAPITOLO 11
PANORAMICA DEL LINGUAGGIO C++ 289

11.8 l'ereditariet public:


voi d set_bedrooms (i nt num);
Come si detto in precedenza in questo capitolo, l'ereditariet uno dei fattori int get_bedrooms();
pi importanti di un linguaggio di programmazione a oggetti. In C++, lereditariet void set_baths(int num);
ottenuta consentendo a una classe di incorporare un'altra classe nella propria i nt get baths ();
dichiarazione. L'ereditariet consente la realizzazione di una gerarchia di classi, ); -
partendo dalla classe pi generale per giungere a quella pi specifica. 11 processo
richiede quindi la definizione di una classe base che definisce le qualit comuni a Si noti il modo in cui vengono ereditate le caratteristiche della classe building.
tutti gli oggetti che derivano da tale classe. La classe base rappresenta la descri- La fonna generale di ereditariet :
zione pi generale. Le classi che discendono dalla classe base sono chiamate clas-
si derivate. Una classe derivata include tutte le funzionalit della classe base e vi class classe-derivata : accesso classe-ereditata {
aggiunge qualit spcifiche. Per dimostrare il funzionamento di questa tecnica, il Il corpo della nuova classe
prossimo esempio crea una serie di classi che consente di catalogare diversi tipi di
edifici.
Per iniziare, viene dichiarata la classe building. Questa classe funger da base Qui, accesso opzionale ma quando presente deve essere public, private o
per due classi derivate. protected (queste opzioni verranno esaminate in dettaglio nel Capitolo 12). Per
ora, tutte le classi ereditate utilizzeranno lo specificatore di accesso public. L'uti-
cl ass buil di ng lizzo di public significa che tutti gli elementi pubblici della classe base diverranno
int rooms;
elementi pubblici anche delle classi derivate. Pertanto i membri pubblici della
int floors;
classe building diverranno membri pubblici della classe derivata house e saranno
i nt area;
public:
disponibili alle funzioni membro di house come se fossero state dichiarate all'in-
void set_rooms(int num); terno di house. Le funzioni membro di house non avranno per accesso agli ele-
i nt get rooms O; menti privati di building. Questo un fatto molto importante: anche se house
void set_floors(int num); eredita building, avr accesso solo ai membri pubblici di building. In questo modo,
int get_floors(); l'ereditariet non si scontra con i principi di incapsulamento necessari nella pro-
void set area(int num); grammazione a oggetti.
int get ;rea();
}; - . Una classe derivata pu accedere direttamente sia ai propri
__ suGG!=_Rl!-!ENTO
membri che ai membri pubblici della classe base da cui deriva.
Poich (semplificando) tutti gli edifici hanno tre caratteristiche in comune
Il seguente programma illustra l'uso dell'ereditariet. Il programma crea due
(una o pi stanze, uno o pi piani e un'area totale) la classe building incorpora
classi derivate di building facendo uso dell'ereditariet: una si chiama house e
queste componenti nella dichiarazione. Le funzioni membro che iniziano con set l'altra school.
impostano i valori dei dati privati. Le funzioni che iniziano con get restituiscono
tali valori. #include <iostream>
Ora possibile utilizzare questa definizione generica di edificio per creare using namespace std;
classi derivate che descrivono tipi specifici di edifici. Ad esempio, ecco una classe
derivata chiamataJ19use: cl ass buil di ng
int rooms;
11 house deriva da bui l di ng int floors;
cl ass house : publ i e buil di ng int area;
int bedrooms-;- - . publ ic:
int baths_;___ _ voi d set_re5oms (i nt num);
290 CAPITOLO 11 PANORAMICA DEL LINGUAGGIO C+ + 291

i nt get_rooms (); return rooms;


voi d set_ fl oors (i nt num);
int get_floors();
void set_area(int num); int building: :get_floors()
int get_area();
J: return f1 oors:

Il house deriva da building


class house : public building int building: :get_area()
i nt bedrooms;
int baths; return area;
public:
void set_bedrooms(int num);
int get_bedrooms(); void house: :set_bedrooms(int num)
void set baths(int num); {
i nt get_baths () ; bedrooms = num;
};

Il anche school deriva da building void house: :set_baths(int num)


class school : publlc building { {
int classrooms; baths = num;
i nt offi ces;
public:
void set_classrooms(int num); int house: :get_bedrooms ()
int get_classrooms(): {
void set_offices(int num); return bedrooms:
i nt get_offi ces () ;
};
int house: :get_baths()
void building::set_rooms(int num) {
{ return baths;
rooms :_ num;

void school: :set_classrooms(int num)


void building: :set_floors(int num) {
{ cl assrooms = num:
fl oors = num;

voi d schoo 1 : : set_offi ces (i nt num)


void building: :set_ar~a(int num) {
{ offices-s num;
area = num;

int school: :get_classrooms()


-int building::ge-t_rooms() {
{
CAPITO L0 _1_1_______ - -
292 PANORAMICA DEL LIN-GUAGGIO C++ 293

return cl assrooms; Parlando del C++, per descrivere la relazione di ereditariet in genere si usano
i termini classe base e classe derivata, ma talvolta si dice anche classe genitore e
classe figlia oppure superclasse e sottoclasse.
int school: :get_offices() Oltre a fornire ivaritaggi della classificazione gerarchica, l'ereditariet funge
anche da supporto per il polimorfismo al momento dell'esecuzione (run-time),
return offices;
grazie al meccanismo delle funzioni virtuali (per informazioni consultare il Capi-
tolo 16).
int main()
{
house h;
school s;
11.9 I costruttori e i distruttori

h.set rooms(12); molto comune che una parte di un oggetto debba essere inizializzata prima
h.set=floors(3); dell'uso. Ad esempio, per tornare alla classe stack sviluppata precedentemente in
h. set_area(4500); questo capitolo, prima di iniziare a utilizzare lo stack, tos deve essere inizializzata
h.set bedrooms(S); a zero. Questo si ottiene richiamando la funzione init(). Poich capita molto spes-
h. set)aths (3); so di dover inizializzare un oggetto, il C++ consente di inizializzare gli oggetti al
momento della creazione. Questa inizializzazione automatica ottenuta grazie
cout << "La casa ha " h.get_bedrooms (); all'impiego di una funzione costruttore.
cout " camere da letto\n"; Unafanzio~e costruttore una particolare funzione membro di una classe che
porta lo stesso nome della classe. Ad esempio, ecco laspetto della classe stack
s. set_rooms (200);
convertita in modo da utilizzare una funzione costruttore per l'inizializzazione:
s.set_classrooms(l80);
s. set_offi ces(S);
s. set_area(25000); Il Creazione della classe stack.
cl ass stack {
cout "La scuola ha" s.get_classrooms(): int stck[SIZE];
cout << " classi\n"; int tos;
cout << "La sua area di " s.get_area(); publ ic:
stack (); 11 costruttore
return O; void push(int i);
int pop();
};

Ecco l'output prodotto dal programma:


Si noti che per il costruttore stack() non viene specificato il tipo restituito. In
La casa ha 5 camere da 1etto C++ le funzioni costruttore non possono restituire valori e pertanto non si deve
La scuola ha 180 classi specificare il tipo restituito.
La sua area di 25000 La funzione stack() pu essere codificata nel seguente modo:

-come si vede da questo programma, il vantaggio principale dell'ereditariet 11 funzione costruttore dello stack
consiste nel fatto che possibile_xeare_ una classificazione generale che pu es~e stack: :stack()
{
re incorporata in oggetti pi specifici. In questo modo, ogni oggetto pu rappre-
tos = O;
_ _____sentar_e_con precisione la propria sottoclasse. cout "Stack inizialiZzato\n..-;------
-~---------P_A_N_O_R_A_M_l_;C_A_IJ_;E_;L::.__L_l-_N_;G_;_A_-_;_G_;G...;.1..:.0_.:.C_.,._.,._-_.....;2=.::95-----

Si deve ricordare che il messaggio Stack inizializzato viene prodotto solo per tos = O;
illustrare l'uso del costruttore. Nella pratica, la maggior parte delle funzioni cout "Stack inizializzato\n";
costruttore non ha bisogno ne di input ne.di output. Semplicemente !~funzione si
occupa di eseguire varie inizializzazioni.
Il funzione distruttore dello stack
Il costruttore di un oggetto viene richiamato automaticamente nel momento stack: :-stack()
in cui deve essere creato l'oggetto. Questo significa che viene richiamata al mo- {
mento della dichiarazione dell'oggetto. Se si abituati a pensare che una dichia- cout << "Stack distrutto\n";
razione sia un'istruzione passiva, occorre prepararsi a cambiare idea. In C++ una
dichiarazione un'istruzione che viene eseguita come qualunque altra. La distin-
zione non puramente accademica. Il codice eseguito per costruire un oggetto Si noti che, come le funzioni costruttore, le funzioni distruttore non restitui-
pu essere anche molto ingente. Il costruttore di un oggetto viene richiamato una scono valori.
sola volta per ogni oggetto globale o locale static. Nel caso di oggetti locali, il Per vedere il funzionamento dei costruttori e dei distruttori, ecco una nuova
costruttore viene richiamato ogni volta che si incontra la dichiarazione di un nuo- versione del programma stack esaminato precedentemente in questo capitolo. Si
vo oggetto. noti che non pi necessario utilizzare la funzione init().
L'operazione complementare del costruttore svolta dal distruttore. In molte
circostanze, un oggetto deve eseguire una o pi azioni nel momento in cui ne #include <iostream>
finisce l'esistenza. Gli oggetti locali vengono costruiti nel momento in cui si entra usi ng namespace std;
nel blocco in cui si trovano e vengono distrutti all'uscita dal blocco. Gli oggetti
globali vengono distrutti nel momento in cui termina il programma. Quando vie- #defi ne SIZE 100
ne distrutto un oggetto, viene automaticamente richiamato il relativo distruttore
(se presente). Vi sono molti casi in cui necessario utilizzare una funzione di- Il Creazione della classe stack.
struttore. Ad esempio, potrebbe essere necessario deallocare la memoria prece- class stack {
dentemente allocata dall'oggetto oppure potrebbe essere necessario chiudere un int stck[SIZE];
file aperto. In C++ la funzione distruttore a gestire gli eventi di disattivazione. Il int tos;
public:
distruttore ha lo stesso nome del costruttore ma preceduto dal carattere -. Ad
stack(); Il costruttore
esempio, ecco una classe stack con le relative funzioni costruttore e distruttore (in
-stack(); 11 distruttore
realt la classe stack non richiede l'uso di un distruttore che viene presentato a void push(int i);
puro scopo illustrativo). int pop();
};
Il Creazione-della classe stack.
cl ass stack { 11 funzione costruttore dello stack
int stck[SIZE]; stack:: stack ()
int tos; {
publ ic: tos = O;
stack(); 11 costruttore cout "Stack inizializzato\n";
-stack (); 11 distruttore
void push(int i);
int pop(); 11 funzione distruttore dello stack
}; -- stack: :-stack()
{
11 funzione costruttore dello stack cout "Stack distrutto\n";
stack: :stack()
{
296 CAPITOLO 11

11.1 O Le parole riservate del C++


void stack: :push (i nt i}
{ Attualmente lo Standard per il linguaggio C++ definisce 63 parole riservate, elen-
if(tos==SIZE} {
cout <<-"Stack esaurito.";
cate nella Thbella 1I.I. Insieme alla sintassi formale del linguaggio esse costitui-
return;
scono il nucleo del linguaggio C++. Le versioni meno recenti di C++ definivano
}
anche la parola overload che oggi obsoleta. Si deve tenere in considerazione il
stck[tos] i; fatto che il C++ distingue fra lettere maiuscole e minuscole e che pertanto le
tos++; parole riservate devono essere scritte in lettere minuscole.

Tabella 11.1 Le parole chiave del C++.


int stack: :pop(}
{ asm auto bool break
i f(tos==O} { case catch char class
cout "Stack vuoto.";
return O; const cons!_cast continue default

delete do double dynamlc_cast


tos--;
return stck[tos]; else enum explicit export

extern false float lor

int main(} frieng goto lnline


{
stack a, b; Il crea due oggetti della classe stack in! long mutable namespace

new operator private protected


a.push(l};
b. push (2}; public register reinterpreLcast return

short signed sizeof static


a.push(3);
b.push(4}; static_cast struct
____
swilch
, ______
tempiate

this throw true try


cout a.pop(} << " ";
cout a.pop(} " "; typedef typeid typename union
cout 6.pop(} " ";
cout b.pop(} "\n"; unsigned using virtual VOid

volatile wchar_t while


return O;

Il programma produce il seguente output: 11.11 La forma generale di un progra~i:na C++


Stack inizializzato Anche se esistono vari stili di programmazione, la maggior parte dei programmi
Stack inizializzato C++ haIaseguente forma generale:
.3 1 4 2
Stack di strutto __ ~ #inc!!)d__
Stack di strutto
dichiarazioni aelle class05se_
298 CAPITOLO 11

dichiara:::ioni delle classi derivate Capitolo 12


prototipi delle funzioni non-membro
int main() Le classi e gU oggetti
{
12.1 Le classi
12.2 Le strutture e le classi
12.3 Le unioni e le classi
definizioni delle funzioni non-membro 12.4 Le funzioni friend
12.5 Le classi friend
~folla
maggior parte dei progetti pi estesi, tutte le dichiarazioni delle classi 12.6 Le funzioni inline
verranno inserite in un file header e incluse in ogni modulo ma l'aspetto generale
del programma rimarr lo stesso. 12.7 Definizione di funzioni inline
all'interno di una classe
I prossimi capitoli esaminano in dettaglio le funzionalit introdotte in questo
12.8 I costruttori parametrizzati
capitolo insieme ad altri aspetti del linguaggio C++.
12.9 I membri static di una classe
12.10 Quando vengono eseguiti
i costruttori e i distr1,1ttori?
12.11 !.!operatore di risoluzione
del campo d'azione
12.12 La nidificazione delle classi
12.13 Le classi locali
12.14 Il passaggio di oggetti a funzioni
12.15 La restituzione di oggetti
12.16 L'assegnamento di oggetti

,. n C++, la classe costituisce la base della programma-


zione a oggetti. In particolare, la classe definisce la natura di un oggetto ed
l'unit principale di incapsulamento del C++.In questo capitolo vengono esami-
nati in dettaglio le classi e gli oggetti.

12.1 le classi
Le classi vengono create mediante la parola chiave class. La dichiarazione-di una
classe definisce un nuovo tipo che racchiude sia il codice che i dati. Questo nuovo
tipo verr utilizzato perdichiarare oggetti di tale classe. Pertanto, una classe
un'astrazione logica mentre un oggetto ha esistenza fisica. In altre parole, un og-
getto un'-istan.:;a-di.una class..::______ . _______ .
-------=..:::::..=..-_ ___ --
300 CAPi10LO 12 LE CLASSI E GLI OGGETTI 301

La dichiarazione di una classe sintatticamente simile a quella di una struttu- class employee {
ra. Nel Capitolo 11 si mostrata una forma generale e semplificata della dichiara- char name[80]; Il dichiarazione privata
zione di una classe. Di seguito viene presentata la forma generale completa della public:
dichiarazione di una classe che non erediti propriet da altre classi. void putname(char *n); Il pubbliche
void getname(char *n);
private:
class nome-classe{
double wage; Il ancora privata
dati e funzioni privati public:
specificatori di accesso: void putwage(double w); Il di nuovo pubbliche
dati e funzioni doub 1e getwage () ;
specificatori di accesso:
dati e funzioni
void employee: :putname(char *n)
{
strcpy(name, n);
specificatori di accesso:
dati e funzioni
void employee: :getname(char *n)
} elenco oggetti; {
strcpy(n, name);
L'elenco oggetti opzionale. Se presente dichiara gli oggetti di tale classe.
Qui la parte specificatori di accesso pu essere rappresentata da una di queste tre
parole chiave del C++: void employee: :putwage(double w)

public wage = w;
prirnte
protected
double employee: :getwage()
{
Le funzioni e i dati dichiarati all'interno di una classe sono normalmente pri-
return wage;
vati di tale classe e possono essere utilizzati solo dagli altri membri della classe.
-}--------
Utilizzando lo specificatore di accesso public si consente per anche ad altre parti
del programma di accedere alle funzioni o ai dati della classe. Lo specificatore di int main()
accesso protected richiesto solo in caso di ereditariet (vedere il Capitolo 15). {
Una volta utilizzato, uno specificatore d'accesso rimane attivo finch non viene employee ted;
indicato un altro specificatore di accesso o finch non viene raggiunta la fine della char name[BO];
dichiarazione della classe. All'interno della dichiarazione di una classe, possibi-
le cambiare specificatore di accesso il numero di volte desiderato. Ad esempio ted.putname("Mario Rossi");
possibile utilizzare lo specificatore public per un gruppo di dichiarazioni e poi ted.putwage(75000);
tornare allo specificatore private. Questa possibilit esemplificata dalla dichia-
razione della seguente classe: -ted.getname(name);
cout << name << " guadagna ";
cout ted.getwage() " Kli re 1 1 anno.";
1include <iostream>
. ii nel ude <cstdng>-.__ _
return O;
us~ng namespace std; - _

-a---- - - -
----
302 CAPITOLO 12 -----------~-__::L:..::E~C:..::L:..::A:..::S:..::S:_:l_..::.E_G~L.'._1~0'._'.G::_:G~-.:_E.:_T.:_T:_I-~3~0-3 - -- -- - -

Qui, employee una semplice classe che pu essere utilizzata per memorizza- #i nel ude <i ostream>
re il nome e lo stipendio di un dipendente. Si noti che lo specificatore di accesso using namespace stc:i;
public viene utilizzato due volte.
Anche se all'interno della dichiarazione di una classe possibile utilizzare gli ...cJ ass mycl ass {
pub li e:
specificatori di accesso il numero di volte desiderato, l'unico vantaggio che si
int i, j, k; //accessibile all'intero programma
trarr consiste nella maggior facilit di lettura e di comprensione del programma. };
Dal punto di vista del compilatore invece l'uso di pi specificatori di accesso non
fa alcuna differenza. I programmatori invece trovano in genere pi comodo avere int main()
una sezione private, una sezione protected e una sezione public in ogni classe. Ad {
esempio, la maggior parte dei programmatori C++ utilizzer una ~lasse employee mycl ass a, b;
simile alla seguente, con tutti gli elementi privati e pubblici raggruppati. a. i = 100; // accesso di retto a i, e k
a.j = 4;
class employee { a.k = a.i * a.j;
char name[BO];
double wage; b.k = 12; //attenzione, a.k e b.k sono diverse
publ i e: cout << a. k << " " << b. k;
void putname(char *n);
void getname(char *n); return O;
void putwage(double w);
doub 1e getwage () ;

Le funzioni dichiarate all'interno di una classe sono chiamatefimzioni mem- 12.2 Le strutture e le classi
bro. Le funzioni membro possono accedere a tutti gli elementi della classe di cui
fanno parte e quindi anche agli elementi private. Le variabili che sono elementi di Le strutture fanno parte del sottoinsieme che il C++ ha ereditato dal C. Come si
una classe sono chiamate variabili membro o dati membri. In senso generale, tutti visto, una classe molto simile a una struttura. Ma le relazioni che legano le classi
gli elementi di una classe sono detti membri di tale classe. e le strutture sono anche maggiori di quanto possa sembrare. Il C++ ha elevato il
Sono poche le restrizioni applicabili ai membri di una classe. Una variabile ruolo della classica struttura C a quello di metodo alternativo per la creazione di
membro non .static non pu avere un inizializzatore. Nessun membro pu essere una classe. Infatti l'unica differenza fra una classe e una struttura il fatto che
un oggetto della classe dichiarata (anche se i memoro--pu essere un puntatore normalmente tutti i membri di una struttura sono pubblici e tutti i membri di una
alla classe dichiarata). Nessun membro pu essere dichiarato come auto, extern o classe sono privati. In tutti gli altri sensi, le strutture e le classi sono equivalenti.
register. In generale, si dovranno rendere tutti i dati membri di una classe privati questo. sig?~fica che in C++ una struttura definisce un tipo di classe. Ad esempio,
di tale classe. Questo consente di mantenere l'incapsulamento dei dati. Tuttavia vi s1 cons1den il breve programma seguente che utilizza una struttura per dichiarare
possono essere situazioni in cui si devono rendere pubbliche una o pi variabili una classe che controlla l'accesso a una stringa.
(ad esempio per una variabile molto utilizzata potrebbe essere necessario consen-
tire un accesso globale in modo da ottenere tempi di esecuzione pi rapidi). Quando #include <iostream>
#i nel ude <estri ng>
una variabile pubblica, possibile accedere ad essa direttamente da qualsiasi
using namespace std;
punto del programma. La sintassi di accesso a dati membri pubblici la stessa di
una chiamata a una funzione membro: si deve specificare il nome detl'oggetto. il struct mystr {
punto e il nome della variabile. Il semplice programma seguente illustra l'uso di void buildstr{char *s); //_pubblica
una variabile pubblica. void showstr();
private: // Q1'.iLJ2ilill _a.l privato
- - - - - - - __LE CLASSI E GLI OG-GET}I 305
304 -c-A-PITOLO 12

consente di far evolvere la definizione di classe. Per fare in modo che il C++
char str[255]; conservi la compatibilit con il C, struct deve invece mantenere il significato ori-
ginale che ha in C.
void mystr: :buildstr(char *s)
Anche se possibile utilizzare una struttura al posto di una classe, questo
generalmente sconsigliabile. In generale si dovr utilizzare una classe quando nel
if(!*s) *str = '\O'; Il inizializzazione della stringa programma si avr bisogno di una classe e una struttura quando si deve realizzare
else strcat(str, s); una classica struttura C. Questo anche lo stile seguito in questa guida.
'.SUGGERIMENTO In C++ la dichiarazione di una struttura definisce un tipo di
voi d mystr:: showstr() classe.
{
cout << str << "\n";

12.3 Le unioni e le classi


int main()
{ Per definire una classe, oltre a una struttura si pu utilizzare una union. In C++ le
mystr s; unioni possono contenere funzioni membro e variabili membro. Inoltre le unioni
possono includere funzioni costruttore e distruttore. In C++ un'unione conserva
s.buildstr('"'); Il init tutte le sue funzionalit e, la pi importante delle quali il fatto che i dati possono
s.buildstr("Salve "); condividere la stessa posizione in memoria. Come nel caso delle strutture, i mem-
s.buildstr("a tutti! 11 ) ; bri delle unioni sono normalmente pubblici e sono completamente compatibili
con il C. Nel prossimo esempio verr utilizzata un'unione per scambiare i due
s.showstr(); byte che compongono un intero unsigned short (in questo esempio si presuppone
che un intero short occupi 2 byte).
return O;

#include <iostream>
using namespace std;
Questo programma visualizza la stringa Salve a tutti!
La classe mystr pu essere riscritta utilizzando una classe nel modo seguente:
uni on swap byte {
voi d swap () :
-- -- -clas-s mystr {- void set_byte(unsigned i);
char str-[255]; void show_word();
publ ic:
void buildstr(char *s); //pubblica unsigned u;
void showstr(); unsigned c.har c[2);
}:

Ci si potrebbe chiedere il motivo per cui il C++ contenga due parole chiave void swap_byte::swap()
praticamente equivalenti come struct e class. Questa che sembra una ridondanza {
giustificata da-vari motivi. Innanzi tutto non vi alcun motivo per non espandere unsigned _char t;
le funzionalit di una struttura. In C le strutture forniscono gi un mezzo per
raggruppare i dati, pertanto, basta poco per consentire che includano funzioni t = c[O];
membro. In secondo luogo, poich le strutture e le classi sono correlate fra loro. c[O] = c[l];
_ill_L::__t_;
pu essere pi facile trasportare i programmi C in-++. Infine;-anche se str~ct e
__j_
class 5orfu-oggi praticamente equivalenti, la presenza di due-diverse parole ch1aYe
- -- -~-=.______:-:_,
306 CAPITOLO 12 LE CLASSI E GLI OGGETTI 307

void swap_byte::show_word() #i nel ude <i os t ream>


#i nel ude <estri ng>
cout u; usi ng namespace std;

int main()
void swap_byte::set_byte(unsigned i) {
{ Il definisce. un'unione anonima
u = i; union {
long l;
double d;
int main() char s[4];
{
swap_byte b;
Il riferimento diretto agli elementi di un'unione
b.set byte(49034); l = 100000;
b.swap(); cout << 1 << 11 " ;
b.show_word(); d = 123.2342;
cout << d << 11 11 ;
return O; strcpy(s, "hi ");
cout s;

Come per le strutture, anche la dichiarazione di un'unione definisce in C++ return O;


un tipo particolare di classe. Questo significa che il principio di incapsulamento
viene sempre conservato.
Vi sono alcune restrizioni che necessario osservare quando si utilizzano . Come si pu vedere, i riferimenti agli elementi dell'unione, avvengono come se
unioni C++. Innanzi tutto un'unione non pu ereditare propriet da altre classi. s~ trattasse di variabili dichiarate come comuni variabili locali. Infatti, dal punto di
Inoltre un'unione non pu essere una classe base. Un'unione non pu contenere vista del programma, questo esattamente ci che avviene. Inoltre, anche se sono
funzioni membro virtuali (le funzioni virtuali verranno discusse nel Capitolo 17). definite all'interno della dichiarazione di un'unione, queste variabili hanno lo stes-
Nessuna variabile static pu essere membro di un'unione, n si pu usare un s~ li_vello di ~isibilit di ogni altra variabile presente nello stesso blocco. Questo
membro rappresentato da un indirizzo. Un'unione non pu avere come membro s1gmfica che i nomi dei membri di un'unione anonima non devono entrare in con-
oggetti che eseguono I' overloading dell'operatore =. Infine nessun oggetto che flitto con altri identifica:tOn1ioti all'interno del campo di visibilit di un'unione.
abbia associata un'esplicita funzione costruttore o distruttore pu essere membro Alle unioni anonime si applicano tutte le restrizioni viste nel caso delle comu-
di un'unione. ni unioni con le seguenti aggiunte. Innanzitutto un'unione anonima pu contenere
solo dati; non consentito quindi l'uso di funzioni membro. Le unioni anonime
non possono contenere elementi private o protected. Infine, le unioni anonime
Unioni anonime globali devono essere specificate come static.
In C++ vi un tipo particolare di unione chiamata unione anonima. Un'unione
anonima non include il nome del tipo e pertanto non consente la dichiarazione di
variabili di tale tipo. Infatti un'unione anonima dice al compilatore che le variabi- 12.4 -~e funzioni friend
li membro dell'unione devono condividere la stessa locazione di memoria. I rife-
rimenti alle variabili avvengono per direttamente senza utilizzare l'operatore Una funzione friend (letteralmente "amica") pu accedere a tutti i membri private
__ punto. Ad esegi.pio, si consideri questo programma: e protected della classe per la quale dichiarata come friend. Per dichiarare una

--~-
------- --
308 CAPITOLO 12 LE CLASSLE GLI OGGETTI 309

funzione friend, se ne deve includere il prototipo nella classe, facendole precedere Anche se non si trae alcun vantaggio dal fatto che sum() sia friend piuttosto
la parola chiave friend. Si consideri il seguente programma: che membro di myclass, vi sono alcuni casi in cui le funzioni friend sono
insostituibili. Innanzi tutto le funzioni friend possono essere utili quando si deve
#include <iostream> eseguire l'overloading di alcuni tipi di operatori (vedere il Capitolo 14). In secon-
using namespace std;
do luogo le funzioni friend semplificano la creazione di alcuni tipi di funzioni di I/
O (vedere il Capitolo 17). La terza situazione in cui pu essere utile l'uso di
cl ass mycl ass
int a, b;
funzioni friend si verifica quando due o pi classi contengono membri correlati
publ ic: con altre parti del programma. Per iniziare si esaminer questo terzo uso.
friend int sum(myclass x); Si immagini che esistano due diverse classi ognuna delle quali visualizza un
void set_ab(int i, int j); messaggio sullo schermo nel caso si verifichi una condizione di errore. In altre
}; parti del programma potrebbe essere necessario conoscere se sullo schermo
attualmente visualizzato un messaggio d'errore prima di iniziare a scrivere sullo
void myclass: :set_ab(int i, int j) schermo (in modo da evitare che il messaggio d'errore possa essere accidental-
{ mente cancellato dal nuovo messaggio). Si potrebbe creare una funzione membro
a= i; in ciascuna classe che restituisca un valore il quale indichi se sullo schermo
b = j; attivo un messaggio ma questo richiede laggiunta di ulteriore codice per verifica-
re la condizione (ovvero due chiamate di funzione al posto di una). Se la condizio-
ne deve essere verificata frequentemente, la continua ripetizione di verifiche po-
Il Nota: sum() non una funzione membro di alcuna classe.
i nt sum(mycl ass x)
trebbe risultare inaccettabile. Se invece si impiega una funzione che sia friend di
{ entrambe le classi, sar possibile verificare lo stato di ogni oggetto richiamando
I* Poi ch sum() fri end di mycl ass, una sola funzione. Pertanto, in questo genere di situazioni, una funzione friend
pu accedere di rettamente ad a e b. *I consente di generare codice pi efficiente. Il concetto illustrato dal seguente
programma.
return x.a + x.b;
#include <iostream>
usi ng namespace std;
int mainO
{ const int IOLE = O;
myclass n; const int INUSE = 1;

n.set_ab{3, 4); class C2; Il dichiarazione forward

cout sum(n); class Cl {


int status; Il IOLE= off, INUSE = sullo schermo
return O;
Il
publ ic:
void set_status(int state);
In questo esempio, la funzione sum() non un membro di myclass. Nonostan- friend int idle(Cl a, C2 b);
te questo, la funzione ha pieno accesso ai membri privati della classe. Inoltre. si };
noti che sum() viene chiamata senza usare l'operatore punto. Poich non si tratta
di una funzione membro, la funzione rion deve essere qualificata dal nome del- class C2 {
____r qggetto. int status; Il IOLE = off, INUSE = sullo schermo
Il ...
- - - -------
310 C A P I TOLO 1 2 LE CLASSI E GLI OGGETTI 311

publ ic: Una funzione friend di una classe pu anche essere membro di un'altra. Ad
void set status(int state); esempio, nel seguente programma la funzione idle() un membro di C1:
friend i~t idle(Cl a, C2 b);
}; #i ne 1ude <i os t ream>
using namespace std;
voi d Cl: :set_status (int state)
{ const int IOLE = O;
status = state; const int INUSE = l;

class C2; Il dichiarazione anticipata


void C2: :set_status(int state)
{
class Cl {
statlls = state; int status; Il IOLE = off, INUSE = sullo schermo
Il
public:
int idle(Cl a, C2 b) void set_status(int state);
{
int idle(C2 b); //ora un membro di Cl
if(a.status Il b.status) return O; };
else return l;
class C2
int status; 11 IOLE = off, INUSE = sullo schermo
int main()
{
Il ...
publ ic:
Cl x; void set_status(int state);
C2 y;
friend int Cl::idle(C2 b);
};
x.set_status(IDLE);
y .set_status(IDLE);
void Cl::set_status(int state)
{
if(idle(x, y)) cout "Si pu usare lo schermo. \n";
status = state;
else cout << "In uso. \n";

x.set_status(INUSE);
void C2: :set_status(int state)
{
if(idle(x, y)) cout "Si pu usare lo schermo. \n";
status = state;
else cout "In uso.\n";

return O;
11 i dl e() membro di Cl e fri end di C2
int Cl::idle(C2 b)
{
Si noti ~he questo programrnl'lillilizza una dichiarazioneforward (anticipata) if(sttus 11 b.status) return O;
per la classe C2, Questa dichiarazione anticipata necessaria poich la dichiara- else return l;
zione di idle() all'interno di C1 fa riferimento a C2 prima che questa venga dichia-
rata. Per creare una dichiarazione anticipata di una classe, basta utilizzare la for-
ma mostrata in questo programma. ---- int main\)
312 CA P I T O LO 1 2 L E C C-A S S-1 E G L I O GcrE1 TT 313

};
Cl x;
C2 y; class Min
publ i c:
x.set_status(IDLE); int min(TwoValues x);
y.set_status(IDLE); };

if(x.idle(y)) cout "Si pu usare lo schermo. \n"; int Min::min(TwoValues x)


else cout <<"In uso.\n"; {
return x.a < x.b ? x.a : x.b;<
x.set_status(INUSE);

if(x.idle(y)) cout "Si pu usare lo schermo.\n"; int main()


else cout "In uso. \n"; {
TwoVal ues ob(lO, 20);
return O; Min m;

cout m.min(ob);
Poich idle() un membro di C1, pu accedere direttamente alla variabile
status di oggetti di tipo C1. Pertanto, alla funzione idle() baster passare oggetti di return O;
tipo C2.
Vi sono due importanti restrizioni che si applicano alle funzioni friend. Innan-
zi tutto, una classe derivata non eredita funzioni friend. In secondo luogo, una Qui, la classe Min ha accesso alle variabili membro a e b nella classe TwoValues.
funzione friend non pu essere dotata di uno specificatore di classe di fondamentale comprendere che quando una classe friend di un'altra, ha
memorizzazione ovvero non pu essere dichiarata come static o extern. solamente accesso ai nomi definiti nell'altra classe ma non eredita le caratteristi-
che dell'altra classe. In particolare i membri della prima classe non divengono
membri della classe friend.
Nella pratica, le classi friend vengono raramente impiegate. La loro presenza
12.5 Le classi friend consente semplicemente di gestire alcune situazioni molto particolari.
anche possibile che un'intera classe sia friend di un'altra classe. In questo caso,
la classe friend e tutte le sue funzioni membro avranno accesso ai membri privati
definiti all'interno dell'altra classe. Si consideri il seguente esempio. 12.6 Le funzioni inline

//Uso di una cl asse fri end In C++ vi una funzionalit molto importante chiamatafunzione inline, comune-
#include <iostream> mente impiegata all'interno delle classi. L parte rimanente di questo capitolo (e
using namespace std; dell'intera guida) far largo impiego di questa funzionalit.
In C++ possibile creare brevi funzioni che non vengono mai effettivamente
class TwoValues richiamate; il loro codice viene infatti espanso nel punto in cui dovrebbero essere
fot a;
richiamate e questo JJ! rende simili alle macro-funzi_Q!li del C. Per fare in modo.
int b;
publ ic:
che una funzione venga espansa in linea invece che richiamata, si deve far prece-
'rwoValues(int i, int j) { a= i; b = j; } --dere-alla sua definizione la parola chiave inline. Ad esempio, nel seguente pro-
friend class- Min;---- gramma la funzione max() non viene richiamata ma espansa in linea.

-_-.::..:_- - .
314 CA P I T O LO 1 2
LE C LASSI E G LI O G G ET TI- 315

#i nel ude <i ostream>


solo funzioni molto brevi. Inoltre, preferibile espandere in linea solo quelle fun-
using namespace std; zioni che hanno un impatto significativo sulle prestazioni del programma.
Come nel caso dello specificatore register, anche inline una semplice richie-
inline int max(int a, int b) sta per il compilatore e non un-comando. Il compilatore pu quindi decidere di
{ ignorare tale richiesta. Inoltre, alcuni compilatori potrebbero rifiutare di espande-
return a>b ? a : b; re in linea alcuni tipi di funzioni. Ad esempio difficilmente un compilatore espan-
der in linea funzioni ricorsive. Per conoscere le restrizioni legate all'uso di fun-
zioni inline si deve pertanto consultare la documentazione del compilatore. Se una
int main() funzione non pu essere espansa in linea, verr semplicemente richiamata come
{
una comune funzione.
cout max(lO, 20);
Le funzioni inline possono anche essere funzioni membro di una classe. Ad
cout << " " max(99, 88);
esempio, questo un programma C++ perfettamente corretto:
return O;
#include <iostream>
using namespace std;
Quindi, per quanto riguarda il compilatore, questo programma sar equiva-
class myclass
lente al seguente: int a, b;
public:
lii nel ude <i ostream> void init(int i, int j);
using namespace std; voi d show();
};
int main()
{ Il Crea una funzione inline
inline void myclass::init(int i, int j)
cout (10>20 ? 10 : 20); {
cout " " (99>88 ? 99 88); a= i;
b = j;
return O;

Il Crea un'altra funzione inline


Il motivo per cui le funzioni inline sono cos importanti dovuto al fatto che inline void myclass::show()
consentono di creare codice molto efficiente. Poich le classi richiedono normal- {
mente di eseguire con grande frequenza alcune funzioni di interfacciamento (che cout << a << " " << b << "\n";
consentono l'accesso ai dati privati), l'efficienza di queste funzioni un fattore
fondamentale in C++. Come il lettore probabilmente gi sa, ogni volta che viene
richiamata una funzione, il meccanismo di chiamata e di uscita richiede una certa int main()
{
quantit di tempo. Normalmente, gli argomenti vengono inseriti nello stack e al
myclass x;
momento della chiamata vengono salvati vari registri che vengono poi ripristinati
=all'uscita della funzfone. Tutte queste operazioni richiedono tempo. Quando invece x.init(lO, 20);
una funzione viene espansa in linea, non si verifu;_a-1).~~s.una di queste operazioni. x.show();
D'altra parte, anche se l'espansione delle chiamate a funzioni pu produrre codice
- - pi_ veloce,-pu anche avere influenze negative sulle dimensioni del codice a causa return O;
____delle: duplicazioni richieste. Per questo motivo--consigliabile espan~r~Jn linea ~-l.--
LTCL ASSI E -G-k-1-0G-G E T-T-1 317
316 CAPITOLO 1 2

void init{int i, int j)


12.7 Definizione di funzioni inline all'interno {
di una classe a= i;
b = j;
possibile definire brevi funzioni anche all'interno della dichiarazione di una -
classe. Quando una funzione definita all'interno della dichiarazione di una clas- void show()
se viene automaticamente resa una funzione inline (se possibile). Non necessa- {
rio (ma non costituisce un errore) far precedere alla dichiarazione la parola inline. eout << a << " " << b << 11
\n";
Ad esempio, il programma precedente pu essere riscritto inserendo le definizio-
ni di init() e show() all'interno della dichiarazione di myclass. };

#i nel ude <i ostream> Tecnicamente, il fatto che la funzione show() sia stata resa inline ininfluente
using namespace std; poich in generale il tempo richiesto da un'operazione di I/O supera notevolmente
l'aggravio di tempo dovuto alla chiamata della funzione. Tuttavia molto comune
class myclass vedere tutte le funzioni membro pi brevi definite all'interno della rispettiva classe
int a, b;
(o meglio difficile trovare all'interno di programmi C++ professionali funzioni
public:
Il inline automatico membro brevi definite all'esterno delle rispettive dichiarazioni di classe).
void init(int i, int j) {a=i; b=j;} possibile definire inline anche le funzioni costruttore e distruttore, sfruttan-
void show() {cout a 11 11 b "\n";} do le caratteristiche del linguaggio (se sono definite all'interno delle rispettive
}; classi) oppure tramite defiriizione esplicita.

int main()
{
myclass x; 12.8 I costruttori parametrizzati
x.init(lO, 20); Le funzioni costruttore possono ricevere argomenti. Normalmente questi argo-
x.show(}; menti aiutano a inizializzare un oggetto al momento della creazione. Per creare
un costruttore parametrizzato, basta aggiungervi parametri cos come si fa con
return O; qualsiasi altra funzione. Quando si definisce il corpo del costruttore si possono
utilizzare i parametri per inizializzare l'oggetto. Ad esempio, ecco una semplice
classe che include un costruttore parametrizzato.
Si noti il formato del codice della funzione all'interno di myclass. Poich le
funzioni inline sono normalmente molto brevi, piuttosto comune la loro codifica #include <iostream>
all'interno di una classe. In ogni caso il programmatore libero di utilizzare il using namespace std;
formato desiderato. Ad esempio, la seguente dichiarazione di classe perfetta-
mente corretta: cl ass mycl ass
int a, b;
#i nel ude <i ostream> public:
using namespace ~!d; myclass(int i, int j) {a=i; b=j;}
void show(} {cout ~"'- a << " 11 b;}
----~ass myclass };
int a, b;
public: i nt mai n (}
- ---- -- Il inline automatico -r-
- -
------------ ~
CAPITOLO 12 ___ ....LL.C..L A-S S I E G L I O G-G E T I 319
318

myclass ob(3, 5); void set_status(int s) {status = s;}


void show();
ab.show(); };

return O; book::book{char *n, char *t, int s)


{
strcpy(author, n);
strcpy(title, t);
Si noti che nella definizione di myclass() per assegnare i valori iniziali ad a e
status = s;
b vengono utilizzati i parametri i e j.
II programma illustra il m:odo pi comune per specificare gli argomenti quan-
do si dichiara un oggetto che utilizza una funzione costruttore parametrizzata. In void book: :show()
particolare, l'istruzione {
cout << titl e << " di " author;
myclass ob{3, 4); cout << 11 ";
if(status==IN) cout "presente. \n";
provoca la creazione di un oggetto chiamato ob e passa gli argomenti 3 e 4 ai else cout "in prestito.\n";
parametri ie j di myclass(). possibile passare gli argomenti anche utilizzando
questo tipo di istruzione di dichiarazione:
int mainO
{
myclass ob = myclass(3, 4); book bl("Dante", "Divina commedia", IN);
book b2 ("Manzoni", "I promessi sposi", CHECKED_OUT) ;
Il primo dei due metodi quello pi ampiamente utilizzato ed anche l'ap-
proccio seguito dalla maggior parte degli esempi di questa guida. Vi una piccola bl.show();
differenza tecnica fra questi due tipi di dichiarazioni che fa riferimento ai costruttori b2.show();
di copie (argomento del Capitolo 14).
Ecco un altro esempio che utilizza una funzione costruttore parametrizzata. Il return O;
programma crea una classe che conserva informazioni relative ai libri di una bi-
blioteca.
Le funzioni costruttore parametrizzate sono molto..utili poich evitano di do-
#include '.:Jctstream> ver eseguire una nuova chiamata di funzione semplicemente per inizializzare una
fi nel ude <cstring> o pi variabili in un oggetto. Ogni chiamata di funzione evitata render il pro-
using namespace std; granuna pi efficiente. Inoltre si noti che le funzioni get_status() e set_status()
sono definite all'interno della classe book. Questa una pratica molto comune in
const int IN = 1; C++. .
const i nt CHECKED_OUT = O;
cl ass book { Un caso particolare: costruttori con un solo parametro
char author[40];
char title[40]; Se un costruttore ha un solo parametro, vi un terzo modo per passare uiivalore
int status; iniziale a tale co~truttore._Ad esempio, si consideri il seguente programma:
publ ic:
book(char *n, . char *t, i nt s);
#include <iostream>
int get_status(} {return status;}
using namespac.e .std.; __
- ----- - - - - -
320 CAPi-TOLO 12
LE CLASSI E GLI OGGETTI 321
class X {
int a; oggetto. Ind~pen~ent~mente ~al numero di oggetti creati di una classe, esister
public: una sola copia dei da.ti r:iembn static. Pertanto, tutti gli oggetti di tale classe utiliz-
X(int j) { a= j; } zeranno la. stes~a ~anab1le. Tutte le variabili staticvengono inizializzate a zero nel
int geta() { return a; momento m cm viene creato il primo oggetto.
quando si _dic~iarano i d~ti membri static all'interno di una classe, non si
};

int main()
defim~con~ t?b dati. Questo significa che non si sta allocando spazio di memoria
{
per tali dati ~m C++, una dichiarazione descrive qualcosa e una definizione crea
X ab = 99; 11 passa 99 a j qu~c~sa), S1 dovr pertanto fornire una definizione globale per i dati membri
st~t1c m un altr? ~unto, all'esterno della classe. Questo pu essere ottenuto di-
cout ob.geta(); 11 stampa 99 c?i~ando la ~ana~de come static e utilizzando l'operatore di risoluzione del campo
d_ azione ~er identificare la classe di appartenenza. Questo provoca l'allocazione
return O; di memona per ~a variabile (si ricordi che la dichiarazione di una classe non che
un costrutto logico che non ha realt fisica).
Per comprendere l'uso e gli effetti dei dati membri static, si consideri questo
Qui il costruttore di X prende un parametro. Si faccia attenzione al modo in programma:
cui ob viene dichiarato in main(). In questo tipo di inizializzazione, 99 viene auto-
maticamente passato al parametro j nel costruttore X(). Pertanto, l'istruzione di #include <iostream>
dichiarazione viene gestita dal compilatore come se fosse scritta nel seguente usi ng namespace std;
modo:
cl ass shared {
static int a;
X ab = X(99); int b;
public:
In generale, ogni volta che un costruttore richiede un solo argomento, si pu void set(int i, int j) {a=i; b=j;}
inizializzare un oggetto con ob(i) oppure ob = i. Il motivo che quando si crea un void show();
costruttore che accetta un argomento, si crea implicitamente una funzione di con-
versione dal tipo dell'argomento al tipo della classe. Si ricordi che l'alternativa
appena illustrata si applica solo ai costruttori che hanno un solo parametro. int shared::a; Il definisce a

voi d shared:: show()


{
12.9 I membri static di una classe cout "Variabile statica a: " a;
cout "\nvariabile non statica b: 11 b;
Le funzioni e i dati che sono membri di una classe possono essere resi static. cout << "\n";
Questa sezione spiega cosa ci significhi per ogni tipo di membro.
int main()
{
Dati membri static
shared x, y;
Quando la dichiarazione di una variabile membro preceduta dalla ~<?l~_cl.1iave
static si chiede al compilatore di creare una sola copia di tale variabile e di utiliz- x.set(l, l}; Il assegna 1 alla variabile a
X.show();
- - - -- zare tale copia per tutti gli oggetti della classe. A differenza dei comuni dati mero-
-- bri, non verr creat~ ima_:_singola copia di una variabile membr-0 statie-per ogn_i_ _ _ _
Y. set (2, 2};~/f -ora 1e assegna 2
- - - - -------
322 CAPITOLO 12 LE CLASSI E GLI OGGETTI 323

y.show() ;_ int shared::a; Il definisce a

x.show{}; f* Qui, a stata modificata sia per x che per y i nt mai n ()


poi ch a condivi sa da entrambi gli oggetti. *I {
Il inizializza a prima di creare qualsiasi oggetto
return O; shared: :a = 99;

cout "Questo il valore iniziale di a: " shared::a;


Questo programma produce il seguente output: cout << "\n";

Variabile statica a: 1 shared x;


Variabile non statica b:
Variabile statica a: 2 cout << "Questo x.a: " << x.a;
Variabile non statica b:
Variabile statica a: 2 return O;
Variabile non statica b:

Si noti che l'intero a dichiarato sia all'interno di shared che al suo esterno. Si noti come il riferimento ad a avvenga tramite l'uso del nome della classe e
Come si detto precedentemente questo necessario poich la dichiarazione di a dell'operatore di risoluzione del campo d'azione. In generale, quando il program-
alrinterno di shared non alloca memoria per la variabile. ma fa riferimento a un membro static indipendentemente da un oggetto, si deve
qualificare il membro static utilizzando il nome della classe di cui membro.
NOTA Per comodit, le prime versioni di C++ non richiedemno la Uno degli utilizzi delle variabili membro static consiste nel fornire un con-
seconda dichiarazione di una variabile membro static. Tuttavia questa comodit trollo per l'accesso ad alcune risorse condivise utilizzate da tutti gli oggetti della
darn origine a gravi incongruenze e fu eliminata molti anni fa. In ogni caso si classe. Ad esempio, si potrebbero creare pi oggetti, ognuno dei quali deve ese-
porrebbe trovare codice C++ non molto recente che non esegue la ridichiara::.ione guire operazioni di scrittura su un determinato file su disco. chiaro per che un
delle variabili membro static. In questi casi sar necessario aggiungere le defini- solo oggetto potr scrivere sul file in un determinato momento. In questo caso, si
zioni richieste. potrebbe voler dichiarare una variabile static che indichi quando il file in uso e
quando disponibile. Prima di iniziare a scrivere sul file ogni oggetto potr quin-
Una variabile membro static esiste prima che venga creato qualsiasi oggetto
di interrogare questa variabile. Il programma seguente mostra questo uso di una
della sua classe. Ad esempio, nel seguente breve programma, a sia public che
variabile static percontroITare l'accesso a una risorsa condivisa.
static. In questo modo main() pu accedervi direttamente. Inoltre, poich a esiste
prima della creazione di qualsiasi oggetto della classe shared, sar possibile asse-
#i nel ude <1 ostream>
gnare un valore ad a in qualsiasi momento. Come si pu vedere nel seguente
using namespace std;
programma, il valore di a non viene modificato dalla creazione dell'oggetto x. Per
questo motivo, entrambe le istruzioni di output visualizzano Io stesso valore: 99. cl ass cl
static int resource;
#include <iostream> publ ic:
using namespace std; i nt get_resource ();
voi d .. free_resource() {resource = O;}-
class shared { };
public:
static int a; int cl::resource; Il definisce la risorsa
} ;
in~ f_l: :get_resource()
LE C~ASSI E GLI OGGETTI 325
324 CAPITOLO 12

Counter o2;
cout "Oggetti esistenti: ";
i f ( resource) return O; 11 1a risorsa gi in uso
cout << Counter:: count << 11 \n";
else {
resource = 1;
f();
return 1; Il la risorsa allocata a questo oggetto
cout "Ogg~tti esistenti: ";
cout Counter: :count << "\n";

return O;
int main() )
I void f()
cl obl, ob2;
I
Counter temp;
if(obl.get_resource()) cout "la risorsa di obl\n";
cout .,Oggetti esistenti: ";
cout << Counter:: count << 11 \n";
if( !ob2.get_resource()) cout "ob2 non pu utilizzare la risorsa\n":
Il temp viene distrutta all'uscita da f()
obl. free_resource(); 11 1a risorsa viene 1i berata

if(ob2.get resource()) Questo programma produce il seguente output:


cout ;;-ora ob2 pu usare la risorsa\n 11 ;
Oggetti esistenti: 1
return O; Oggetti esistenti: 2
Oggetti esistenti:
Oggetti esistenti: 2
Un altro interessante uso di una variabile membro static consiste nel registra-
re il numero di oggetti esistenti di una determinata classe. Ad esempio: Come si pu vedere, la variabile membro static count viene incrementata ogni
volta che viene creato un oggetto e decrementata quando viene distrutto un ogget-
#include <iostream> to. In questo modo registra sempre il numero di oggetti Counter esistenti.
using namespace std; L'impiego di variabili membro static dovrebbe consentire di eliminare la ne-
cessit di utilizzare variabili globali. Il problema derivante dall'uso di variabili
cl ass Counter { globali in tecniche di programmazione a oggetti consiste nel fatto che quasi sem-
public: - pre esse violano il principio di incapsulamento.
static int count;
Counter() { count++; )
-Counter() { count--; ) Funzioni membro static
);
i nt Counter:: count; Anche le funzioni membro possono essere dichiarate static. Vi sono per molte
restrizioni relative all'impiego di funzioni membro static. Innanzi tutto, tali fun-
void f(); zioni possono accedere solo a membri static della classe (naturalmente le funzio-
ni membro static possono accedere a tutte le funzioni e ai dati globali). In secondo
int main(void)
luogo, le funzioni membro static non possono avere un puntatore this (per infor-
I
Counter ol; . mazioni consultare il Capitolo 13). Infine non possono esistere versioni static e
----eout- "Oggetti esistenti: "; non static della stessa funzione. Una funzione membro static non pu essere vir-
cout Counter: :count - "\n''-;-- -- tuale e non pu essere dichiarata come const o volatile. --- ------ -
326 CAPITOLO 12 - - - L ~-C-b-A-S-S-1- E-GLI OGGETTI 327

Di seguito viene presentata una versione leggermente modificata del program- return O;
ma a risorse condivise .della sezione precedente. Si noti che ora get_resource()
dichiarata come static. Come viene illustrato nel programma, l'accesso a
get_resource() pu avvenire da se stessa (indipendentemente dgli oggetti che In realt, le funzioni membro static hanno applicazioni piuttosto limitate ma
utilizzano il nome della classe e l'operatore di risoluzione del campo d'azione) sono ad esempio utili per "preinizializzare" i dati privati static prima della crea-
oppure in connessione con un oggetto. zione di qualsiasi oggetto. Ad esempio, questo un programma C++ perfettamen-
te corretto:
#include <iostream>
using namespace std; #i ne 1ude <i os t ream>
usi ng namespace std;
cl.ass cl {
static int resource; cl ass stati c_type
publ ic: static int i;
static int get_resource(); public:
voi d free _resource () {resource = O;} static void init(int x) {i = x;}
}; voi d show{) {cout i;}
};
int cl::resource; //definisce la risorsa
int static_type::i; /I definisce i
int cl: :get_resource()
{ int main()
if(resource) return O; /I la risorsa gi in uso {
else { Il inizializza i dati static prima della creazione dell'oggetto
resou ree = 1; static_type: :init(lOO);
return 1; // 1a risorsa allocata a questo oggetto
static_type x;
x.show{); // visualizza 100

int main() return O;


{
cl obl, ob2;

I* get_resource() static per poter essere richiamata in modo indipendente


da qual si asi oggetto. */ 12.1 O Quando vengono eseguiti i costruttori
if(cl::get_resource()) cout "la risorsa di obl\n"; e i distruttori?
if(!cl::get_resource()) cout "ob2 non pu utilizzare la risorsa\n"; Come regola generale, il costruttore di un oggetto viene richiamato nel momento
in cui l'oggetto inizia ad esistere mentre il distruttore dell'oggetto viene richia-
obl. free_resource(); mato nel momento in cui l'oggetto deve essere distrutto. Ma quando hanno luogo
esattamente queste operazioni?
. if(ob2.get_resource()) /I pu essere richiamata utilizzando La funzione costruttore di un oggetto locale viene eseguita nel momento-in
la sintassi degli oggetti cui viene incontrata l'istruzione di dichiarazione dell'og~Q.1:,~funzioni distrut-
cout "ora ob2 pu usare 1a ri sorsa\n";
tore per gli oggetti locali vengono eseguite in ordine inverso rispetto alle funzioni
costruttore.
LE CL-A-SSI E GLI OGGETTI 329
328 CAPITOLO 12
Questa non sar la prima riga visualizzata.
Le funzioni costruttore degli oggetti globali vengono ese~~lite p~im~ che ~ni~i Inizializzazione di 4
l'esecuzione di main(). I costruttori globali vengono esegmtl nell o~dme d1 di: Distruzione di 4
chiarazione nel file. Non possibile conoscere l'ordine di esecuzione dei cos~~~n Distruzione di 3
globali specificati all'interno di vari fik I.distru~tori.globali vengono esegmu m Distruzione di 2
Distruzione di
ordine inverso dopo il termine dell'esecuzione d1 mam(). . . . .
Il seguente programma illustra lesecuzione dei costrutton e dei d1strutton.
A causa delle differenze esistenti fra compilatori ed ambienti operativi, le
ultime due righe potrebbero non venire visualizzate.
#include <iostream>
using namespace std;

class myclass
12.11 l'operatore di risoluzione del campo d'azione
pub li c:
int who;
myclass(int id);
L'operatore:: consente di collegare il nome di una classe con il nome di un mem-
-myclass(); bro pr comunicare al compilatore la classe a cui appartiene il membro. L' ope-
} glob_obl(l), glob_ob2(2): ratore di risoluzione del campo d'azione ha per un altro utilizzo: consente infatti
di accedere a un nome che si trova all'interno del campo di visibilit e che
mycl ass: :mycl ass (i nt id) nascosto da una dichiarazione locale avente lo stesso nome. Ad esempio, si consi-
{ deri il segue1,1te frammento di codice:
cout "Inizializzazione di u << id<< 11\nu;
who = id;

myclass: :-myclass()
{
11
int i; Il i globale
cout "Distruzione di << who "\n";
void f{)
{
int main() int i; Il i locale
{
myclass Jocal_ob1(3); = 10; Il usa la i locale

cout "Questa non sar la prima riga visualizzata.\n";

myclass local_ob2(4);

return O;

Il programma produce il.seguente output:


-omedice il commento, l'assegnamento i= 10 fa riferimento alla variabile i
Inizializzazione di locale. Ma cosa accade se la funzione f() deve accedere alla versione globale di i?
Inizializzazione gj .2 _._!3aster~ f~ precedere alla variabile i l'operatore::=---= __ __ __ _
Inizializzazione di 3
LE CLAS-:SI GLI OGG-:TT~ 331
330 CAPITOLO 12

f();
Il myclass non nota in questo punto
return O;

int i; 11 i globale
void f()
void f() {
{ cl ass mycl ass
int i; Il i locale int i;
publ ic:
::i = 10; Il ora fa riferimento alla i globale void put_i(int n) {i=n;}
int get_i () {return i;}
ob;

ob.put_ i (10);
cout ob.get_i ();

Quando si dichiara una classe all'interno di una funzione, la classe sar nota
solo all'interno di tale funzione e non sar utilizzabile all'esterno.
Le clas.si locali sono soggette a notevoli restrizioni. Innanzi tutto, tutte le fun-
zioni membro devono essere definite all'interno della dichiarazione della classe.
12.12 la nidificazione delle classi La classe locale non dovrebbe utilizzare n effettuare accessi alle variabili locali
possibile definire una classe ali' interno di un'altra definendo una classe nidificata.
della funzione in cui dichiarata (ma una classe locale pu avere accesso alle
Poich la dichiarazione di una classe definisce a tutti gli effetti le regole di visibi- variabili locali static dichiarate all'interno della funzione e a quelle dichiarate con
lit, una classe nidificata valida solo all'interno del campo d'azione della classe extern). Tuttavia pu accedere ai nomi di tipi e agli enumeratori definiti nella
che la racchiude. In realt, l'uso di classi nidificate molto limitato. Grazie alla funzione in cui contenuta. Nessuna variabile static pu essere dichiarata all'in-
flessibilit e alla potenza del meccanismo di ereditariet del C++, in pratica non terno di una classe locale. A causa di queste restrizioni, l'impiego delle classi
locali non molto comune in C++.
vi alcun bisogno di utilizzare classi nidificate.

12.14 Il passaggio di oggetti a funzioni


12.13 le classi locali
~li oggetti ~ossono ~ssere p~ssati alle funzi<:mi come qualsiasi altro tipo di varia-
Una classe pu essere definita all'interno di una funzione. Ad esempio. questo
btle. In particolare gli oggetti vengono passati alle funzioni utilizzando il classico
un programma C++ perfettamente corretto:
meccanismo di chiamata per valore. Questo significa che in realt alla funzione
viene passata una copia dell'oggetto. Ma questo a sua volta significa che viene in
#include <iostream>
effetti creato un nuovo oggetto. Sorgono spontanee due domande: quando viene
using namespace std;
creata la copia dell'oggetto viene richiamata la funzione costruttore dell'oggetto?
void f(); E quando la copia viene distrutta viene richiamata la funzione distruttore? La
risposta a queste due-domande pu essere per certi versi sorprendente. Per inizia-
int main() re, ecco un breve esempio:
{
332 CAPITOL.0--12 LE CLASSI E GLI OGGETTI 333

Il Passaggio di un oggetto a una funzione Questo programma produce il seguente output:

#include <iostream> Costruzione di 1


using namespace std; Questa la i locale: 2
Distruzione di 2
cl ass mycl ass Questa la i di main:
int i; Distruzione di 1
publ i c:
myclass(int n); Si noti che vengono eseguite due chiamate alla funzione distruttore e una sola
-mycl ass ();
chiamata alla funzione costruttore. Come si pu vedere dall'output, quando a ab
void set i(int n) {i=n;)
int get_iO {return i;)
(all'interno di f()) viene passata la copia di o (in main()), non viene richiamata la
);
funzione costruttore. Il motivo per cui non viene chiamata una funzione costruttore
quando deve essere prodotta una copia dell'oggetto molto semplice: quando si
myclass::myclass(int n) passa un oggetto a una funzione, si intende lo stato corrente di tale oggetto. Se
{ venisse richiamato il costruttore per creare la copia, avverrebbe una completa
i = n; inizializzazione dell'oggetto che nel frattempo potrebbe essere stato modificato.
cout <<"Costruzione di " <<i << "\n"; Pertanto, quando viene generata la copia di un oggetto per la chiamata di una
funzione non pu essere eseguita la funzione costruttore.
Anche se quando un oggetto viene passato a una funzione non viene richia-
mycl ass: :-mycl ass () mata la funzione costruttore, necessario invece richiamare il distruttore nel mo-
{
mento in cui la copia deve essere distrutta (la copia viene distrutta come qualsiasi
cout "Distruzione di " << i << "\n";
altra variabile locale nel momento in cui termina la funzione). Si ricordi che la
copia dell'oggetto esiste fintantoch la funzione sar in esecuzione. Questo signi-
void f(myclass ab); fica che la copia potrebbe eseguire operazioni che rendono necessaria la distru-
zione da part'lella funzione distruttore. Ad esempio, la copia dell'oggetto po-
int main() trebbe allocare memoria che dovr essere liberata al momento della distruzione.
{ Per questo motivo, per distruggere la copia necessario richiamare la funzione
myclass o(l); distruttore.
--P-er-riassumere: quando viene generata una copia di un oggetto per consentire
f(o); il passaggio. di tale oggetto a una funzione, non viene richiamata la funzione
cout "Questa la i di main: "; costruttore dell'oggetto. Quando invece deve essere distrutta la copia dell'oggetto
cout o.get_i() "\n";
all'interno della funzione, viene richiamata la funzione distruttore.
Normalmente, la copia di un oggetto una copia bit per bit. Questo significa
return O;
che ogni nuovo oggetto una copia identica dell'originale. Ma ci pu in alcuni
casi dare origine a problemi. Anche se gli oggetti vengono passati alle funzioni
voi d f (mycl ass ob) tramite il normale meccanismo del passaggio per valore, che in teoria protegge e
isola l'argomento chiamante, comunque possibile che si verifichi un effetto
ob.set_i(2); collaterale che modifichi e.persino distrugga l'oggetto utilizzato come argomen-
to. Ad esempio, se loggetto utilizzato come argomento alloca memoria che viene
cout "Questa la i locale: " b.get_i(); poi liberata al momento della distruzione della copia locale dell'oggetto che si
11
cout << \n 11 ; trova all'interno della funzione, verr liberata la stessa area di memoria. Questo
provocher un danneggiamento dell'oggetto originario che diverr inlltltzz-abile.
-LE CLASSI E GLI OGGETTI 335
----
334 CAPITOLO 12

Co~e si vedr nel Capitolo 14, possibile evitare questo genere di problemi defi- Quando una funzione restituisce un oggetto, viene automaticamente creato un
nendo un'operazione di copia relativa a una determinata classe creando un tipo oggetto temporaneo elle contiene il valore restituito. La funzione quindi restitui-
particolare di costruttore chiamato costruttore di copie. sce in effetti questo secondo oggetto (temporaneo). Dopo la restituzione del valo-
re, l'oggetto viene distrutto. La distruzione di questo oggetto temporaneo pu in
alcuni casi provocare effetti collaterali indesiderati. Ad esempio, se l'oggetto re-
stituito dalla funzione ha un distruttore che libera la memoria allocata dinamica-
12.15 la restituzione di oggetti mente, tale memoria verr liberata anche se l'oggetto che riceve il valore restitu-
ito continuer a utilizzarla. possibile risolvere questo problema utilizzando tec-
Un funzione pu restituire al chiamante un oggetto. Ad esempio, questo un niche di overloading dell'operatore di assegnamento (vedere il Capitolo 15) e
programma C++ perfettamente corretto: definendo un costruttore di copie (vedere il Capitolo 14).

Il Restituzione di oggetti da parte di una funzione

#i nel ude <iostream> 12.16 L'assegnamento di oggetti


using namespace std;
Assumendo che entrambi gli oggetti siano dello stesso tipo, possibile assegnare
cl ass mycl ass un oggetto a un altro oggetto. In questo modo i dati dell'oggetto che si trova sul
int i; lato destro del segno di uguaglianza verranno copiati nei dati dell'oggetto che si
public: trova a sinistra. Ad esempio, il seguente programma visualizza il valore 99.
void set i(int n) {i=n;}
int getj() {return i;} 11 Assegnamento di oggetti
};
#i nel ude <i ostream>
myclass f(); Il restituisce un oggetto di tipo myclass
using namespace std;

int main() class mycl ass


{
int i;
myclass o;
public:
void set_i (int n) {i=n;}
o = f(); i nt get i () {return i ; }
}; -
cout 0-.get_i () "\n";
int main()
return O; {
myclass obl, ob2;

myclass f()
obl.set_i (99);
{
ob2 = obl; Il assegna i dati da obl a ob2
mycl ass x;
cout "questa 1a i di ob2: " ob2.get_::i ();
x.set_i (1);
return x;
.return O;
}
336 e A PTf O 1:0 12

Capitolo 13
Normalmente tutti i dati dell'oggetto di destra vengono assegnati all'oggetto
di sinistra utilizzando una copia bit-a-bit. per possibile eseguire l~overloading Gli array, i puntatori,
dell'operatore di assegnamento e definire altre procedure di assegnamento (vede-
re il Capitolo 15).
gli indirizzi e gli operatori
di allocazione dinamica

13.1 Gli array di oggetti


13.2 I puntatori a oggetti
13.3 Verifiche di tipo sui puntatori C++
13.4 Il puntatore this
13.5 I puntatori a tipi derivati
13.6 I puntatori ai membri di una classe
13.7 Gli indirizzi
13.8 Questione di stile
13.9 Gli operatori di allocazione
dinamica del C++

ella Parte prima stato discusso largomento dei


puntatori relativi ai tipi standard C++. Questo capitolo si occupa dei puntatori e
degli indirizzi di memoria. Al termine del capitolo si trover una discussione ri-
guardante gli operatori di allocazione dinamica.

13.1 Gli array di oggetti


In C++ possibile creare array di oggetti. La sintassi per la dichiarazione e l'uso
di array di oggetti esattamente la stessa gi vista per altri tipi di array. Ad esem-
pio, il seguente programma utilizza un array di tre oggetti:

#include <iostream>
usi ng namespace std;

cl ass cl
int i;
publ ic:
void set_i{int j) {i=j;}
int gt i() {return i;}
+.---=-
- GLI ARRA.V_ I PU.NTATORI, GLI INDIRIZZI 339
338 CAPITOLO 13

Anche questo programma visualizza i numeri 1, 2 e 3.


int main()
(
In realt, la sintassi di inizializzazione utilizzata nel programma precedente
cl ob[3]; una versione abbreviata della seguente forma:
int i;
cl ob[3] = c1(1), cl(2), c1(3) ;
for(i=O; i<3; i++) ob[i] .set_i (i+l);
Qui viene richiamato esplicitamente il costruttore di cl. Naturalmente molto
for(i=O; i<3; i++) pi comune trovare la forma abbreviata utilizzata nel programma. La forma ab-
cout ob[i].get_i() "\n"; breviata funziona grazie alla conversione automatica che si applica ai costruttore
che accettano un solo argomento (vedere il Capitolo 12). Pertanto la forma abbre-
return O;
viata pu essere utilizzata solo per inizializzare gli array di oggetti i cui costruttori
richiedono un solo argomento.
Questo programma visualizza sullo schermo i numeri 1, 2 e 3. Se il costruttore di un oggetto richiede due o pi argomenti, sar necessario
Se una classe definisce un costruttore parametrizzato, possibile inizializzare ricorrere alla forma di inizializzazione pi estesa. Ad esempio:
ogni oggetto di un array specificando una lista di inizializzazione cos come si fa
per altri tipi di array. Tuttavia, la forma della lista di inizializzazione verr decisa #i nel ude <i os t ream>
dal numero di parametri richiesti dalla funzione costruttore dell'oggetto. Nel caso using namespace std;
di oggetti i cui costruttori richiedano un solo parametro, basta specificare un elen-
cl ass cl
co dei valori iniziali utilizzando la normale sintassi di jnizializzazione degli array. int h;
Ogni valore della lista verr passato, nell'ordine, alla funzione costruttore per la int i;
creazione di ciascun elemento dell'array. Ad esempio, ecco una versione legger- pubi ic:
mente diversa del programma precedente che fa uso dell'inizializzazione: cl (i nt j, i nt k) { h=j; i=k; } // costruttore con due parametri
int get_i () {return i;}
#include <iostream> int get h() {return h;}
using namespace std;
}; -

cl ass cl int main()


int i; {
public: cl ob[3] = { - - -----
cl(int j} {i=j;} //costruttore cl (1, 2),
int geti() {return i;} cl (3, 4),
}; - cl (5, 6)
}; // inizializzatori
int main()
{ int i;
cl ob[3] {l, 2, 3}; // inizializzatori
int i; for( i =O; i <3; i++) {
cout ob[i] .get_h();
for(i=O; i<3; i++) cout-<:< n. 11
; -

cout ob[i] .get_i () "\n"; cout << ob[i] .get_i () "\n";

_return.~O~--
return O;
-----
---- - -
340 CAPITOLO 13
GLI ARRAY, I PUNTATORl.-GLI INDIRIZZI ... 341
In questo esempio, il costruttore di cl ha due parametri e, pertanto, richiede
due argomenti. Questo significa che non possibile utilizzare la forma di Data questa classe, sar consentito l'uso di entrambe le istruzioni seguenti:
inizializzazione "abbreviata" e sar necessario utilizzare la forma estesa mostrata
cl al[3) = {3, 5, 6}; Il inizializzato
nell'esempio.
cl a2[34); Il non inizializzato

Creazione di array inizializzati e non inizializzati

Una situazione ~articolare si verifica quando si cerca di creare array di oggetti in


13.2 I puntatori a oggetti
parte inizializzati e in parte non inizializzati. Si consideri la seguente classe:
Cos come possibile definire puntatori ad altri tipi di variabili, possibile anche
definire puntatori a oggetti. Quando si deve accedere a un membro di una classe
cl ass cl
int i;
dato un puntatore a un oggetto, al posto dell'operatore punto si utilizza l'operato-
publ ic: re freccia(->). Il programma seguente illustra l'accesso a un oggetto tramite un
puntatore:
cl(int j) {i=j;}
int get_i() {return i;}
}; #include <iostream>
using namespace std;
Qui, la funzione costruttore definita da cl richiama un parametro. Questo si-
class cl
gnifica che qualsiasi array dichiarato di questo tipo dovr essere inizializzato ma int i;
anche che non possibile utilizzare la seguente dichiarazione di array: publ ic:
cl (int j) {i=j;}
cl a[9]; Il errore, il costruttore richiede l'uso di inizializzatori i nt get i () {return i;}
}; -
Questa istruzione non corretta (cos com' attualmente definita cl) poich
implica che a cl sia associato un costruttore senza parametri in quanto non viene int main()
{
specificato alcun inizializzatore. Ma come si pu vedere cl non ha un costruttore
cl ob(88), *p;
senza parametri. Poich non vi alcun costruttore valido che corrisponda a questa
dichiarazione, il compilatore presenter un messaggio d'errore. . - __ . .- ___ _
Per risolvere questo problema si deve eseguire l'overloading della funzione
P = &ob; 11 legge l'indirizzo di ob
costruttore aggiungendone una versione che non richieda parametri. In questo
cout p->get_i(); Il usa-> per richiamare get_i()
modo sar possibile creare array inizializzati e non inizializzati. Ecco una nuova
versione di cl: return O;

cl ass cl
int i;
Quando si incrementa un puntatore, questo punter all'elemento successivo
publ ic:
dello stesso tipo. Ad esempio, un puntatore a interi punter all'intero successivo.
cl() {i=O;} Il richiamata per array non inizializzati
cl (int j) {i=j;} Il richiamata per array inizializzati
In generale, tutta l'aritmetica dei puntatori si basa sul tipo dell'.elemento puntato _
int get_i() {return i;} dal puntatore (ovvero sul tipo dei dati specificato al momento della dichiarazione
}; del puntatore). La stessa regola vale anche per i puntatori a oggetti. Ad esempio, il
seguente programma utilizza un puntatore per accedere ai tre elementi dell'array
ob dopo che a ob stato-assegnato-urri:ndirizzo iniziale. ------ - - -- -
---'342 CAPITOLO 13 GLI ARRA Y, I PUNTATORI, GLI IN O I RIZZI.... 343

#i nel ude <i os tream> p = &ob. i; 11 legge 1 'indirizzo di ob. i


usi ng namespace std;
cout << *p; Il accede a ob.i _!_ramite p
cl ass cl
int i; return O;
public:
cl() {i=O;}
cl(int j) {i=j;} Poich p punta a un intero, viene dichiarato come puntatore a interi. In questa
int get_i() {return i;} situazione, il fatto che i sia un membro dell'oggetto ob irrilevante.
}:

int main{)
{
13.3 Verifiche di tipo sui puntatori C++
cl ob [3) {1, 2, 3} ;
cl *p;
Vi un fatto importantissimo da comprendere relativo all'uso dei puntatori in
int i;
C++: possibile eseguire un assegnamento da un puntatore a un altro solo se i tipi
p = ob; Il punta all'inizio dell 'array
dei due puntatori sono compatibili. Dati i puntatori:
for(i=O; i<3; i++) {
cout p->get_i () "\n"; int *pi;
p++; Il punta all'oggetto successivo float *pf;

in C++ il seguente assegnamento non consentito:


return O;
pi = pf; 11 errore per differenza di tipo

possibile assegnare a un puntatore l'indirizzo di un membro pubblico ~i u~ Naturalmente possibile bypassare le incompatibilit di tipo utilizzando una
oggeno e poi utilizzare il puntatore per accedere a tale membro. Ad esempio, ~I conversione cast ma in questo modo verr prodotta una violazione del meccani-
seguente programma, perfettamente corretto in C++, visualizza sullo schermo 11 smo di verifica dei tipi del C++.
numero 1:
~].T~::_~~ ''~:' ~: Le forti verifiche di tipo che il C++ applica ai puntatori rap-
#include.<iostream> presentano una differenza fondamentale rispetto al C in cui possibile assegnare
using namespace std; a un puntatore un valore qualsiasi.

cl ass cl
public:
int i; 13.4 Il puntatore this
cl (int j) {i=j;)
}; Quando viene richiamata una funzione membro, le viene automaticamente passa-
to murgomento implicito costituito da un puntatore all'oggetto chiamante (ovve-
int main() ro l'oggetto su cui viene richiamata la funzione). Questo puntatore chiamato
{ this. Per comprendere il significato del puntatore this si consideri innanzi tutto un
cl ob(l); puntatore che crea una classe chiamata pwr la quale calcoli il risultato di una b!J,~e
_ ~nt *p_:_ _ _ _ elevata a un esponente: -
- .-- :._-.
~-==-
GLI ARRAY, I PUNTATORI, GLI INDIRIZZI ... 345

richiamata da x (ad esempio con x(4.0, 2)), il puntatore this dell'istruzione prece-
#i ne 1 ude <i os t ream> -dente avrebbe puntato a x. bene ricordare che tralasciando il puntatore this si
using namespace std;
utilizza in effetti una forma abbreviata dell'istruzione.
class pwr {
Ecco l'aspetto della funzione pwr() facendo uso del puntatore this:
double b;
int e; pwr: :pwr(double base, int exp)
doul:il " va 1 ; {
public: thi s->b = base;
pwr(doubl e base, int exp); this->e = exp;
double get_pwr() {return val;} this->val = 1;
}; if(exp==O) return;
for( ; exp>O; exp--)
pwr: :pwr(double base, int exp) this->val = this->val * this->b;
{
b = base;
e = exp; Nessun programmatore C++ scriverebbe mai la funzione pwr() in questo se-
val = 1; condo modo poich in realt non si guadagna nulla e la forma abbreviata pi
i f ( exp==O) return; semplice. Tuttavia, il puntatore this molto importante nel caso di overloading
for( ; exp>O; exp--) val = val * b;
degli operatori e in tutti i casi in cui una funzione membro debba utilizzare un
puntatore all'oggetto che l'ha richiamata.
Il puntatore this viene passato automaticamente a tutte la funzioni membro.
i nt mai n ()
Pertanto, get_pwr() potrebbe essere riscritta anche nel seguente modo:
{
pwr x(4.0, 2), y(2.5, 1), z(S.7, O);
double get_pwr() {return this->val ;}
cout x.get_pwr() " ";
cout y.get_pwr() " "; In questo caso, se get_pwr() fosse richiamata nel seguente modo:
cout z.get_pwr() "\n";
return O;} y.get_pwr();
. . d". " . --- embfol'accesso ai membri della classe avviene
All'mtemo 1una1unz1one m Pertanto this punterebbe all'oggetto y.
1
direttamente senza che sia necessario qualificare l'oggetto 0 la casse. '
Due ultime annotazioni relative al puntatore this. Innanzi tutto, le funzioni
all'interno di pwr() l'istruzione: friend non sono membri di una classe e pertanto ad esse non viene passato alcun
puntatore this. In secondo luogo, le funzioni membro static non hanno alcun
b = base; puntatore this.
significa che alla copia di b associata all'oggetto chiamante v_iene ~ssegn::~~~
valore contenuto in base. Si sarebbe potuta scrivere la stessa istruzione n
guente modo: 13.5 I puntatori a tipi derivati-

_tl!_!_~~b = base; In generale, un puntatore di un determinato tipo non pu puntare a un oggetto di


un tipo differente. Vi per un'importante eccezione a questa regola che riguarda
II puntatore this punta all'oggetto che ha richiamato pwr(). Pertant~, con:::~ 0Io-1e-classi derivate.-s!assuma d!ufilzzare-de classi chiamate B-e De-che-O
__ -->:b si fa riferimen~ alla copia di b contenutain_tal~_oggetto. Se pwr() osse
5
---w ~-:.~-
346 CAPITOLO 13
GLI AARAY. PUNT
ATOAI, Gli INDIRIZZI 347
derivi dalla classe base B. In questa situazione, un puntatore di tipo B* pu anche bp->set_j (88); 11 errore
puntare a un oggetto di tipo D. In senso pi generale, un puntatore a una classe cout bp->get_j O; 11 errore
base pu anche essere utilizzato come puntatore a un oggetto di una qualsiasi
classe derivata da tale classe base. *I
Anche se un puntatore alla classe base pu essere utilizzato per puntare a un retum O;
oggetto derivato, non possibile applicare la regola inversa. Un puntatore di tipo D*
non pu puntare a un oggetto di tipo B. Inoltre, anche se possibile utilizzare un
puntatore base per puntare a un oggetto derivato, sar possibile accedere solo ai Come si pu vedere per accedere .
membri del tipo derivato che sono stati importati dalla classe base. Quindi non si za un puntatore base. , a un oggetto di una classe derivata si utiliz-
sar in grado di accedere ai membri aggiunti dalla classe derivata ( per possibile Anche se questa tecnica richiede attenz . .
avere accesso ali' intera classe derivata eseguendo una conversione cast del puntatore conversione cast del puntatore bas . ione, essa consente di esegm.re una
alla classe base, trasformandolo quindi in un puntatore alla classe derivata) .. accedere ai membri della classe d~ I? un pu~~atore alla classe derivata in modo da
Ecco un breve programma che usa un puntatore alla classe base per accedere pio, questo codice C++ perfettam~:::~!~~~~ndo il puntatore base. Ad esem-
agli oggetti derivati.
11 accesso consentito grazie al cast
#i nei ude <iostream> ((derived *}bp)->set_j(88 );
using namespace std; cout ((derived *)bp)->get_j();

cl ass base importante ricordare che laritmetica d . . . .


int i; base. Per questo motivo quando ei puntaton fa nfenmento al puntatore
public: l'incremento del puntat;re non fa ~n pu~atore base punta a un oggetto derivato,
void set_i (int num) {i=num;} vo del tipo derivato. AI contrario i~ :tao che questo pu~ti all'oggetto successi-
int get_i () {return i;} l'oggetto successivo nel tipo base to~e punter a, ci_ c~e dovrebbe essere
}; esempio, il seguente ro ra . uesto i_n ge~ere da ongme a problemi. Ad
sto errore logico. p g mma anche se smtatticamente corretto contiene que-
class derived: public base {
int j;
public: #include <iostream>
void set_j (i-nt num) {j=num;} using namespace std;
int get_j~) {return j;}
}; cl ass base
int i;
public:
int main()
{ void set_i(int num) {i=num;}
base *bp; int get_i() {return i;}
};
derived d;

bp = &d; 11 il puntatore base punta all'oggetto derivato class derived: public base {
int j;
public:
Il accesso all'oggetto derivato utilizzando il puntatore base
bp->set_i (10); ~ai d set_j (i nt num) J.i:num;J
cout << bp->get_i () " "; };
1nt get j O {return J".}
- .
---=---/*Questo non funziona. Non possibile accedere a un. elemento di
una cl asse derivata utilizzando un. puntatore a11-a- ciasse bas~_,_ -:::=::---::---
GLl--A-R-R-A-Y-;--1 PUNTATORI, GLl_J_lJDIRIJ'._Zl ... 349

int val;
int main() int double val() {return val+val ;}
{ }; -
base *bp;
deri ved d [2] ; int main()
{
bp = d;
int cl: :*data; 11 puntatore a membro (dati)
int (cl: :*fune) O; 11 puntatore a membro (funzione)
d[O].set_i(l); cl obl(l), ob2(2); Il crea gli oggetti
d[l] .set_i (2);
data= &cl::val; Il calcola il valore di scostamento di val
cout bp->get_ i() " ";
fune = &cl: :double_val; Il calcola il valore di scostamento
bp++; Il rispetto alla classe base e non alla classe derivata
di double_val ()
cout bp->get_i O; 11 viene visualizzato un val ore senza senso
cout "'"' "Ecco i va 1ori : ";
return O;
cout "'"' obl. *data "'"' " " << ob2. *data "'"' "\n";

cout "Ecco i valori raddoppiati:";


L'uso di puntatori base a tipi derivati utilissimo nella realizzazione del cout (obl.*func)() 11 " ;
polimorfismo run-time mediante il meccanismo delle funzioni virtuali (vedere il cout . (ob2.*func) () "\n";
Capitolo 17).
return O;

13.6 I puntatori ai membri di una classe All'interno di main(), questo programma crea due puntatori a membri: data e
fune. Si osservi attentamente la sintassi delle due dichiarazioni. Quando si dichia-
Il C++ consente di generare un tipo particolare di puntatore che punta generica- r~ u.n pun~atore a un membro, si deve specificare la classe e utilizzare l'operatore
mente a un membro di una classe e non a una specifica istanza di tale membro in di ns~l~zmne del cai:ipo :d'azione. Il programma crea inoltre i due oggetti ob1 e
un oggetto. Questo genere di puntatore chiamato puntatore a un membro della o~2 d1 t~po cl. Coi:ne s1 puo vedere, i puntatori a membri possono puntare a funzio-
classe o puntatore a membro. Un puntatore a membro non la stessa cosa di un ni o dati. Inoltre, il programma ricava gli indirizzi di val e double_val(). Come si
-- - - -comune puntatore C++.Esso infatti fornisce solo un valore di scostamento all'in- detto precedentemente, questi "indirizzi" non sono altro che valori di scostamento
terno di un oggetto appartenente alla classe del membro in cui possibile trovare all'!nt~mo di. un ~ggett~ di tipo cl in cui possibile trovare val e double_val().
tale membro. Poich i puntatori a membro non sono veri puntatori, non possibi- Qumd1, per v1sual1zzare 1valori val degli oggetti viene eseguito un accesso trami-
le applicarvi gli operatori . e ->. Per accedere a un membro di una classe dato un te data. Infi~e, il ~ro~ramma utilizza fune per richiamare la funzione double_val().
puntatore ad esso, si deve utilizzare uno degli operatori specifici dei puntatori a 1:e parentesi aggmntive sono necessarie per associare correttamente l'operatore
membri ovvero .*e->. Lo scopo di questi operatori di consentire l'accesso ai
membri di una classe dato un puntatore a tale membro. . .<?uando si accede a un membro di un oggetto utilizzando un oggetto o un
Ecco un esempio: mdmzz~ (discusso. success~va1?~nte in questo capitolo), si deve utilizzare l'ope-
r~tore .. Quando mvece s1 utthzza un puntatore all'oggetto, si deve utilizzare
#i nel ude <i ostream> l operatore->*, come illustrato dalla seguente versione del programma. _
usi ng namespace s td;
#include <i ostream>
class cl {
std;
public:
cl (iriCi) -{val =i;}
--------==-=-=----;.__ -
G LI A R.R.AY, I P U N T A TOR I , G LI I NO I RIZZI ... 351
350 CAPITOLO 13

Qui, p un puntatore a un intero all'interno di un oggetto ben determinato. AI


class cl {
public:
contrario, d semplicemente un valore di scostamento che indica l'indirizzo in
cl(int i) {val=i;} cui possibile trovare val all'interno di ogni oggetto di tipo cl. _
int val; In generale, gli operatori dei puntatori a membri sono applicati in alcuni casi
int double_val() {return val+val;) particolari e non- sono molto utilizzati nella co~une programmazione.
};

int main()
{ 13.7 Gli indirizzi
i nt cl: :*data; // puntatore a membro (dati)
int (cl: :*fune)(); // puntatore a membro (funzione) Il C++ contiene una funzionalit in stretta relazione con i puntatori: l'indirizzo.
cl obl(l), ob2(2); // crea gli oggetti Un indirizzo essenzialmente un puntatore implicito. Un indirizzo pu essere
cl *pl, *p2; utilizzato in tre modi: come parametro di una funzione, come valore restituito da
una funzione e come indirizzo a s stante.
pl = &obl; // Accesso agli oggetti tramite un puntatore
p2 = &ob2;
Gli indirizzi come parametri
data= &cl::val; //calcola il valore di scostamento di val
fune= &cl::double val; //calcola il valore di scostamento di Probabilmente l'uso pi importante degli indirizzi quello di consentire di creare
- double_val ()
funzioni che utilizzano automaticamente il passaggio di parametri per indirizzo.
Come si detto nel Capitolo 6, gli argomenti possono essere passati alle fun-
cout << "Ecco i valori: ;
cout << pl->*data << " " << p2->*data << "\n";
zioni in due diversi modi: per valore o per indirizzo. Quando si usa un passaggio
per valore, alla funzione viene passata una copia dell'argomento. Con un passag-
cout "Ecco i valori raddoppiati:"; gio per indirizzo si passa alla funzione l'indirizzo dell'argomento. Normalmente
cout (pl->*func) O " "; il linguaggio C++ utilizza la chiamata per valore ma fornisce due modi per otte-
cout (p2->*func) () "\n"; nere il passaggio dei parametri per indirizzo. Innanzitutto possibile passare espli-
citamente un puntatore all'argomento. In alternativa si pu utilizzare un parame-
return O; tro indirizzo. In molti casi quest'ultima rappresenta la soluzione migliore.
Per comprendere cos' un parametro indirizzo e la sua importanza, si parler
del modo in curiina cliiffiata per indirizzo pu essere generata impiegando un
In questa versione, p1 e p2 sono puntatori a oggetti di tipo cl. Pertanto. per puntatore. Il seguente programma crea manualmente un parametro puntatore per
accedere a val e a double_val() viene utilizzato l'operatore->*. . . la funzione neg() la quale inverte il segno della variabile intera puntata dal suo
Si ricordi che i puntatori a membri sono diversi ri~pett~ ai pu.ntat?:1 a specifi- argomento.
che istanze degli elementi di un oggetto. Ad esemp10, s1 cons1den il seguent~
frammento di codice (immaginando che cl sia dichiarata come nei programmi // Crea manualmente una chiamata per indirizzo con un puntatore
precedenti).
#include <iostream>
int cl::*d; using namespace std;
int *p;
cl o; void neg(int *i);

p = &o.val //questo l'indirizzo di uno specifico val int main()


{
d = &cl-::val //-questo lo scostame~to_di-val generico ___ in:Lx; ..
352 CAPITOLO 1 r - - - - - -
GLI ARRAY, PUNTATORI, GLI INDIRIZZI ... 353

X = 10;
void neg(int &i); Il ora i un indirizzo
cout << x << " a1 negativo ugua1e a ";
int main()
neg(&x); {
cout << x << 11 \n";
int x;

return O;
X = 10;
cout << x << " al negativo uguale a ";
void neg(int *i)
neg (x); 11 non pi necessari o 1 'operatore &
{
cout << x << "\n";
*i = -*i;
return O;

In questo programma, neg() prende come parametro un puntatore all'intero di void neg(int &i)
cui si deve invertire il segno. Pertanto, neg() deve essere richiamata esplicitamen-
te con l'indirizzo di i. Inoltre, all'interno di neg(), per accedere alla variabile pun- i = -i; Il ora un indirizzo e non pi necessario usare*
tata da i si deve utilizzare l'operatore *. In questo modo si genera una chiamata per
indirizzo "manuale" in C++ ed anche l'unico modo per ottenere una chiamata di
questo tipo in C. Fortunatamente in C++ possibile rendere automatica questa Per ricapitolare: quando si crea un parametro indirizzo, tale parametro fa au-
funzionalit utilizzando un parametro indirizzo. tomaticamente riferimento (o punta implicitamente) all'argomento utilizzato per
Per creare un parametro indirizzo si deve far precedere al nome del parametro richiamare la funzione. Pertanto, nel programma precedente, l'istruzione:
il carattere &. Ecco come possibile dichiarare neg() utilizzando un indirizzo:
i = -i ;
void neg(int &i);
opera direttamente su x e non su una sua copia. Non vi sar pi alcuna necessit di
Questa forma chiede al compilatore di rendere i un parametro indirizzo. Fatto applicare l'operatore & in un argomento. Inoltre, all'interno della funzione, il pa-
ci, i diviene a tutti gli effetti un altro nome per qualsiasi argomento utilizzato per rametro indirizzo viene utilizzato direttamente senza necessit di applicare l'ope-
richiamare neg(). Ogni operazione eseguita su i influenzer l'argomento chia- ratore*.
mante. In termini tecnici, i un puntatore implicito che fa automticamertfflife- In generale, quando si assegna un valore a un indirizzo, il valore viene asse-
rimento all'.argomento utilizzato per richiamare neg(). Dopo che i stato tramuta- gnato alla variabile cui punta l'indirizzo. Nel caso dei parametri di funzione, si
to in un puntatore indirizzo, non sar pi necessario (n consentito) applicare tratter della variabile utilizzata per richiamare la funzione.
I' operatore*. Al coqtrario, ogni volta che si utilizzer i, si intender implicitamen- All'interno della funzione non possibile cambiare ci a cui punta il parame-
te l'indirizzo dell'argomento e le modifiche apportate a i modificheranno in realt tro indirizzo. Quindi, un'istruzione come:
l'argomento. Inoltre, quando si richiamer neg() non sar pi necessario (n con-
sentito) far precedere al nome dell'argomento l'operatore &. Tutta l'operazione i++;
verr automaticamente eseguita dal compilatore. Ecco quindi una nuova versione
del programma precedente che impiega un parametro indirizzo: all'interno di neg() incrementa il valore della variabile utilizzata nella chiamata_e
non fa in modo che i punti a un nuovo indirizzo. =- -
Il Uso di un parametro indirizzo Ecco un altro esempio. Questo programma utilizza parametri indirizzo per
scambiare il valore delle variabili in cui la funzione viene richiamata (fa funzione
#1 nel ude <i ostream>
swap() il classico esempio-i-pass-aggio di parametri per indjrizzor.------- --
--using namespace std;
----- --~-:, ___ v:~ ~
354 CAPITOLO 13 GLI ARRAY, I PUNTATORI, GLI INDIRIZZI 355

#include <iostream> Passaggio di indirizzi a oggetti


using namespace std;
Nel Capitolo 12 si detto che quando un oggetto viene passato come argomento a
void swap(int &, int &j); una funzione, viene in effetti eseguita una copia di tale oggetto. AI termine della
funzione viene chiamata la funzione distruttore per eliminare la copia. Se, per
int main()
qualche motivo, non si desidera che venga richiamata la funzione distruttore, ba-
{
ster passare l'oggetto per indirizzo (pi avanti in questa guida si vedranno alcuni
int a, b, c, d;
esempi di questo tipo di chiamata). Con il passaggio per indirizzo, non viene
l; eseguita alcuna copia dell'oggetto. Questo significa che nel momento in cui la
b = 2; funzione termina non verr distrutto alcun oggetto utilizzato come parametro,
c = 3; ovvero non verr chiamata la funzione distruttore sul parametro. Ad esempio, si
d = 4; provi il seguente programma:

cout << 0
a e b: "
<< a << 11 " << b << 11 \n 11 ; #include <iostream>
swap{a, b); //non necessario l'operatore & using namespace std;
cout << "a e b: 11 << a << 11 11 << b << 11 \n 11 ;
class cl {
cout << "ce d: " << c << " " << d << "\n"; int id;
swap(c, d); publ ic:
cout << 11 c e d: 11 << e << 11 11 << d << 11 \n"; int i;
cl (int i);
return O; -cl();
void neg(cl &o) {o.i =-o.i;} //non viene creato un oggetto temporaneo
};
void swap{int &i, nt &j)
{ cl::cl(int num)
int t; {
cout << "Costruzione di " << num << "\n";
t =i; //non necessario l'operatore* id = num;
i ,. j_;
j = t;
cl: :-cl()
{
Questo programma produce il seguente output: cout << "Distruzione di " id << "\n";

a e b: 1 2
a e b: 2 1 i nt mai n ()
{
c e d: 3 4
=c e d: 4 3 -cl o(l);

o. i= 10;
o.neg(o);

'--cut <<o.i << "\n";


. -- . --
- - - - - --- - - - - - - ---:.=,. __ -____ --
356 CAPITOLO 13 GLI ARRAY, I PUNTATORI, GLI INDIRIZZI ... 357

return O; cout << s;

return O;
Questo l'output del programma:
char &replace(int i)
Costruzione di {
-10 return s[i];
Distruzione di

Come si pu vedere, viene eseguita una sola chiamata alla funzione distrutto- Questo programma sostituisce lo spazio fra "Salve" e "a tutti" con una "X". In
re di cl. Se o fosse stata passata per valore, all'interno di neg() sarebbe stato creato pratica, il programma visualizza la stringa "SalveXa tutti". Ma come si ottiene
un secondo oggetto e sarebbe stata richiamata una seconda volta la funzione di- questo risultato?
struttore per distruggere l'oggetto all'uscita da neg(). Innanzitutto, replace() restituisce l'indirizzo di un array di caratteri. Cos come
Come si pu capire dal codice di neg(), quando si accede a un membro di una realizzata, replace() restituisce I' indirizzo dell'elemento di s specificato dal suo
classe tramite il suo indirizzo, si usa l'operatore punto. L'operatore freccia uti- argomento i. L'indirizzo restituito da replace() viene utilizzato in main() per asse-
lizzato solo per i puntatori. gnare a tale elemento il carattere X.
Quando si esegue il passaggio di parametri per indirizzo, si deve ricordare che Una cosa cui fare attenzione quando si restituisce l'indirizzo il fatto che
le modifiche agli oggetti che si trovano all'interno della funzione alterano l'og- I' oggetto cui si fa riferimento non esca dal campo di visibilit al termine della
getto utilizzato per la chiamata. funzione.
Infine si deve ricordare che il passaggio per indirizzo di un oggetto di dimen-
sioni non banali molto veloce. Gli argomenti vengono normalmente passati sul-
lo stack, pertanto il passaggio per valore di grandi oggetti richiede grandi quantit Indirizzi indipendenti
di cicli di CPU per le operazioni di push e pop dell'oggetto sullo stack.
Gli utilizzi di gran lunga pi comuni degli indirizzi sono il passaggio di un argo-
mento tramite chiamate per indirizzo e l'impiego come valore restituito da una
Restituzione di indirizzi funzione. Ma anche possibile dichiarare un indirizzo che sia semplicemente una
variabile. Questo tipo di indirizzo chiamato indirizzo indipendente.
Una funzione pu restituire un indirizzo. Questo significa che una funzione pu Quando si crea un indirizzo indipendente, non si fa altro che creare un altro
essere utilizzata anche sul 1ato siffi.Sfroclriin' istruzione di assegnamento! Ad esem- nome per una variabile. Tutte le variabili indirizzo indipendenti devono essere
pio, si consideri questo semplice programma: inizializzate al momento della creazione. Il motivo ovvio: tranne che
nell'inizializzazione, non possibile modificare l'oggetto a cui punta la variabile
#i nel ude <iostream>
indirizzo. Pertanto tale variabile deve essere inizializzata al momento della di-
using namespace std;
chiarazione (in C++, l'inizializzazione un'operazione completamente distinta
dal!' assegnamento).
char &replace(int i); Il restituisce un indirizzo
Il seguente programma illustra l'uso degli indirizzi indipendenti.
char s[80] "Salve a tutti";
#include <iostream>

__
int main()
_{
using namespace std;

replace(S) ='X'; Il assegna X allo spazio dopo Salve


358 CAPITOLO 13 G L I A R R A y. I p u N T A T 6 R I -:-G L I I N o I R I z zr... - 359

int &ref = a; // indirizzo indipendente possibile creare un puntatore a un indirizzo. Non possibile conoscere l'indirizzo
di un campo bit.
a = 10; Una variabile indirizzo deve essere inizializzata al momento della dichiara-
cout << a << 11
" << ref << 11 \n 11 ; zione a meno che non sia un membro di una classe, il parametro di una funzione
o il valore restituito da una funzione. proibito l'uso di indirizzi nulli.
ref = 100;
cout << a << " " << ref << "\n";

int b = 19; 13.8 Questione di stile


ref = b; //inserisce in a il valore di b
cout << a << " " << ref << "\n"; Quando si dichiarano variabili puntatore e indirizzi, alcuni programmatori C++
utilizzano un particolare stile di programmazione che associa agli operatori * e &
ref--; /I decrementa a il nome del tipo e non quello della variabile. Ad esempio queste due dichiarazioni
11 non modifica ci a cui punta ref
sono equivalenti:
cout << a << " " << ref << "\n";
int& p; /I & associato al tipo
return O; int &p; // & associato alla variabile

L' assoc;iazione degli operatori * o & al nome del tipo riflette il desiderio di
Il programma visualizza questo output: alcuni programmatori di utilizzare in C++ un tipo puntatore distinto. In questo
senso, il problema che sorge associando tali operatori al nome del tipo piuttosto
10 10 che al nome della variabile consiste nel fatto che secondo la sintassi formale del
100 100 C++, n & n * sono distributivi in un elenco di variabili. Pertanto, questo potreb-
19 19 be portare alla creazione di dichiarazioni fuorvianti. Ad esempio, la dichiarazione
18 18 seguente crea uno e non due puntatori a interi.

In realt, gli indirizzi indipendenti sono molto poco utilizzati in quanto si int* a, b;
tratta semplicemente di nomi diversi per una determinata variabile. L'utilizzo di
due nomi per identificare la stessa variabile complica inutilmente il programma. Qui, b viene dichiarato come intero (e non come puntatore a intero) poich, in
base alla sintassi del C++, quando l'opratore (ma-anche -&)viene utilizzato in
una dichiarazione, fa riferimento al nome della variabile seguente e non al nome
L'indirizzo di un tipo derivato del tipo precedente.
Il problema con questo tipo di dichiarazioni che il messaggio visivo sugge-
Come si detto per i puntatori, l'indirizzo di una classe base pu essere utilizzato risce che sia a che b siano puntatori mentre in effetti solo a un puntatore. Questa
anche per far riferimento a un oggetto appartenente a una classe derivata. Un' ap-
confusione visiva non trae in inganno solo i programmatori alle prime armi ma
plicazione di ci nei parametri delle funzioni. Un parametro corrispondente a un anche i professionisti pi esperti.
indirizzo della classe base pu ricevere oggetti della classe base o anche oggetti
importante comprendere che, per quanto riguarda il compilatore C++, non
appartenenti a una classe da essa derivata.
importa che si scriva int *po int* p. Pertanto, si liberi di specificare l 'associazio-
ne alla variabile o al tipo. In ogni caso, per evitare confusioni, questa guida aaotta
Restrizioni relative a$1i indirizzi l'associazione degli op~rntgri ~e & al nome delle variabili su cui operano piutto-
sto che al tipo.
Gli indirizzi sono soggetti a un gran-numero d! restrizioni. Non possibile c9no---- --
--'---=- - -scere l'indirizzo di un indirizzo. Non possibile creare array di indirizzi._:1'1G-n-~
------
--- --- -
360 CAPITOLO 13 GLI ARRAY, I PUNTATORI, GLI INDIRIZZI 361

13.9 Gli operatori di allocazione dinamica del C++ il proprio compilatore dovesse gestire un problema di allocazione in modo diffe-
rente, sar ovviamente necessario apportare al programma le modifiche af)propriate.
Il linguaggio C++ fornisce un sistema di allocazione dinamica che si basa sui due Ad esempio, il _seguente programma alloca la m_emoria necessaria per conte-
operatori new e delete. Come si vedr, vi sono sostanziali vantaggi nell'approccio nere un intero:
del C++ all'allocazione dinamica della memoria.
Gli operatori new e delete sono utilizzati per allocare e liberare la memoria #i nel ude <i ostream>
run-time. L'allocazione dinamica della memoria una parte importante di quasi #i nel ude <new>
ogni programma. Come si detto nella Parte prima, il linguaggio C++ supporta using namespace std;
anche le funzioni di allocazione dinamica della memoria malloc() e free() che
sono state incluse per compatibilit con il linguaggio C. Tuttavia quando si lavora
in C++, opportuno utilizzare gli operatori new e delete che offrono numerosi int main()
{
vantaggi. L'operatore new alloca un'area di memoria e restituisce un puntatore
int *p;
all'inizio di tale area L'operatore delete libera la memoria precedentemente allocata
con new. Di seguito vengono presentate le forme generali di new e delete: try {
P = new int; 11 alloca spazio per un int
var_p = new tipo; catch (bad_alloc xa) {
cout "Errore di allocazione\n";
delete var_p; return.1;

Qui, var_p una variabile puntatore che riceve uii. puntatore a un'area di me-
moria sufficientemente estesa da contenere un oggetto di tipo tipo. *p = 100;
Dato che l'heap ha dimensioni finite, pu giungere ad esaurimento. Se la
eout << n In n << P << u u;
memoria disponibile insufficiente per esaudire la richiesta di allocazione, allora
cout << "si trova il valore " << *p << "\n";
la richiesta new non verr esaudita e verr generata l'eccezione bad_alloc. Questa
eccezione definita nell'header <new>. II programma dovrebbe gestire questa delete p;
eccezione e prendere le misure appropriate (la gestione delle eccezioni descritta
nel Capitolo 19). Se il programma non gestisce l'eccezione, verr automatica- return O;
mente chiuso ..
Le azioni eseguite da new, cos come sono state descritte, sono specificate
dallo standard del linguaggio C++. Il problema che non tutti i compilatori, spe- Questo programma assegna a p un indirizzo dello heap le cui dimensioni
cialmente quelli meno recenti, implementano new secondo lo standard. Quando sono sufficienti per contenere un intero. Poi assegna a tale area di memoria il
venne inventato il C++, in caso di fallimento new restituiva il valore nullo. Suc- valore 100 e visualizza il contenuto della memoria sullo schermo. Infine libera la
cessivamente venne deciso che in caso di fallimento new dovesse lanciare un'ec- memoria allocata dinamicamente. Si ricordi he se il compilatore implementa
cezione. Infine venne deciso che un fallimento di new generasse un'eccezione e new in modo da fargli restituire un valore nullo, sar necessario adattare il pro-
che, opzionalmente, venisse restituito un puntatore nullo. Pertanto new stato gramma precedente.
implementato in modo differente a seconda dei momenti e del produttore del com- L'operatore delete deve essere utilizzato solo con un puntatore valido allocato
pilatore. Anche se alla fine tutti i compilatori implementeranno new secondo Io precedentemente tramite new. Se si utilizza delete con un altrp tipo di puntatore,
standard, attualmente l'unico modo per-sapere il modo in cui viene gestito il fal- il risultato sar indefinif e provocher quasi certmente il blocco del sistema.
limento di new consiste nel consultare la documentazione del compilatore. Anche se new e delete eseguono funzioni simili a malloc() e free(), questi due
Dato che lo standard del C++ specifica che new generi un'eccezionem-caso operatori presentano numerosi vantaggi. Innanzi tutto, new alloca automatica-
______di fallimento, qu~sto. il modol_i:!_c!-!i__y~rr scritto il codice in questo volume. Se mente la memoria necessaria per contenere un oggetto del tipo specificato~ Quin-
di non sar pi_necessario utilizzare l'operatore sizeof. Poich le.dimensioni del-
------ ------ - ,. ___ ------ ":_--
362 CAPITOLO 13 GLI A f1R"A', I PUNTATORI, GLI IN O I RIZZI ... 363

l'area allocata vengono calcolate automaticamente, si elimina ogni possibilit di Allocazione degli array
errore. In secondo luogo, new restituisce automaticamente un puntatore del tipo
specificato. Non necessario utilizzare una conversione di tipo esplicita cos come L'operatore new consente anche di allocare array utilizzando la fol"Ilf~ generale:
si fa quando si alloca la memoria utilizzando malloc(). Infine, sia new che delete
possono essere modificati tramite overloading consentendo perci di creare siste- var_p = new tipo_array [dim];
mi di allocazione personalizzati.
Anche se non vi alcuna regola formale che stabilisce ci, meglio non uti- Dove dim specifica il numero di elementi dell'array. Per liberare la memoria
lizzare insieme new e delete con malloc() e free() nello stesso programma in quan- occupata dall'array si deve utilizzare questa forma di delete:
to non vi alcuna garanzia che essi siano compatibili.
delete [ ] var_p;
Inizializzazione della memoria allocata
Qui, la coppia di parentesi quadre ([ ]) informa delete che si deve rilasciare la
possibile inizializzare la memoria allocata con un determinato valore inserendo memoria occupata da un array.
un inizializzatore dopo il nome del tipo nell'istruzione new. Ecco la forma gene- Ad esempio, il programma seguente alloca un array formato da dieci elementi
rale di new quando viene inclusa anche l'inizializzazione: interi.

#i nel ude <i ostream>


var_p = new tipo (inizializzatore);
#include. <new>
using namespace std;
namralmente il tipo dell'inizializzatore deve essere compatibile con il tipo dei
dati per i quali stata allocata la memoria. Ad esempio, il seguente programma d i nt mai n ()
all" intero allocato il valore iniziale 87. {
int *p, i;
#include <iostream>
#i nel ude <new> try {
using namespace std; P = new int [10]; 11 alloca un array di 10 interi
catch(bad_alloc xa) {
i nt ma in() cout "Errore di allocazione\n";
{ .
return .1; ... - - ---
int "'p;

try { for(i=O; i<lO; i++ )


p = new int (87); Il inizializzato a 87 p[i] = i;
catch(bad_alloc xa) {
cout <<"Errore di allocazione\n"; for(i=O; i<lO; i++)
return 1; cout << p[i] << " ";

delete [] p; Il libera la memoria


cou t << 11 In 11 << p << 11 11 ;
cout <<"si trova il valore " << *p << "\n"; return O;
delete p;

return O; Si noti l'istruzione delete. Come si appena detto, quando si libera la memo-----
a_ occupata da un array allocato=-da-new, necessario specificare che si sta libe~ - --
364 CAPITOLO 13
GLI ARRAY, I PUNTATORI, GLI INDIRIZZI ... 365
rando la memoria di un array utilizzando delete insieme a [](come si vedr nella try {
prossima sezione, questo accorgimento particolarmente importante quando si p = new balance;
devono allocare array di oggetti). catch(bad_aHoc xa)
Vi una restrizione all'allocazione di array: non possibile assegnare valori cout "Errore di allocazione\n":
iniziali ad array dinamici. Quindi, quando si alloca un array non possibile speci- return l;
ficare un inizializzatore.

p->set(l2387.87, "Mario Rossi"):


Allocazione di oggetti
p->get_bal (n, s):
L'operatore new consente anche di allocare dinamicamente oggetti. L'operazione
crea un oggetto e restituisce un puntatore a tale oggetto. L'oggetto creato dinamica- cout << s << " saldo: " << n:
mente si comporta come qualsiasi altro oggetto e nel momento in cui viene creato, cout << "\n";
viene richiamata (se esiste) la sua funzione costruttore. Nel momento in cui l' ogget-
del ete p;
to viene eliminato con delete viene richiamata la sua funzione distruttore.
Ecco un breve programma che crea una classe chiamata balance che collega
return O;
al nome di una persona il suo saldo di conto corrente. All'interno di main() viene
creato dinamicamente un oggetto di tipo balance.

lii nel ude <i ostream> Po~ch~ ~con~iene un puntatore a un oggetto, per accedere ai membri dell'og-
lii nel ude <new>
getto s1 utilizza l operatore freccia.
#i nel ude <estri ng> Come ~i d7tto, gli ~ggetti allocati dinamicamente possono essere dotati di
usi ng namespace std; costrutt~n e d1:trutto.n: Inoltre, le funzioni costruttore possono essere
parametnzzate. S1 esam1m la seguente versione del programma precedente.
class balance {
double cur bal; #include <iostream>
char name [SO] ; #i nel ude <new>
public: #include <cstring>
void set(doubl e n, char *s) { usi ng namespace s td;
cur_bal = n;

strcpy(name, s); cl ass ba lance {

' double cur bal;


char name [SO];

I
void get_bal (double &n, char *s) { publ ic:
n = cur_bal; balance(double n, char *s)
strcpy(s, name); cur_bal = n;
} strcpy(name, s);
};
--ba lance() . {
int main() cout << "Distruzione di ";
{ cout << name << "\n";
______ balance *p;
.char s [80]; void get_bal (double~n. chai:...!.s.)._.{_ -
double n; n = cur_bal;
strcpy(s, name);
CAPITOLO 13 G L I A R R A Y , I P U N T TORT, -G-Cl"I N D I R I Z Z I 367.

char name[80];
}; publ ic:
balance(double n, char *s)
int main() cur_bal = n;
{ strcpy{name, s);
balance *p;
char s[80]; balance() {} 11 costruttore senza parametri
double n; --balance() {
cout << "Distruzione di ";
/I questa versione usa un inizializzatore cout << name << "\n";
try {
p = new ba lance (12387 .87, "Mario Rossi"); void set(double n, char *s)
catch(bad_alloc xa) { cur_bal = n;
cout "Errore di allocazione\n"; strcpy{name, s);
return 1;
void get_bal (double &n, char *s) {
n = cur_bal;
p->get_bal (n, s); strcpy(s, name);
}
cout << s << " saldo: " << n; };
cout << "\n";
int main()
delete p; {
bal ance *p;
return O; char s [80];
doubl e n;
int i;
I parametri della funzione costruttore dell'oggetto sono specificati dopo il
try {
nome del tipo come avviene in qualsiasi altra inizializzazione.
P = new balance [3]; Il alloca l'intero array
possibile-allocare anche array di oggetti ma vi una limitazione. Poich
catch(bad_alloc xa) {
nessun array allocato da new pu essere inizializzato, necessario assicurarsi che cout "Errore di allocazione\n";
se la classe contiene pi funzioni costruttore, una non preveda parametri. In caso return 1;
contrario, quando si cercher di allocare l'array il compilatore C++ non trover
un costruttore adatto e non consentir la compilazione del programma.
In questa versione del programma precedente viene allocato un array di og- Il si noti l'uso del punto, non della freccia
getti balance e viene richiamato il costruttore senza parametri. p[O].set(l2387.87, "Mario Rossi");
p[l] .set(l44.00, "Bruno Bianchi");
#include <iostream> p[2]. set(-11.23, "Paolo Verdi");
#i.nel ude <new>
#i nel ude <estri ng> for(i=!);_ i<3; i++) {
using namespace std; p[i].get_bal(n, s);

class balance { cout << s << " saldo: " << n;


-cour-<~ "\n~", - - - -
-
_ -=-==--:-::-::--_.QQ.uble cur_bal;
}
----
368 CAPITOLO 13 G.L..L-A-a.f\..A Y; I PUNTA T_O_R_I , GLI INDIRIZZI ... 369

del ete [] p; int main()


return O; {
int *p, i;

Ecco l'output prodotto dal programma: p = new(nothrow) int[32]; Il uso dell'opzione nothrow
if( !p) {
cout "Errore di allocazione.":
Mario Rossi saldo: 12387 .9 return l;
Bruno Bianchi sa 1do: 144
Paolo Verdi saldo: -11.23
Distruzione di Paolo Verdi for(i=O; i<32; i++) p[i] = i;
Distruzione di Bruno Bianchi
Distruzione di Mar.~o Rossi for(i=O; i<32; i++) cout p[i] " ":

Un motivo per cui si deve utilizzare la forma deleteO quando si deve cancella- delete [] p; Il memoria libera
re un array di oggetti allocati dinamicamente il fatto che la funzione distruttore
deve essere richiamata per ogni oggetto dell'array. return O;

L'alternativa di new e delete Come si pu vedere in questo programma, quando si impiega l'approccio
nothrow occorre controllare il puntatore restituito da new dopo ogni richiesta di
Lo standard del linguaggio C++ consente di fare in modo che, in caso di fallimen- allocazione.
to nell'allocazione della memoria, new restituisca null invece di lanciare un'ecce-
zione. Questa forma di new utile soprattutto quando si deve compilare codice
non recente con un compilatore C++ standard. Inoltre utile quando si devono Altre forme di new e delete
sostituire con new le vecchie chiamate a malloc() (avviene quando si deve aggior-
nare al C++ del codice C). V anche un'altra forma speciale di new che pu essere utilizzata per specificare
Ecco l'utilizzo di questa forma di new: un metodo alternativo di allocazione della memoria. Tale forma utile soprattutto
quando si esegue l'overloading di new per casi particolari. L'implementazione
p_var = new(nothrow) tipo; standard di questa forma dell'operatore new ha il seguente aspetto:
- ----------
Qui, p::var una variabile puntatore di tipo tipo. La forma nothrow di new p_var = new (location) type;
funziona esattamente come la forma originale di new, nata qualche tempo fa.
Poich in caso di fallimento restituisce null, pu essere inserita nel vecchio codice Qui, posizione specifica l'indirizzo restituito da new.
evitando quindi di dover aggiungere la gestione delle eccezioni. Tuttavia, quando Per liberare la memoria allocata con questa forma di new occorre impiegare la
si deve realizzare nuovo codice, opportuno impiegare le eccezioni. Per utilizza- corrispondente forma di delete.
re l'opzione nothrow, si deve includere l'header new.
Il seguente programma mostra l'uso di nothrow.

11 Dimostrazione della forma nothrow di new.


#include <iostream>
#.i nel ude <new>
using namespace s~_;
Capitolo 14

Overloading di funzioni,
costruttori di copie
e argomenti standard

14.1 Overloading delle funzioni


14.2 Overloadlng delle funzioni costruttore
14.3 I costruttori di copie
14.4 Ricerca dell'Indirizzo di una funzione
modificata tramite overloading
14.5 L:anacronismo della parola
riservata overload
14.6 Gli argomenti standard delle funzioni
14.7 Overloading di funzioni e ambiguit

.,,..,,
"-~uesto capitolo esamina gli argomenti dell'overloa-
ding delle funzioni, dei costruttori di copie e degli argomenti standard.
L' overloading delle funzioni uno degli aspetti fondamentali del linguaggio di
programmazione C++. Infatti l'overloading delle funzioni non solo fornisce il
supporto per il polimorfismo in fase di compilazione ma aggiunge al linguaggio
flessibilit e comodit. Tra le funzioni modificate tramite overloading, quelle pi
importanti sono i costruttori. La forma pi importante costituita dal costruttore
di copie. Gli argomenti standard sono strettamente correlati al concetto di
overloading delle funzioni. Gli argomenti standard" posscin-"talvolta costituire
un'alternativa all'overloading delle funzioni.

14.1 Overloading delle funzioni


L'overloading consiste nell'impiegare lo stesso nome per due o pi funzioni. Il
.segreto dell'overloading il fatto che ogni ridefinizione della funzione deve uti-
lizzare parametri di tipo differente oppure in numero differente. Grazie a q_ueste
differenze, il compilatore sa quale funzione richiamare in una determinata situa-
zione. Ad esempio, questcrprogramma esegue l' overloading della funzione myfunc()
utilizzando parametri di tipo differente.

- --- - ------ - --------


-~ ~
----=----~ -:
372 CAPITOCO 14
OVERLOADING DI FUNZIONI ... 373

!include <iostream>
int myfunc(int i, int j)
usi ng namespace std;
I
return i *j;
int myfunc(int i}; Il differenze nel tipo dei parametri
double myfunc(double i};

int main(} Come si detto, la caratteristica principale dell' overloading delle funzioni il
{ fatto che queste devono differire per quanto riguarda il tipo e/o il numero dei
parametri. Dunque due funzioni non possono differire solo per il tipo di dati resti-
cout << myfunc(lO) " "; Il richiama myfunc(int i) tuito. Ad esempio, ecco un modo errato per eseguire l'overloading di myfunc():
cout myfunc(S.4); Il richiama myfunc(double i)
return O; i nt myfunc (i nt i); 11 Errore: non sufficiente 1a differenza
float myfunc(int i); Il del solo valore restituito.

double myfunc(double i)
Talvolta due dichiarazioni di funzioni sembrano differenti mentre in realt
{
return i;
non cos. Ad esempio, si considerino le due dichiarazioni seguenti.

void f(int *p);


int myfunc(int i) void f(int p[]); Il errore, *p la stessa cosa di p[]
{
return i; Si deve sempre ricordare che per il compilatore * p uguale a p [ ]. Pertanto
anche se i due prototipi sembrano diversi quanto al tipo dei loro parametri, in
realt non lo sono.
Questo programma invece esegue l'overloading di myfunc() impiegando un
numero differente di parametri:

#include <iostream> 14.2 Overloading delle funzioni costruttore


using namespace std;
Le funzioni costruttore possono essere modificate tramite overloading; nella pra-
int myfunc(int i); Il differiscono per il numero dei parametri tic- corr:nine,-i costruttori modificati tramite overloading sono molto impiegati. Vi
int myfunc(fiit i, int j); sono principalmente tre motivi che spingono a eseguire l'overloading di una fun-
zione costruttore: la maggiore flessibilit, la possibilit di creare oggetti inizializzati
int main() e non inizializzati e la possibilit di definire costruttori di copie. In questa sezione
{ verranno esaminati i primi due aspetti. L'argomento dei costruttori di copie verr
cout myfunc(lO) " ": Il richiama myfunc(int i) descritto nella prossima sezione.
cout << myfunc(4, 5); Il richiama myfunc(int i, int j)
return O;
Overloading di un costruttore per acquisire
maggiore flessibilit
int myfunc(int i)
I Spesso si crea una classe per la quale esistono due o pi modi per costruire un
return i; oggetto. In questi casi opportuno fornire una funzione costruttore modificata
--}---
tramite overloading per entrambi questi metodi. Questa una-regolafondamenta-
le in quanto se skerca di creare un oggetto per il quale non esiste un costruttore,
viene prodotto un errore in fase di compilazione. - -- --- ---=-o_ -
OVERLOADING DI FUNZIONI ... 375
374 CAPITOLO 14

Offrendo un costruttore per ognuna delle modalit in cui un utilizzatore della In questo programma si inizializza un oggetto di tipo date; l'operazione pu
classe pu voler costruire un oggetto, si aumenta la flessibilit della classe. L'utente essere eseguita specificando la data tramite tre interi oppure utilizzando una strin-
libero di scegliere il modo migliore per costruire un oggetto in una determinata -ga che contiene la data specificata in una fonna generale:
circostanza. Si consideri il seguente programma che crea una classe chiamata
date che contiene una data. Si noti che esistono due versioni del costruttore: mmldd/yyyy

#include <iostream> Poich si tratta in entrambi casi di metodi comuni di rappresentazione di una
#include <cstdio> data, opportuno consentire all'utente di costruire l'oggetto in entrambi i modi.
using namespace std; Come illustrato dalla classe date, forse il motivo pi comune che spinge a
eseguire 1'overloading di un costruttore quello di consentire di creare un oggetto
class date { util.izzando il metodo pi appropriato e naturale in una determinata circostanza.
i nt day, month, year; Ad esempio, nel seguente main(), all'utente viene richiesta la data che viene intro-
public: dotta nel!' array s. Questa stringa pu anche essere utilizzata direttamente per cre-
date(char *d); are d. Non vi alcuna necessit di convertirla in un'altra forma. Se date() non
date(int m, int d, int y);
fosse stata modificata tramite overloading per accertare la forma di stringa, sareb-
void show_date();
be stato necessario convertire manualmente la data in tre interi.
};

Il Inizializzazione con una stringa. int main(}


date: :date(char *d) {
{ char s (80);
sscanf(d, "%d%*c%d%*c%d", &month, &day, &year); cout << "Immettere la nuova data: ";
cin s;

Il Initializazione con interi. date d(s);


date::date(int m, int d, int y) d. show_date();
{
day = d; return O;
month = m;
year = y;
Vi potrebbe essere anche una situazione in cui pu essere pi comodo
inizializzare un oggetto di tipo date utilizzando tre interi. Ad esempio, se la data
void date: :show_date(} viene generata da qualche metodo di calcolo, allora e pi naturale e appropriato
creare un oggetto date utilizzando la forma date(int, int, int). Qui lo scopo
cout month "I" .day; dell'overloading del costruttore di date quello di rendere pi flessibile e sempli-
cout << u;n << year << u Il;
ce il suo uso. Questa maggiore flessibilit e facilit d'uso particolarmente im-
portante quando si creano librerie di classi che verranno utilizzate da altri pro-
int main()
{
grammatori.
date ob1(12, 4, 2001), ob2("10l22l2001"};

obl.show_date()-;--~ Creazione di oggetti inizializzati e non inizializzati


ob2. show_date();
Un altr motivo che spesso spinge a eseguire )'overloading dele funzioni
return O; costrutt~rc:_
_t:Cnsiste nl cof!~e_nti~_ !a-cr~~zio_nedi ~getti inizializzati e non
376 CAPITOLO 14
OVERLOADING DI FUNZIONI ... 377

inizializzati (o, pi precisamente, oggetti a inizializzazione standard). Questo Il Visualizza le potenze di 3


particolarmente importante se si vuole essere in grado di creare array dinamici di cout "Potenze di 3: ";
oggetti di una determinata classe poich non possibile inizializzare un array for(i=O; i<S; i++) {
allocato dinamicamente. Per consentire l'uso degli array di oggetti inizializzati e cout ofThree[i] .getx() " ";
non inizializzati, occorre includere un costruttore che supporti l'inizializzazione
e uno che non la supporti.
Ad esempio, il seguente programma dichiara due aqay di tipo powers; uno cout <<" \n \n" ;
viene inizializzato l'altro no. Quindi il programma alloca dinamicamente un array.
Il allocazione dinamica di un array
try {
#include <iostream>
#i nel ude <new> p = new powers[S]; Il nessuna inizializzazione
using namespace std; catch {bad_alloc xa) {
cout "Errore di allocazione\n";
cl ass powers return l;
int x;
public:
Il overload del costruttore in due modi /I Inizializza l 'array dinamico con le potenze di 2
for(i=O; i<S; i++) {
powers() { x = O; } Il senza inizializzatore
p[i] ,setx(ofTwo[i] .getx());
powers(int n) { x = n; } Il con inizializzatore

int getx() { return x;


void setx(int i) { x = i; 11 Visualizza le potenze di 2
}; cout << "Potenze di 2: ";
for(i=O; i<S; i++) {
int mai n() cout p[i] .getx() " ";
{
powers ofTwo[] = { 1, 2, 4, 8, 16}; Il inizializzato cout << "\n\n";
powers ofThree[S]; Il non inizializzato
powers *p; del ete [] p;
int i; return O;

Il Visualizza le potenze di 2
cout "Potenze di 2: "; In questo esempio, sono necessari entrambi i costruttori. Il costruttore standard
for(i=O; i<S; i++) viene impiegato per costruire I' array non inizializzato oIThree e l' array allocato
cout ofTwo [i]. getx () 11
"; dinamicamente. Il costruttore parametrizzato viene richiamato per creare gli og-
getti dell' array oITwo.
cout << "\n\n";

Il
imposta le potenze di 3
oflhree[O]. setx (1); 14.3 I costruttori di copie
ofThree[l] .setx(3);
ofThree[2] .setx(9); Uno di;:i costruttori pi importanti da modificare trlllllite overloading il costruttore
ofThree[3] .setx(27); di copie. La creazione di un costruttore dj copie pu aiut~~ a evitareJ_proble~ _
-----of:-Three[4] .setx(Sl);
che sorgono quando si uiiiizz~ ~n ~ggett~ per inizializzarne un a:ltrQ,_,
378 CAPITOLO 14 OVERLOADING DI FUNZIONI... -379--

Si pu iniziare ricordando il problema che il costruttore di copie deve risolve- caso l'assegnamento. Il secondo l'inizializzazione che pu verificarsi in tre
re. Normalmente, quando si usa un oggetto per inizializzarne un altro, il linguag- modi:
gio C++ preve.de lesecuzione di una copia bit a bit. Questo significa che l'oggetto quando un oggetto inizializza esplicitamente un altro oggetto, come nelle di-
di destinazione sar una copia identica dell'oggetto utilizzato per l'inizializzazione. chiarazioni;
Anche se questo comportamento appropriato per la maggior parte dei casi (e in quando viene eseguita una copia di un oggetto che deve essere passato a una
genere esattamente ci che si vuole ottenere) in alcuni casi non si deve usare una funzione;
copia a bit. Ad esempio uno dei casi piil comuni si presenta quando la creazione di
quando viene creato un oggetto temporaneo (normalmente come valore resti-
un oggetto richiede l'allocazione di un area di memoria. Ad esempio, si supponga
tuito da una funzione).
che la creazione di una classe chiamata MyClass allochi un'area di memoria per
ciascun oggetto e poi si immagini un oggetto A di tale classe. Questo significa che Il costruttore di copie viene applicato solo nel caso delle inizializzazioni. Ad
A ha gi allocato la propria memoria. Inoltre si supponga di utilizzare A per esempio, supponendo che esista una classe chiamata myclass e che y sia un ogget-
inizializzare 8 come nel seguente esempio: to di tipo myclass, l'inizializzazione viene impiegata da ciascuna delle seguenti
istruzioni:
MyCl ass B = A;
myclass x = y; Il y inizializza esplicitamente x
Se viene eseguita una copia bit a bit, llora 8 sar una copia esatta di A. Que- func(y); 11 y passata come parametro
sto significa che B utilizzer la stessa area di memoria allocata per A, non una
Y = fune O; 11 y riceve da fune() un oggetto temporaneo
propria area distinta. Chiaramente questo non il risultato desiderato. Ad esem-
pio, se MyClass include un distruttore che libera la memoria, allora la distruzione Di seguito viene presentato un esempio dove necessario impiegare un
di A e 8 provocher la doppia cancellazione della stessa area di memoria! costruttore di copie esplicito. Questo programma crea un array di interi "sicuro"
Lo stesso tipo di problema pu verificarsi in altri due casi: innanzitutto quan- che impedisce il superamento dei limiti. Nel Capitolo 15 si trova un esempio
do viene eseguita una copia di un oggetto nel momento in cui questo viene passa- migliore che crea un array sicuro tramite operatori modificati tramite overloading.
to come argomento a una funzione; in secondo luogo quando viene creato un La memoria per gli array viene allocata tramite new e all'interno di ciascun og-
oggetto temporaneo restituito da una funzione. Si ricordi che gli oggetti tempora- getto array viene gestito un puntatore alla relativa area memoria.
nei vengono creati automaticamente per contenere il valore restituito da una fun-
zione ma possono anche essere creati in altre situazioni. /* Questo programma crea una classe per array "sicuri".
Per risolvere il problema appena descritto, il linguaggio C++ consente di cre- Poich lo spazio per l 'array viene allocato con new, viene fornito
un costruttore di copie per allocare la memoria quando si utilizza
are un costruttore -di-eopie-che il compilatore impiega quando si usa un oggetto
un oggetto dell 'array per ini.zializzarne un altro.
per inizializ7arne un altro. Quando esiste un costruttore di copie, viene impiegato
al posto del costruttore bit a bit. La forma piil generale di costruttore di copie :
*I
#i nel ude <i ostream>
#i nel ude <new>
classname (const nome-classe &o) { #i nel ude <cstdl i b>
Il corpo del costruttore using namespace std;
}
class array
Qui o un riferimento all'oggetto che si trova sul lato destro int *p;
int size;
dell'inizializzazione. Un costruttore di-copie pu essere dotato di parametri ag-
public:
giuntivi sempre che siano stati definiti degli argomenti standard. Comunque, in
array(int sz) {
ogni caso, il primo parametro deve essere un riferimento all'oggetto che esegue try {
linizializzazione. . p = new int[sz];
importante comprendere che il linguaggio C++ definisce due diversi-O.pi di - - - - Jcafi:ll- (bad_::aTlocXTr
- - -situazioni i~ cui a un oggetto vleneassegi1at6il valore di un altro oggetto. II prim0- - . ~~. cout_5<-"Errore di allcicazione\n";
OVERLOADING DI FUNZIONI ... 381
380 CAPITOLO 14

Ecco cosa accade quando si usa num per inizializzare x nell'istruzione:


exi t (EXIT_FAILURE);

size = sz; -
array x(num); Il richiama il costruttore di copie

-array() { delete [] p; } Viene richiamato il costruttore di copie, viene allocata l'area di memoria per
il nuovo array contenuta in x.p e nell'array dix viene copiato il contenuto di num.
11 costruttore di copi e In questo modo, gli array x e num conterranno gli stessi valori ma ciascun array si
array(const array &a); trover in un'area di memoria distinta. Questo significa che num.p e x.p non pun-
tano alla stessa area di memoria. Se non fosse stato creato il costruttore di copie,
void put(int i, int j) { l'inizializzazione bit a bit avrebbe fatto in modo che x e num condividessero la
if(i>=O && i<size) p[i] j;
stessa area di memoria (pertanto num.p e x.p avrebbero puntato alla stessa area di
memoria).
in~ get{int i) { -
return p[i];
Si ricordi che il costruttore di copie viene richiamato solo per le inizializzazioni.
Ad esempio, questa sequenza non richiama il costruttore di copie definito nel
}; programma precedente:

Il Costruttore di copie array a(lO);


array: :array(const array &a) Il
int i; array b(lO):

try { b = a; 11 non richiama il costruttore di copi e


p = new int[a.size];
catch (bad_alloc xa) In questo caso, b =a esegue l'operazione di assegnamento. Se= non modi-
.cout "Errore di allocazione\n"; ficato tramite overloading (come in questo caso), verr eseguita una copia bit a
exit(EXIT_FAILURE); bit. Pertanto, in alcuni casi, per evitare problemi si deve eseguire l' overloading
dell'operatore= oltre a creare un costruttore di copie (vedere il Capitolo 15).
for(i=O; i<a.size; i++) p[i] a.p[i];

int main()
14.4 Ricerca dell'indirizzo di una funzione
{
modificata tramite overloading
array num(lO);
int i;
Come si detto nel Capitolo 5, possibile ottenere lindirizzo di una funzione. Ad
for(i=O; i<lO; i++) num.put(i, i);
esempio l'indirizzo pu essere assegnato a un puntatore per poter richiamare la
for(i=9; i>=O; i--) cout << num.get(i); funzione tramite tale puntatore. Se la funzione non ha subito overloading, questa
cout << 11 \n"; operazione immediata. Al contrario, per le funzioni modificate tramite
overloading, l'operazione leggermente pi complessa. Per capire il motivo di
Il crea un altro array e lo inizializza con num questa complessit, si consideri innanzitutto la seguente istruzione che assegna a
array x(num); Il richiama il costruttore di copie un puntatore chiamato p l'indirizzo di una funzione chiamata myfunc():
for(i=O; i<lO; i++) cout x.get(i);
p = myfunc;
return O; .
382 CAPITOLO 14 OVERLOADING DI FUNZIONI ... 383

Se myfunc() non modificata tramite overloading, allora esister una e una In generale, quando si assegna a un puntatore a funzione l'indirizzo di una
sola funzione chiamata myfunc() e il compilatore -non avr alcuna difficolt ad funzione modificata tramite overloading, la dichiarazione del puntatore che de-
assegnare a p il suo indirizzo. Se invece myfunc() stata modificata tramite termina la funzione il cui indirizzo verr assegnato. Inoltre occorre notare che la
overloading, come pu il compilatore sapere qual la versione di myfunc() che si dichiarazione del puntatore a funzione deve corrispondere esattamente a una e
intende assegnare a p? Ecco la risposta: tutto dipende dal modo in cui stato una sola delle dichiarazioni delle funzioni in overloading.
dichiarato p. Ad esempio, si consideri il seguente programma:

#i nel ude <i ostream>


using namespace std; 14.5 L'anacronismo della parola riservata overload
int myfunc(int a); Quando venne creato il linguaggio C++, per creare una funzione modificata tra-
int myfunc(int a, int b); mite overloading era necessario impiegare la parola riservata overload. Ora tale
parola riservata obsoleta e non viene pi usata n supportata. Infatti non nep-
int main(} pure una parola riservata del linguaggio C++ standard. Tuttavia, poich potrebbe
{ capitare di incontrare qualche vecchio programma che la impiega, utile cono-
int (*fp)(int a); //puntatore a int f(int) scerne l'utilizzo. Ecco la forma generale della parola riservata overload:
fp = myfunc; // punta a myfunc(int)
overlo_ad nome-funzione;
cout fp(5);
Qui, nome-funzione il nome della funzione di cui si sta eseguendo
return O; l'overloading. Questa istruzione deve precedere tutte le dichiarazioni della fun-
zione modificata tramite overloading. Ad esempio, la seguente istruzione dice a
un vecchio compilatore che si intende eseguire l 'overloading di una funzione chia-
i nt myfunc (i nt a) mata test():
{
return a; overl oad test;

int myfunc(int a, int b)


{ .
14.6 Gli argomenti standard delle funzioni
return .a*b;
Il linguaggio C++ consente a una funzione di assegnare un valore standard a un
parametro nel caso in cui nella chiamata a tale. funzione non sia specificato alcun
Qui esistono due versioni di myfunc(). Entrambe restituiscono un valore int argomento corrispondente a tale parametro. II valore standard specificato in un
ma una accetta un unico argomento intero mentre l'altra richiede due argomenti modo sintatticamente simile all'inizializzazione di una variabile. Ad esempio, il
interi. Nel programma, fp dichiarato come un puntatore a una funzione che resti- seguente listato dichiara una funzione myfunc() che accetta un argomento double
tuisce un intero e che prende un argomento intero. Quando a fp viene assegnato avente un valore standard uguale a O.O:
l'indirizzo di myfunc(), il linguaggio C++ utilizza questa informazione per sele-
zionare la versione myfunc(int a). Se fp fosse stato dichiarato nel seguente modo: void myfunc(double d = O.O)
{ -~~

int (*fp)(int a, int b); Il ...


)
--'--=-=---,1\Jl()_ra a fp sarebbe stato assegnato I'indirizzJ> ~lla versione myfunc(int a, int b)_:,__:-_- - -------*--
-- - -- ...
_.____- -
_
_. - ----
~

:,_ - -
CAPITOLO 14
OVERLOADING DI FUNZIONI ... 385

Ora myfunc() pu essere richiamata in due modi, come illustrato dai seguenti return O;
esempi:

nyfunc(l98.234); Il passa un valore esplicito void cl rscr(int size)


nyfunc (); /I la funzione usa il default
for(; size; size--) cout endl;
La prima chiamata passa a d il valore 198.234. La seconda chiamata assegna
automaticamente ad il valore O.
Uno dei motivi che ha spinto a inserire gli argomenti standard nel linguaggio Come si pu vedere in questo programma, quando l'argomento standard
C++ il fatto che essi costituiscono un metodo con il quale il programmatore pu appropriato alla situazione, non necessario specificare alcun argomento quando
gestire elevati livelli di complessit. Per gestire la pi ampia variet di situazioni, viene richiamata clrscr(). Nel contempo resta possibile modificare il valore standard
capita frequentemente che una funzione contenga molti pi parametri di quanti e assegnare a size un valore differente.
siano necessari nell'uso comune. Pertanto, quando vengono impiegati gli argo- Un argomento standard pu essere utilizzato anche per chiedere alla funzione
menti standard, necessario specificare unicamente gli argomenti che hanno un di riutilizzare l'argomento precedente. Per illustrare questo uso, verr sviluppata
significato nella situazione in cui ci si trova e non tutti gli argomenti previsti per il una funzione chiamata iputs() che fa rientrare automaticamente una stringa di un
caso pi generale. Ad esempio, molte delle funzioni di I/O del C++ utilizzano determinato numero di caratteri. Per iniziare ecco una versione di questa funzione
argomenti standard proprio per questo motivo. che non utilizza alcun argomento standard:
Un semplice esempio dell'utilit degli argomenti standard delle funzioni
rappresentato dalla funzione clrscr() del seguente programma. Questa funzione void iputs(char *str, int indent)
cancella il contenuto dello schermo producendo in output una serie di codici di {
fine riga (line feed); non si tratta del metodo pi efficiente ma adatto per questo if(indent < O) indent O;
esempio. Poich normalmente un monitor visualizza 25 righe di testo, viene im-
piegato un argomento standard pari a 25. Ma poich alcuni terminali possono for( ; indent; indent--) cout " ";
visualizzare un numero maggiore o inferiore di righe (spesso il valore dipende
cout << str << "\n";
dalla modalit video impiegata), si pu alterare l'argomento standard specifican-
done uno esplicitamente.
Questa versione di iputs() viene richiamata specificando come primo argo-
'fi nel ude <i ostream>
asing namespace std; mento la stringa da produrre in output e come secondo argomento lentit del
rientro; :Anche se questo modo di scrivere iputs() non ha niente di sbagliato, se ne
void clrscr\int size=25); pu migliorare l'utilizzabilit fornendo un parametro standard indent che chiede a
iputs() di creare un rientro uguale al precedente. infatti molto comune visualizzare
int main() un blocco di righe rientrate della stessa entit. In questa situazione, invece di
{ dover fornire ripetutamente un argomento di inde.ntazione, si pu dare a indent un
regi ster i nt 1; valore standard che chieda a iputs() di far rientrare le righe come nella chiamata
precedente. Questo approccio illustrato dal seguente programma:
for(i=O: i<30; i++) cout <<i << endl;
cin.get();
#include <iostream>
clrscr(); Il cancella l5_ righe using namespace std;
for(i=O; i<30; i++) cout <<i<< endl;
cin.get();
I* Rientro standard -1. Questo valore dice alla funzione
di riutilizzare il valore precedente. *I
crrscr(lO); 11 cancella _10 ri-ghe--- - -
void iputs(char *str, int indent = -1);
----- --------
386 CAPITOLO 14 OVElUOADING DI FUNZIONI ... 387

int main() 11 errato!


{ void iputs(int indent = --1, char *str);
iputs("Salve a tutti", 10);
iputs("Riutilizza il rientro di 10 spazi"); Dopo aver iniziato a definire i parametri che accettano valori standard, non
i puts ("Rientro di 5 spazi", 5): pi possibile specificare altri parametri che non prevedono valori standard. Per-
iputs("Non rientrato", O);
tanto anche la seguente dichiarazione errata e non verr compilata:
return O;
int myfunc(float f, char *str, int i=lO, int j);

void iputs(char *str, i nt indent) Poich a i viene assegnato un valore standard, occorre assegnare un valore
{ standard anche a j.
static i = O; 11 memorizza il rientro precedente I parametri standard possono essere utilizzati anche nella funzione costruttore
di un oggetto. Ad esempio, la classe cuba illustrata di seguito gestisce le dimen-
i f(i ndent >= O) sioni di un cubo. Se non vengono specificati argomenti, la funzione costruttore
i = indent; assegna il valore O a tutti i valori, come illustrato dal seguente esempio:
else Il riutilizza il vaiore di rientro precedente
indent = i;
#include <iostream>
using namesp~ce std;
for( : i ndent; i ndent--) cout " ":

cl ass cube {
cout << str << "\n";
int x, y, z;
publ ic:
cube(int i=O, int j=O, int k=O) {
Questo programma produce il seguente output: x=i;
y=j;
Salve a tutti z=k;
Riutilizza il rientro di 10 spazi
Rientro di 5 spazi
Non rientrato int volume() {
return x*y*z;
. Quando-si creano funzioni che hanno argomenti standard, importante ricor- }
dare che i valori standard devono essere specificati una sola volta e questa deve };
essere la prima volta che la funzione viene dichiarata all'interno del file. Nel-
int main()
l'esempio precedente, l'argomento standard stato specificato nel prototipo di
{
iputs(). Se si cerca di specificare un nuovo valore (o anche lo stesso valore) nella
cube a(2,3,4), b;
definizione di iputs(}, il compilatore produrr un messaggio d'errore e si rifiuter
di compilare il programma. Anche se non possibile ridefinire gli argomenti cout a.volume() endl;
standard della funzione, comunque possibile specificare argomenti standard dif- cout b.volume();
ferenti per ciascuna versione di una funzione modificata tramite overloading-. - .

Tutti i parametri che hanno valori standard devono comparire a destra di quel- return O;
li che non prevedono valori. standard. Ad esempio, errato definire iputs() nel
seguente modo:
388 CAPITOLO 14 O V E AL O AD IN G DI FU N ZIO N.I... 389

L'uso degli argomenti standard (quando appropriato) offre due v_antaggi in void mystrcat(char *sl, char *s2, int len = -1);
una funzione costruttore. Innanzitutto evita di dover fornire un costruttore modi-
int main()
ficato tramite overloading che non accetti alcun parametro. Ad esempio, se ai {
parametri di cube() non fosse stato assegnato un valore standard, il secondo char strl[80] = "Questa una prova";
costruttore avrebbe dovuto gestire la dichiarazione di b (che non contiene argo- char str2[80] = "0123456789";
menti).
mystrcat(strl, str2, 5}; Il concatena 5 caratteri
cube () {x=O; y=O; z=O} cout << strl << '\n';

In secondo luogo, il fatto di impiegare valori standard decisamente pi co- strcpy(strl, "Questa una prova"); Il reinizializza di strl
modo rispetto al diversificare i valori ogni volta che l'oggetto viene dichiarato.
mystrcat(strl, str2}; //concatena l'intera stringa
cout strl '\n 1 ;
Argomenti standard e overloading
return O;
In alcune situazioni, gli argomenti standard possono essere utilizzati come una
forma semplificata di overloading. Il costruttore della classe cube un esempio di
questo tipo. Ora si prover a vedere un altro esempio. Si immagini di voler creare
Il Versione personalizzata di strcat{).
void mystrcat(char *sl, char *s2, int len)
due versioni personalizzate della funzione standard strcat(). La prima funzione si
comporter come strcat() concatenando l'intero contenuto di una stringa al termi- eIl Trova la fine di sl
ne dell'altra. La seconda versione accetta un terzo argomento che specifica il while(*sl) sl++;
numero di caratteri da concatenare. Pertanto la seconda versione concatena alla
fine di una stringa il numero di caratteri specificato tratto dall'altra stringa. Sup- if(len == -1) len = strlen(s2);
ponendo di chiamare le funzioni personalizzate con il nome mystrcat(), queste
avranno i seguenti prototipi: while{*s2 && len) {
*sl = *s2; Il copia caratteri
void mystrcat(char *sl, char *s2, int len); sl++;
--~d-~~!_!rcat(char *sl, char *s2);
s2++;
l en--;
La pri"tna versione copier len caratteri di s2 alla fine di s1. La seconda versio-
ne copier l'intera stringa puntata da s2 alla fine della stringa puntata da s1 e
*.sl '\O'; Il Chiude la stringa sl
dunque si comporter come strcat().
Anche se non vi sarebbe nulla di errato a implementare due versioni di
mystrcat() e creare le due versioni che si desiderano, esiste anche un modo pi
Qui, mystrcat() concatena alla fine della stringa puntata da s2, un numero di
facile. Utilizzando un argomento standard possibile implementare una sola ver-
caratteri pari a len tratti dalla stringa puntata da s1. Se per ten uguale a -1, come
sione di mystrcat() che svolge entrambe le operazioni. Si osservi ad esempio il
nel caso previsto dall'argomento standard, mystrcat() concatena ad s1 l'intera
seguente programm~
stringa puntata da s2 (pertanto, quando len uguale a -1 la fun~jone si comporta
come la funzione strcat() standard). Utilizzando un argomento standard per len
11 Versione personalizzata di strcat(). possibile combinare entrambe le operazioni in una sola funzione. In questo modo,
#include <iostream>
si nota che talvolta gli argoment standard costituiscono un'alternativa
#include <estri ng>
-all! overloading delle funzioni.
using namespa_~ s_!:d_;
390 CAPITOLO 14 OVERLOADING DI FUNZIONI ... 391

Uso corretto degli argomenti standard fl oat myfunc (fl oat i);
double myfunc(double i);
Anche se gli argomenti standard possono rappresentare uno stru.~ento molto po-
tente se utilizzati correttamente, possono talvolta essere impiegati in modo errato. int main()
Lo scopo degli argomenti standard quello di consentire a una funzione di svol- {
gere il proprio lavoro in modo efficiente e semplice, per aumentare considerevol- cout myfunc(lO.l) " "; Il non ambigua, richiama myfunc(double)
mente la flessibilit. Pertanto tutti gli argomenti standard dovrebbero rappresen- eout myfune(lO); 11 ambigua
tare l'uso pi generale o ragionevole di una funzione. Quando non esiste un valore
che venga normalmente associato a un parametro, non vi alcun motivo per di- return O;
chiarare un argomento standard. Infatti la dichiarazione di argomenti standard
senza motivo riduce la strutturazione del codice in quanto spinge chiunque legge-
float myfune(float i)
r il programma a chiedersi i motivi di questa scelta. {
Un'altra indicazione importante da seguire quando si impiegano gli argomen- return i;
ti standard la seguente: nessun argomento standard dovrebbe provocare azioni
pericolose o distruttive. In altre parole, un utilizzo accidentale di un argomento
standard non deve provocare gravi danni. double myfunc(double i)
{
return -i;

14.7 Overloading di funzioni e ambiguit


Qui myfunc() viene modificata tramite overloading in modo da accettare argo-
Talvolta possibile creare una situazione in cui il compilatore non riesce a sce- menti di tipo float o double. Nella riga non ambigua, viene richiamata
gliere fra due o pi funzioni in overloading. In questi casi, si dice che la situazion~ myfunc(double) poich, tranne quando sono specificate esplicitamente come float,
ambigua. Le istruzioni ambigue costituiscono errori e i programmi contenenti tutte le costanti in virgola mobile del C++ sono automaticamente di tipo double.
ambiguit non possono essere compilati. La causa principale di ambiguit riguar- Pertanto tale chiamata non ambigua. Quando invece myfunc() viene richiamata
da le conversioni automatiche di tipo eseguite dal linguaggio C++.Come si sa, il con l'intero 1O, si introduce un'ambiguit poich il compilatore non pu sapere se
linguaggio C++ tenta automaticamente di convertire gli argomenti utilizzati per il valore dovr essere convertito in float o double. Questo provoca la visualizzazione
richiamare una funzione nel tipo di argomenti attesi dalla funzione stessa. Ad di un messaggio di errore e impedisce la compilazione del programma.
esempio si cens-ideri-il seguente frammento di codice: Come illustrato dall'esempio precedente, non l'overloading di myfunc() per
valori double o float a causare l'ambiguit. Piuttosto la responsabilit della spe-
i nt myfunc (doub 1e d) ; cifica chiamata a myfunc() che utilizza un argomento di tipo indeterminato. In
11 altre parole, l'errore non causato dall'overloading di myfunc() ma dalla chiama-
cout myfunc('c'); Il non un errore, viene applicata la conversione
ta alla funzione. Ecco un altro esempio di ambiguit provocata dalle conversioni
automatiche di tipo del C++.
Come dice il commento, questo non un errore poich il linguaggio C++
converte automaticamente il carattere c nel suo equivalente double. In C++, sono #include <iostream>
molto poche le conversioni di questo tipo non consentite. Anche se le con~er~ioni using namespaee std;
a,utomatiche di tipo sono molto comode, rappresentano anche la causa pnnc1pale
cii ambiguit. Ad esempio, si consideri il seguente programma: char myfune(unsigned ehar eh);
char myfunc(char eh);
#include <iostream>
usi ng namespace std; int-main()
{ .
~ ~ !... n !... V,.., i..-''-'
392 CAPITOLO 14

cout myfunc('c'}; // richiama myfunc{char}


cout myfunc(88) "; //_ambigua
Qui, nella prima chiamata a myfunc(), vengono specificati due argomenti;
return O; _ pertanto non vi alcuna ambiguit e viene richiamata myfunc(int i, int j). Ma nella
seconda chiamata a myfunc(), si verifica un'ambiguit poich il compilatore non
sa se richiamare la versione di myfunc() che accetta un argomento oppure applica-
char myfunc(unsigned char eh} re gli argomenti standard alla versione che accetta due argomenti.
{ Alcuni tipi di funzioni modificate tramite overloading sono invece
return ch-1; inerentemente ambigue anche se, a prima vista, non sembrerebbe. Ad esempio, si
consideri il seguente programma:
char myfunc(char eh)
// Questo programma contiene un errore.
{
lii nel ude <i ostream>
return ch+l; using namespace std;

void f(int x):


In C++, i tipi unsigned char e char non sono ambigui. Ma quando si richiama void f(int &x); // errore
la funzione myfunc() utilizzando l'intero 88, il compilatore non sa quale funzione
chiamare. In altre parole, il numero 88 deve essere convertito in char o in unsigned int main()
char? Un altro modo in cui pu sorgere un'ambiguit si presenta quando vengono {
impiegati argomenti standard nelle funzioni modif.icate tramite overloading. Si int a=lO;
provi a esaminare il seguente programma:
f(a); //errore, quale f()?
lii nel ude <i ostream>
using namespace std; return O;

int myfunc(int i);


int myfunc(int i, int j=l); void f(int x}
{
int main() cout "In f(int}\n";
{
cout <::_ myfunc(4, 5) " "; // non ambigua
cout << myfunc(lO); //ambigua void f(int &x}
{

return O; cout "In f(int &}\n";

int myfunc(int i} Come si pu dedurre dal commento, non possibile eseguire l'overloading di
{ due funzioni quando l'unica differenza consiste nel fatto che una accetta un para-
return i: metro passato per indirizzo e l'altra accetta un normale parametro passato per
valore. In questo caso, il compilatorenon ha alcuna possibttit di sapere quale
versione della funzione si intende richiamare. Si ricordi che non vi alcuna diffe-
int myfunc(int i, int j} renza sintattica nel modo in cui un argomento viene specificato quando deve esse-
i re ricevuto per indirizzo o per valore.
return i *j;
Capitolo 15

Overloading
degli-operatori

15.1 Creazione di una funzione operator


membro
15.2 Overloading di operatori tramite
funzioni friend
15.3 Overloading di new e delete
15.4 overloading di alcuni operatori
particolari
15.5 Overloading dell'operatore virgola

., 'overloading degli operatori strettamente correlato


all'argomento dell'overloading delle funzioni. In C++ possibile eseguire
l'overloading della maggior parte degli operatori per consentire loro di svolgere
operazioni particolari rispetto a determinate classi. Ad esempio, una classe che
esegua la gestione di uno stack potrebbe eseguire I' overloading dell'operatore +
l
per svolgere l'operazione push e dell'operatore - per svolgere !"operazione pop.
Quando si esegue l'overloading di un operatore, non si perde nessuno dei suoi
utilizzi originali. Al contrario, si estende lo spettro dei tipi ai quali pu essere
1 applicato l'operatore.
La possibilit di eseguire l' overloading degli operatori una delle caratteristi-
l
1
che pi potenti del linguaggio C++. Essa consente di integrare completamente
nuovi tipi di classi nell'ambiente di programmazione. Se si esegue l'overloading
degli operatori appropriati, sar possibile utilizzare gli oggetti nelle espressioni
esattamente come si utilizzano i tipi di dati standard del C++. L' overloading degli
operatori sta anche alla base delle operazioni di I/O del C++.
L'overloading degli operatori viene eseguito creando funzioni operator. Una
funzione operator definisce le specifiche operazioni che dovranno essere svolte dal-
!' operatore modificato tramite overloading rispetto alla classe specificata. Le fun-
zioni operator possono essere o meno funzioni membro della classe su cui operano.
Le funzioni operator non membro sono quasi sempre funzioni friend della classe.
Le fu11z_ioni operator membro e le funzioni operator friend devono essere realizzate_
in modo diverso. Pertanto, questi due argomenti verranno esaminati separatamente,-
iniziando dalle funzioni operator che sono membri della classe.
C A P I T O L0_1_5__ - OVERLOADING DEGLI OPERATORI 397
396

15.1 Creazione di una funzione operator membro


loc temp;
Le funzioni operator membro di una classe hanno la seguente forma generale:
temp. longitude = op2. longitude + longitude;
temp. latitude = op2. latitude + latitude;
tipo-restituito nome-classe::operator#(elenco-argomenti)
{ return temp;
Il operazioni
}
int main{)
Spesso, le funzioni operator restituiscono un oggetto della classe su cui ope- {
rano ma tipo-restituito pu essere un qualsiasi tipo valido. Il carattere # deve loc obl{IO, 20), ob2( 5, 30);
invece essere sostituito dall'operatore di cui si esegue l' overloading. Ad esempio,
se si esegue l' overloading dell'operatore/, si dovr utilizzare l'indicazione operator obl.show{); Il visual"izza 10 20
/.Quando si esegue l'overloading di un operatore unario, elenco-argomenti deve ob2.show(); Il visualizza 5 30
essere vuoto. Quando si esegue l' overloading di operatori binari, l'elenco-argo-
menti deve contenere un solo parametro (il motivo di ci sar chiaro tra breve). obl = obl + ob2;
obl. show{); /I visualizza 15 50
Di seguito viene presentato un primo e semplice overloading degli operatori.
Questo programma crea una classe chiamata loc che memorizza valori di longitu- return O;
dine e latitudine. Tale classe esegue l'overloading dell'operatore+ rispetto a se
stessa. Si esamini attentamente il programma, facendo particolare attenzione alla
definizione di operator +(). Come si pu vedere, operator+() ha un solo parametro anche se esegue
l'overloading dell'operatore binario+ (invece dei due parametri corrispondenti ai
#i nel ude <i ostream> due operandi dell'operatore binario). Ci dovuto al fatto che !'operando sul lato
using namespace std; sinistro del + viene passato implicitamente alla funzione utilizzando il puntatore
this. L'operando a destra viene passato nel parametro op2. Il fatto che !'operando
class loc {
di sinistra venga passato utilizzando this ha un'altra implicazione fondamentale:
int longitude, latitude;
quando si esegue l'overloading di operatori binari, sar l'oggetto a sinistra a ge-
public:
l oc() {) nerare la chiamata alla funzione operator.
loc(in_!: lg, int lt) Come si detto, molto facile che una funzione operator modificata tramite
longitude = lg; overloading restituisca un oggetto della classe su cui opera. In questo modo con-
1atitude = lt; sente all'operatore di essere utilizzato in espressioni C++ pi complesse. Ad esem-
pio, se la funzione operator+() restituisse un valore di altro tipo, questa espressio-
ne non sarebbe valida:
void show()
cout << l ongi tu de << " "; obl = obl + ob2;
cout latitude << 11 \n";

Per assegnare a ob1 la somma di ob1 e ob2, il risultato dell'operazione deve


loc operator+{loc op2); essere un oggetto di tipo loc.
--n-- Inoltre, poich operator+() restituisce un oggetto di tipo loc possibile utiliz- -
zare is.!!Uzioni simili alla s~uente:_______ ..
11 Overl oadi ng di + per l oc
- -- - foc loi:_:::!Pe:i::.ator:r(.l~c;_ op2)
--.~----"' -
398 '.:APITOLO 15 OVERLOAOING DEGLI OPERATORI 399

In questa situazione, ob1 +ob2 genera un oggetto temporaneo che cessa di temp. longitude = op2.-longitude + longitude;
esistere al termine della chiamata a show(). temp. latitude = op2. latitude + latitude;
importante comprendere che una funzione operator pu restituire un valore
di quiliiasi tipo e che il tipo del valore restituito dipende unicamente dall'utilizzo return temp:
che il programmatore intende fame. Semplicemente, molto spesso una funzione
operator restituisce un oggetto della classe su cui opera.
Cn"ultima annotazione relativa alla funzione operator+(): tale funzione non
11 Overl cadi ng di - per 1oc
loc loc: :operator-(loc op2)
modifica gli operandi. Poich l'uso tradizionale dell'operatore+ non modifica gli { .
operandi, ha senso creare versioni tramite overloading che conservino questa ca- loc temp;
ratteristica (ad esempio 5+7 fornisce il valore 12 ma senza modificare n 5 n 7).
Anche se all'interno di una funzione operator si pu eseguire qualsiasi operazio- Il si noti l'ordine degli operandi
ne, in genere meglio considerare il contesto in cui viene utilizzato l'operatore temp. longitude = longitude - op2. longitude;
"normale". temp. latitude = latitude - op2. latitude;
Il programma successivo aggiunge alla classe loc tre nuovi operatori: -, = e
l'operatore unario++. Si faccia particolare attenzione al modo in cui sono definite . return- temp;
queste funzioni.

lii nel .1e <i ostream>


11 Overl oadi ng dell'assegnamento per 1oc
loc loc: :operator=(loc op2)
usi n namespace std;
{
longitude = op2. longitude;
class loc {
latitude = op2.latitude;
in: longitude, latitude;
public:
loc(} {} Il necessario per costruire valori temporanei
return *this; Il ovvero restituisce l'oggetto che ha generato la chiamata
loc(int lg, int lt) {
1ongitude = 1g;
latitude = lt;
11 Overl oadi ng di ++ (prefisso) per l oc
loc loc::operator++()
{
1ongi tude++;
void show()
1atitude++;
cout 1ongi tude " ";
cout latitude "\n";
return *thi s;

1 e: operator+ (1 oc op2);
int mai n ()
loc operator-(loc op2); {
lcc operator=(loc op2);
loc obl(lO, 20), ob2( 5, 30), ob3(90, 90);
1 oc operator++():
};
obl. show();
ob2.show();
I! Overl cadi ng di + per 1oc
1oc 1oc: :operator+(l oc op2)
{ - ----- ++obl~-
obl. show();_ Jl_yisu~_ljzza 11 21
lcc temp;
- -- ~--=-=- --.
400 CAPITOLO 15

ob2 = ++obl; Creazione di forme prefisse e postfissa degli operatori


obi.show{); //visualizza 12 22 di incremento e decremento
ob2.show{); I/ visualizza 12 22
Nel programma precedente, stato eseguito l'overloading solo della forma prefissa
obl = ob2 = ob3; /I assegnamento multiplo
dell'operatore di incremento. Nella versione standard del C++ possibile creare
obl.show(); Il visualizza 90 90
esplicitamente versioni prefisse e postfisse degli operatori di incremento e decre-
ob2.show(); Il visualizza 90 90
mento. A tale scopo si devono definire due versioni della funzione operator++().
Una sar definita come illustrato nel precedente programma mentre l'altra sar
return O; dichiarata nel seguente modo:

loc operator++(int x);


Innanzitutto, si esamini la funzione operator(). Si noti l'ordine degli operandi
nella sottrazione. Per conservare il significato della sottrazione, I' operando sul Se il ++ precede il suo operando, verr richiamata la funzione operator++().
lato destro del segno meno viene sottratto dall'operando che si trova a sinistra. Se il++ segue il proprio operando, verr richiamata la funzione operator++(int x)
Poich l'oacretto a sinistra che genera la chiamata alla funzione operator-(), i e x avr il valore O.
dati di op2 d~~ono essere sottratti dai dati puntati da this. importante ricordare L'esempio precedente pu essere generalizzato. Ecco l'aspetto generale delle
1' operando che genera la chiamata alla funzione. forme prefissa e postfissa degli operatori ++ e :
In C++, se il segno =non viene modificato tramite overloading, verr automa-
ticamente creato un operatore di assegnamento standard per ogni classe definita. Il Incremento prefisso
L'assegnamento standard costituito da una copia membro a membro e bit a bit. tipo operator++.( ) {
Eseguendo l'overloading dell'operatore= possibile definire esplicitamente ci Il corpo dell'operatore prefisso
che l'assegnamento dovr eseguire per una determinata classe. In questo esem- l
pio, l'operatore = modificato tramite overloading esegue esattamente la stessa
11 Incremento postfi sso
tipo operator++(int x) {
operazione dell'operatore standard ma in altre situazioni potrebbe eseguire ope- 11 corpo del 1 'operatore postfi sso
razioni diverse. Si noti che la funzione operator=() restituisce *this, ovvero l'og-
getto che ha generato la chiamata. Questo accorgimento necessario per consen- 11 Decremento prefisso
tire il concatenamento di pi assegnamenti come nel seguente esempio: ti po operator- - ( ) {
Il corpo dell'operatore prefisso
obl = ob2 = ob3; Il assegnamento multiplo l
11 Decremento postfi sso
Ora si osservi la definizione di operator++(). Come si pu vedere, l'operatore tipo operator- - (int x)
non ha parametri. Poich ++ un operatore unario, il suo unico operando verr 11 corpo dell'operatore postfissa
passato implicitamente utilizzando il puntatore this.
Si noti che entrambe le funzioni operator=() e operator++() modificano il va-
lore di un operando. Nel caso dell'assegnamento, viene assegnato un nuovo valo- '.NOTA Si deve fare attenzione quando si interviene su vecchi pro-
re ali' operando a sinistra (quello che genera la chiamata alla funzione operator=()). grammi C++ che eseguono l'overloading degli operatori++ e, in quanto nei
Nel caso di ++, l 'operando viene incrementato. Come si detto precedentemente. vecchi compilatori era impossibile definire versioni prefisse e posifcsse differenti
anche se si liben di far eseguire a queste funzioni le perazioni desiderate, degli operatori. In entrambi i casi veniva utilizzata lafonna pr~fissa.
sempre bene che le nuove operazioni siano congruenti rispetto al significato ori-
ginale dell'operatore.
402 CAPITOLO 15 -G-V-E R L O A O I N G O E G L I O P E RATO R I 403

Overloading delle forme abbreviate degli operatori Escludendo l'operatore =,le funzioni operator vengono ereditate da tutte le
classi derivate. Tuttavia, una classe derivata libera di eseguire l'overloading di
In C++ possibile eseguire l'overloading anche delle forme abbreviate degli opera- qualsiasi operatore (inclusi quelli modificati tramite overloading in una classe
-=
tori, come ad esempio +=, e di tutte le forme analoghe. Ad esempio, questa fun- base).
zione esegue l'overloading dell'operatore+= rispetto alla classe loc:

1oc 1oc: : opera tor+= ( 1oc op2)


{
15.2 Overloading di operatori tramite funzioni friend
longitude = op2. longitude + longitude;
latitude = op2. latitude + latitude; Si pu eseguire l'overloading di un operatore per una classe anche utilizzando
una funzione non membro, normalmente tramite una funzione friend. Poich una
return *this;
funzione friend non un membro della classe, non potr impiegare il puntatore
this. Pertanto, a una funzione di overloading friend operator gli operandi devono
essere passati esplicitamente. Questo significa che una funzione friend che esegua
Quando si esegue I' overloading di uno di questi operatori, si deve ricordare che
l'overloading di un operatore binario richiede due parametri mentre una funzione
si sta semplicemente unificando un assegnamento con un altro tipo di operatore.
friend che esegua l'overloading di un operatore unario avr bisogno di un parame-
tro. Quando si esegue l'overloading di un operatore binario con una funzione
Restrizioni all'overloading degli operatori friend, !'operando di sinistra deve essere passato come primo parametro e I' ope-
rando di destra deve essere passato come secondo parametro.
L'overloading degli operatori deve sottostare ad alcune restrizioni. Non possibile Nel seguente programma, l'operatore+ viene modificato tramite overloading
modificare la precedenza di un operatore. Non si pu cambiare il numero degli utilizzando una funzione friend.
operandi richiesti da un operatore (anche se si pu decidere di ignorare un operan-
do). Ad eccezione dell'operatore di chiamata a funzione (descritto pi avanti), le #include <iostream>
funzioni operator non possono avere argomenti standard. Infine non possibile using namespace std;
eseguire l'.overloading dei seguenti operatori:
class loc {
.* ? int longitude, latitude;
public:
Tecl!:camente all'interno di una funzione operator vi la possibilit di esegui- 1oc() {} I/ nec_(;)~fil:.i q_ p!!_~ costruire val ori temporanei
re qualsiasi operazione. Ad esempio, si pu eseguire l'overloading dell'operatore loc(int lg, int lt) {
longitude = lg;
+ in modo che scriva per dieci volte una frase sul disco. Tuttavia se si altera si-
latitude = lt;
gnificativamente il normale significato di un operatore, si corre il rischio di di-
struggere la chiarezza di un programma. Ad esempio, quando qualcuno leggendo
il programma trover un'istruzione come Ob1+0b2 si attender che debba avere void show()
luogo un qualche genere di somma e non un accesso a disco. Pertanto, prima di cout << 1-ongi tude " ";
alterare il significato di un operatore, si consideri se vi sono motivi tali da rendere cout latitude << "\n";
assolutamente necessaria questa operazione. Un ottimo esempio in cui era conve-
niente alterare il significato degli operatori costituito dagli operatori di I/O e
>>. Anche se le operazioni di I/O non hanno alcuna relazione con lo scorrimento friend loc operator+(loc opl, 1oc op2}; 11 ora una fri end
dei bit, tali operatori forniscono un'immagine visiva del loro significato rispetto loc operator-(loc op2);
-sia ali operazione di I/O che allo-scoi:rlmento-di.biLe quindi l'alterazione del loc operator=(loc op2};
significato ha senso. In generale perLc.onsigliabile conservare il contesto Q._l ___ _ 1oc operator++();
-r1onnale sig~!fiat~-dll-' oeerat~r.<:.'..- :--:: .. - - . __l~--
404 CAPITOLO - - - - - - -

11 + viene modificato trami te una funzione di overl oadi ng fri end obl.show();
loc operator+(loc opl, loc op2)
{
return O;
loc temp;

temp. longitude = opl. longitude + op2. longitude;


temp.latitude = opl.latitude + op2.latitude;
L'impiego di funzioni friend operator impone alcune restrizioni. Innanzi tutto,
utilizzando una funzione friend non possibile eseguire l'overloading degli ope-
return temp; ratori =, (), []o->. In secondo luogo, come verr descritto nella prossima sezione,
se si esegue l'overloading degli operatori di incremento o decremento utilizzando
una funzione friend, si dovr utilizzare un parametro indirizzo.
11 Overloading di -- per loc
l oc l oc:: operator-(1 oc op2)
{ Uso di una funzione friend per eseguire l'overloading
loc temp; degli operatori ++ e -

II si ncit; 1 'ordine degli operandi Se si desidera utilizzare una funzione friend per eseguire l'overloading degli opera-
temp. longitude = longitude - op2. longitude; tori di incremento o decremento, si deve passare !'operando come parametro indi-
temp. latitude = latitude - op2.1atitude; rizzo. Questo dovuto al fatto che le funzioni friend non hanno il puntatore this. Per
conservare il significato originale degli operatori++ e - -, queste operazioni devono
return temp; modificare il loro operando. Tuttavia, se si esegue l'overloading di questi operatori
utilizzando na funzione friend, I' operando verr passato per valore. Questo signifi-
ca che una funzione friend operator non ha alcuna possibilit di modificare !'ope-
11 Overl oadi ng de 11 'assegnamento per l oc rando. Poich alla funzione friend operator non viene passato un puntatore this al-
loc loc: :operator=(loc op2) l 'operando ma una copia dell'operanda, nessuna modifica apportata al parametro
{ modificher !'operando che ha generato la chiamata. possibile risolvere questa
1ongitude = op2. 1ongi tude; situazione specificando il parametro della funzione friend operator come un para-
latitude = op2. latitude; metro indirizzo. In questo modo ogni modifica apportata al parametro all'interno

l
della funzione modificher anche !'operando che ha generato la chiamata.Ad esem-
return *this; Il ovvero restituisce l'oggetto che ha generato la chiamata pio, il seguente programma utilizza funzioni friend per eseguire l'overloading delle
versioni prefisse di ++ e - - rispetto alla classe loc.

Il Overloading di ++ per loc #include <iostream>


loc loc: :operator++() using namespace std;
(
longi tu de++; class loc {
latitude++; int longitude, latitude;
public:
return *thi s; loc{) {}
loc(int lg, int lt)
longi tude = 1g;
int main() latitude = lt;
{
------
loc obl(lO, 20), ob2( s, 30);
void show().
obl "= obl + ob2; cout longitude 11
.!!..;-- __

-- ----- - -
406 CAPITOLO 15 O V E R L O A IJTITT>--D E <H r O P E RA T O R I

cout l atitude "\n"; --ob2;


ob2.show(}; Il visualizza 11 21
return O;
loc operator= (1 oc op2);
friend loc operator++(loc &op};
fri end 1oc operator--(1 oc &op}; Per eseguire l'overloading delle versioni postfisse degli operatori di incre-
};
mento e decremento utilizzando una funzione friend basta specificare un secondo
parametro intero fittizio. Ad esempio, ecco il prototipo delle versioni postfisse
Il Overloading dell'assegnamento per loc
loc loc: :operator=(loc op2) friend dell'operatore di incremento rispetto alla classe loc:
i
longitude = op2. longitude; Il friend, versione postfissa di ++
latitude = op2. latitude; friend loc operator++(loc &op, int x};

return *this; Il ovvero restituisce l'oggetto che ha generato la chiamata


Le funzioni friend operator offrono
una maggiore flessibilit
11 ora una friend - utilizza un parametro indirizzo
loc operator++(loc &op) In molti casi, se si esegue l'overloading di un operatore utilizzando una funzione
{ friend o una funzione membro, non si riscontrano differenze funzionali. In tali casi
op. longitude++; generalmente consigliabile eseguire l'overloading utilizzando funzioni membro. Vi
op. latitude++; per una situazione in cui l'uso di una funzione friend aumenta la flessibilit di un
operatore.
return op; Come si detto in precedenza, quando si esegue l' overloading di un operatore
binario utilizzando una funzione membro, l'oggetto sul lato sinistro dell'opera-
tore a generare la chiamata della funzione operator che esegue l' overloading. Inoltre
11 rende op-- un fri end - usa un indirizzo viene passato tramite il puntatore this un puntatore a tale oggetto. Ora, si suppon-
loc operator-- (1 oc &op} ga di utilizzare una classe CL nella quale sia stata definita la funzione membro
I operator+() che somma un oggetto della classe e un intero. Dato un oggetto Ob di
op. 1ongi tude--; tale classe, la seguente es.pressione sar corretta .
. op. latitde-:;-- -- ----

Ob + 100 // corretta
return op;

In questo caso Ob a generare la chiamata alla funzione che esegue


int main() l' overloading di + e viene eseguita la somma. Ma cosa sarebbe accaduto se l' espres-
I sione fosse stata scritta nel seguente modo?
loc obl(lO, 20), ob2;
100 + Ob // errata
obl. show();
++cib1; In questo caso, sul lato sinistro dell'operatore appare l'intero. Poich l'intero
obl.show(); Il visualizza 11 21 . un tipo predefinito, non si trover alcuna operazione definita fra un intero e ui1
ob2 = ++obl;
oggetto del tipo di Ob. Pertanto, il compilatore non accetter-Fespressione. Come
si pu immaginare, in alcune applicazioni il fatto di dover sempre posizionare
_ _ _ ob2.show(); Il visualizza -~-E- __ . l'oggetto a sinistra pone hmiti inutili oltre a esser~ fonte di errori.
- . -- - --- -
- - - - OVERLOADING -DEGLI OPERATORI 409
408 CAPITOLO 15

La soluzione del problema prec~dente costituita dall' overloading di una som-


ma utilizzando una funzione friend al posto della funzione. membro. In tal modo, loc temp;
alla funzione operator verranno esplicitamente passati entrambi gli argomenti.
temp. longitude = opl + op2. longitude;
Pertanto, per consentire l'uso delle espressioni oggetto+intero e intero+oggetto temp. latitude = opl + op2. latitude;
baster eseguire due volte I' overloading della funzione: una versione per ogni
situazione. Quindi, quando si esegue l'overloading di un operatore utilizzando return temp;
due funzioni friend l'oggetto potr trovarsi sia sul lato sinistro che anche sul lato
destro dell'operatore.
Questo programma illustra il modo in cui possibile utilizzare funzioni friend int main()
per definire un'operazione che interessi un oggetto e un tipo predefinito. {
loc obl{lO, 20), ob2( 5, 30), ob3(7, 14):
#include <iostream>
using namespace std; obl.show();
ob2.show();
class loc { ob3.show0;
int longitude, latitude;
public: obl = ob2 + 10; 11 entrambe queste forme
loc() {} ob3 = 10 + ob2; 11 sono corrette
loc(int lg, int lt)
longitude = lg; obl.show();
latitude = lt; ob3.show0:

return O;
void show()
cout << 1ongi tu de << 11 11 :
cout << latitude "\n";

15.3 Overloading di new e delete


friend loc operator+(loc opl, int op2};
friend loc Cperator+(int opl, loc op2}; In C++ possibile eseguire l'overloading anche di new e delete. Questo potrebbe
}; consentire di utilizzare particolari metodi di allocazione della memoria. Ad esem-
pio, si potrebbe aver bisogno di routine di allocazione che inizino automaticamente
Il Overloading di + per loc + int a utilizzare un file su disco come memoria virtuale nel caso la memoria dello heap
1oc operator+(l oc opl, i nt op2) fosse esaurita. Qualunque sia il motivo, molto semplice eseguire l'overloading di
{ questi operatori.
loc temp; Di seguito viene presentata la struttura di base delle funzioni che eseguono
l'overloading di new e di delete.
temp. longitude = opl. longitude + op2;
temp .1 ati tude = opl. 1ati tude + op2; 11 Alloca un oggetto
void *operator new(sizeJ size)
return temp; { __
1*
---
esegue l'allocazione e in caso di fallimento lancia 1 'eccezione
bad alloc
Il Overloading di + per int + loc ---Viene automaticamente richiamato il costru.t_!_QJ~.e !:.L ___ .
loc operator+(int opl, loc-dp2)-
--- --=-----.- -~

OVERLOADING DEGLI OPERA-TORI 411


410 CAPITOLO 15

r:turn puntatore_al l a_memori a; longitude = lg;


latitude = lt;

II Canee 11 a un oggetto
voi: operator delete(void *p} void show()
( cout << 1ongitude << " ";
/ .. 1ibera 1a memori a puntata da p cout << latitude "\n";
Viene automaticamente richiamato il distruttore *I
void *operator new(size_t size);
void operator delete(void *p);
Il tipo size_t definito come un tipo in grado di contenere la pi ampia area di
};
memoria allocabile e corrisponde in pratica al tipo intero unsigned. Il parametro
size contiene il numero di byte necessari per contenere loggetto da allocare. Questa Il new modificato rispetto a loc
k quantit di memoria che deve essere allocata new. La funzione new, dopo voi"d *loc: :operator new(size_t size)
I' oYerloading, deve restituire un puntatore alla memoria allocata oppure in caso di
errore deve lanciare un'eccezione bad_alloc. Considerando questi unici vincoli, void *p;
la mova funzione new pu eseguire qualsiasi altra operazione. Quando si alloca
la memoria per un oggetto con new (la versione di base o una versione cout -;< "Nuovo new\n";
per:>0nalizzata) viene automaticamente richiamato il costruttore dell'oggetto. p = malloc(size);
La funzione delete riceve un puntatore alla regione di memoria da rendere if {!p) {
nuC1vamente disponibile per il sistema. bad_alloc ba;
Gli operatori new e delete possono essere modificati con un overloading glo- throw ba;
bale in modo che vengano sempre utilizzate le versioni modificate. Altemativa-
return p;
merrre l'overloading pu riferirsi solo a una o pi classi. Per iniziare si vedr un
esempio delle funzioni new e delete modificate rispetto a una classe. Per sempli-
cit. non verr utilizzato uno schema di allocazione completamente nuovo. Gli Il delete modificato rispetto a loc
operatori modificati tramite overloading richiameranno semplicemente le funzio- void loc: :operator del ete(void *~)
ni standard rnalloc{) e free{) (in generale il programmatore deve invece implemen- {
tare uno schema di allocazione alternativo). Per eseguire I' overloading degli ope- cout << "Nuovo delete\n";
ratori new e delete per una classe, basta rendere le funzioni operator di overloading free(p);
membri -della classe. Ad esempio, ecco il modo in cui possibile eseguire ,}
l'overloading degli operatori new e delete per la classe loc:
int main()
Ni nel ude <iostream> {
Ni ncT ude <cstdl i b> loc *pl, *p2;
Hi nel ude <new>
usin:;i namespace std; try {
pl = new loc (10, 20);
catch (bad a 11 oc xa) { . -
'Cfss 1oc {
cout "E~ror_e~~cazione per pl\n";
int longitude, latitude;
pub11 e: return 1;
le:() {)
lcc(int lg, int lt} {
try
- ----- - - _": --
p2 = new loc (10, 20);
specifiche. Se non esiste una versione specifica, il C++ utilizzer le ~ersioni ne"'.' e
catch (bad_alloc xa) { delete definite globalmente. Se le versioni globali sono state modificate trru_:Ute
cout "Errore di allocazione per p2\n"; overloading, verranno impiegate le versioni modificat~. . . ..
return 1; Per vedere un esempio di overloading globale d1 ne~e delete, s1 esamm1 il
seguente programma:
pl->show(); #include <iostream>
p2->show () ; #include <cstdlib>
#i nel ude <new>
delete pl; using namespace std;
del ete p2;
class loc {
return O; int longitude, latitude;
publ ic:
loc() {}
Ecco l'output prodotto dal programma: loc(int lg, int lt)
longttude = lg;
Nuovo new 1ati tu de = 1t;
Nuovo new

voi d show()
cout longitude << " ";
10 20
cout latitude "\n";
-10 -20
Nuovo del ete
};
Nuovo de 1ete

11 new gl oba 1e
Quando new e delete vengono modificate tramite overloading per una deter- void *operator new(size_t size)
minata classe, l'uso di tali operatori su altri tipi provocher la chiamata degli {
operatori originali. Gli operatori modificati tramite overloading verranno appli- voi d *p;
cati solo ai tip~ per i quali sono stati definiti. Questo significa che se si aggiunge a
main() la seguente riga, verr utilizzato l'operatore new standard. p = malloc(size);
i f ( !p) {
int *f = new float; Il usa il new standard bad_alloc ba;
throw ba;

Per eseguire un overloading globale degli operatori new e delete basta eseguire return p;
I' overloading ali' esterno della dichiarazione di qualsiasi classe. Quando new e delete
vengono modificati con un overloading generale, gli operatori new e delete standard
del C++ verranno sempre ignorati e per tutte le richieste verranno impiegati i nuoYi II de 1ete g1oba1 e
operatori. Naturalmente, se sono state definite nuove versioni di new e delete relati- void operator delete(void..:p)
ve a una o pi classi, per allocare gli oggetti della classe verranno sempre utilizzate {
le versioni specifiche. In altre parole, quando si incontrer un new o un delete,_iL free(p);
compilatore controller innanzi tutto se esiste una versione dell'operatore specifica
---_per la classe su cui sta-oper~do.-ln-caso-affermativo verranno utilizzate le versioni
int main()
-- --- -:.....:.....:.:, - ----
414 -c-KP I TOLQ 1 5 _ _ _ _ _ _ _ _ _ _ _o;;_;_v;:_E.;Ac:;.~;:_O;_A-=D-=l_N_G_D;_E;_G_L_l__;.O_P_E_R_A_T;_O;_R_l_ __;.41_5-- - --- - -

e delete. Per allocare e dea!locare array, si dovranno utilizzare queste nuove versio-
loc *pl, *p2; ni di new e delete:
float *f;
11-A11 ocaun array di oggetti.
try { void *operator new[] (size_t size)
pl = new loc (10, 20); {
catch (bad_alloc xa) { I* Esegue 1 'allocazione. In caso di fallimento lancia 1 'eccezione bad_alloc
cout "Errore di allocazione per pl\n"; Viene automaticamente richiamato il costruttore di ciascun elemento *I
return l; return puntatore_alla_memoria;

try 11 Cancella un array di oggetti.


p2 = new loc (10, 20);" void operator delete[](void *p)
catch (bad_a ll oc xa) { {
cout "Errore di allocazione per p2\n"; I* Libera la memoria puntata da p.
return 1; Viene automaticamente richiamato
il distruttore di ciascun elemento.

try
*I
f = new float; Il usa la versione modificata di new
catch (bad_alloc xa) {
Quando viene allocato un array, viene automaticamente richiamata la funzio-
cout "Errore di allocazione per f\n";
return l; ne costruttore per ogni oggetto dell'array. Nel momento in cui l'array viene
deallocato, viene automaticamente richiamato il distruttore su ogni oggetto
dell' array. Quindi non sar necessario utilizzare codice specifico per eseguire tali
*f = 10.10; azioni.
cout << *f << "\n"; Il programma seguente alloca e poi dealloca un oggetto e un array di oggetti
di tipo loc.
pl->show();
p2->show(); #include <iostream>
#include <cstdlib>
del ete pl; #i ne 1ude <new>
delete p2; usi ng namespace std;
delete f; Il usa la versione modificata di delete
class loc {
return O; int longitude, latitude;
public:
loc() {longitude = latitude = O;}
Si provi a eseguire questo programma per dimostrare il funzionamento loc(int 19, int lt} {
dell'overloading globale degli operatori new e delete. 1ongi tude = l g;
latitude = lt;

Overloading di.new e delete pr l'impiego con array


void show() {
Se si vuole essere in grado di allocare array di oggetti utilizzando un proprio sistema cout-<< 1ong+tude-<< " ";
di allocazione, sar-nece~sario eseguire u~ec_2ndo overloading degli-operatori new
------- - -
416 e A P-1 r o L o 1 s O V E R l O A D I N G D E G l I -OP E RATO R J 417

cout 1ati tude << "\n"; Il delete modificato rispetto a loc per gli array
void loc: :operator delete[] (void *p)
{
void *operator new(size_t size); cout "Cancellazione dell 'array con il nuovo delete[]\n";
free(p);
void operator delete(void *p};

void *operator new[] (size_t size); int main()


{
void operator delete[](void *p}; loc *pl, *p2;
); int i;

11 new modificato rispetto a 1oc try {


void *loc: :operator new(size_t size) pl = new loc (10, 20); Il alloca un oggetto
{ catch (bad_alloc xa) {
void *p; cout "Errore di allocazione per pl\n";
return l;
cout << "Nuovo new\n";
p = malloc(size);
if (!p) { try
bad_alloc ba; p2 = new loc [10); Il alloca un array
throw ba; } catch (bad_alloc xa)
cout "Errore di allocazione per p2\n";
return p; return l;

Il del ete modificato rispetto a loc pl->show();


void 1oc::operator delete(void *p)
{ for(i=O; i<lO; i++)
cout "Nuovo delete\n"; p2[i] .show();
- -f:l'ee(p};
} delete pl; Il libera un oggetto
delete [] p2; Il libera un array
Il new modificato rispetto ad array di loc
void *loc: :operator new[] (size_t size) return O;
{
void *p;

cout << "Nuovo new\n"; Overloading della versione nothrow di new e delete
p = malloc(size);
if (!p} { anche possibile eseguire l'overloading delle versioni nothrow di new e delete.
bad_alloc ba; Ecco come dovr essere la struttura del programma
throw ba; ~-- ---~

Il versione nothrow di new.


return p; _ vo1if*0perafor new(size_t size, C.P.!!Sl. nothr_ow_t &n)
418 CAPITOLO 15 OVERLOADING DEGLI OPERATORI 419

tipo nome-classe::operator[ ](int i)


Il Esegue l'allocazione. {
if(successo) return puntatore_alla_memoria; /I ...
else return O;
}

11 versione nothrow di new per gli array. Tecnicamente, il parametro non deve necessariamente essere di tipo int ma
void *operator new[] {size t size, const nothrow_t &n) una funzione operatorO() viene utilizzata per fornire lindice di un array e pertanto
{ - viene normalmente utilizzato un valore intero.
11 Esegue 1'a 11 ocazi one. Dato un oggetto chiamato O l'espressione:
if{successo) return puntatore_alla_memoria;
else return O; 0(3]

si traduce nella seguente chiamata alla funzione operatorO():


void operator delete{void *p, const nothrow_t &n)
{
o.operator(] {3)
11 1i be rare 1a memori a

Ovvero, al parametro esplicito della funzione operatorOO viene passato il va-


void operator delete[] (void *p, const nothrow_t &n) lore dell'espressione che si trova all'interno dell'operatore di indicizzazione. Il
{ puntatore this punter a O, loggetto che ha generato la chiamata.
11 1i be rare 1a memori a Nel programma seguente, atype dichiara un array di tre interi. La sua funzio-
ne costruttore inizializza ogni membro dell'array. La funzione di overloading
operatorOO restituisce il valore dell'array che si trova alla posizione indicata dal
Il tipo nothrow_t definito in <new>. Questo il tipo dell'oggetto nothrow. Il valore del suo parametro.
parametro nothrow_t non viene utilizzato.
#include <iostream>
using namespace std;

15.4 Over.loading di alcuni operatori particolari class atype


int a [3];
II C++ deffuisce l'indicizzazione di array, la chiamata di funzioni e l'accesso ai mem- public:
bri di una classe come operazioni. Gli operatori che eseguono queste funzioni sono atype{int i, int j, int k) {
rispettivamente O.() e ->.Anche questi operatori possono essere modificati tramite a[O] " i;
overloading in C++, dando origine ad alcuni utilizzi molto interessanti. a[l) = j;
Nell'overloading di questi operatori si applica un'importante restrizione: le a[2] = k;
funzioni di overloading devono essere funzioni membro non statiche. In partico-
lare non possono essere funzioni friend. int operator[](int i) {return a[i] ;}
};

Overloading di [] int main()


{
In C++, quando si esegue l'overloading, la coppia O considerata un operatore bina- a type ob (1, 2, 3) ;
rio. Pertanto, la forma generale di una funzione membro operatorO() la seguente: - - - -
cout obQJ+--rrvi sua lizza 2
420 CAPITOLO 15 ----o V E-R LO AD IN G- DE Gli OPERATOR I 421

retum O; Se invece si crea una classe che contiene l'array e che consente di accedere a
a
tale array solo attraverso loperatore di indicizzazione modificato tramite
overloading, sar possibile intercettare tutte le richieste che superano i limiti
possibile realizzare la funzione operator[]() in modo tale che l coppia O dell'array. Ad esempio, il seguente programma aggiunge la verifica dei limiti al
possa essere utilizzata sia sul lato sinistro che sul lato destro di un'istruzione di programma precedente.
assegnamento. A tale scopo, basta specificare il valore restituito da operator[]()
come un indirizzo. Il seguente programma apporta questa modifica e ne mostra Il Un esempio di array sicuro.
l'utilizzo. #include <iostream>
#include <cstdlib>
#include <iostream> using namespace std;
using namespace std;
class atype
cl ass atype { int a[3];
int a(3]; public:
public: atype(int i, int j, int k) {
atype(int i, int j, int k) { a[O] = i;
a(O] =i; a(l] = j;
a[l] = j; a [2] = k;
a[2] = k;
int &operator[](int i);
}; .
int &operator[](int i) {return a[i];}
};
Il Verifica i limiti per atype.
int main() int &atype: :operator[](int i)
{ {
atype ob(l, 2, 3); if(i<O Il i> 2) {
cout "Superamento dei 1imiti \n";
cout ob[l]; Il visualizza 2 exit(l);
cout << ;
return a(i];
ob(l] = 25; Il [] alla sinistra di =
cout ob[l]; Il ora visualizza 25 int main()
{
return O; atype ob(l, 2, 3);

cout ob[l]; Il visualizza 2


cout << 11 11
;
Poich ora operatorO() restituisce un indirizzo all'elemento dell'array indica-
to da i, la coppia [] pu essere utilizzata anche sul lato sinistro di un'istruzione di
ob (1) = 25; 11 (] si trova a sinistra
assegnamento in modo.da modificare un elemento dell'arr~y (naturalmente conti- cout ob(l]; /I visualizza 25
nua a poter essere utilizzata sul lato destro di un'istruzione di assegnamento).
La possibilit di eseguire I' overloading di un operatore 0 consente di imple- ob[3) = 4"4:11 genera un errore run-time perch 3 oltre i limiti
mentare in C++ un metodo di indicizzazione "sicura" degli ari:ay. Come si sa, in return O;
C++ possibile superare i Imuu superiore o inferiore aniffii.rray senza fie-vnga - J ______ -
generato alcun messaggio di errore al momento deir.esecuzion-.-
---- -~---
422 CAPITOLO 15
O V E AL O A O IN G O EGLI OP E A A T O RT" 423

In qu_esto programma, quando viene eseguita l'istruzione: loc(int lg, int lt)
1ongitude = l g;
ob[3] = 44; latitude = lt;

l'errore di superamento dei limiti viene intercettato da operatorOO e il programma


verr fermato prima che possa provocare danni (nella pratica, dovr essere realiz- voi d show()
zata una funzione per la gestione dell'errore che si occupi della condizione di cout << 1ongi tude << " ";
superamento dei limiti e che consenta al programma di continuare). cout latitude << "\n";

Overloading di O loc operator+(loc op2);

Qando si esegue l'overloading dell'operatore di chiamata a funzione(), non si sta loc operator()(int i, int j);
in realt creando un nuovo modo per richiamare una funzione. Piuttosto si sta cre- . };
ando una funzione operator alla quale possibile passare un numero arbitrario di
parametri. Per iniziare ecco un esempio: data la dichiarazione della funzione operator: Il Overloading di () per loc
loc loc::operator()(int ;, int j)
{
double operator()(int a, float f, char *s);
longitude = i;
l atitude' = j;
e un oggetto O di tale classe, l'istruzione:
return *thi s;
0(10, 23.34, "hi ");

provocher questa chiamata alla funzione operator(): Il Overloading di +per loc


loc loc::operator+(loc op2)
O.operator()(lO, 23.34, "hi"); {
loc temp;
In generale, quando si esegue l'overloading dell'operatore(), si definiscono i
parametri che si intendono passare a tale funzione. Quando si utilizza l'operatore temp. longitude = op2. longitude + longitude;
temp. latitude = op2. latitude + latitude;
() in un pr:.ogramma, in tali parametri vengono copiati gli argomenti specificati.
Come sempre, il puntatore this punta all'oggetto che ha generato la chiamata (in
return temp;
questo esempio 0).
Ecco un esempio di overloading dell'operatore () rispetto alla classe loc. In
questo esempio il valore dei due argomenti viene assegnato alla longitudine e alla int main()
latitudine 'dell'oggetto a cui viene applicato l'operatore. {
loc obl{lO, 20), ob2(1, l);
#include <iostream>
usi nii namespace std; obl. show();
obl(?-;- 8); Il pu essere utiliizata da soia ...
class loc { obl. show();
int longitude, latitude;
-publ-ic: ----- obl = ob2 + obl(lO, 10); Il ... o all'.!_nterno~~s~ioni
loc(}_{l-- obl. show();
----~--- - --
424 CAPITOLO 15 OVE-RLOADING DEGLI OPERATORI 425

return O; cout << ob. i << 11 11


<< ob->i:

return O;
Ecco I' output prodotto dal programma: r-
10 20 Una funzione .operator->() deve essere membro della classe su cui opera.
7 8
11 11

15.5 Overloading dell'operatore virgola


P i E +H Quando si esegue t 'overtoading di o possibile utilizzare qual-
siasi tipo di parametri e restituire un valore di qualsiasi tipo. In pratica tali tipi In C++ possibile anche eseguire l' overloading dell'operatore virgola. La virgola
dipendono dalle richieste del programma. anche possibile specificare degli ar- un operatore binario e, come per ogni altro operatore, I' overloading pu farle
gomenti standard. eseguire qualsiasi operazione. Ma se si vuole che anche dopo l' overloading l' ope-
ratore virgola esegua un'operazione analoga a quella originaria, esso dovr libe-
rarsi del valore di tutti gli operandi ad eccezione di quello pi a destra, il quale
Overloading dell'operatore -> diverr il risultato dell'operazione di virgola. Come si sa, questo il modo in cui
la virgola funziona nel C++ standard. Ecco un programma che illustra l'effetto
In caso di overloading l'operatore di accesso ai membri della classe-> considerato dell' overloading dell'operatore virgola:
un operatore unario. Di seguito viene presentato il s~o utilizzo generale:
#i ne 1ude <i os t ream>
oggetto->elemento;
using namespace std;

Qui, oggetto l'oggetto che attiva la chiamata. La funzione operator->() deve cl ass loc {
restituire un puntatore a un oggetto della classe su cui opera operator->(). elemen- int longitude, latitude;
to deve essere un membro accessibile dall'interno dell'oggetto. public:
Il seguente programma illustra l'overloading dell'operatore -> mostrando loc() {}
l'equivalenza esistente fra ob.i e ob->i quando operator>() restituisce il puntatore loc(int lg, int lt)
this. longitude = lg;
latitude = lt;
#i nel ude -<i os tream>
using namespace std;
voi d show()
class myclass cout 1ongitude 11 11
;

pub li e: cout latitude 11


\n";
int i;
mycl ass *operator->{) {return thi s;}
}; loc operator+(loc op2);
loc operator,(loc op2);
1nt ir.ain() };
{
myclass ob; Il Overloading dell'operatore virgola per loc
loc ioc::operator,(loc op2)
ob->i ::JQ; _/J_ugua 1e a ob. i {
loc temp; - -
--
---------
426 _CAPITOLO 15 - - 0-VE R-L-0 A O IN G DE G L LO PER A.T 0-R I 427

Si noti che anche se vengono eliminati tutti i valori degli operandi a sinistra, il
temp. longitude = op2. longitude; compilatore valuter comunque ogni espressione e quindi verr eseguito qualsiasi
temp. latitude = op2. latitude; effetto collaterale predisposto dal programmatore.
cout << op2. longitude << " " << op2. latitude- << "\n"; Si ricordi che l' operando di sinistra viene passato tramite il puntatore this e
che il suo valore viene eliminato utilizzando la funzione operator(). La funzione
return temp;
restituisce il valore dell'operando pi a destra. In questo modo anche dopo
l' overloading la virgola si comporta in modo analogo alla sua operazione standard.
Il Overl oadi ng di + per 1oc Se si desidera che dopo l' overloading l'operatore virgola esegua una diversa ope-
loc loc: :operator+(loc op2) razione, si dovranno modificare queste due funzionalit.
{
loc temp;

temp. longitude = op2. longitude + longitude;


temp. latitude = op2. latitude + latitude;

return temp;

int main()

loc obl(lO, 20), ob2( 5, 30), ob3(1, 1);

ob 1. show() ;
ob2. show();
ob3.show();
cout << "\n";

obl = (obl, ob2+ob2, ob3);

----~-~~:show(); Il visualizza 1 1, il valore di ob3


return O;

Questo programma visualizza il seguente output:

D 20
s.30
l l

l:i 60
!. l
l l
: Capitolo 16

L'ereditariet

16.1 Controllo dell'accesso alla classe base


16.2 Ereditariet dei membri protected
16.3 Ereditariet da pi classi base
16.4 Costruttori, distruttori ed ereditariet
16.5 Accesso alle classi
16.6 Classi base virtuali

L'ereditariet costituisce una delle pietre angolari del-


la programmazione orientata agli oggetti in quanto consente di creare classifica-
zioni gerarchiche. Utilizzando l'ereditariet, possibile creare una classe genera-
le che definisce le caratteristiche comuni a una serie di oggetti correlati. Questa
classe pu in seguito essere ereditata da una o pi classi, ognuna delle quali ag-
giunge alla classe ereditata solo elementi specifici.
Per conservare la terminologia standard del C++, la classe ereditata viene
chiamata classe base. La classe che "riceve" l'eredit detta classe derivata. A
sua volta, una classe derivata pu fungere da classe base per un'altra classe deri-
vata. In questo modo possibile riprodurre a pi livelli il meccanismo di
ereditariet.
Il supporto dell'ereditariet del C++ ricco e flessibile. Il meccanismo di
ereditariet, introdotto nel Capitolo 11 forma l'argomento di questo capitolo.

16.1 Controllo dell'accesso alla classe base


Quando una classe ne eredita un'altra, i membri della classe base divengono membri
della classe derivata. L'ereditariet delle classi ha la seguente forma generale:

class nome-classe-derivata : accesso nome-classe-base {


Il corpo della classe
}; -~ -

Il tipo di accesso che la classe derivata pu avere sui membri della classe base
determinato dallo specificatore accesso. Lo specificatore d'accesso della classe - - - - -
base pi:_ ~~ere public, private o psot~cted.
-'-"""..=-e-_:::.-.------------

430 CAPITOLO 16 L'EREDITARIET 431

Se non si indica uno speciflcatore d'accesso, si possono verificare i seguenti Quando la classe base ereditata tramite lo specificatore d'accesso private,
casi: se la cl!isse derivata una class, lo specificatore d'accesso sar private; se la tutti i membri pubblici e protetti della classe base diverranno membri privati della
classe derivate una struct, lo specificatore d'accesso standard sar public. Ora classe derivata Ad esempio, il programma seguente non potr nemmeno essere
verranno esaminate le implicazioni dell'uso degli specificatori public e private (lo compilato poich sia set() che show() sono ora elementi privati di derived.
specificatore protected verr esaminato nella prossima sezione).
Quando lo specificatore d'accesso alla classe base public, tutti i membri 11 Questo progranma non verr compilato.
pubblici della classe base diverranno membri pubblici della classe derivata e tutti
i membri protetti della classe base diverranno membri protetti anche della classe #include <iostream>
derivata. using namespace std;
In tutti i casi, gli elementi privati della classe base rimarranno privati della
cl ass base (
classe base e non saranno pertanto accessibili da parte dei membri della classe int i, j;
derivata. Ad esempio, come si pu vedere nel seguente programma, gli oggetti di public:
tipo derived possono accedere direttamente ai membri pubblici di base. void set(int a, int b) {i=a; j=b;}
void show() { cout i " " j "\n";}
#include <iostream> }:
using namespace std;
Il Gli elementi pubblici di base sono privati in derived.
class base { class derived private base {
int i, j; int k;
publ ic: publ ic:
void set(int a, int b) {i=a; j=b;} derived(int x) (k=x;}
void show() { cout << i " " << j "\n";} void showk() {cout k "\n";}
}; };

class derived public base { int main()


int k; {
publ ic: derived ob(3);
deri ved (i nt x) ( k=x;}
voi d showk() {cout k "\n":} ob.set(l, 2); Il errore: non si.pi.ioaccedre a set()
}; ab.show(); 11 errore: non si pu accedere a show()

i nt mai n () return O;
{
deri ved ob (3) ;

ob.set(l, 2); Il accesso a un membro di base 'SUGGERIMENTQ_; Quando lo specificatore d'accesso di una classe base priva-
ab.show(); 11 accesso a un membro di base te, i membri pubblici e protetti della classe base divengono membri privati della
classe derivata. Questo significa eh~ rimarranno accessi~iJJ.. da parte dei membri
cb. showk(): 11 usa un membro della cl asse derivata o.
della classe derivata e in&cessibili da parte di altri punti del programma che non
siano membri della classe base o della classe derivata.
return O;

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


_ _ -=-:--:-- -

432 CAPITOLO 16 ----- L' E REDI T A R I ET__ 433

16.2 Ereditariet dei membri protected ab.show(); Il OK, noto in derived

La parola riservata protected stata inclusa nel C++ per introdurre un maggior ob.setk(};
livello di flessibilit nel meccanismo di ereditariet. Quando un membro di una ab. showk (} ;
classe dichiarato protected, tale membro non sar accessibile da parte di altri return O;
elementi non membri della classe. Con un'importante eccezione, l'accesso ai
membri protetti equivale all'accesso ai membri privati ovvero l'accesso pu av-
venire solo da parte dei membri della classe. L'unica eccezione si verifica quando Qui, poich base ereditata da derived come public e poich i e j sono dichia-
viene ereditato un membro protected. In questo caso, un membro protected rati protected, la funzione setk() della classe derived pu avere accesso a i e j. Se i
molto diverso da un membro private. e j fossero state dichiarate come private di base, la classe derived non avrebbe
Come si detto nella sezione precedente, un membro private di una classe potuto eseguire accessi e non sarebbe stato neppure possibile compilare il pro-
base non accessibile da altre parti del programma, incluse tutte le classi deriva- gramma.
te. I membri protected si comportano in modo diverso: se la classe base ereditata Quando una classe derivata utilizzata come classe base di un'altra classe
come public, i membri protected della classe base diverranno membri protected derivata, tutti i membri protected della classe base iniziale ereditati (come public)
della classe derivata e saranno prtanto accessibili alla classe derivata stessa. In dalla prima classe derivata potranno essere ereditati nuovamente come protected
altre parole, utilizzando la parola riservata protected, un programmatore pu cre- anche dalla seconda classe derivata. Ad esempio, il seguente programma corret-
are membri della classe che siano privati di tale classe ma che possano essere to e derived2 potr avere accesso a i e a j.
ereditati e manipolati dalle classi derivate. Ecco un esempio:
#i nel ude <ostream>
#i nel ude <i ostream> using namespa.ce std;
using namespace std;
cl ass base {
cl ass base { protected:
protected: int i, j;
int i, j; Il privato di base, ma accessibile da derived public:
public: void set(int a, int b) {i=a; j=b;}
void set(int a, int b) {i=a; j=b;} void show(} { cout i " " j "\n";}
void show() { cout i << " " << j "\n";} };
};
11 i e j ereditati come protected.
class derived public base { class derivedl : public base {
int k; int k;
publ ic: public:
Il derived pu accedere alle variabili e j di base void setk() {k = i*j;} Il consentito
void setk() {k=i*j;) void showk() {cout k "\n";}
};
void showk() {cout k "\n";}
}; Il i e j sono ereditate indirettamente tramite derivedl.
class derived2 : public derivedl {
i nt mai_n (} int mi-
{ --
publ ic:
derived ab; voi d setm() {m = i-j;} 11 consentito
voi d showm() {cout m 11 \n";}
ob....set(2, 3)..;-f/--OK.--noto. in derived };

--- ---- -- -- -
434 CAPITOLO 16 435

int main() void showk() {cout k "\n";}


{ }; -
derivedl obl;
derived2 ob2; 11 L'accesso a i, j, set{) e show() non viene ereditato.
class derived2 : public derivedl {
obl.set(2, 3); int m;
obl.show(); publ ic:
obl.setk(); Il non consentito poich i e j sono private di derivedl
obl.showk(); voi d setm() {m = i-j;} 11 errore
void showm() {cout m "\n";}
ob2.set(3, 4); };
ob2.show();
ob2.setk(); int main()
ob2.setm(); {
ob2.showk(); deri vedl obl;
ob2.showm(); deri ved2 ob2;

return O; obl.set(l, 2); Il errore, non si pu usare set{)


obl.show_(); 11 errore, non si pu usare show()

Se invece base fosse stata ereditata come private, tutti i membri di base sa- ob2.set{3, -4); Il errore, non si pu usare set()
rebbero divenuti membri privati di derived1 e sarebbero pertanto risultati inacces- ob2.show(); Il errore, non si pu usare show()
sibili da parte di derived2 (anche se i e j sarebbero rimasti accessibili da parte di
return O;
derived1). Questa situazione illustrata dal seguente programma che contiene un
errore e non verr compilato. Gli errori sono descritti dai commenti.

11 Questo programma non verr compilato. NOTA Anche se base ereditata come private da derived1, quest'ul-
tima potr comunque avere accesso agli elementi public e protected di base anche
#include <iostream>
se non potr trasmettere questo privilegio.
usi ng namespa"ce std ;.

cl ass base- {
Ereditariet di una classe base come protected
protected:
int i, j;
In C++ possibile ereditare una classe base come protected. In tal caso, tutti i
public:
membri public e protected della classe base diverranno membri protected della
void set(int a, int b) {i=a; j=b;}
void show() { cout i " " j "\n";}
classe derivata. Ecco un esempio:
};
#include <iostream>
Il Ora, tutti gli elementi di base sono privati di der-i-vedl. usi ng namespace std;
class derivedl : private base {
-i-nt-k-; class base {
public: protected:
Il consentito, poich i e j sono private di derivedl -~i~nt~i, j; Il privato _91_~se, ma accessi bi]:_ da derived
-- - -vo'[d setk(LiL= i~j;L/ I OK publ ic:
- - -voidSetijJi~t_a! :int-!>) {-i~a;-=-;;-b;}
436 CAPITOLO 16
L'EREDITARIET 437

void showij(} ( cout i " " j "\n";}


using namespace std;
};
cl ass basel
11 Eredita base come protected.
protected:
class derived protected base(
int x;
int k;
publ ic:
pub li c:
void showx(} {cout x "\n";)
11 derived pu accedere a i, j e setij (} di base. );
void setk() (setij(lO, 12); k = i*j;)
cl ass base2
protected:
11 qui pu accedere a showi j (}
int y;
void showall () (cout k " "; showij ();}
pub li e:
};
void showy(} {cout y "\n";)
);
int main(}
(
derived ob;
Il Eredita da pi classi base.
class derived: public basel, public base2
public:
11 ob.setij (2, 3); 11 non consentito, setij O
void set{int i, int j) {x=i; y=j;)
/I membro protected di cleri ved
);

ob.setk(); Il OK, membro pubblico di derived


int main()
ob.showall (); /I OK, membro pubblico di derived
{
derived ob;
Il ob.showij(); /I non consentito: showij(} un membro
11 protected di deri ved
ob.set(lO, 20); Il fornita da derived
ob.showx(); Il da basel
return O;
ob.showy(); Il da base2

return O;
Come si pu vedere leggendo i commenti, anche se setij() e showij() sono
membri pubblici di base, divengono membri protected di derived poich sono
stati ereditati utilizzando lo specificatore d'accesso protected. Questo significa Come si pu vedere nell'esempio, per ereditare da pi di una classe base, si
che main() non avr accesso a tali funzioni. deve utilizzare un elenco di classi separate da virgole. Inoltre, si deve utilizzare
uno specificatore d'accesso per ogni classe ereditata.

16.3 Ereditariet da pi classi base


16.4 Costruttori, distruttori ed ereditariet
Una.classe derivata pu ricevere in eredit elementi di due o pi classi base, In
questo breve esempio, derived eredita sia da base1 che da base2: - Vi sono due problemi principali derivanti dall'uso di costruttori e distruttori quan-
do si impiega l'ereditanet-:-1n1fanzi tutto, in quale momento vengono richiamate
Il Un esempio di classi base multiple. le funzioni costruttore e distruttore della classe base e della classe derivata? E poi,
-_ -- come possibil~ pi!_S!!are parametri alle funzioni costruttore della cl!!~SF'--base? __
--=--'---#include <iostream>
------- ------
-- -"
Questa sezione esamina questi due -Hnpoi'fanti" argon:ienti.
----=---:: ~- ...
438 CA..P-1TOLO- 16 L'EREDITARIETA-:- 439

Quando vengono eseguite le funzioni costruttore Il risultato di questo esperimento pu essere generalizzato. Quando viene cre-
e distruttore ato un oggetto di una classe derivata, se la classe base contiene un costruttore,
questo verr richiamato per primo seguito dal costruttore della classe derivata.
possibile che una classe base, una classe derivata-o entrambe contengano fun- Quando viene distrutto un oggetto derivato, prima viene richiamato il suo distrut-
zioni costruttore e/o distruttore. importante comprendere l'ordine in cui vengo- tore seguito dal distruttore della classe base (sempre che esista). In altre parole, le
no eseguite tali funzioni nel momento in cui si crea o si distrugge un oggetto della funzioni costruttore sono eseguite nell'ordine di derivazione e le funzioni distrut-
classe derivata. Per iniziare, si esamini questo breve programma: tore sono eseguite in ordine inverso rispetto a quello di derivazione.
Se si riflette sull'argomento ha senso che le funzioni costruttore vengano ese-
#include <iostream>
using namespace std;
guite nell'ordine di derivazione. Poich una classe base non conosce l'aspetto di
una classe derivata, ogni operazione di inizializzazione che dovr eseguire dovr
cl ass base { anche essere distinta e precedere qualsiasi inizializzazione eseguita dalla classe
publ ic: derivata. Pertanto dovr essere eseguita per prima.
base() {cout "Costruzione di base\n";} Analogamente piuttosto ovvio che i distruttori vengano eseguiti in ordine
-base() {cout "Distruzione di base\n";} inverso di derivazione. Poich la classe base funge da fondamento per la classe
}; derivata, la distruzione dell'oggetto base implica la distruzione dell'oggetto deri-
vato. Pertanto, il distruttore derivato dovr essere richiamato prima che l'oggetto
class derived: public base venga completamente distrutto.
public: In caso di ereditariet a pi livelli (ovvero quando una classe derivata diviene
derived() {cout "Costruzione di derived\n";} la classe base di un'altra classe derivata) si applica la regola generale: i costruttori
-derived() {cout "Distruzione di derived\n";} vengono richiamati in ordine di derivazione e i distruttori in ordine inverso. Ad
};
esempio, questo programma:
int rr.ain()
{ lii nel ude <i ostream>
derived ob; using namespace std;

I/ non fa nulla tranne costruire e di struggere ob cl ass base {


public:
return O; base() {cout "Costruzione di base\n";}
-1-- ----- -base() {cout "Distruzione di base\n";}
};

Come si pu vedere dal commento contenuto in main(), questo programma class derivedl : public base {
costruisce e quindi distrugge un oggetto chiamato ob appartenente alla classe public:
derived. II programma produce il seguente output: derivedl() {cout "Costruzione di derivedl\n":l
-derivedl() {cout "Distruzione di derivedl\n";}
Costruzione di base };
Costruzione di derived
Distruzione di derived class derived2: public derivedl {
- Distruzione di base.- publ ic:
deri ved2 ()- {cout "Costruzione di deri ved2\n ';}
Come si pu vedere, innanzi tutto _viene eseguito il costruttore della classe -derived2() {cout "Distruzione di derived2~n";}
};
base seguito dal costruttore di derived. Quindi (poich in questo programma ob
viene distrutto immediatamente), viene richiamato il distruttore di derivecfsegui-
to da quello -dr5as-e-:---- - _int main()
440 CAPITTTO 16
L ' E A E D I T AA I E T 441

deri ved2 ob; 11 costru sce e di strugge ob

return O;
11 costruisce e di strugge ob

return O;
produce il seguente output:

produce il seguente output: Costruzione di basel


Costruzione di base2
Costruzione di base Costruzione di derived
Costruzione di derivedl Distruzione di derived
Costruzione di derived2 Distruzione di base2
OiS truzi one di derived2 Distruzione di basel
Distruzione di derivedl
Distruzione di base Come si pu vedere, i costruttori vengono richiamati in ordine di derivazione
(da sinistra a destra) cos come specificato nell'elenco di ereditariet di derived.
La stessa regola generale si applica in tutte le situazioni che prevedono l'uso I distruttori vengono richiamati in ordine inverso (da destra verso sinistra). Que-
di pi classi base. Ad esempio, questo programma: sto significa che se base2 fosse stata specificata prima di base1 nell'elenco di
derived, COI):le nell'esempio seguente:
#include <iostream>
usi ng namespace std; class derived: public base2, public basel

class basel { l'output del programma avrebbe avuto il seguente aspetto:


public:
basel () {cout "Costruzione di basel \n";} Costruzione di base2
-basel () {cout "Distruzione di basel \n";} Costruzione di basel
};
Costruzione di derived
Distruzione di derived
cl ass base2 {
Distruzione di basel
pub li e:
Distruzione di base2 --------
base2() l cout "Costruzione di base2\n";}
-base2() {cout "Distruzione di base2\n";}
};
Passaggio di parametri ai costruttori della classe base
class derived: public basel, public base2 { Fino ad ora, nessuno degli esempi ha introdotto funzioni costruttore che richie-
public:
dessero argomenti. In tutti i casi in cui solo il costruttore della classe derivata
derived() {cout "Costruzione di derived\n";}
richieda uno o pi parametri, baster utilizzare la sintassi standard dei costruttori
-derived() {cout "Distruzione di derived\n";}
}; parametrizzati (vedere il Capitolo 12). Ma come possibile passare argomenti a
una funzione costiuttor.e situata nella classe base? La risposta prevede l'uso di
int main() una forma estesa di dichiarazione del costruttore della classe derivata che consen-
{ te di passare argomenti a uno o pi costruttori della classe base.
derived ob; La forma generale della dichiarazione estesa di un costruttore di una classe
derivata la seguente:

- -- --=------~~ - ..
--442-=-- E~t. o 1 s

costruttore-derivata(elenco-argomenti) : base] (elenco-argomenti), return O;


base2(elencocargomenti),

Qui, il costruttore di derived viene dichiarato in modo da accettare due para-


metri, x e y. Tuttavia, derived utilizza solo x; y viene passata a base(). In generale,
baseN(elenco-argomenti) il costruttore della classe derivata deve dichiarare tutti i propri parametri e tutti
quelli richiesti dalla classe base. Come si vede nell'esempio, tutti i parametri
Il corpo del costruttore derivato richiesti dalla classe base vengono passati ad essa nell'elenco degli argomenti
} . della classe specificati dopo i due punti.
Ecco un esempio che utilizza pi classi base:
Qui, i nomi da base] a baseN sono i nomi delle classi base ereditate dalla
classe derivata. Si noti il carattere di due punii che separa la dichiarazione del #include <iostream>
costruttore della classe derivata dalle indicazioni delle classi base e si noti anche using namespace std;
che, se vi sono pi classi base, i loro nomi sono separati da virgole. Si consideri il
seguente programma: class basel
protected:
int i;
#include <iostream>
publ ic:
using namespace std;
basel(int x) {i=x; cout "Costruzione di basel\n";}
-base1() {cout "Distruzione di basel\n";}
cl ass base
};
protected:
int i;
public: cl ass base2
protected:
base(int x) {i=x; cout "Costruzione di base\n";}
int k;
-base() {cout "Distruzione di base\n";}
}; pub li e:
base2(int x) {k=x: cout "Costruzione di base2\n";}
class derived: public base { -base2() {cout "Distruzione di basel\n";}
};
int j;
public:
class derived: public basel, public base2 {
11 derived usa x; y viene passata a base. int j;
derived(int x, int y): base(y)
pub li c:
{j=x; cout << "Costruzione di deri ved\n";}
derived(int x, int y, int z): basel(y), base2{z)
{j=x; cout "Costruzione di derived\n";}
-derived(} {cout << "Distruzione di derived\n";}
void show() {cout i " " j "\n";)
); -deri ved() {cout "Distruzione di deri ved\n";)
void show() {cout << i << " " << j << " " << k << "\n";}
int main() };
{
derived ob(3, 4); int main()
{
derived ob(3, 4, 5);
ob. show() ; 11 vi sua 1 i zza 4 3
444 -CAPITOLO 16 L'EREDITARIET 445

ob.show(}; Il visualizza 4 3 5 };

return O; int main(}


{
derived ob(3, 4);
importante comprendere che gli argomenti per il costruttore della classe
base vengono passati attraverso gli argomenti del costruttore della classe derivata. ob.show(}; Il visualizza 3 4
Pertanto, anche se un costruttore della classe derivata non utilizza argomenti, ne
return O;
dovr comunque dichiarare uno nel caso in cui la classe base lo richieda. In questa
situazione, gli argomenti passati alla classe derivata vengono semplicemente tra-
sportati verso la classe base senza essere modificati. Ad esempio, nel seguente
programma il costruttore della classe derivata non richiede argomenti i quali ven- La funzione costruttore di una classe derivata libera di utilizzare tutti i para-
gono passati a base1 () e base2(). metri dichiarati, anche nel caso in cui questi debbano essere passati a una classe
base. In altre parole, il passaggio di un argomento a una classe base, non impedi-
#include <iostream> sce di utilizzarlo anche dalla classe derivata, Ad esempio, questo frammento di
usi ng namespace std; codice perfettamente corretto:

cl ass basel cl ass deri ved: publ i e base {


protected: int j;
int i; publ ic:
public: Il derived usa sia x che y e poi li passa a base.
basel(int x) {i=x; cout << "Costruzione di basel\n";) derived(int x, int y): base(x, y)
-basel() {cout "Distruzione di basel\n";) {j = x*y; cout "Costruzione di derived\n";}
};
Un'ultima annotazione relativa al passaggio di argomenti ai costruttori di una
class base2 classe base: l'argomento pu essere costituito da qualsiasi espressione valida.
protected:
Questo significa che gli argomenti possono essere chiamate a funzioni e variabili.
int k;
Questo in linea con il fatto che il C++ consente l'inizializzazione dinamica.
public:
base2(int x) {k=x-;-coat"<<--costruzione di base2\n";}
-base2(} {_out "Distruzione di base2\n";}
};
16.5 Accesso alle classi
class derived: public basel, public base2 {
Quando una classe base ereditata come private, tutti i suoi membri pubblici e
public:
protected divengono membri private della classe derivata. In alcuni casi per si
I* Il costruttore di derived non richiede parametri,
ma deve essere di chi arato in questo modo
desidera ripristinare le specifiche di accesso originali di uno o pi dei membri
per passare gli argomenti 'alle classi base. ereditati. Ad esempio, si potrebbe voler assicurare ad alcuni membri pubblici del-
*I la classe base Io status di public nella classe derivata anche se la classe base stata
ereditata come private.
derived(int x, int y): basel(x), base2(y) Nel C++ standard si pu ottenere questo in due modi. Innanzitutto si pu
(cout << "Costruzione di derived\n";} utilizzare un'istruzione using (che la soluzione preferible)-:L'iStruzione using
ha principalmente Io scopo di garantire il supporto dei namespace e verr descrit-
-derived(} {cout "Distruzione di derived\n";) ta nel Capitolo ~3-:lf seconao-modo per ripristinare 1~. s_p_ecificI:ie fii accesso di un
_ -=-="oid-_ ~~o~H- {cout << i .ti"-:;:-\n";}
L'EREDITARIET 447
446 CAPITOLO 16

membro ereditato consiste nell'impiegare una dichiarazione di accesso nella clas- public da una classe derivata (se il C++ consentisse ci, verrebbe distrutto il mec-
se derivata. Anche se le dichiarazioni di accesso sono ancora supportate dal C++ canismo di incapsulamento).
standard, se ne sconsiglia l'uso. Questo significa che non dovrebbero essere uti- Il seguente programma illustra l'uso della dichiarazione di accesso. Si noti
lizzate nella realizzazione di nuovo codice. Ma poich vi sono ancora molti pro- come vengano utilizzate le dichiarazioni di accesso per ripristinare lo status publk
grammi che impiegano queste dichiarazioni di accesso, opportuno esaminare di j, seti() e geti().
anche questa possibilit.
Una dichiarazione di accesso ha la seguente forma generale: #include <iostream>
using namespace std;
classe-base: :membro;
class base {
int i; Il privata di base
La dichiarazione di accesso deve seguire l'intestazione di accesso appropriata publ ic:
nena dichiarazione della classe derivata. Si noti che nella dichiarazione di accesso int j, k;
non richiesta (n consentita) alcuna dichiarazione di tipo. void seti (int x) {i = x;}
Per vedere come funziona la dichiarazione di accesso, si inizier con un breve int geti() {return i;}
frammento di codice: };

cl ass base { 11 Eredita base come privata.


public: cl ass deri ved: private base {
int j; Il pubblica in base publ ic:
}; I* Le tre istruzioni seguenti ignorano
il fatto che base venga ereditata come private,
Il Eredita base come privata. e ripristinano l'accesso pubblico di j, seti() e geti(). *I
class derived: private base { base::j; Il rende j (ma non k) nuovamente pubblica
publ ic: base::seti; Il rende seti() pubblica
base: :geti; 11 rende geti() pubblica
/I ecco 1a di chi arazi one di accesso
base::j; Il rende j nuovamente pubblica Il base::i; Il non consentito: non si pu elevare l'accesso

int a; Il public
};
};
int main()
Poich base ereditata da derived come private, il membro pubblico j diviene {
derived ob;
in derived un membro privato. Ma se si utilizza:
llob.i = 10; Il non consentito: i privata in derived
base: :j;
ob.j = 20; 11 consentito poich j resa pubblica in derived
che dovr essere inclusa come dichiarazione di accesso-sotto lintestazione public Ilob. k = 30; 11 non consentito poi ch k privata in derived
della classe derived, j torner al suo status di public.
--mfa dichiarazione di accesso consente anche di ripristinare i diritti di accesso ob.a = 40; Il consentito poich a pubblica in derived
dei membri public e protected. Non invece possibile utilizzare una dichiarazione ____ob_. ~eti (10);
_ ____Qi11c_5!sso per innalzare o abbassare Io status qi_ l!_cesso di un membro. Ad esem-
-- ----_______
cout oh.geti (~'.' ..-o.b:j- " " ob.a;
pio; un membrlJdchiarato _come private di unacTasse b-ase non pu essere reso - - ,_
448 CAPITOLO 16 _____
-L'EaEOITARIETA-- 449

return O; public:
int sum;
};
In C++ le dichiarazioni di accesso sono ammesse poich consentono di risol-
vere situazioni in.cui la maggior parte degli elementi di una classe ereditata deve int main()
rimanere privata mentre alcuni membri possono conservare in alcuni casi lo status {
public o protected. deri ved3 ob;

:$~E6l'!EN:f~ Il C++ standard consente ma sconsiglia l'uso delle dichiara- ab.i = 10; Il ambiguit: quale i???
zioni di accesso. Questo significa che anche se ora sono consentite, potrebbero ob.j = 20;
non essere pi presenti nelle versioni future dello standard. Lo standard suggeri- ob.k = 30;
sce di ottenere lo stesso effetto applicando la parola riservata using.
Il anche qui i ambigua
ob.sum =ab.i + ob.j + ob.k;

16.6 Classi base virtuali Il ancora ambigua, quale i?


cout << ab. i << " 11 ;
Quando vengono ereditate pi classi base in un programma viene introdotto un
elemento di ambiguit. Ad esempio, si consideri il seguente programma (errato): cout << ob.j << " " << ob.k << " ";
cout .ab. sum;

11 Questo programma contiene un errore e non verr compilato.


return O;
#include <iostream>
using namespace std;

cl ass base Come si vede dai commenti, la classe base ereditata sia da derived1 che da
public: derived2. In seguito derived3 eredita sia derived1 che derived2. Questo significa
int i; che in un oggetto di tipo derived3 sono presenti due copie di base. Pertanto, in
}; un'espressione come:

11 deri vedl eredita base. ab. i = 20;


class derivedl : public base
public: _
int j; a quale i si fa riferimento? A quella di derived1 o a quella di derived2? Poich in
}; un oggetto di tipo ob sono presenti due copie di base, sono anche presenti due
variabili ob.i! Come si pu vedere, l'istruzione per sua natura ambigua.
11 deri ved2 eredita base. Vi sono due modi per risolvere la situazione generata dal precedente pro-
cl ass deri ved2 : publ i c base gramma. La prima consiste nell'applicare alla variabile i l'operatore di risoluzio-
public: ne del campo d'azione e nella selezione manuale di una i. Ad esempio, la seguente
int k; versione del programma verr compilata e funzioner nel modo atteso:
};
Il Questo programma seleziona una i con l'operatore di risoluzione del campo -
/*derived3 eredita sia derivedl che derived2. d'azione.
Questo si gni fica che in deri ved3 vi sono #include <iostream>
due copi e di base! *I using namespace std;
___:~-~:. derived3 : public derivedl, public derived2--

--- --- ---


____ -_--:-- ...

450 CAPITOLO 16 L'EREDITARIET 451

_cl ass base Come si pu vedere, grazie all'impiego dell'operatore ::, il programm~ ha
public:
selezionato manualmente la versione di base contenuta in derived2. Ma questa
. int i;
};
soluzione porta alla luce un problema pi grave: come fare per utilizzare una sola
copia di base? Vi un modo per evitare che in derived3 vengano incluse due
11 deri vedl eredita base. copie della classe base? La soluzione prevede l'uso di classi base virtuali.
class derivedl : public base Quando due o pi oggetti sono derivati da una classe base comune, possibile
public: evitare che in un oggetto derivato da questi oggetti siano presenti pi copie della
int j; classe base, dichiarando la classe base come virtual nel momento in cui viene
}; ereditata. A tale scopo si deve far precedere al nome della classe base la parola
riservata virtual. Ad esempio, ecco un'altra versione del programma di esempio in
11 derived2 eredita base. cui derived3 contiene una sola copia di base:
class derived2 : public base
public:
Il Questo programma usa classi base virtuali.
int k;
#include <iostream>
};
using namespace std;

/* derived3 eredita sia derivedl che derived2. class base


Questo significa che in derived3 vi sono publ ic: .
due copie di base! *I int i;
class derived3 : public derivedl, public derived2 };
public:
int sum;
};
11 deri vedl eredita base come vi rtua l .
class derivedl : virtual public base {
publ ic:
int main()
int j;
{
};
derived3 ob;
I/ derived2 eredita base come virtual.
ob.derivedl::i 10; /I viene usata la di derivedl class derived2 : virtual public base {
ob.j = 20; pubfiC:- -
ob.k =-30; int k;
};
11 risalta l 'ambiguit
ob.sum = ob.derivedl::i + ob.j + ob.k; I* derived3 eredita sia derivedl che derived2.
Questa volta in derived3 vi una sola opia di base. *I
Il risolta anche qui class derived3 : public derivedl, public derived2 {
cout ob.derivedl: :i " "; public:
int sum;
cout << ob.j << " " << ob.k << " "; };
cout ob.sum;
int main()
return O; {
deri ved3 ob;

- ---
-- ----~ - -
452 CAPITOLO 16

ob. i = 10; 11 nessuna ambiguit Capitolo 17


ob.j = 20; .
ob.k = 30; Funzioni virtuali
11 non ambigua e polimorfismo
ob.sum =ab.i + ob.j + ob.k;
17.1 Le funzioni virtuali
11 non ambigua
17.2 L'attributo virtual viene ereditato
cout << ob. i << " ";
17.3 Le funzioni virtuali sono gerarchiche
cout << ob.j << " " << ob.k << " "; 17.4 Le funzioni virtuali pure
cout ob.sum;
17.5 Uso delle funzioni virtuali
return O; 17.6 Il binding anticipato
e il binding ritardato

Come si pu vedere, la parola riservata virtual precede tutte le specifiche di


accesso della classe ereditata. Ora che sia derived1 che derived2 hanno ereditato
: l polimorfismo supportato dal C++ sia al momento
base come virtual, ogni operazione ereditaria successiva provocher l'uso di una della compilazione (compile-time) che al momento dell'esecuzione del program-
sola copia della classe base. Pertanto in derived3 vi sar una sola copia di base e ma (run-time). Come si detto in precedenza, il polimorfismo in compilazione
l'istruzione ob.i=10 perfettamente corretta e non presenta alcuna ambiguit. trova la sua espressione nell'uso di funzioni e di operatori modificati tramite
Un'ultima annotazione: anche se derived1 e derived2 specificano base come overloading. II polimorfismo run-time viene ottenuto utilizzando l'ereditariet e
virtual, la classe base continuer ad essere presente in tutti gli oggetti di entrambi le funzioni virtuali e verr trattato nel presente capitolo.
i tipi. Ad esempio, il seguente frammento di codice perfettamente corretto.

Il definisce una classe di tipo derivedl


derivedl myclass; 17.1 Le funzioni virtuali
mycl ass. i = 88; Unafunzione virtuale--una-funzione membro dichiarata come virtual in una clas-
se base e ridefinita da una classe derivata. Per creare una funzione virtuale, la sua
L'unica differenza fra una comune classe base e una classe base virtuale il dichiarazione deve essere preceduta dalla parola chiave virtual. Quando si eredita
fatto che quando un oggetto eredita pi di una copia della classe base, se vengono una classe contenente una funzione virtuale, la classe derivata ridefinisce la fun-
utilizzate pi classi base virtuali, nell'oggetto sar presente una sola copia della zione virtuale secondo le sue esigenze. In pratica le funzioni virtuali implementa-
classe base. In caso contrario loggetto conterr pi copie della classe base. no la filosofia "un'interfaccia per pi metodi" che sta alla base del polimorfismo.
La funzione virtuale contenuta nella classe base definisce la forma dell'interfaccia
della funzione. Ogni ridefinizione della funzione virtuale da parte di una classe
derivata implementa le operazioni da eseguire con riferimento alla classe derivata
stessa. In.pratica la funzione virtuale ridefiRita implementa un metodo specifico.
Quando si accede "normalmente" a una funzione virtuale, questa si comporta
come qualsiasi altro tipo di funzione membro. Ci che rende le funzioni virtua~i
cos importanti e che le rende capaci di supportare il polimorfismo run-time il
modo in cui si comportano quando si accede ad esse tramite un puntatore. Come
_ ~i~-Qet_tp_neLCapitolo 13, un puntat0reallac1asse base pu essere utilizzato pe~--
---~-:-- -

454 CAPITOLO 1 1 - - - - -
FUNZIONI VIRTUALI E POLIMORFISMO 455

puntare a qualsiasi classe derivata dalla base. Quando un puntatore base punta a 11 punta a deri ved2
un oggetto derivato che contiene una funzione virtuale, il C++ determina quale p = &d2;
versione di tale. funzione richiamare sulla base del tipo di oggetto puntato dal p->vfunc(); Il accede alla vfunc() di derived2
puntatore. Questa determinazione viene eseguita run-time. Pertanto, quando cambia
loggetto puntato dal puntatore cambier anche la versione della funzione virtuale return O;
che verr eseguita. Lo stesso effetto si applica ai riferimenti alla classe base.
Per iniziare, ecco un breve esempio:
Questo programma visualizza:
#include <iostream>
using namespace std; Questa la funzione vfunc() della classe base.
Questa la funzione vfunc() della classe derivedl.
class base { Questa la funzione vfunc() della classe derived2.
publi~:
vi rtua 1 voi d vfunc ()
In questo programma, all'interno di base viene dichiarata la funzione virtuale
cout "Questa la. funzione vfunc() della classe base.\n";
} vfunc(). Si noti che la parola riservata virtual precede la parte rimanente della
}; dichiarazione della funzione. Quando vfunc() viene ridefinita da derived1 e
derived2, la parola riservata virtual non necessaria (anche se non un errore
class derivedl public base { includerla quando si deve ridefinire una funzione virtuale all'interno di una classe
pub li e: derivata).
void vfunc() In questo programma, base ereditata da derived1 e derived2. All'interno
tout "Questa la funzione vfunc() della classe derivedl. \n"; della definizione di queste due classi, vfunc() viene ridefinita rispetto a tali classi.
}
In main() vengono dichiarate quattro variabili:
);

class derived2 public base { NOME TIPO


pu.blic:
Puntatore alla classe base
void vfunc() { .
cout <<"Questa la funzione vfunc() della classe derived2.\n"; Oggetto di base
}
}; d1 Oggetto di derived1

d2 Oggetto di derlved2
int main()
{
base *p, b; Quindi, a p viene assegnato l'indirizzo di be tramite p viene richiamata vfunc().
derivedl dl; Poich p punta a un oggetto di tipo base, verr eseguita tale versione di vfunc().
derived2 d2; Quindi, a p viene assegnato l'indirizzo di d1 e viene nuovamente richiamata vfunc()
utilizzando p. Questa volta p punta a un oggetto di tipo derived1. Quindi verr
11 punta a base eseguita la funzione derived1 ::vfunc(). Infine, a p viene assegnato l'indirizzo di d2
p &b; = e p->vfunc() provoca !'esecuzione di vfunc() ridefinita all'interno di derived2. In
p->vfunc(); Il accede alla vfunc() di base sostanza, il genere dell'oggetto a cui punta p a determinare la v_erjione di vfunc()
che verr eseguita. Inoltre, questa verifica viene eseguita al momento dell'esecu-
11 punta a deri vedl zione e questo processo sta alla base del polimorfismo run-time.
p = &dl; Anche se possibie richiamare una funzione virtuale in modo. "normale"
-~~ p->vfu-nc(Ji Il accede alla Y.f.un.Q_dj_d~rivedl utilizzando il nome di un oggettoeFoperfore punto, solo-quando P-accesso---
-----
---
---------~F~U::.:.:N~Z~l~O~N~l_:_V~IR~T.:....::.U~A~L~l-E=-.cP_O.:._::_L_IM_O_R_F_l_S_M_0_ _45T_- - -
456 CAPITOLO 17

lii nel ude <i ostream>


avviene attraverso un puntatore (o un indirizzo) alla classe base che possibile using namespace std;
ottenere il polimorfismo run-time. Ad esempio, prendendo in considerazione
l'esempio precedente, l'istruzione seguente sintatticamente corretta:- class base {
publ ic:
d2. vfunc () ; 11 richiama 1a funzione vfunc () di deri ved2 virtual void vfunc()
cout "Questa la funzione vfunc() di base. \n";
Anche se questo modo di richiamare una funzione virtuale non errato, sem-
plicemente non sfrutta i vantaggi della natura virtuale di vfunc(). };
A prima vista, la ridefinizione di una funzione virtuale da parte di una classe
class derivedl publ ic base {
derivata sembra simile alla tecnica di overloading delle funzioni. In realt si tratta
publ ic:
di meccanismi completamente diversi e non si applicato il termine overloading void vfunc() {
alla ridefinizione delle funzioni virtuali poich esistono molte differenze. La pi cout " Questa la funzione vfunc() di derivedl. \n";
importante il fatto che il prototipo di una funzione virtuale ridefinita deve corri- }
spondere esattamente al prototipo specificato nella classe base. Nel caso delle };
comuni funzioni modificate tramite overloading, il tipo restituito e il numero e il
tipo dei parametri pu (anzi deve) variare:- grazie a queste differenze che il C++ class derived2 public base {
pu selezionare la versione corretta della funzione. Quando invece viene ridefinita publ ic: .
una funzione virtuale, non deve invece variare I' aspetto dei prototipi. Se si cambia voi d vfunc ()
il prototipo nel tentativo di ridefinire una funzione virtuale, il compilatore C++ cout <<" Questa la funzione vfunc() di derived2. \n";
considerer l'operazione come un overloading della funzione e si perder pertan- }
};
to la sua natura virtuale. Un'altra importante restrizione il fatto che le funzioni
virtuali devono essere membri non static delle classi di cui fanno parte. In partico-
Il Usa un parametro indirizzo della classe base.
lare non possono essere friend. Infine, le funzioni costruttore, al contrario di quel- void f(base &r)
le distruttore, non possono essere virtuali. r.vfunc();

La chiamata di una funzione virtuale int main()


tra_mite l'ind~r~~-~~lla classe base {
base b;
Nell'esempio precedente, veniva richiamata una funzione virtuale tramite un deri vedl dl;
puntatore alla classe base ma la natura polimorfica di una funzione virtuale deri ved2 d2;
disponibile anche quando essa viene richiamata attraverso l'indirizzo della classe
base. Come si detto nel Capitolo 13, un indirizzo un puntatore implicito. Per- f(b); 11 passa a f() un oggetto base
tanto si pu utilizzare l'indirizzo della classe base per far riferimento a un oggetto f(dl); Il passa a f() un oggetto derivedl
della classe base o a un oggetto derivato da tale classe. Quando si richiama una f(d2); Il passa a f() un oggetto derived2
funzione virtuale tramite l'indirizzo della classe base, la funzione che verr ese-
guita dipende dall'oggetto cui si fa riferimento al momento della chiamata. return O;
La situazione-pi comune in cui unaTunzione virtuale viene richiamata tramite
l'indirizzo della classe base si verifica quando l'indirizzo il parametro di una fun-
zione. Ad esempio si consideri la seguente variante del programma precedente. Questo programma produce lo stesso output della versoneprecedente. In
questo es~Qlpio, la funzione !() gefinisce un parametro indirizzo di _ti~o _base. Al-
I* Qui si usa l'indirizzo dell.L-<;la~se b?se_ l'interno di maio!),Ja funzione viene richiamata utilizzando_oggettt d1 tipo base,
- . ----.--: --
---percced_ere a una funzione virtuale. *I
458 CAPITOLO 17 FUN7TOXI / I R TU AL I E-POLIMORFISMO 459

derived1 e derived2. In f(), la versione di yfunc() che verr richiamata dipende dal base *p, b;
tipo di oggetto cui si fa riferimento nella chiamata a funzione. deri vedl dl;
Per.semplici~, i rimanenti esempi di questo capitolo richiameranno le funzio- deri ved2 d2;
ni virtuali tramite puntatori alla classe base ma nel caso di indirizzi leffetto
idntico. 11 punta a base
p = &b;
p->vfunc () ; 11 accede a11 a vfunc () di base

17.2 l'attributo virtual viene ereditato 11 punta a derivedl


p = &dl;
p->vfunc(); Il accede alla vfunc() di derivedl
Quando viene ereditata una funzione virtuale, viene ereditata anche la sua natura
virtuale. Questo significa che quando una classe derivata che abbia ereditato una
11 punta a deri ved2
funzione virtuale viene, a sua volta, utilizzata come classe base per un'altra classe p = &d2;
derivata, la funzione rimarr virtuale. Ad esempio, si consideri questa variante del p->vfunc (); 11 accede a11 a vfunc () di deri ved2
programma precedente:
return O;
#include <iostream>
usi ng namespace std;
Come i si pu attendere, il programma produce l seguente output:
cl ass base {
public:
Questa 1a funzione vfunc() della cl asse base.
vi rtua 1 voi d vfunc ()
Questa la funzione vfunc() della classe derivedl.
cout "Questa la funzione vfunc() della classe base. \n";
Questa la funzione vfunc() della classe derived2.
}
};
In questo caso, derived2 eredita da derived1 invece che da base ma la funzio-
class derivedl public base ( ne vfunc() rimane virtuale.
public:
void vfunc()
cout "Questa la funzione vfunc() della classe derivedl. \n"; ---------
17.3 le funzioni virtuali sono gerarchiche
};
Come si detto, quando una funzione viene dichiarata come virtual da una classe
I* derived2 eredita la funzione virtuale vfunc() base, pu essere ridefinita da una classe derivata. Questa non per una necessit.
da derivedl. *I Se una classe derivata non ridefinisce una funzione virtuale, quando un ogget-
class derived2 : public derivedl to della classe derivata tenter di accedere a tale funzione, verr utilizzata la fun-
public: zione definita nella classe base. Ad esempio, si consideri il seguente programma
Il vfunc() ancora virtuale in cui derived2 non modifica vfunc():
void vfunc() {
cout <<"Questa la funzione vfunc() della classe derived2.\n";
#i nel ude <i ostream>
}
};
using name~p~~ std;

cl ass base {
int main()
public;___ _
{
460 CAPITOLO 17 rs .M O
F U N Z I O N I V I A T U A LI E P O L I M O A F 461

virtual void vfunc() {


Poich derived2 non ridefinisce vfunc(), quando si richiama vfunc() con og-
cout "Questa la funzione vfunc () della cl asse base. \n"; getti di tipo derived2 verr utilizzata la funzione definita in base.
};
Il programma precedente illustra un caso speciale di una regola generale. Poi-
ch in C++ l'ereditariet gerarchica, ha senso che anche le funzioni virtuali
class derivedl public base { siano gerarchiche. Questo significa che quando una classe derivata non ridefinisce
public: una funzione virtuale, verr utilizzata la prima ridefinizione presente in ordine
voi d vfunc () inverso di derivazione. Ad esempio, nel seguente programma, derived2 derivata
cout "Questa la funzione vfunc() della classe derivedl. \n"; da derived1 che a sua volta derivata da base. Ma derived2 non ridefinisce vfunc().
I Questo significa che rispetto a derived2, la versione pi vicina di vfunc() quella
}; presente in derived1. Pertanto, quando un oggetto di derived2 cerca di richiamare
vfunc() verr utilizzata la versione della funzione derived1 ::vfunc().
c 1ass deri ved2 : pub li e base {
public: -
#include <iostream>
11 vfunc() non viene sostituita da quella di derived2 e viene usata quella using namespace std;
di base
};
cl ass base {
publ ic:
int main()
virtual void vfunc()
{
cout <<"Questa la funzione vfunc() della classe base.\n";
base *p, b;
}
deri vedl dl;
};
deri ved2 d2;
class derivedl publ ic base {
11 punta a base publ ic:
p = &b;
voi d vfunc ()
p->vfunc(); Il accede alla vfunc() di base cout "Questa la funzione vfunc() della classe derivedl. \n";

11 punta a deri vedl };


p = &dl;
p->vfunc O; 11 accede alla vfunc () di deri vedl class derived2 : public derivedl {
public:
I I punta a deri ved2 /* vfunc() non viene ridefinita da derived2.
p = &d2;
In questo caso, poich derived2 deriva da
p->vfunc O; 11 usa la vfunc () di base
deri vedl, viene usata la vfunc () di deri vedl.
return O; *I
};

int main()
Il programma produce il seguente output: {
base *p, b;
Questa la funzione vfunc() della classe base. deri vedl dl;
Questa la funzione vfunc() della classe derivedl. derived2 d2;
Questa la funzi~ne vfunc() della classe base_.__ _
-- 11 punta a bas_l!_ _ _ _ _
p = &b;
462 CAPIHHO 17 FUNZIONI VIRTUALI E POLIMORFISMO 463

p->vfunc (); 11 accede a11 a vfunc () di base tano number e ridefiniscono show() in modo che visualizzi il valore di val nelle
varie basi numeriche (esadecimale, decimale e ottale).
I I punta a deri ved 1
p = &dl; #include <iostream>
p->vfunc(): Il accede alla vfunc() di derivedl
using namespace std;
Il punta a derived2
class number
p = &d2;
protected:
p->vfunc(); Il u~a la vfunc() di derivedl
int val;
publ ic:
return O;
void setval(int i) {val =i;}

Il show() una funzione virtuale pura


Il programma produce il seguente output: virtual void show() = O;
I:
Questa la funzione vfunc() della classe base.
Questa la funzione vfunc() della classe derivedl. class hextype public number
Questa la funzione vfunc() della classe derivedl. publ ic:
voi d show()
cout <<. hex << val << "\n":

17.4 Le funzioni virtuali pure };

Come si visto negli esempi della sezione precedente, quando una funzione vir- class dectype public number {
tuale non viene ridefinita da una classe derivata, viene impiegata la versione defi- public:
nita nella classe base. In molti casi per non vi una definizione appropriata di void show()
una funzione virtuale all'interno della classe base. Ad esempio, una classe base cout << val << 11 \n";
potrebbe non essere in grado di definire un oggetto in modo sufficiente per con-
};
sentire la crcuzione di una funzione virtuale nella classe base. Inoltre, in alcuni
casi necess't1rio assicurarsi che tutte le classi derivate ridefiniscano una funzione
class-octtype public number {
virtuale. Per gestire questi due casi; il C++ prevede l'uso di funzioni virtuali pure. publ ic:
Una fwr::.one 1>irtuale pura una funzione virtuale che non viene definita void show()
all'interno della classe base. Per dichiarare una funzione virtuale pura, si utilizza cout << oct << val << "\n";
questa forma genl'rale:
};
virtual tipo nome-funzione(elenco-parametri) =O;
int main()
{
Quando una funzione virtuale viene resa pura, ogni classe derivata deve forni-
dectype d;
re una propria delinizi_me. Se la classe derivata non ridefinisce la funzione virtua-
liextype h;
le pura, questo pn1vocher un errore di compilazione. octtype o;
11 seguente programma contiene un semplice esempio di funzione virtuale
pura. II tipo b~1se number contiene un intero chiamato val, la funzione setvar() e la d.setval (20);
----fililzione vinuale\'iUrashow():-l::;e-classi-deri_vate hextype, dectype e octtype eredi- ci.show(); Il visualizza 20 - decimale
--------- ----- - ------- ~--:.-.
464 CAPITOLO 17 FUNZIO.ffl VIRTUALI E POLIMORFISMO 465

h. setval (20); implementer le sps:cifiche operazioni in relazione al tipo di dati impiegato dal
h.show(); Il visualizza 14 - esadecimale tipo derivato.
_ .Uno dei modi pi potenti e flessibili per implementare l'approccio
o. setval (20); "un'interfaccia, pi metodi" prevede l'uso di funzioni virtuali, di classi astratte e
o.show(); Il visualizza 24 - ottale del polimorfismo run-time. Utilizzando queste funzionalit possibile creare una
gerarchia di classi che passi dal caso generale a quello specifico (dalla classe base
return O;
alle classi derivate). Secondo questa filosofia, si definiscono tutte le funzionalit e
i metodi di interfacciamento comuni all'interno di una classe base. Nei casi in cui
determinate azioni possono essere implementate solo dalla classe derivata si uti-
Anche se questo esempio piuttosto semplice, illustra il modo in cui una
lizza una funzione virtuale. In pratica, nella classe base si crea e definisce tutto
classe base potrebbe non essere in grado di definire una funzione virtuale che
ci che fa riferimento al caso generale. I dettagli vengono completati dalle classi
abbia un senso. In questo caso, number fornisce semplicemente l'interfaccia co-
derivate.
mune utilizzata dai tipi derivati. Non vi alcun motivo di definire show() all'in-
Quello che segue un semplice esempio che illustra il valore della filosofia
terno di number poich la base del numero indefinita. Naturalmente, si pu
"un'interfaccia, pi metodi". Viene creata una gerarchia di classi che eseguono
sempre creare una definizione fittizia di una funzione virtuale. Tuttavia, l'utilizzo
conversioni da un sistema di unit a un altro sistema (ad esempio da litri a gallo-
della versione pura di show() assicura che tutte le classi derivate eseguano la
ni). La classe base convert dichiara due variabili, val1 e val2, le quali devono
ridefinizione della funzione in base alle proprie esigenze.
contenere rispettivamente i valori iniziale e convertito. Inoltre, definisce le fun-
Si deve ricordare che quando una funzione virtuale viene dichiarata pura, tut-
zioni getinit() e getconv() che restituiscono il valore iniziale e il valore convertito.
te le classi derivate sono obbligate a ridefinirla. Se una classe derivata non
Questi elementi di convert sono fissi e applicabili a tutte le classi derivate che
ridefinisce la funzione, verr prodotto un errore di compilazione.
ereditano convert. Ma la funzione che eseguir effettivamente la conversione,
compute() una funzione virtuale pura che deve essere definita dalle classi deri-
Le classi astratte vate da convert. La specifica natura di computa() sar determinata dal tipo di
conversione eseguita.
Una classe che contenga almeno una funzione virtuale pura chiamata classe
astratta. Poich una classe astratta contiene una o pi funzioni per le quali non Il Esempio pratico di funzione virtuale.
presente alcuna definizione (ovvero funzioni virtuali pure), l'uso di una classe
astratta non consentir la creazione di alcun oggetto. Al contrario, una classe astratta #include <iostream>
costituisce un tipo incompleto utilizzato come pas_t:;J2~lt:;_cl;;tssi derivate. using namespace std;
Anche se non possibile creare oggetti di una classe derivata, possibile
cl ass convert {
creare puntatori e richiederne l'indirizzo. Questo consente alle classi astratte di protected:
supportare il polimorfismo run-time, selezionando la funzione virtuale corretta double vall; Il valore iniziale
sulla base dei puntatori e degli indirizzi della classe base. double va12; Il valore convertito
public:
convert ( doub 1e i)
vall = i;
17.5 Uso delle funzioni virtuali
doub 1e getconv () {return va 12;}
Uno degli aspetti fondamentali della programmazione a oggetti il principio doubl e geti nit() {return vall;}
"un'interfaccia pi metodi". Questo significa che possibile definire una classe
generale di azionT1ilfiifl'interfaccia rimane costante e ogni derivazione defini- vi.rtua 1 voi d compute O = O;
sce operazioni specifiche. In termini pratici, una classe base pu essere utilizzata }; -----
per definite l!!._nfilm:!!.deJI'interfaccia di una classe generale. Ogn~S~~s~.d~vata
-------

--=--FUNZIONI VIRTUALrE-POLIMUlir-;:::i::1;Lv. ---'!!!.!_

Il Litri in galloni.
class l_to_g : public convert Uno dei pregi delle classi derivate e delle funzioni virtuali il fatto che sem-
public: plificano notevolmente la gestione di un nuovo caso. Ad esempio, sempre tenen-
l_to_g(double i) : convert(i) { ) do in considerazione il programma precedente, si potrebbe aggiungere una con-
voi d compute () { versione da piedi in metri includendQ. questa classe:
val2 = vall I 3.7854;
} 11 da piedi in metri
); class f_to_m : public convert
public:
Il Fahrenheit in Celsius f to m(double i) : convert(i) { )
cl ass f _to_c : pub li c convert v~id-compute{) {
public: va12 = vall I 3.28;
f_to_c(double i) : convert(i) { )
voi d compute () { };
val2 = (vall-32) I 1.8;

);
Un utilizzo importante delle classi astratte e delle funzioni virtuali avviene
nelle librerie di classi. infatti possibile creare una libreria di classi generiche ed
int main() estendibili utilizzabili da altri programmatori. Un altro programmatore erediter
{ la vostra classe generale che definisce linterfaccia e tutti gli elementi comuni a
convert *p; Il puntatore alla classe base tutte le classi da essa derivate, aggiungendo semplicemente le funzioni specifiche
della classe derivata. La creazione di librerie di classi consente di creare e control-
l _ to _g l gob{4); lare l'interfacCia di una classe generale lasciando agli altri programmatori l'adat-
f _ to_ c fcob (70); tamento per situazioni specifiche.
Un'ultima annotazione: la classe base convert un esempio di classe astratta.
11 per la conversione impiega una funzione virtuale La funzione virtuale compute non definita in convert poich non pu essere forni-
p = &lgob; ta una definizione che abbia un qualche senso. Semplicemente la classe convert non
cout p->getinit() " litri = ";. contiene sufficienti informazioni per definire compute. Sar solo quando convert
p->compute () ;
sar ereditata da una classe derivata che sar possibile creare un tipo completo.
cout p->getconv () " ga 11 oni \n"; 11 l _ to_g

p = &fcob;
cout <-< p->get i nit() <<
p->compute();
Il
in gradi Fahrenheit uguale a "; 17.6 Il binding anticipato e il binding ritardato
cout p->getconv() " in gradi Celsius\n"; Il f_to_c Prima di coneludere questo capitolo riguardante le funzioni virtuali e il
return O;
polimorfismo nin-time, vi sono due termini che devono essere definiti poich
molto utilizzati nelle discussioni che riguardano il C++ e la programmazione a
oggetti: binding anticipato e binding ritardato.
Con binding anticipato si intendono gli eventi che si verificano al momento
Questo programma crea due classi derivate a partire da convert, chiamate l_to_g della compilazione. In pratica, si intende che tutte le informazioni richieste per
_ ~-!_-to_c. Queste cla~si eseguono le conversioni da litri in galloni e da Fahrenheit richiamare una funzione sono note al momento della compilazione. In altre paro-
in Celsius. Ogni classe derivata ridefinisce compute nel modo richiesto dalla con- le, b~ding anticipato significa che un oggetto e una chiamata a funzione possono.
versione. Tuttavia, anche se la conversione eseguita (ovvero il metodo) diverso essere associati durante la compilazione. Gli esempi di binding anticipato inclu-
da l_to_g e Uo_c, !"interfaccia rimane cOstarite.
dono le comuni chiamate a funzioni (incluse le funzioni della libreria standard),

------ -- ---- -- ---


468 CAPITOLO 17

le chiamate alle funzioni modificate tramite overloading e l'uso degli operatori : CapitoloJ 8
modificati tramite overloading. Il vantaggio principale del binding anticipato
l'efficienza. Poich tutte le informazioni necessarie per richiamare una funzione I template
vengono determinate al momento della compilazione, questo tipo di chiamate a
funzione risulta molto veloce.
Il contrario di binding anticipato il binding ritardato. Nel C++, il binding 18.1 Funzioni generiche
ritardato si riferisce a chiamate a funzioni che non possono essere determinate se 18.2 Uso delle funzioni generiche
non al momento dell'esecuzione del programma. Per ottenere il binding ritardato 18.3 Classi generiche
si utilizzano funzioni virtuali. Come si sa, quando l'accesso avviene tramite un
18.4 Le parole riservate typename ed export
puntatore (o un indirizzo) alla classe base, la funzione virtuale effettivamente
richiamata viene determinata dal tipo di oggetto puntato dal puntatore. Poich 18.5 La potenza del template
nella maggior parte dei casi questo non noto al momento della compilazione,
l'oggetto e la funzione non vengono collegati se non al momento dell 'eseuzione.
Il vantaggio principale del binding ritardato la flessibilit. A differenza del binding
anticipato, il binding ritardato consente di creare programmi che siano in grado di
I template (modelli) sono una delle funzionalit pi so-
fisticate e potenti del C++. Anche se i template non facevano parte delle specifi-
rispondere a eventi che si verificano durante l'esecuzione del programma senza che originali del C++, sono stati aggiunti molto tempo fa e attualmente sono
dover creare codice di gestione dei vari casi. Questo significa anche che, poich supportati da tutti i compilatori C++ pi recenti. Con un template possibile
una chiamata a funzione non pu essere risolta se non al momento dell'esecuzio- creare fun.zioni e classi generiche. In una funzione o classe generica, il tipo dei
ne, il binding ritardato pu in una certa misura rallentare i tempi di esecuzione. dati su cui la funzione o la classe operano viene specificato come parametro.
Pertanto, si potr utilizzare una funzione o una classe con vari tipi di dati senza
dover ricodificare esplicitamente una diversa versione specifica per ogni tipo di
dati.

18.1 Funzioni generiche


Una funzione generica definisce una serie di operazioni generali applicabili a vari
tipi di dati. Il tipo dei dati su cui si trover ad operare viene passato tramite un
parametro. Grazie a una funzione generica, la stessa procedura generale pu esse-
re applicata a un'ampia gamma di dati. Molti algoritmi sono infatti logicamente
identici indipendentemente dal tipo di dati su cui operano. Ad esempio, I' algoritmo
di ordinamento Quicksort uguale sia che operi su un array di interi, sia che
debba ordinare un array di float. L'unica cosa che cambia il tipo dei dati da
ordinare. Creando una funzione generica possibile definire, indipendentemente
dai dati, la natura dell'algoritmo. In questo modo, il compilatore generer auto-
maticamente il codice corretto per il tipo di dati effettivamente utilizzato per ri-
chiamare la funzione. In pratica, quando si crea una funzione generica, si crea una
funzione che in grado di eseguire automaticamente l'overloading di s stessa. -
Per creare una funzione generica si utilizza la parola chiave template. Tale
parola chiave crea un modello che descrive le operazioni svolte da una funzione,
lasciando al compilatru-e il c.omple.tamento dei dettagli. La forma generale della
definizione di una funzione _!~e!_ate _:_ - --- ---- -
470 CA PH-0-Hl--+8 -----=--- ---~- -'"".:'

template <class tipo> tipo-restituito nome-funzione(elenco parametri) swapargs (a, b); 11 scambia due caratteri
{
Il corpo della fanzione cout << "i e j scambiati: " i << ' ' << j << endl;
}
cout << "x e y scambiati: " << x << ' ' << y << endl;
cout "a e b scambiati: " a ' ' b endl;
n Qui ~po indie~ ~l nome del tipo che verr utilizzato dalla funzione. Questo
OJDe pu.o essere utilizzato anche nella definizione della funzione. Tuttavia si trat- return O;
~ sol~ d1 u.n segnaposto che il compilatore sostituir automaticamente con il vero
~ .d1 dati nel momento in cui si trover a creare una versione specifica della
di nz!one .. Anch~ se normalmente per specificare un tipo di dati generico nella Osservando attentamente il programma, si pu notare che la riga:
ch1araz1one ?1 un .template si usa la parola riservata class, si pu utilizzare an-
che la parola nservata typename. template <class X> void swapargs(X &a, X &b)
Il breve ~se~~io seguente crea una funzione generica che scambia il valore
delle ~ue ~anab1li e~~ ~e ~uali viene richiamata. Poich il processo generale di dice al compilatore due cose: che si deve creare una funzione template e che sta
~bio d~ due valon e md1pendente dal tipo delle variabili, questa funzione un per iniziare una definizione generica. Qui, X un tipo generico utilizzato come
candidato ideale per la trasformazione in funzione generica. segnaposto. Dopo la porzione template viene dichiarata la funzione swapargs()
utilizzando X per definire il tipo dei valori che devono essere scambiati. In main(),
11 Esempio di funzione templ ate. la funzione swapargs() viene richiamata con tre diversi tipi di dati: int, double e
char. Poih swapargs() una funzione generica, il compilatore crea automatica-
#include <iostream>
mente tre versioni di swapargs(): una di esse scambia valori interi, una valori in
using namespace std;
virgola mobile e la terza caratteri.
Ecco altri termini che vengono normalmente utilizzati quando si parla di
11 Questa . una funzione template.
template. Innanzitutto, una funzione generica (ovvero la definizione di una fun-
~emplate <class X> void swapargs(X &a, x &b)
zione preceduta dall'istruzione template) viene anche chiamatafanzione templare.
X temp; In questo volume verranno utilizzati indifferentemente entrambi i termini. Quan-
do il compilatore crea una versione specifica di tale funzione si dice che ha creato
temp = a; una specializzazione o unafanzione generata. L'atto di generazione di una fun-
a = b; zione viene chiamato istanziazione. In altre parole, una funzione generata una
b = temp; specifica istanza di una funzione template.
Poich il C++ non riconosce il codice di fine riga come chiusura dell 'istruzio-
ne, la clausola templare della definizione di una funzione generica non deve ne-
int main()
{
cessariamente trovarsi sulla stessa riga del nome della funzione. Ad esempio ecco
un altro modo molto comune per scrivere la funzione swapargs().
int i=lO, j=20;
float x=l0.1, y=23.3;
char a='x', b='z'; template <class X>
void swapargs(X &a, X &b)
{
cout "i e j origina 1i : " << i << , .
. J .:2 endl; X temp;
cout "x e Y-.originalf: " x << , y << endl;
_ _ _ _ _cout "a e b originali:"<< a ' , b endl;
temp = a;
swapargs (i j l; 11 scambi a due interi a = b;
.... - ---- - swapargs (x~-!~;__ 11 scambia due float ____,, ____ _ .t-:;;=:::--- Jl. = temp;
}
472 CAPITOLO 18

Utilizzando questa forma importante comprendere che non vi possono esse- In questo esempio, i tipi type2 e type2 vengono sostituiti dal compilatore ri-
re altre istruzioni fra l'istruzione template e finizio della definizione della fun- spettivamente con i tipi int e char* e double e long nel momento in cui il compila-
zione generica. Ad esempic:2 questo frammento di codice non verr compilato. tore genera le specifiche istanze di myfunc() all'interno di main().

Il questa forma non verr compilata. :N!)'l'A'VP~~ Quando si crea una funzione templare, in realt si chiede al
template <class X> compilatore di generare tutte le versioni di tale funzione necessarie per gestire
int i; Il questo un errore tutte le situazioni nelle quali il programma deve impiegare tale funzione.
voi d swapa rgs (X &a, X &b)
{
X temp; Overloading esplicito di una funzione generica

temp = a; Anche se una funzione generica esegue I' overloading di s stessa sulla base delle
a = b; situazioni in cui si trova ad operare, rimane comunque possibile eseguire un
b = temp; overloading esplicito della funzione. Questa tecnica chiamata specializzazione
esplicita. Se si esegue l'overloading di una funzione generica, la versione modifi-
cata tramite overloading "nasconder" la funzione generica relativa a questa spe-
Come si pu leggere nei commenti, la parte template deve precedere imme- cifica versione. Ad esempio, si consideri questa nuova versione dell'esempio che
diatamente la definizione della funzione. eseguiva lo scambio degli argomenti.

Il Modifica di una funzione template.


Una funzione con due tipi generici
#include <iostream>
Con l'istruzione template possibile definire anche pi di un tipo di dati generico, usi ng namespace s td;
utilizzando un elenco separato da virgole. Ad esempio, il programma seguente

j crea una funzione template che utilizza due tipi generici.

#i nel ude <i ostream>


template <class X> void swapargs(X &a, X &b)
{
X temp;
using namespace std;
l templ ate <cl ass typel, cl ass type2>
temp = a;
a = b;
voi d myfunc ( type 1 x, type2 y) b = temp;
{ cout "Nel template di swapargs(int &, int &) \n";
cout x ' ' << y endl ;
Il Questa versione nasconde la versione generica di swapargs().
void swapargs(int &a, int &b)
int main() {
{ int temp;
myfunc(lO, "Programmare in C++");
temp = a;
myfunc(0.23, 19L); a = b;
b = temp;
return O; cout "Nella spec.i.alizzazione int di swapargs(int &, int &).\n";

----- --------
474 C .A:P I T O LO 1 8
TEMPLATE 475

i nt ma in()
temp = a;
{
a = b;
int i=lO, j=20;
b = temp;
float x=lO.l, y=23.3;
cout "In swapargs con special i zzazi one i nt. \n~;
char a='x', b='z';

cout "i e j originali: "<<i << ' ' << j << endl;
cout "x e y originali: " << x << ' ' << y << endl; Come si pu vedere, la nuova sintassi indica la specializzazione tramite il
cout << "a e b originali: " << a << ' << b << endl; costruttore template<>. Il tipo di dati per il quale viene creata la specializzazione
deve essere specificato fra le parentesi angolari dopo il nome della funzione. Que-
swapargs(i, j); Il richiama la swapargs() modificata tramite overloading sta stessa sintassi utilizzata per specializzare ogni tipo di funzione generica.
swapargs (x, y}; I I richiama 1a swapargs () generi ca Anche se attualmente non esistono vantaggi a utilizzare una version della sintas-
swapargs(a, b}; Il richiama la swapargs() generica si di specializzazione rispetto all'altra, a lungo termine probabilmente meglio
utilizzare la nuova sintassi. -
cout "i e j scambiati.: " << i << ' ' <;< j << endl; La specializzazione esplicita di un template consente di personalizzare una
cout << "x e y scambiati: " << x << ' << y << endl; versione di una funzione generica per rispondere a situazioni specifiche, ad esem-
cout "a e b scambiati: ".<< a ' << b << endl; pio per sfruttare alcuni_stratagemmi prestazionali che si applicano a un solo tipo
return O; di dati. Tuttavia, in generale, se si devono creare versioni differenti di una funzio-
ne per tipi di dati differenti, si dovrebbero usare le funzioni modificate tramite
overloading piuttosto che i template.
Corr:e ~i pu dedu~e dai _commenti, quando viene richiamata swapargs(i, j),
questa nch1ama la versione di swapargs() che modificata tramite un overloadino Overloading di un template di funzioni
esplicito. Pertanto, il compilatore non generer questa versione della funzion:
generica swapargs() poich la funzione generica viene nascosta dall' overloading Oltre a creare versioni esplicite e modificate tramite overloading di una funzione
esplicito. generica, si pu eseguire l'overloading anche dello stesso template. A tale scopo
L'esec~zione di un overloading manuale di un iemplate, come si vede in que- basta creare una nuova versione del template che differisca dalle altre versioni per
sto esempio, consente di adattare con precisione una versione di una funzione il tipo dei parametri. Ad esempio:
generica_in_mo?o da gestire una particolare situazione. Tuttavia, in generale, se
necessano 1~p1egare versioni diverse di una funzione per i diversi tipi di dati, si ___ ..,L,LD'lerl.oading della dichiarazione di un template di funzioni.
d?vrann~ utilizzare funzioni modificate tramite overloading piuttosto che funzio- #include <iostream>
ni genenche. using namespace std;
Recentemente stata introdotta una nuova forma sintattica che denota la
spe~ializzazione esplicita di una funzione. Questo nuovo metodo utilizza la paro- II Prima versione del template di f().
la riservata template. Ad esempio, utilizzando questa nuova sintassi di template <class X> void f(X a)
{
specializzazione, la funzione swapargs() modificata tramite overloading presen-
cout "In f(X a)\n";
tata nel precedente programma assumer il seguente aspetto:

Il Uso della nuova_ forma sintattica. Il Seconda versione.del template di f().


template<> void swapargs<int>(int &a, int &b)
{ template <class X, class Y> void f(X a, Y b)
{
int temp;
cout "In f(X a, Y b)\n":
int main()
Questa una prova
{
100-
f(lO); Il richiama f{X) X
f(lO, 20); Il richiama f{X, Y) 3

return O;
Nel programma, la funzione tabOut() visualizza il suo primo argomento nella
posizione specificata dal secondo argomento. Dato che il primo argomento di
tipo generico, tabOut() pu essere utilizzata per visualizzare qualsiasi tipo di dati.
Qui il template di f() viene modificato tramite overloading per accettare uno 0 Il parametro tab un parametro standard con chiamata per valore. L'utilizzo con-
due parametri.
temporaneo di parametri generici e non generici non provoca problemi ed anzi
molto comune e utile.
Uso di parametri standard nelle funzioni template
Restrizioni delle funzioni generiche
In una funzione template possibile usare insieme parametri standard e parametri
di tipo generico. Questi parametri non generici funzionano esattamente come ocrni Le funzioni generiche sono simili alle funzioni modificate tramite overloading
0
altra funzione. Ad esempio:
ma hanno maggiori restrizioni. Quando viene eseguito l'overloading di una fun-
zione, nel corpo di ogni funzione possono essere eseguite azioni diverse. Una
11 Uso di parametri standard in una funzione temp 1ate.
funzione g~nerica deve invece eseguire la stessa azione generale per tutte le ver-
#include <iostream>
using namespace std; sioni (l'unica. differenza costituita dal tipo). Si considerino le funzioni modifica-
te tramite overloading contenute nel seguente programma. Non possibile utiliz-
const int TABWIDTH = 8; zare una funzione generica al posto delle funzioni modificate tramite overloading
poich vengono eseguite operazioni diverse.
Il Visualizza i dati alla posizione di rientro specificata.
template<class X> void tabOut(X data, int tab) #i nel ude <i ostream>
{ #i nel ude <cmath>
for(; tab; tab--) usi ng namespace std;
for(int i=O; i<TABWIDTH; i++) cout ' 1;

void myfunc(int i)
cout data "\n"; {
11
cout << "Il valore : << i << "\n";

int main()
I void myfunc(double d)
tabOut("Questa una prova", O); {
tabOut(lOO, 1); double intpart;
tabOut ('X', 2); double fracpart;
tabOut(lOl3, 3);
fracpart = moM(d, &intpart);
return O; cout "Parte decimale: " << fracpar.t~
cout << "\n";
. cout "Parte intera: " intpart;
Ecco l'output prodotto dal programma.
-~-T-t;..1.4-P-LA TE 479

int main() X t;
{
myfunc{l); for(a=l; a<count; a++)
myfunc{l2.2); for{b=count-1; b>=a; b--)
if {i tems [b-1] > i tems [b])
return O; Il scambia gli elementi
t = items[b-1];
items[b-1] = items[b];
items[b] = t;

18.2 Uso delle funzioni generiche


Le funzioni generiche sono una delle funzionalit pi utili del C++.Tali funzioni int main()
possono essere applicate a vari tipi di situazioni. Come si detto in precedenza, {
quando si utilizza una funzione che definisce un algoritmo generalizzabile, la si int iarray[7] = (7, 5, 4, 3, 9, 8, 6};
double darray[S] = {4.3, 2.5, -0.9, 100.2, 3.0);
pu trasformare in una funzione template. In questo modo, la funzione potr esse-
re utilizzata con qualsiasi tipo di dati senza necessit di ricodifica. Prima di parla-
int i;
re delle classi generiche, questa sezione presenta due esempi di funzioni generi-
che. Questi esempi illustrano quanto sia facile sfruttare i vantaggi di questa im- cout ,;Ecco l 'array di interi non ordinato: ";
portante funzionalit. for(i=O; i<7; i++)
cout iarray[i] ' ';
cout endl ;
Ordinamento generico
cout "Ecco 1 'array di double non ordinato: ";
L'ordinamento il genere di operazioni che ha suggerito l'introduzione di funzio- for(i=O; i<S; i++)
ni generiche. In generale, un algoritmo di ordinamento non cambia quando varia cout << darray[i] ' ';
il tipo dei dati ordinati. Il programma seguente illustra questo concetto creando cout endl ;
un algoritmo di Bubblesort generico. Anche se il Bubblesort un algoritmo di
basse prestazioni, il suo funzionamento semplice e non presenta aspetti oscuri e bubble(iarray, 7);
- --qliestO lo rende un esempio di facile comprensione. La funzione bubble() in bubble(darray, 5);
grado di ordinare qualsiasi tipo di array. La funzione deve essere richiamata con cout "Ecco 1 'array di interi ordinato: ";
un puntatore al primo elemento dell'array e con il numero di elementi contenuti for(i=O; i<7; i++)
nell' array. cout << iarray[i] <<' ';
cout endl ;

11 Bubb 1esort generico. cout "Ecco 1 'array di double ordinato: ";


for(i=O; i<5; i++)
#include <iostream>
cout darray[i] ' ';
using namespace std;
cout endl ;

template <class X> void bubble(


return O;
X *items, Il puntatore all'array da ordinare
int count) Il numero di elementf contenuti nell 'array

registirr_int. a, .b; Ql.festo programma generai! ~guen~e ~utput:


I TEMPLATE 481

Ecco l'array di interi non ordinato: 7 5 4 3 9 8 6 int main()


Ecco l 'array di double non ordinato: 4.3 2.5 -0.9 .100.2 3 {
Ecco l'array di interi ordinato: 3 4 5 6"7 8 9 int nums[7) = {O, 1, 2, 3, 4, 5, 6};
Ecco l 'array di double ordinato: -0.9 2.5 3 4.3 100.2 char str[IS] = "Funzioni generiche";

Come si pu vedere, il programma precedente crea due array: uno di interi e int i;
uno di double. Quindi procede a ordinarli entrambi; poich bubble() una funzio-
ne template, subir un overloading automatico per accettare i due diversi tipi di cout << 11 Ecco 1 'array di interi non compattato: ";
dati. for(i=O; i<7; i++)
cout nums[i] ' ';
cout endl ;
Compattamento di un array
cout "Ecco la stringa non compattata: ";
for(i=O; i<l8; i++)
Un'altra funzione che consente di sfruttare i vantaggi dati dall'uso dei template
cout str[i] ' ';
compact(). Questa funzione compatta gli elementi di un array. Capita spesso di
cout endl ;
dover eliminare elementi da un array e quindi di dover spostare gli elementi rima-
nenti in modo da lasciare tutti gli elementi inutilizzati alla fine. Questo tipo di compact(nums, 7, 2, 4);
operazione la stessa per tutti i tipi di array poich non dipende dal tipo dei dati compact(str, 18, 6, 10);
su cui si opera. La funzione generica compact() mostrata nell'esempio seguente
deve essere richiamata con un puntatore al primo elemento dell'array, il numero cout "Ecco l 'array di interi compattato: ";
di elementi contenuti nell'array e gli indici iniziale e finale degli elementi da for(i=O; i<7; i++)
eliminare. La funzione elimina dall'array tali elementi e quindi compatta l'array. cout nums [i] ' ';
AI termine inserisce il valore zero in tutti gli elementi inutilizzati che si trovano cout endl ;
alla fine dell'array e che sono stati liberati dal compattamento.
cout "Ecco 1a stringa compattata: ";
for(i=O; i<l8; i++)
Il Una funzione generica per il compattamento di un array.
cout str[i] ' ';
cout endl ;
#include <iostream>
using n_~fil!.a<;~-~_td;
return O;
template <class X> void compact(
X *items, Il puntatore all'array da compattare
int count, 11 numero di elementi contenuti nell 'array Questo progranuna compatta due tipi di array. Uno un a:ra.y ~i int7ri e l' altr~
int start, Il indice iniziale della regione compattata una stringa. La funzione compact() funzioner con qualsiasi tipo d1 array. D1
int end) Il indice finale della regione compattata seguito viene presentato l'output del programma:

regi ster int i; Ecco l 'array di interi non compattato: O 1 2 3 4 5 6


Ecco la stringa non compattata: F u n z i o n i g e n e r i c h e
for(hend+l; i<count; i++,.s.tart++) Ecco~'array di interi compattato: O 1 5 6 O O O
items[start] = items[i); Ecco la stringa compattata: F u n z i o e n e r i c h e

I* Per semplicit, la parte rimanente Come si pu dedurre dagli esempi precedenti, quando si iniz~a a ~en~~e i~
dell 'array viene azzerata. *I terminidi_t~m.plate (modeli). naturale che vengano in me_nte le s1tuaz1o_n~~n-~u1
for( ; start<count;-...start_+) items[start] (X) O;
TE MPT;;+E- 483
482 CAPITOLO 18

possibile utilizzare tale tecnica. Tutti i casi in cui la logica operativa di una no creati uno stack di caratteri e uno stack di numeri in virgola mobile ma
funzione non cambia sono candidati ideali per l'utilizzo di una funzione generica. possibile utilizzare dati di qualsiasi tipo.

11 Creazione di una classe generica per stack.

18.3 Cla~si generiche llinclude <iostream>


usi ng namespace std;
Oltre alle funzioni generiche anche possibile definire una classe generica. In tal
caso si crea una classe che definisce tutti gli algoritmi utilizzati da tale classe ma const int SIZE = 100;
nella quale il tipo di dati da manipolare viene specificato come parametro nel
momento in cui vengono creati gli oggetti della classe. Il Creazione della classe generica stack.
Le classi generiche sono utili nel caso in cui una classe contenga elementi templ ate <cl ass StackType> cl ass stack {
logici generalizzabili. Ad esempio, lo stesso algoritmo che esegue la gestione di StackType stck[SIZE);
int tos; Il indice della cima dello stack
una coda di interi funzioner anche per una coda di caratteri. Lo stesso meccani-
smo che gestisce una lista concatenata di indirizzi funzioner anche per gestire
publ ic:
una lista concatenata di informazioni relative alle parti di ricambio di un'automo- stack() { tos = O; } ;
bile. Una classe generica pu eseguire l'operazione definita, come ad esempio la void push(StackType ob);
gestione di una coda o di una lista concatenata per qualsiasi tipo di dati. II compi- StackType pop O;
latore generer automaticamente il tipo di oggetti corretti sulla base del tipo spe- };
cificato al momento della creazione dell'oggetto.
La forma generale della dichiarazione di una classe generica : 11 Push di un oggetto nello stack.
templ ate <cl ass StackType> voi d stack<StackType>:: push (StackType ob)
template <class tipo> class nome-classe { {
if(tos==SIZE) {
cout "Lo stack pieno.\n";
return:
}
stck[tos) = ob:
tos++:
Qui tipo un segnaposto a cui dovr essere sostituito il nome del tipo al mo-
mento de~l'istanziazione della classe. Se necessario, si pu definire pi di un tipo
di dati generico utilizzando un elenco separato da virgole. 11 Pop di un oggetto dallo stack.
Dopo aver creato una classe generica, sar possibile creare una specifica istanza template <class StackType> StackType stack<StackType>: :pop()
di tale classe utilizzando la forma generale: {
if(tos==O) {
cout "Stack vuoto. \n";
nome-classe<tipo> oggetto;
return O;

Qui tipo il nome del tipo dei dati su cui la classe si trover ad operare. Le tos--;
funzioni membro di una classe generica sono anch'ese (automaticamente) gene- return stck[tos);
riche. Non necessario usare template per specificarle esplicitamente c;me tali.
Nel programma seguente, la classe stack (introdotta nel Capitolo 1 1) viene
rielaborata e trasformata in una classe generica. In questo modo potr essere int main()
riutilizzata pefinemonzzare oggetti di qualsiasi "tipo:-In questo -esempio, verran- {
--J-1 -dimostra l 'us~-~J11~ stack di caratteri -~--=-- __ _
484 CAPITOLO 18 I TEMPL.A_TE 485

stack<char> sl, s2; 11 crea due stack di caratteri stack, cambia il tipo di dati che verr conservato nello stack. Ad esempio, si po-
int i; trebbe creare un altro stack che conservi i puntatori-a caratteri utilizzando la di-
chiarazione:
sl.push("a"};
s2.push{"x");
stack<char *> chrptrQ;
sl.push("b");
s2.push("y");
sl.push("c"); Ma anche possibile creare stack per contenere tipi di dati definiti dal pro-
s2.push("z"); grammatore. Ad esempio, per conservare indirizzi, si pu utilizzare la seguente
struttura:
for(i=O; i<3; i++) cout "Pop sl: " sI.pop() \n;
for(i=O; i<3; i++) cout "Pop s2: " s2.pop() \n; struct addr
char name [ 40) ;
Il dimostra 1 'uso degli stack di double char street[40];
stack<double> dsl, ds2; Il crea due stack di double char city[30];
char state[3];
dsl.push(I.l); char zip[12];
ds2.push(2.2);
dsl.push(3.3);
ds2.push(4.4}; Quindi, si potr utilizzare stack per generare uno stack che conterr oggetti di
dsl.push(S.5); tipo addr utilzzando una dichiarazione simile alla seguente:
ds2.push(6.6);
stack<addr> obj;
for(i=O; i<3; i++) cout "Pop dsl: " dsl.pop() \n;
for(i=O; i<3; i++) cout "Pop ds2: " ds2.pop() \n;
Come si pu dedurre dall'esempio sulla classe stack le funzioni e le classi
return O; generiche costituiscono strumenti importantissimi per sfruttare al massimo il pro-
prio lavoro di programmazione poich consentono di definire la forma generale
di un oggetto che potr essere utilizzata con qualsiasi tipo di dati. Si potr cos
Come si pu vedere, la dichiarazione di una classe generica simile a quella ______ _ evitare di creare implementazioni distinte per ogni tipo di dati su cui un algoritmo
di una fu~ione generica. Il tipo di dati generico viene utilizzato nella dichiara- - dovr operare. Le versioni specifiche della classe verranno create automatica-
zione della classe e delle sue funzioni membro. Il tipo di dati da impiegare effet- mente dal compilatore.
tivamente noto solo nel momento in cui viene dichiarato un oggetto dello stack.
Quando viene dichiarata una specifica istanza di stack il compilatore genera auto-
maticamente tutte le funzioni e le variabili necessarie per gestire i dati effettiva- Un esempio con due tipi di dati generici
mente impiegati. In questo esempio, vengono dichiarati due tipi di stack (due per
Una classe template pu utilizzare anche pi di un tipo di dati generico. A tale
i caratteri e due per i double ). Si faccia particolare attenzione alle seguenti dichia-
razioni: scopo baster dichiarare tutti i tipi di dati richiesti dalla classe in un elenco sepa-
rato da virgole all'interno delle specifiche di template. Il seguente breve esempio
crea una classe che utilizza due tipi di d~ti generici.
stack<char> sl, s2; 11 crea due stack di caratteri
stack<double> dsl, ds2; Il crea due stack di-double
I* Questo esempio utilizza due tipi di dati generici
nella definizione di una classe. *I
- - - Si notiihnodo in ui il tipo di dati viene passato fra parentesi angolari. Cam-
- ______ l?.iando11-tipo di dati specificato nel moihffto-1n-cui-vengonQ_cre..ati_ glj_9ggetti --- --#-i nel ude <iostream>
..---=.__-=::.-- - --- - -- - - - -
---------- --
TEMPLATE 487
486 CAPITOLO 18

using namespace std; Combinando l'ov~rlo~ding degli operatori e le classi template, . possibile
creare un tipo di array sicuro e generico che pu essere utilizzato per cr~are .arz:~y
template <class Typel, class Type2> class myclass sicuri di qualsiasi tipo di dati. Il seguente programma illustra un esempio d1 c10:
{
Typel i; Il Esempio di array generico sicuro.
Type2 j;
public: #include <iostream>
myclass(Typel a, Type2 b) { i = a; j = b; } #i nel ude <cstdl i b>
void show() { cout i ' ' << j 1 \n'; using namespace std;
};
const int SIZE ~ 10;
int main()
{ template <class AType> class atype
myclass<int, double> obl(lO, 0.23); AType a[SIZE];
mycl ass<char, char *> ob2 ('X' , "Questa una prova"); publ i e:
atype() {
obl.show(); Il int e double regi ster i nt i ;
ob2.show(); Il char e char * for(i=O; i<SIZE; i++) a[i] i;
}
return O; AType &operator[](i nt i);
};

Questo programma produce il seguente output: Il Verifica dei limiti per atype.
template <class AType> AType &atype<AType>: :operator[] (int i)
10 0.23 {
X Questa una prova if(i<O 11 i> SIZE-1) {
cout "\nL' indi ce ";
cout i " fuori dai limiti dell 'array. \n";
Il programma dichiara due tipi di oggetti: ob1 che utilizza dati interi e double
exit(l);
e ob2 che utilizza caratteri e puntatori a caratteri. In entrambi i casi, il compilatore
generer automaticamente i dati e le funzioni necessari per gestire gli oggetti
----+------
return a[i];
creati. _

int main()
Creazione di una classe template: {
una classe generica per array atype<int> intob; 11 array di interi
atype<double> doubleob; Il array di double
Per illustrare i vantaggi pratici delle classi template si parler del loro utilizzo
pratico. Come si detto nel Capitolo 15 possibile eseguire l'overloading anche int i;
dell'operatore [J. In tal modo possibile creare proprie implementazioni degli
array "sicuri" ch contengono la venfica del superamento dei limiti. Come si cout "Array di interi: ";
detto, in C++ possibile superare i limiti superiore o inferiore-di- un -array nel for(i=O; i<SIZE; i++) intob[i] i;
momento dell'esecuzione senza che venga generato alcun messaggio di errore. Se for(i=O; i<SIZE; i++) cout intob'[i] u ";

per si crea una classe chl\\-contienel'array e consente di accedere a tale array solo cout << '\n' ;
attraverso I' operatore.::f]-modificato dall' overloading, sar~p0sSil5ile intercettare_ -- --:- ----w- ---=...=_-::.....:..___ - __ .
- --ognLrifenmeniOerrato. -- -- - - - - --- -
-.uis---c-A Pi T O LO 1 8 --T~:PLATE 489

cout "Array di doubl e: "; Il Verifiche dei limiti per atype.


for(i=O; i<SIZE; i++) doubleob[i] = (double) il3; template <class A!ype, int size>
for(i=O; i<SIZE; i++) cout doubleob[i] " "; AType &atype<AType, size>:: operator O(int i)
cout '\n'; _{
if(i<O 11 i> size-1) {
intob[12] = 100; Il genera un errore run-time cout "\nll valore dell'indice di ";
cout i " fuori dai 1imi ti. \n";
return O;
exit(l);

return a[i];
Questo programma implementa un tipo di array generico sicuro e ne dimostra
l'uso creando un array di interi e un array di double (ma si possono creare anche
altri tipi di array). Come si pu vedere dall'esempio, una parte delle potenzialit int main()
delle classi generiche dovuta al fatto che esse consentono di scrivere il codice {
una sola volta, di correggerlo e quindi di applicarlo a qualsiasi tipo di dati senza atype<int, 10> intob; 11 array di int di dimensioni 10
dover continuamente intervenire per ogni diversa applicazione. atype<doubl e, 15> doubl eob; 11 array di doubl e di dimensioni 15

int i;
Uso di argomenti non-tipi nelle classi generiche
cout ."Array di interi: ";
for(i=O; i<lO; i++) intob[i] i;
Nelle specifiche del template per una classe generica, anche possibile indicare
for(i=O: i<lO: i++) co.ut intob[i] " ";
argo~enti n??-tipi.. Questo significa che nelle specifiche di un tempia te si pu cout << '\n' ;
specificare CIO che SI pensa sar un argomento standard, ad esempio un intero o un
puntatore. La forma sintattica per ottenere ci essenzialmente la stessa dei nor- cout "Array di double: ";
mali P'.11"ametri di funzioni: basta includere il tipo e il nome dell'argomento. Ad for(i=O; i<l5; i++) doubleob[i] = (double) i/3;
esemp10, ecco un modo migliore per implementare la classe per array sicuri pre- for(i=O; i<lS; i++) cout doubleob[i] " ";
sentata nella sezione precedente. cout '\n' ;

11 Dimostra l'uso di argomenti non-tipi. intob[12] = 100; 11 genera un errore runtime


#i nel ude <iostream>
#include <cstdlib> return O;
using namspace std;

Il Qui, int size un argomento non-tipo. Si osservi attentamente il template di atype. Si noter che size dichiarato
temp 1ate <e 1ass AType, i nt si ze> cl ass atype { come int. Questo parametro viene poi utilizzato .in atype per dichiarare le dimensioni
AType a[size]; Il in size viene passata la lunghezza dell'array dell'array a. Anche se size rappresentato come "variabile" nel codice sorgente,
publ ic:
atype() { il suo valore noto al momento della compilazione. Questo consente di utilizzar-
register int i; lo per definire le dimensioni dell'array. size viene utilizzato anche nella verifica
for(i=O; i<size; i++) a(i] ". i; dei limiti all'interno della funzione operator[](). In main() si noti come vengono
creati gli array di interi e di numeri in v~rgola mobile. Il secondo parametro speci-
AType &operator[] (int i); fica le dimensioni di ciascun array. I parametri non-tipi possono essere interi,
}; puntatori o indirizzi. Non possibile utilizzare altri tipi, ad esempio val~ri ~l?at.
Gli argomenti che si passano a un parametro non-tipo devono essere costttmtl da.
unacostante intera o un puntatore o llclinzzo ~i lm~~~ifilzbne o un oggetto globa- -- - ---
--. - --
--------- -
-
--
__ -
..
." .,
------
491
490 - -e A P I TO L O 18

le. Pertanto i parametri non-tipi dovrebbero essere pensati come costanti in quan- atype() {
regi ster i nt i;
to il loro valore non pu essere cambiato. Ad esempio, all'interno-di operator[](),
for(i=O; i<size; i++) a[i] = i;
non possibile utilizzare la seguente istruzion~.
}
AType &operator[]{int i);
size = 10; Il Error
};

Poich i parametri non-tipi sono trattati come costanti, possono essere utiliz- Il Verifiche dei limiti per atype.
zati per impostare le dimensioni di un array, il che offre un notevole vantaggio template <class AType, int size>
pratico. AType &atype<AType, size>: :operator[] (int i)
Come illustrato dall'esempio dell'array sicuro, l'uso parametri non-tipi estende {
notevolmente l'utilit delle classi template. Anche se le informazioni contenute if(i<O 11 i> size-1)
cout "\nil valore dell'indice di ";
nell'argomento non-tipo devono essere note al mpmento della compilazione, que-
cout i " fuori dai 1imiti. \n":
sta restrizione accettabile in considerazione delle potenzialit offerte dai para-
exit(l);
metri non-tipi.
l
return a[i];
Uso di argomenti standard nelle classi template
int main()
Una classe template pu avere un argomento standard associato a un tipo generi-
{
co. Ad esempio: atype<int, 100> intarray; Il array di int, size = 100
atype<doub 1e> doub 1ea rray: Il array di double, valore standard di size
template <class X=int> class myclass { 11 ... atype<> defarray; Il usa lo standard int; size = 10

Qui il tipo int verr utilizzato solo nel caso in cui non venga specificato un int i:
altro tipo quando viene istanziato un oggetto di tipo myclass.
Gli argomenti non-tipi possono anche prendere valori standard. Il valore cout "Array di int: ":
standard viene utilizzato quando non viene specificato alcun valore esplicito al for{i=O; i<lOO; i++) intarray(i] i:
momento dell'istanziazione della classe. Gli argomenti standard per parametri for{i=O; i<lOO; i++) cout intarray[i] << Il ";

-- -- -non~tipi vengono specificati utilizzando la stessa sintassi degli argomenti standard cout '\n' :
dei param~tri delle funzioni.
cout "Array di double: ":
Ecco un'altra versione della classe per array sicuri che utilizza argomenti for(i=O; i<lO; i++) doublearray[i] = (double) il3:
standard sia per il tipo dei dati che per le dimensioni dell'array. for(i=O; i<lO; i++) cout << doublearray[i] <<
11
";

cout << '\n';


11 Dimostra l'uso di argomenti standard.
#include <iostream> cout "Array standard: ":
#include <cstdl ib> for(i=O: i<lO; i++) defarray[i] i:
using namespace std: for(i=O: i<lO; i++) cout defarray[i] <<Il ";
cout << '\n' :
Il Qui AType nonnalmente int e size nonnalmente 10.
template <class AType=int, int size=lO> class atype { return O;
AType a[sizeJ; Il in size vengono passate le dimensioni dell 'array
public: ------
492 CAPITOLO 1 8 -- I TEMPLATE 493

Si faccia particolare attenzione alla seguente riga: cout "Nella specializzazione myclass<int> \n";
x :; a * a;
template <class AType=int, int size=lO> class atype {
j i nt getx () { return x;

l
};
Qui l'impostazione standard di AType il tipo int e size ha il valore standard
1O. Come illustrato dal programma, gli oggetti atype possono essere creati in tre
int main()
modi: {
11 specificando esplicitamente il tipo e le dimensioni dell'array; myclass<double> d(lO.l);
11
11 specificando esplicitamente il tipo ma utilizzando le dimensioni standard pari cout "doubl e: 11 d.getx() \n\n";
a 10;
myclass<int> i (5);
11 . lasciando il tipo standard int e utilizzando le dimensioni standard 10.
cout << "int: 11 i .getx() << "\n";
L'uso degli argomenti standard (specialmente nel caso del tipo) aumenta la
versatilit delle classi template. possibile fornire un valore standard corrispon- return O;
dente al tipo pi utilizzato ma consentendo nel contempo all'utente delle classi di
specializzarle a piacere.
Questo programma visualizza il seguente output:
~pecializzazioni esplicite di una classe In myclass generica
doubl e: 10.1
Come nel caso delle funzioni template, possibile creare una specializzazione
esplicita di una classe generica. A tale scopo si deve utilizzare il costruttore Nella specializzazione myclass<int>
template<> che funziona esattamente come per le specializzazioni esplicite delle int: 25
funzioni. Ad esempio:
In questo programma si deve fare particolare attenzione alla seguente riga:
Il Dimostra la specializzazione delle classi.
#include <iostream> template <> class myclass<int> {
using namespace std;
Questa riga dice-al compilatore che si sta per creare una specializzazione inte-
template <class T> class myclass
T x;- ra esplicita della classe myclass. Questa stessa forma sintattica generale viene
public: utilizzata per ogni tipo di specializzazione di classi.
mycl ass (T a) La specializzazione esplicita di classi estende l'utilit delle classi generiche
cout "In myclass generica\n"; in quanto consente di gestire con facilit uno. o due casi specializzati lasciando
X = a; che tutti gli altri casi vengano gestiti automaticamente dal compilatore. Natural-
mente se si devono creare troppe specializzazioni probabilmente meglio evitare
T getx() { return x; } di utilizzare una classe template.
};

Il Specializzazione per int.


templ ate <> cl ass mycl ass<i nt> { 18.4 Le parole riservate typename ed export
int x;
pu.blic.:._ -- Recentemente al linguaggio C++ sono state aggiunte due parole riservate dedica-----
-~y~~ as s~t a) te_i~~odo specifico ai template~-typename ed export.
494 C A P I T O L()18- --- - . I TEMPLATE 495

Entrambe giocano ruoli specializzati nella programmazione C++. classe ternplate, si sar creato un solido componente software utilizzabile con
La parola riservata typename ha due utilizzi. Innanzitutto, come si detto in sicurezza in varie situazioni. Dunque non sar pi necessario creare
precedenza, pu sostituire la parola riservata class nella dichiarazione di un implementazioni distinte per ciascun tipo di dati cui dovr essere applicata la
template. Ad esempio, la funzione template swapargs() potrebbe essere specifica- classe. Anche se Tu effetti la sintassi template pu inizialmente intimidire, vale la
ta nel seguente modo: pena di approfondire l'argomento in modo da sfruttare al meglio le sue potenzialit.
Le funzioni e le classi template stanno diventando sempre pi comuni in pro-
template <typename X> void swapargs(X &a, X &b) grammazione e questa tendenza sembra affermarsi con il tempo. Ad esempio, la
{ libreria STL (Standard Template Library) definita dal C++ , come si pu dedurre
X temp; dal nome, costruita sui template. Un ultimo commento: anche se i template ag-
giungono un livello di astrazione al programma, producono comunque codice
temp = a;
oggetto ad alte prestazioni.
a = b;
b = temp;

Qui typename specifica il tipo generico X. Non vi differenza fra usare class
e typename in questo contesto.
Il secondo uso di typename consente di informare il compilatore che un nome
utilizzato nella dichiarazione di un template fa riferimento a un tipo e non al nome
di un oggetto. Ad esempio:

typename X: :Name someObject;

assicura che X::Name venga trattato come il nome di un tipo.


La parola riservata export pu precedere una dichiarazione template. Essa
consente ad altri file di utilizzare un template specificando solo la sua dichiara-
zione senza duplicare l'intera definizione.

18.5 la potenza dei template

I !emplate aiutano a raggiungere uno degli obiettivi pi difficili della programma-


zione: creare codice riutilizzabile. Grazie all'uso di classi template possibile
creare strutture di applicazioni che possono essere utilizzate pi volte e applicate
a varie situazioni di programmazione. Ad esempio si consideri la classe stack. La
prima versione presentata nel Capitolo 11 poteva essere utilizzata solo per rnerno-
riz~ar~. va!ori ~nteri. Anche se l'algoritmo poteva essere utilizzato per ogni tipo di
dati, I md1caz1one esplicita del tipo dei dati della classe stack limitava aravernen-
t~ la sua applicazione. Se si trasforma_:?tack in una classe generica, div:nta possi-
bile creare uno stack per qualsiasi tipo di dati.
____ . ..I.; funzi.oni e le classi generiche costituiscono strumenti molto potenti per
amplificare i propri sforzi .di programmazione. D<.>:e?. ~~er scritto e corretto u~-= ___ .
-
--.-:::-----

- - -=------ -

Capitolo 19

' Gestione delie eccezioni

19.1 Principi di gestione delle eccezioni


19.2 Gestione delle eccezioni per classi
derivate
19.3 Opzioni della gestione delle eccezioni
19.4 Le funzioni terminate() e unexpected()
19.5 La funzione uncaught_exception()
19.6 Le classi exception e bad_exception
19.7 Applicazioni della gestione
delle eccezioni

<....'uesto capitolo si occupa del sottosistema di gestione


delle eccezioni. Con gestione delle eccezioni si intende la gestione degli errori
che si verificano al momento dell'esecuzione, per evitare che provochino il bloc-
co del programma. Utilizzando la gestione delle eccezioni del C++, nel momento
in cui si verifica un errore il programma pu richiamare automaticamente una
routine per la gestione dell'errore. Il principale vantaggio derivante dalla gestione
delle eccezioni il fatto che automatizza una gran parte del codice che preceden-
temente doveva essere codificato "a mano'.' in qualsiasi programma di dimensioni
non banali.

19.1 Principi di gestione delle eccezioni


Tutto il sistema di gestione delle eccezioni del C++ racchiuso in tre parole chia-
ve: try, catch e throw. In termini generali, le istruzioni del programma che si desi-
dera controllare devono essere contenute in un blocco try. Se all'interno di un
blocco try si verifica un'eccezione (ovvero un errore), questa verr "lanciata" tra-
mite throw. L'eccezione viene poi "raccolta" tramite catch ed elaborata. La se-
guente discussione descrive pi dettagliatamente questa procedura.
- Il codice che si ritiene dj dover monitorare deve essere eseguito all'interno di
un blocco try (ma anche le funzioni richiamate dall'interno di try possono lanciare
un'eccezione). Le eccezioni che possono essere lanciate dal codice monitorato
vengono raccolte dall'istruzione catch che segue immediatamente l'istruzione try
che ha lanciato l'eccezione. Di seguito sono-illustrate le forme genernJi diJry e ____ _
-'-catch:
CAPI TO co----r-s--

try { provoca la chiamata della funzione standard terminate(). Normalmente, termina-


Il blocco try te() richiama a sua volta abort() che termina il programma ma, come si vedr in
} questo capitolo, possibUe specificare altri tipi di terminatori. . .
catch (tipo] arg) { Ecco un esempio molto semplice che illustra il funzionamento del sistema d1
Il blocco catch gestione delle eccezioni del C++:
}
catch (tipo2 arg) { Il Un semplice esempio di gestione delle eccezioni.
Il blocco catch
} #include <iostream>
catch (tipo3 arg) { using namespace std;
Il blocco catch
int main()
}
{
cout << "Inizio\n";

try { Il inizio del blocco try


catch (tipoN arg) { cout "Siamo all'interno del blocco try\n";
Il blocco catch throw 100; Il "lancia" un errore
} cout "Questa istruzione non viene eseguita";

Il blocco try pu avere dimensioni estremamente variabili da poche righe di catch (int i). { /I raccoglie l'errore
cout " stata raccolta un'eccezione -- i1 suo valore : ";
una funzione fino all'intera funzione main() (per tenere sotto controllo l'intero
programma). cout << i << "\n";
Quando viene lanciata un'eccezione, questa viene raccolta dalla corrispon-
dente istruzione catch che gestisce l'eccezione. anche possibile associare pi cout << "Fine";
istruzioni catch a un determinato try. L'istruzione catch utilizzata verr determi-
nata dal tipo dell'eccezione. Ovvero se il tipo di dati specificato da un'istruzione return O;
catch corrisponde con quello dell'eccezione, verr eseguita tale istruzione catch
(e tutte le altre .verranno ignorate). Quando viene raccolta un'eccezione, arg rice-
ver il proprio valore. possibile raccogliere qualsiasi tipo di dati, incluse le Questo programma visualizza il seguente output:
classi create dal programmatore. Se non viene lanciata alcuna eccezione (ovvero
all'interno del blocco try non si verifica alcun errore) non verr eseguita alcuna Inizio
istruzione catch. Si amo al 1 'interno del blocco try
La forma generale dell'istruzione throw : stata raccolta un'eccezione -- il suo valore : 100
Fine
throw eccezione;
Si osservi attentamente il programma precedente. Come si pu vedere, vi un
throw genera l'eccezione specificata da eccezione. Se l'eccezione deve essere blocco try contenente tre istruzioni e un'istruzione catch(int i) che elabora un'ec-
raccolta, throw deve essere eseguita dall'interno deHu stesso blocco try o da qual- cezione intera. All'interno del blocco try vengono eseguite solo due delle tre istru-
_ _siasi altra funzione richiamata (direttamente o indirettamente) dall'interno del zioni: la prima istruzfon-cout e throw. Quando viene lanciata un'eccezione, il
blocco try. Il valore di eccezione il valore "lanciato". controllo passa all'espressione catch e il blocco try ha termine. Quind!, la ~~~c_h
Se si lancia un'eccezione per la quale non vi alcuna istruzione catch verra non viene richiamata, piuttosto l'esecuzione del programma viene traster1ta ad
-- - ----prov~c~a ~a fin~normale del programma:J1 l!1E_:_i?_~i un'eccezione non gestita essa (lo stack del program:ma vfone.automaticamente aggiornato per rendere pos-
500 CAPITOLO 19 G-&&+-+.O-NE DELLE E e C_E zio N r 501

sibile l'operazione). Pertanto, l'istruzione cout_che segue la throw non verr mai L'eccezione pu essere lanciata anche dall'esterno del blocco try ma in una
eseguita. funzione richiamata da un'istruzione contenuta all'interno del blocco try. Ad esem-
Normalmente, il codice.presente all'interno di un'istruzione catch cerca di pio, questo un programma perfettamente corretto:
risolvere un errore eseguendo un'azione appropriata. Se l'errore pu essere cor-
retto, l'esecuzione continuer con l'istruzione che segue la catch. Spesso per un j* Il lancio dell'eccezione avviene da una funzione
errore non pu essere risolto e il blocco catch conclude il programma con una che si trova all'esterno del blocco try. *I
chiamata a exit() o abort().
Come si detto, il tipo dell'eccezione deve corrispondere con il tipo specifi- #include <i ostream>
cato in un'istruzione catch. Ad esempio, nell'esempio precedente, se si cambia il using namespace std;
tipo specificato nell'istruzione catch sostituendogli double, l'eccezione non verr
void Xtest(int test)
raccolta e il programma terminer in modo anormale. Di seguito viene illustrata
{
questa modifica:
cout "All'interno di Xtest, test uguale a 11
test 11 \n";
i f(test) throw test;
Il Questo esempio non funziona.

-ftincl ude <iostream> int main()


using namespace std; {
cout "I.ni zi o\n";
int main()
{
try { Il iniiio del blocco try
cout "Inizio\n"; cout "Siamo all'interno del blocco try\n";
Xtest(O);
try { Il inizio del blocco try Xtest(l);
cout << "Siamo all'interno del blocco try\n"; Xtest(2);
throw 100; Il "lancia" un errore }
cout "Questa istruzione non viene eseguita"; catch (int i) { Il raccoglie 1 'errore
cout " stata raccolta un'eccezione -- il suo valore : ";
catch (double i) { Il Non funziona per eccezioni int cout << i << "\n";
cout " stata raccolta un'eccezione - il suo valore : ";
cout << i << 11 \n";
cout <<"Fine";

cout << "Fine"; return O;

return O;
Questo programma produce il seguente output:
Questo programma produce il seguente output poich l'eccezione intera non
Inizi o
verr raccolta dall'istruzione catch(double i). Siamo all'interno del blocco try
All'interno di Xtest, test uguale a O
Inizio All'interno di Xtest, .test uguale a
Siamo all'interno del' blocco try stata raccolta un'eccezione -- il suo valore :
Fine anorma 1e de 1 programma Fine
502 - CAPITOLO 19
GESTIONE DELLE ECCEZIONI

Un bl?cco try pu~ essere inserito anche all'interno di una funzione. In questo Quando la funzione viene nuovamente richiamata, il sistema di gestione delle
c~so, og~1 vol~a che Il programma accede alla funzione, verr reinizializzato il eccezioni viene reinizializzato.
s~s~ema d1 gestione delle eccezioni rispett0--a tale funzione. Ad esempio, si esami-
ni 11 seguente programma:
importante comprendere che il codice associato a un'istruzione catch verr
eseguito solo se raccoglie un'eccezione. In caso contrario, l'eccezione salter sem-
#include <i ostream> plicemente l'istruzione catch (ovvero l'esecuzione non entrer nell'istruzione
using namespace std; catch). Ad esempio, nel programma seguente, non verr lanciata alcuna eccezio-
ne e quindi non verr eseguita alcuna istruzione catch.
/* le istruzioni try/catch possono anche trovarsi
all'interno di una funzione diversa da main(). */ #i ne 1ude <i os t ream>
using namespace std;
void Xhandler(int test)
{ int main()
try{ {
if(test) throw test; cout << "Inizio\n";

catch(int i) { try { // inizio del blocco try


cout << "Raccolta 1 'eccezionE: n. " << << '\n'; cout "Siamo all'interno del blocco try\n";
cout _"Siamo ancora all'interno del blocco try\n";

catch (int i) { // raccoglie 1 'errore


int main{) cout " stata raccolta un'eccezione -- il suo valore : ";
{ cout << i << "\n";
cout << "Inizio\n";

Xhandler(l); cout << "Fine";


Xhandl er{2);
Xhandler(O); return O;
Xhandl er(3);

cout << "Fine"; Questo programma produce il seguente output:


return O;
Inizio
Siamo all'interno del blocco try
Siamo ancora all'interno del blocco try
Questo programma visualizza il seguente output: Fine

Inizio Come si pu vedere, l'istruzione catch viene saltata dal flusso dell'esecuzione.
Raccolta 1 'eccezione n.
Raccolta l'cczione n. 2
Racco 1ta l 'eccezione n. Eccezioni per classi
Fine
Un'eccezione pu essere di qualsiasi tipo, incluse le classi create per il program-
_ Come ~i 2l.!9 v_edere, vengono lanciate tre ecZe~ioni. Al termi~~ di O"ni ecce- a:-Irr realt, nei programmi la maggior parte delle eccezioni riguarda le classi e
z10ne, avviene l'uscita dalla funzione. __ ~ - - - ~ ---~ ::__:::::-=---=-=-:::: _ --- non i tipi standard del linguagglo.ilrnotvo ~~~m~ne:_p~dl quale si definisce --------
504 CAPITOLO 19 -- GESTIONE DELLE ECCEZIONI 505

una classe per eccezioni consiste nel creare un oggetto che descriver l'errore Il programma chiede all'utente un numero positivo. Se viene specificato un
verificatosi. Questa informazione pu essere utilizzata dal gestore dell'eccezione numero negativo, il programma crea un oggetto della classe MyException che
l - per elaborare l'errore. Questa possibilit illustrata dal seguente programma. descrive l'errore. Pertanto MyException incapsula le informazioni riguardanti rer-
rore. Queste informazioni vengono poi utilizzate dal gestore dell'eccezione. In
11 Raccolta di eccezioni per cl assi. generale opportuno creare classi per eccezioni che incapsulino le informazioni
llinclude <iostream> relative a un errore per consentire al gestore delle eccezioni di rispondere in modo
llinclude <cstring> appropriato.
using namespace std;

cl ass MyException { Uso di pi istruzioni catch


public:
char str_what[SO]; Come si detto, possibile associare a una try pi di una catch. Nella pratica ci
int what; avviene molto frequentemente. Tuttavia, ogni catch deve raccogliere un tipo di-
verso di eccezione. Ad esempio, questo programma raccoglie interi e stringhe.
MyException() { *str_what =O; what = O; }

MyException(char *s, int e) lii nel ude <i ostream>


strcpy(str_what, s); using namespace std;
what = e;
11 Possono essere raccolti vari tipi di eccezioni.
};
void Xhandler(int test)
int main() {
{ try{
int i; if(test) throw test;
else throw "Il valore uguale a zero";
try { }
cout "Immettere un numero positivo: "; catch(int i) {
cin i; cout << "Raccolta l'eccezione n. " << i << '\n';
if(i<O)
throw MyException("Il numero negativo", i); catch(char~.rLL.
cout "Raccolta una stringa: ";
catch (MyExcepti on e) { 11 Raccogli e l 'errore cout<<str<< '\n';
cout << e.str_what << ": ";
cout << e.what << "\n";

int main()
return O; {
cout "Inizio\n";

-,- Xhandl er(l);


Ecco una prova di esecuzi~ie:
Xhandl er{2);
Xhandl er{O);
Irrmettere un numero positivo: -4 Xhandl er{3);
I i numero T negativo: -4 - - - .

------ - - - - ---
506 CAPITOLO 19 GESTIONE DELLE ECCEZIONI 507

cout << "Fine";


D derived;
return O;
try {
throw deri ved;
Questo programma produce il seguente output: }
catch(B b) {
Inizio cout "Raccolta la classe base.\n";
Raccolta 1 'eccezione n. 1 }
Raccolta ] 'eccezione n. 2 catch(D d) {
Raccolta una stringa: Il valore uguale a zero cout "Non viene eseguita. \n";
Raccolta l'eccezione n. 3
Fine
return O;
Come si pu vedere, ogni istruzione catch risponde solo a eccezioni del pro-
prio tipo.
In generale, le espressioni catch vengono controllate nell'ordine in cui appa- Qui, poich derived un oggetto che deriva da B, verr raccolt~ dalla prim~
iono nel programma. Verr eseguita solo l'istruzione corrispondente e tutti gli clausola catch e la seconda clausola non verr mai eseguita. Alcuni compilaton
altri blocchi catch verranno ignorati. rilevano questa condizione con un messaggio warning. Altri producono ~n m:s-
saggio d'err>re. In entrambi i casi, per correggere la situazione basta mvertrre
l'ordine delle clausole catch.

19.2 Gestione delle eccezioni per classi derivate

Occorre fare attenzione all'ordine delle istruzioni catch quando si cerca di racco- 19.3 Opzioni della gestione delle eccezioni
gliere dei tipi di eccezioni che riguardano classi base e classi derivate in quanto
una clausola catch per una classe base risponde anche ad ogni classe derivata da II sistema di gestione delle eccezioni del C++ prevede numerose funzionalit e
tale base. Pertanto se si vogliono raccogliere eccezioni sia della classe base che sfumature che ne semplificano e agevolano l'uso.
della classe derivata si deve porre la classe derivata per prima nella sequenza
catch. In caso contrario, il catch della classe base raccoglier anche-eczioni
Raccolta di tutte le eccezioni
per le classi-derivate. Ad esempio, si consideri il seguente programma.
In alcune circostanze si desidera che un gestore di eccezioni raccolga tutte le
// Raccolta di eccezioni per cl assi derivate. eccezioni e non solo quelle di un determinato tipo. A tale scopo basta utilizzare la
#i nel ude <i os tream>
seguente forma di catch:
usi ng namespace std;

cl ass B { catch(... ) {
}; Il elabora tutte le eccezioni
}
class D: public B {
}; Qui, i tre puntini di sospensione chiedono di accettare qualsiasi tipo di dati.
Il seguente programma illustra l'uso di catch( ... ).
_ _ _ _fat ma in()

- - - - -- - --- -
508 ::: A P++-0-1:-0-1-9 --------=-- -
G Es r 1o NE - o EC1.-i:-ec-ee-z 1-e N 1 509
Il Questo esempio raccoglie tutte le eccezioni.
Il Questo esempio impiega catch( ) come default.
#inclu:ie <iostream>
using r.amespace std; #include <iostream>
usi ng namespace std;
void Xhandler(int test)
{ voi d Xhandl er{ i nt test)
try{ {
if(test==O) throw test; Il lancia un int try{
if(test==l) throw 'a'; Il lancia un char if{test==O) throw test; Il lancia un int
if(test==2) throw 123.23; Il lancia un double if{test==l) throw 'a'; /I lancia un char
if{test==2) throw 123.23; Il lancia un double
catch( .. ) { Il raccoglie tutte le eccezioni }
ccut "Raccolta un'eccezione!\n"; catch{int i) { Il raccoglie le eccezioni int
cout << "Raccolta un'eccezione int\n";
}
catch {.. ) { 11 raccogli e tutte 1e altre eccezioni
int mafo() cout << "Raccolta un'eccezione!\n";
{
cout << "Inizio\n";

Xhandler(O); int main{)


Xhan:l er(l); {
Xhan:l er(2); cout "Inizi o\n";

cout << 11
Fi ne 11 ; Xhandl er{O);
Xhandl er{l):
retu'"'.'1 O; Xhandl er{2);

cout << "Fine";


Questo programma visuali;j;za-il-seguente output:
return O;
Inizio
Raccolta un'eccezione!
Raccolta un'eccezione! Questo programma visualizza il seguente output:
Raccolta un 'eccezione!
Fine Inizio
Raccolta un'eccezione int
Raccolta un'eccezione!
Come si pu vedere, le tre eccezioni lanciate tramite throw venaono raccolte
da un'unica istruzione catch. "' Raccolta un'eccezione!
Fine
_In~ltre, catcfl( ...) consente di raccogliere sol~d~terminati sottoinsiemi di-ec-
--~eziom. I~ questo modo, la.forma catch( ... ) si dimostra utilissima per fungere da
Come si pu vedere in questo esempio, la forma catch( ... ) otti~J~~~cco
eestore d1 raccolta. gener~I~zzata. Ad esempio, questa versione del programma
aliere tutte le eccezioni che non si desidera gestire esplicitamente. Inoltre, m que-
prece~eme raccoglie esplicitamente le eccezioni intere mentre lascia ogni altra -1-~-- -- ~to modo si raccoglieranno__tu_tteJe ec_eziqp.i prodotte evitando quindi la possibi-
- ----_ -eccez~atc~{::.J ________ ---
-_ lit che si verifichi un~J:liusura anormale del programma. - - - - - -
- - - - --==--....::.-: _ _ - - --
-G-E-S-f+G-NE DELLE EC.CEZIO-NI 511
Restrizione delle eccezioni
catch(int i) {
cout "Raccolta un'eccezione i nt\n";
anche possibile restringere il tipo di ecce;ioni che la funzione pu lanciare. }
Addirittura si pu impedire che una funzione possa lanciare qualsiasi eccezione. catch ( char c) {
Per attivare queste restrizioni, si deve aggiungere alla definizione della funzione cout "Raccolta un eccezione char\n";
una clausola throw: }
catch(double d) {
tipo-restituito nome-funzione(elenco-argomenti) throw(elenco-tipi) cout "Raccolta un eccezione doubl e\n";
{ .
Il ...
} cout << "Fine";

return O;
Qui, la funzione potr lanciare eccezioni relative ai soli tipi di dati contenuti
in elenco-tipi e separati da virgole. La generazione di altri tipi di espressione
provocher la fine anormale del programma. Se si desidera che una funzione non
In questo programma, la funzione Xhandler() pu lanciare ec.cezio.ni inter_e, ~i
possa essere in grado di lanciare eccezioni, si dovr utilizzare un elenco vuoto.
caratteri e double. Se la funzione tenter di lanciare un altro tipo d1 eccez1om,
- Ogni tentativo di lanciare un'eccezione non consentita a una funzione provo-
cher Ja chiamata della funzione unexpecterd() contenuta nella libreria standard. provocher la fine anormale del prog~ain:111~ (ov_vero ~err ?c?iama.ta ~a f~nzione
unexpected()). Per vedere un esempio di ci, s1 provi a ehmmare il tipo mt dal-
Normalmente tale funzione richiama a sua volta la funzione abort() che provoca
l'elenco e a rieseguire il programma.
la fine anormale del programma. Se si preferisce per possibile specificare altri
importante comprendere che le limitazioni per la funzio~e rig_uai:dano solo
gestori di terminazione, come descritto pi avanti in questo stesso capitolo.
i tipi delle eccezioni che vengono lanciate dal blocco try che 1_ha nch1amata: In
Il segu~nt: programma mostra il modo in cui possibile restringere i tipi
delle eccez1oru che possono essere lanciate da una funzione. altre parole, un blocco try che si trova all'interno di una funz10~~ pu lan.c1are
qualsiasi tipo di eccezione sempre che questa v:nga r~cco~ta ~l mte~o di .tale
funzione. La restrizione si applica solo quando 11 lancio di un eccez10ne viene
Il Riduzione dei tipi di eccezioni lanciabili da una funzione.
eseguito all'esterno di una funzione. La seguente modifica a Xhandler() evita che
#include <iostream> questa possa lanciare eccezioni.
using namespace std;
Il Questa funzione NON pu lanciare :ccezioni!
Il Questa funzione pu lanciare solo int, char e double.
void Xhandler(int test) throw(int, char, double) void Xhandler(int test) throw()
{ {
if(test==O) throw test; Il lancia un int I* Le seguenti istruzioni non funzionano e provocano
if(test==l) throw 'a'; Il lancia un char una fine anorma 1e de 1 programma. *I
i f ( test==2) throw 123. 23; 11 lanci a un doub le if(test==O) throw test;
if(test==l) throw 'a';
if(test==2) throw 123.23;
int main()
{
cout "Inizio\n";
;N!:>l~C.~:i~;~;?i~1 Al momento attuale, il compilatore Microsoft Vsual C++ non
try{ supporta la clausOlathrow() per le funzioni.
Xhandl er(O); /I cerca anche ~--passa.t:e....Le-2. .a Xhandl er()

- - - -------
----=---- -~-:

512 CAPITOLO 19
GESTIONE DELLE ECCEZIONI 513

Rilancio di un'eccezione
Questo programma visualizza il seguente output:
Se si desidera rilanciare un'eccezione dal!' interno del gestore d~ll' eccezione stess
baster richiamare throw senza specificare alcuna eccezione. In questo modo I' e~
Inizi o
Raccolta un eccezione char * in Xhandler
cezione corrente verr passata a una sequenza try/catch pi esterna. Questa tecni- Raccolta un eccezione char * in main
ca consente di accedere alle eccezioni da parte di pi gestori. Ad esempio un Fine
gestore potrebbe occuparsi si un aspetto di un'eccezione mentre un secondo ae-
store potrebbe concentrarsi solo su un altro. Un'eccezione pu essere rilanci;ta
solo dall'interno del blocco catch (o da una funzione richiamata all'interno del
blocco). Quando si rilancia un'eccezione, questa non verr nuovamente raccolta 19.4 Le funzioni terminate(} e unexpected()
dalla stessa istruzione catch ma piuttosto si propagher all'istruzione catch suc-
cessiva. Il seguente programma illustra il rilancio di un'eccezione di tipo char*. Come si detto in precedenza, quando qualcosa va storto durante il processo di
gestione delle eccezioni, vengono richiamate le funzioni terminate() e unexpected().
Il Esempio di "rilancio" di un'eccezione. Tali funzioni vengono fomite dalla libreria standard del C++. Questi sono i loro
prototipi:
#include <iostream>
using namespace std;
void terminate( );
void une~pected( );
voi d Xhandl er()
{
try { Queste funzioni richiedono l'impiego dell'header <exception>.
throw "sahe"; Il lancia un char * La funzione terminate() viene richiamata quando il sottosistema di gestione
delle eccezioni non riesce a trovare un'istruzione catch che corrisponda a un'ec-
catch(char ) { Il raccoglie un char * cezione. Viene richiamata anche se il programma tenta di rilanciare un'eccezione
cout "Raccolta un eccezione char * in Xhandler\n"; quando in precedenza non era stata lanciata alcuna eccezione. La funzione termi-
throw ; Il rilancia un char * fuori dalla funzione nate() viene richiamata anche in altre circostanze. Ad esempio, quando nel pro-
cesso di chiusura dello stack causato da un'eccezione, il distruttore di un oggetto
lancia un'eccezione. In generale, terminate() l'ultimo livello di gestione delle
eccezioni, quando non sono disponibili altri gestori. Normalmente terminate()
int main()
{
--nchiama abort().
cout "Inizio\n"; La funzione unexpected() viene richiamata quando una funzione tenta di lan-
ciare un'eccezione che non consentita dall'elenco throw. Normalmente
try{ unexpected() richiama terminate().
Xhandl er();

catch(char ") { Impostazione dei gestori di terminate() e unexpected()


}
cout "Ra:colta un eccezione char * in main\n"
. Le funzioni terminate() e unexpected() non fanno altro che richiamare altre fun-
zioni che gestiscono un errore. Come si appena detto, normalmente terminate()
cout << "Fine; richiama abort() e unexpected() richiama terminate(). Pertanto in genere entram-
be le funzioni bloccano l'esecuzione del programma ogni volta che si verifica un
return O;_ _ errore di gestione delle eccezioni. tuttavia possibile cambiare le funzioni richia-
mate da terminate() e unexpected() per dare al programma il .pk....mDtrollo del
sottosistema di gestione delle eccezioni.
__
----_:_:.::.:_:.~
515
514 CA P I T O LO 19

Per cambiare il gestore di terminate() si usa la funzione set_terminate():


Il impostazione del nuovo gestore per terminate()
set_tenni nate (my_Thandl er) ;

tenninate_handler set_terminate(terminate_handler newhandler) throw( ) ;


try {
cout "Ne1 b1occo try";
Qui newhandler un puntatore al nuovo gestore di terminate(). La funzione throw 100; 11 1ancia un errore
restimisce un puntatore al vecchio gestore di terminate(). Il nuovo gestore di ter- }
minate() deve essere di tipo terminate_handler, definito nel seguente modo: catch (doubl e i) { 11 non rileva un'eccezione i nt
11
typedef voi d (*terminate_handl er) ( ) ;

L'unica cosa che il gestore di terminate() deve fare fermare l'esecuzione del return O;
programma. Non deve tornare al programma o riprendere l'esecuzione in alcun
modo. Per cambiare il gestore di unexpected() si usa set_unexpected():
Elenco l'output prodotto dal programma.
unexpected_handl er set_unexpected(unexpected_handl er newhandler) throw( ) ;
Nel blocco try
Nel nuovo gestore di tenni nate()
Qui newhandler un puntatore al nuovo gestore di unexpected(). La funzione
fine anorm111 e de1 progranma
restituisce un puntatore al vecchio gestore di unexpected(). Il nuovo gestore di
unexpected() deve essere di tipo unexpected_handl!=!r, definito nel seguente modo:

typedef void (*unexpected_handler) ( ) ;


19.5 La funzione uncaught_exception{)
Anche questo gestore deve lanciare un'eccezione, fermare il programma o
richiamare terminate() e non deve tornare al programma. Il sistema di gestione delle eccezioni del C++ fornisce un'al~ra funzione che tal-
Sia set_terminate() che set_unexpected() richiedono l'uso dell'header volta molto utile: uncaught_exception(). Ecco il suo prototipo:
<exception>.
Ecco un semplice esempio che definisce uno specifico gestore di terminate(). bool uncaught_exception( );

11 Uso del gestore per terminate(). Questa funzione restituisce true quando un'eccezi~ne lanci.at~ non ancora
#i nel ude - <i ostream> stata raccolta. Dopo aver raccolto un'eccezione la funzione restituisce false.
#include <cstdl ib>
#include <exception>
using namespace std;
19.6 Le classi exception e bad_exception
void my_Thandler() {
cout "Nel nuovo gestore di terminate()\n"; Quando una funzione fornita dalla libreria standard C++ lancia un'eccezione:
abort(); uesto sar un oggetto derivato dalla classe base excepti?n. Il gestore d~
~nexpected() pu lanciare un oggett_oJ:lel.la classe bad_except1on. Queste classi
int main0--- - richiedono l'impiego dell'header <exception>.
{
516 CA-PITOLO 19 G E s T I o N E D E-ti.-E---E-e e E"Z I o N I 517- -

19.7 Applicazioni della gestione delle eccezioni numeri. Quindi, lerrore stato gestito in modo ordinato e l'utente potr continua~
re l'esecuzione del programma. Lo stesso concetto si applica anche a situaziom
La gestione delle eccezioni fornisce al programma un metodo strutturato per ge- pi complesse. . . . . .
stire eventi anormali. Questo significa che- il gestore dell'errore deve eseguire La gestione delle eccezioni particolarmente utile per uscrre da una sene d1
un'operazione che ponga rimedip all'errore. Ad esempio, si consideri il seguente routine molto nidificate in cui si verificato un grave errore. In questo senso, la
breve programma. Il programma legge due numeri e divide il primo per il secon- gestione delle eccezioni del C++ pu anche sostituire il rozzo approccio del C che
do utilizzando la gestione delle eccezioni per gestire errori per divisione per zero: si basa sulle funzioni setjmp() e longjmp().
Il motivo principale che consiglia l'uso dei gestori di eccezioni la possibilit
#include <iostream> di utilizzare un metodo ordinato per gestire gli errori. Questo significa anche che,
usi ng namespace std; in alcuni casi, sar possibile correggere la situazione.
void divide(double a, double b);

int main()
{
double i, j;

do {
cout "Inmettere il numeratore (O per uscire): ";
cin i;
cout "!omettere il denominatore: ";
cin j;
dlvide(i, j);
while(i != O);

return O;

void divide{double a, double b)


I
-- --- try I
if(!b)_throw b; //verifica della divisione per zero
cout "Risultato:" a/b endl;

catch (double b) {
cout "Non si pu dividere un numero per zero. \n";

Anche se_!_!_programma precedente un esempio molto semplice, illustra la


natura essenziale della gestione delle eccezioni. Poich la divisione per zero non
consentita, il programma non pu continuare nel caso in cui venga immesso uno
zero come secondo numero. In questo caso, l'eccezione semplicemente non ese-
gu~ la di~isione (che provocherebbe la conclusione anormatelielprogrammal e
notifig-=Lerrore.alrutente. Il programma quindi chiede-di immettere altri due ____ _
: Capitolo 20

Il sistema di I/O C++:


le basi

~ 20.1 Operazioni di I/O C++ vecchie e nuove


20.2 Gli stream del C++
20.3 Le c~assi per stream C++
20.4 Operazioni di I/O formattato
20.5 Overloadlng di << e >>
20.6 Creazione di funzioni di manipolazione

il C++ supporta due diversi sistemi di I/O: quello ere-


ditato dal Ce il proprio sistema di I/O a oggetti che in seguito verr semplicemen-
te chiamato sistema di I/OC++. Il sistema di I/OC stato gi descritto nella Parte
prima e quindi qui si parler unicamente del sistema di I/O C++. Anche il sistema
di I/O C++ (come quello C) perfettamente integrato nel linguaggio. Questo
significa che i vari aspetti del sistema di I/O C++, come le operazioni di I/O su
console e su disco, sono in realt aspetti diversi dello stesso meccanismo.
Questo capitolo discute le basi del sistema di I/O a oggetti del C++. Sebbene gli
esempi di questo capitolo utilizzino operazioni di I/O su console, le informazioni si
applicano anche ad altri dispositivi, inclusi i file (discussi nel Capitolo 21).
Poich il sistema di I/O C estremamente ricco, flessibile e potente, ci si
potrebbe chiedere perch il C++ debba definire un nuovo sistema. Questo dovu-
_to al fatto che il sistema di I/OC non prevede l'uso di oggetti. Pertanto, per fare in
modo che il e++ fornisse un completo supporto alla programmazione a oggetti,
era necessario creare un sistema di I/O che potesse operare sugli oggetti definiti
dall'utente. Oltre al supporto degli oggetti, vi sono altri vantaggi derivanti dal-
l'uso del sistema di I/OC++ anche in programmi che non fanno grande uso (o non
fanno affatto uso) di oggetti definiti dall'utente. In pratica, tutto il nuovo codice
C++ impiega le ~e funzionalit a oggetti mentre il sistema di I/O C stato
_mantenuto solo per motivi di compatibilit.
In questo capitolo viene descritta la fonnattazione dei dati. In particolare ven-
gono esposti i metodi di overloading degli operatori<< e del C++ per consentir-
n1'uso con tutte le classi. Inoltre sfVedr il modo in cui possibile creare partico-
lari funzioni di I/O, i manipolatori, in grado di rendere pi efficienti i programmi.

----- ____ ----


, -. .. -
520 CAPITOLO 20
IL SISTEMA DI 110 C++: LE BASI 521

20.1 Operazioni di I/O C++ vecchie e nuove


Le classi di I/O partono da un sistema di classi template. Come si detto nel
Attualmente sono in uso due versioni di librerie di I/O a oggetti: la pi vecchia si Capitolo 18, una classe template definisce la forma di una classe senza spe:ificare
basa sulle specifiche originali del C++ mentre la pi recente quella definita dal completamente i dati su cui essa si trover ad operare. Dopo aver.defimto u~a
classe template, possibile crearne istanze specifiche. Per quanto nguarda la h-
C++ standard. La vecchia libreria di I/O supportata dal file header <iostream.h>.
La nuova libreria di I/O supportata dall'header <iostream>. Nella maggior parte breria di I/O il C++ standard crea due specializzazioni delle classi template: una
dei casi, per il programmatore queste due librerie hanno lo stesso aspetto. Questo per caratteri' a 8 bit e una per caratteri estesi. Questo volum~ util~~zer solo le
dovuto al fatto che la nuova libreria di I/O in pratica una versione migliorata ed classi per caratteri a 8 bit in quanto sono di gran lunga quelle pi utthzzate, ma le
estesa della vecchia. Infatti la maggior parte delle differenze fra le due riguardano stesse tecniche si applicano a entrambe. . .
ci che si trova "sotto la superficie" ovvero il modo in cui le librerie sono imple- Il sistema di I/O del C++ si basa su due gerarchie di classi template differenti ma
mentate e non il loro uso. Dal punto di vista del programmatore, esistono princi- correlate. La prima deriva dalla classe di I/O a basso livello chiamata
palmente due differenze fra queste due librerie di I/O. Innanzitutto la nuova libre- basic_streambuf. Questa classe fornisce le operazioni di input e output. a basso
ria di I/O contiene alcune funzionalit e definisce alcuni nuovi tipi di dati. Pertan- livello e fornisce il supporto per l'intero sistema di I/O del C++.Se non s1 devono
to la nuova libreria di I/O in pratica un super-insieme della vecchia. Quasi tutti eseguire operazioni di I/O molto avanzate, raro che si debba usare direttamente
i programmi originariamente scritti per la vecchia libreria possono essere compi- basic_streambuf. La gerarchia di classi su cui ci si trover pi frequen:emente a
lati senza modifiche sostanziali con la nuova. In secondo luogo, la libreria di I/O operare derivata da basic_ios. Questa una cla_sse di. I/O ad_ alto hvell? che
vecchio stile opera nel namespace globale. La nuova libreria opera nel namespace fornisce operazioni di formattazione, verifica deg!t erron e gestione ~elle ~nf?r
std (il namespace utilizzato da tutte le librerie del C++ standard). Dato che la mazioni di stato rispetto alle operazioni di I/O su stream. La classe base d_1 ?as1c_1os
vecchia libreria di I/O obsoleta, questo volume descrive solo la nuova libreria si chiama ios base e definisce numerose versioni non template utilizzate da
ma la maggior parte delle informazioni applicabile anche alla vecchia versione. basic ios. ba~c ios viene utilizzata come base per varie classi derivate, fra le
quali basic_istre;m, basic_ostream e b~~ic_iostream. Q~este classi sono utiliz~a
te rispettivamente per creare stream d1 mput, output e mpu.t/o~tput. ~ome s1
detto, la libreria di I/O crea due specializzazioni delle gerarchie d1 classi template:
20.2 Gli stream del C++ una per caratteri a 8 bit e una per caratteri estesi. Ecco un _ele~co che rapprese~t~
il mappaggio dei nomi delle classi template nelle loro vers1om a caratteri semphc1
Anche il sistema di I/OC++ opera mediante stream. Gli stream sono stati discussi e caratteri estesi.
in dettaglio nel Capitolo 9 e tale argomento non verr trattato nuovamente in
questo capitolo. Per riassumere, uno stream un dispositivo logico che produce o
CLASSE TEMPLATE CLASSE PER CARATIERI SEMPLICI CLASSE PER CARATIERI ESTESI
consuma informazioni collegato a un dispositivo fisico tramite il sistema di I/O
C++. Tutti gli stream si comportano nello stesso modo anche se il dispositivo basic_streambuf streambui wstreambuf
fisico a cui sono connessi pu essere molto diverso. Per questa ragione, le stesse basic ios ios wios
funzioni di I/O C++ possono operare praticamente su qualsiasi tipo di dispositivo
fisi~o. Ad esempio, si potr utilizzare la stessa funzione che scrive su un file per basic istream istream wistream

scnvere sulla stampante o sullo schermo. II vantaggio di questo approccio consi- basic_ostream ostream wostream
ste nel fatto che si deve apprendere un solo sistema di I/O.
basie iostream iostream wiostream

basic fstream fstream wfstream

20.3 le classi per stream C++ basic ifstream jfstream .l!'ilstream

C~':1e si d~tto, il C++ standard fon:iisce il supporto al proprio sistema di I/O


basic_ofstream ofstream wofstream

ncll hea?er <1ost~eam>. In qu~sto file, viene definito un insieme _pl!!gosto com-
plesso d1 gerarchi~~ cl~ssi ~e supporta le operazioni dil/D .. Nel volume verranno utilizzate le classi per caratteri semplici, cos come avviene
--normalmente n~i_p.rQgrammi. Esse hanno anche lo stesso nome utilizzato dalla
IL SISTEMA DI I/O C+ +: LE- BASI 523
522 CAPITOLO 20

possibile utilizzare particolari funzioni chiamate manipolatori che possono esse-


vecchia libreria di I/O. Questo il motivo per cui la vecchia e la nuova libreria di
JJO sono compatibili fra loro a livello di codice sorgente. re incluse all'interno di un'espressione di I/O. -
La discussione inizier daj!e operazioni di I/O formattato utilizzando le fun-
La classe ios contiene molte funzioni e variabili membro che controllano o
mo_nitorizzano le operazioni di base di uno stream. Tali elementi vengono utiliz- zioni membro e i flag della classe ios.
zati con ~de frequenza. Baster ricordare che se si include <iostream> nel pro-
gramma, s1 fa accesso a questa importante classe. Formattazione utilizzando i membri di ios

Ad ogni stream associata una serie di flag che controllano la formattazione delle
Gli stream predefiniti del C++ informazioni. La classe ios dichiara un'enumerazione a bit chiamata fmtflags nel-
la quale sono definiti i seguenti valori (tecnicamente questi valori sono definiti in
Quando inizia l'esecuzione di un programma C++ vengono automaticamente aperti
quattro stream di I/O: ios_base che, come si detto in precedenza la classe base di ios.

adjustfield basefield boolalpha dee


STREAM SIGNIFICATO DISPOSITIVO floatfield hex internal
fixed
oct right scientific
cin Input standard Tastiera left
showpoint showpos skipws
cout
showbase
Output standard Schermo
unitbuf uppercase
cerr Output di errort standard Schermo

clog
Questi valori sono utilizzati per impostare o cancellare il valore dei flag di
Versione bufferizzata di cerr Schermo
formattazione. I vecchi compilatori non definiscono il tipo enumerativo fmtflags.
In questo caso i flag di formattazione verranno codificati in un intero long.
Gli stream cin, cout e cerr corrispondono agli stream stdin, stdout e stderr del c. Quando si attiva il flag skipws, tutti gli spazi vuoti iniziali (spazi, tabulazioni
Normalmente, gli stream standard sono utilizzati per comunicare con la con- e codici di fine riga) vengono eliminati dall'input. Quando skipws uguale a zero,
sole.~ ambienti che consentono la redirezione delle operazioni di I/O (come ad
gli spazi vuoti non vengono eliminati.
ese~p1? DOS, UNIX, OS/2 e Windows), gli stream standard possono essere Quando il flag left attivo, l'output viene allineato a sinistra. Quando attivo
r~diretti ad altri dis~ositivi o file. Per semplicit, negli esempi di questo capitolo right, l'output viene allineato a destra. Quando attivo il flag internal, i valori
s1 assume che non sta consentita la redirezione delle operazioni di I/O. numerici vengono completati da una serie di spazi inseriti fra il segno o il caratte-
Il C~+ standard definisce anche quattro.stream aggiuntivi: win, wout, w~rr_e_ ----- re di base in modo da far rientrare il numero in un campo. Nel caso in cui nessuno
wlog. S_1 tr~tta delle ver~ioni a caratteri estesi ("wide") degli stream standard. I di questi flag fosse impostato, I' output viene allineato a destra.
c~atten wide sono del tipo wchar_t, che corrisponde generalmente a valori a 16 Come impostazione standard, i valori numerici vengono visualizzati in base
bit, e sono utilizzati per contenere interi alfabeti associati ad alcune lingue. decimale. per possibile cambiare la base numerica: impostando il flag oct,
l'output viene visualizzato in ottale. Impostando il flag hex l'output viene
visualizzato in numeri esadecimali. Per tornare a visualizzare r output in numeri
20.4 Operazioni di I/O formattato decimali, si deve impostare il flag dee.
Impostando showbase viene visualizzata la base dei valori numerici. Ad esem-
Il si~te_ma. di I/OC++ consente di formattare le operazioni di I/O. Ad esempio. pio, se la base numerica utilizzata esadecimale, il valore lF ,iene visualizzato
p~ss1b1l~ impostare lampiezza dei campi, specificare una base numerica o deter- -come OxlF.
mmare 11_ numero di cifre da visualizzare dopo il punto decimale. Quando si utilizza la notazione scientifica la "e" dell'esponente sar in lettere
I dati possono essere formattati in duemi:licorrelati ma concettualmente minuscole come pure la "x" che indica l'utilizzo della base esadecimale. Quando si
differenti. Innanzi tutto, si pu accedere direttamente ai membri della classe ios. attiva il flag uppercase questi caratteri vengono visualizzati in lettere maiuscole.
I~ p~olru:'e~ possibile impostare v_arilndici!mrl(flag) di stato definiti all'inter- _ Impostando showpos si visua!Eza il_s:gno + davanti ai valori p6Si1iW: - ~----- -
------.-- . -n~~l~ _classe ios o richiamare varie funzioni membrG-Cfi!Os-: 'In -alte1"!1ativa --- ----~ -- ~
524 CAPITOLO 20 525

Impostando showpoint si visualizzano il punto e i decimali per tutti i numeri return O;


in virgola mobile (anche quando il numero non presenta decimali).
Impostando il flag scientific i numeri in virgola mobile vengono visualizzati
in notazione scientifica. Quando si imposta fixed, i numeri in virgola mobile ven- importante comprendere che setf() una funzione membro della classe ios
gono visualizzati in notazione normale. Quando non impostato alcun flag, sar e che pertanto opera sugli stream creati da tale classe. Pertanto, ogni chiamata a
il compilatore a scegliere un metodo appropriato. setf() verr eseguita rispetto a un determinato stream. Non ha senso chiamare
Quando impostato unitbuf, il sistema di I/O viene vuotato (flush) dopo ogni setf() da sola. In altre parole, in C++ non esiste il concetto di stato di formattazione
operazione di inserimento. Quando si imposta boolalpha diventa possibile esegui- globale. Ogni stream conserva le proprie informazioni sullo stato della
re l'input e l'output di valori booleani utilizzando le parole true e false. formattazione.
Poich si fa frequentemente uso dei campi oct, dee ed hex, si pu far riferi- Anche se non vi nulla di tecnicamente errato nel precedente programma, vi
mento alla loro globalit con il nome basefield. Analogamente i campi left, right e un modo pi efficiente per scriverlo. Invece di eseguire pi chiamate a setf(),
internal possono essere impiegati con il nome generale adjustfield. Infine i campi baster utilizzare l'operatore OR su tutti i flag da impostare. Ad esempio, la se-
scientific e fixed sono raggruppati in floatfield. guente chiamata esegue le stesse operazioni viste nel programma precedente.

11Si pu anche esegui re l 'OR di due o pi f1 ag,


Impostazione dei flag di formattazione cout.setf(ios: :showpoint I ios: :showpos);

Per impostare un flag di formattazione si utilizza la funzione setf{). Questa fun-


zione un membro di ios. Di seguito viene illustrata la sua forma pi comune: Sl)GGERIP/IEN-TO Poich i flag di formattazione sono definiti nella classe ios,
per accedere ai loro valori si deve utilizzare ios e l'operatore di risoluzione del
fmtf1ags setf(fmtflags flag); campo d'azione. Ad esempio, showbase non verr di per se stessa riconosciuta.
Si dovr specificare ios::showbase.
Questa funzione restituisce la precedente impostazione del flag di formattazione
e attiva i flag specificati daflag. Tutti gli altri flag rimangono inalterati. Cancellazione dei flag di formattazione
Ad esempio, per attivare il flag showpos, si deve utilizzare l'istruzione:
La funzione complementare di setf() unsetf(). Questa funzione membro di ios
stream.setf(ios: :showpos); consente di azzerare uno o pi flag di formattazione. La sua forma generale :

Qui, stream -Io sfieam su cI si deve operare. Si noti che showpos qualifi- void unsetf(fmtflags flag);
cato corrios::. Poich showpos una costante enumerativa definita dalla classe
ios, quando viene utilizzata deve essere qualificata da ios::. Questa funzione azzera i flag specificati daflag (tutti gli altri flag non vengo-
Il seguente programma visualizza il valore 100 attivando i flag showpos e no modificati). Viene invece restituita l'impostazione precedente dei flag.
showpoint. Il seguente programma illustra l'uso di unsetf(). Innanzi tutto il programma
attiva i flag uppercase e scientific. Poi visualizza 100.12 in notazione scientifica.
#include <iostream> In questo caso, la E utilizzata per la notazione scientifica apparir in lettere maiu-
using namespace std; scole. Quindi il programma azzera il flag uppercase e visualizza nuovamente il
int main() ,.
numero 100.12 in notazione scientifica utilizzando una "e".
{
cout.setf(ios: :showpoint); #i nel ude <iostream>
cout. setf (i os:: showpos); usi ng namespace std;

int main()
- -- --~_---.:...=...~- -
526 CA P I T O LO 2 o----
-- IL SISTEMA DI I/OC++: LE BASI 527

cout.setf(ios::uppercase I ios::scientific); mano questi_ raggruppamenti sono mutuamente esclusivi, occorre disattivare un
flag quando se ne attiva un altro. Ad esempio, il seguente progr~a produce un
cout << 100.12; Il visualizza l.0012E+02 output esadecimale. Per eseguire loutput in notazi~ne esade~1~ale alcune
implementazioni richiedono la disattivazione degli altn flag relativi alla ba~e e
cout.unsetf(ios: :uppercase); 11 azzera uppercase l'attivazione del flag hex. Questa operazione pu essere ottenuta con maggiore
facilit se si usa la forma a due parametri di setf().
cout " \n" 100.12; Il visualizza l.0012e+02

return O; #in cl ude <i ostream>


using namespace std;

int main()
{
Una forma di setf() modificata tramite overloading cout.setf(ios: :hex, ios: :basefield);

V una forma di setf{) che ha il seguente aspetto: cout << 100; Il visualizza 64

fmtflags setf(fmtflags flagsl, fmtflags flags2); return O;

In questa versione, vengono modificati solo~ flag specificati daflag2. Questi


vengono prima azzerati e poi attivati sulla bas dei flag specificati daflagl. Si Qui i flag.di basefield (ovvero dee, oet ed hex) vengono innanzitutto cancella-
noti che anche se flag 1 contiene anche altri flag, vengono alterati solo i flag spe- ti e poi viene attivato il solo flag hex. . . .
cificati daflag2. La funzione restituisce l'impostazione precedente dei flag. Ad Si ricordi che solo i flag specificati da flags2 possono e~ser~ mfluenz~t1 da~
esempio: flag specificati da flags1. Ad esempio in questo programma il pnmo tentativo di
impostare il flag showpos non ha successo.
#include <iostream>
using namespace std; 11 Questo programma non funziona.
#include <iostream>
int main()
using namespace std;
{
cout:setf(ios: :showpoint ios::showpos, ios::showpoint); int main()
{
cout 100.0 " \n"; Il visualizza 100.0 e non +100.0 cout.setf(ios::showpos, ios::hex); Il errore, showpos non impostato

return O; cout << 100 << '\n'; Il visualizza 100 e.non +100

cout.setf(ios::showpos, ios::showpos); Il corretto


Qui viene impostato showpoint ma non showpos che non specificato dal
secondo parametro. cout << 100; Il visualizza +100
L'uso pi comune di questa forma.a due parametri di sett{) consente di impo-
star:e la ba~e_del numero, l'allineamento e i flag di formattazione. Come si detto.
return O;
i riferimenti ai campi oet, dee ed hex possono essere raccolti in basefield. Anak>-
gamente i campi left, right e internal possono essere raccolti in adjustfield. Infine i
La maggior parte delle volte-si uti_lizzer--~nsetf(). per azzerare .i flag. e la:er- __ _
- earnpi seientifi:_ ~i~:~ 2-C>_....~q11_o essere raccolti in floatfield.-Poich i flag che for-
sione-a un unico parametro di setf()~escritta m precedenza per attivare 1 fla.,. La-
- - - ---=.:=::::... - - - :...._~ -
--528 CAPITOLO 20 Il S I S T E M A O I lTO-CT + : -LE BA S I - 529

versione setf(fmtflagsjlagJ, fmtflagsjlag2) viene utilizzata in particolari situazio- f = (long) cout.flags(); Il legge le impostazioni dei flag
ni, ad esempio per impostare la base del numero. Ad esempio si potrebbe utilizza-
re un modello di flag che specifichi lo~tato di tutti i flag di formattazione ma si Il controlla ogni flag
potrebbe voler modificare solo un paio di flag. In questo caso, si potrebbe specifi- for(i=OX4000; i; i = i 1)
care il modello injlagl e utilizzareflag2 per specificare i flag da modificare. if(i & f) cout "l ";
e1se cout << "O ";

11 valore dei flag di formattazione cout <<' " \n";

In alcuni casi si desidera semplicemente conoscere il valore attuale dei flag di


formattazione senza modificarne alcuno. A tale scopo, ios include la funzione Di seguito viene presentato loutput del programma:
membro flags() che semplicemente restituisce l'impostazione corrente di ogni
flag di formattazione codificata come un intero long. Il prototipo della funzione ooooo 1 oooo oo oo 1
il seguente: o 1 ooo 1 o 1 oo 1 o oo 1
fmtflags flags();
Attivazione di tutti i flag
Il seguente programma utilizza flags() per visualizzare l'impostazione dei flacr La funzione flags() ha un secondo formato che consente di attivare tutti i flag
di formattazione di cout. Si faccia particolare attenzione alla funzione showflagsO associati a un. determinato stream. II prototipo di questa versione di flags() :
che potrebbe rivelarsi utile in molti programmi.

#include <iostream> fmtflags flags(fmtflags j);


using namespace std;
Quando si utilizza questa versione della funzione, il modello di bit contenuto
voi d showfl ags () in f viene utilizzato per impostare i flag di formattazione associati allo stream.
Pertanto, vengono modificati tutti i flag di formattazione. La funzione restituisce
int :-iain() l'impostazione precedente.
{ Il seguente programma illustra questo utilizzo di flags(). Il programma innan-
Il mostra la condizione standard dei flag di formattazione zi tutto costruisce una maschera di flags che attiva showpos, showbase, oct e
showfl ags O; right. Tutti gli altri flag vengono disattivati. Il programma quindi utilizza-fiags()
per impostare i flag di formattazione associati a cout. La funzione showflags()
cout.setf(ios::right I ios::showpoint I ios::fixed); verifica che i flag siano stati impostati come indicato ( la stessa funzione utiliz-
showfl ags (); zata nel programma precedente).

return O; #include <iostream>


using namespace std;

Il Questa_funzione visualizza lo St!lto dei flag di formattazione. voi d showfl ags ()

voi d showfl ags () int main()


{ {
ios::fmtflags f; 11 mostra 1a condizione standard dei f1 ag di formattazione
-- lE_ng _!i -showfl ags ();
IL SISTEMA DI I/OC++: LE BASI 531

showbase, oct, right sono on, gli altri sono off Quando deve essere riempito un campo, il carattere utilizzato per riempire gli
!.~:fs::showpos I ios::showbase I ios::oct I ios:: right; spazi vuoti in genere lo spazio. per possibile specificare un diverso carattere -
,~tlags{f); Il attiva tutti i flag di riempimento utilizzando la funzione fili(). Il suo prototipo :_

char fill(char eh);

Dopo una chiamata a fill(), il nuovo carattere di riempimento sar eh mentre la


funzione restituir il carattere precedentemente utilizzato. Il programma seguente
illustra l'uso di queste tre funzioni:
Uso di width(), precision{) e fil.I()
#include <iostream>
Oltre ai flag di formattazione, all'interno della classe ios vi sono tre funzioni using namespace std;
membro che impostano i seguenti parametri di formattazione: ampiezza del cam-
po, precisione e carattere di riempimento. i nt mai n ()
Normalmente, quando viene prodotto in output un valore, esso occupa uno {
spazio pari al numero di caratteri visualizzato. per possibile specificare un' am- cout. preci si on (4)
piezza minima di campo tramite la funzione width(). Di seguito ne viene presenta- cout.width(lO);
to il prototipo:
cout 10.12345 "\n"; Il visualizza 10.12
streamsize width(streamsize w);
cout. fi 11 ( '*');

Qui, w diviene lampiezza del campo e la funzione restituisce lampiezza pre- cout.width(lO);
cedente del campo. In alcune implementazioni, l'ampiezza del campo deve essere cout 10 .12345 "\n"; 11 vi su al i zza *****10.12
impostata prima di ogni operazione di output. In caso contrario, viene utilizzata
un'ampiezza di campo standard. Il tipo streamsize corrisponde a una forma di Il l'ampiezza di campo si applica anche alle stringhe
intero. cout.width(lO);
Dopo aver impostato lampiezza minima del campo, se un valore non utilizza cout "Hi!" << "\n"; Il visualizza *******Hi!
interamente l'ampiezza specificata, il campo viene riempito con il carattere di cout.width(lO);
riempimento corrente (normalmente lo spazio) in modo da raggiungere l'ampiez- cout.set.f(.ias.::Jef_t); Il allineamento a sinistra
za del carpo. Se invece le dimensioni del valore superano l'ampiezza del campo, cout << 10.12345; Il visualizza 10.12*****
verranno superati i limiti. In nessun caso vengono troncati i valori.
return O;
Quando si produce in output un valore n virgola mobile, possibile determi-
nare il numero di cifre da visualizzare dopo il punto decimale utilizzando la fun-
zione precision{). Il suo prototipo :
Di seguito viene presentato loutput del programma:
streamsize precision(streamsize p); 10.12
*****10.12
Qui, la precisione viene impQstata ap cifre decimali e viene restituito il valore '*******Hi !
precedente. La precisione standard pari a sei cifre. In alcune implementazioni. 10.12*****
la precisione deve essere impostata prima di ogni operazione di output di valori1i1--
virgola mobile. In caso contrario viene utilizzata una precisione standard. Esistono anche versioni d width(), precision() e fili() modificate tramite--- --
overloading che ottengono.il_v~lor~ commte senza modificarlo:
532 CAPITOLO 20 IL SISTEMA_DI I/OC++: LE BASI 533

char fill (); Tabella 20.1 I manipolatori del e++. (conUnua)


streamsize width();
streamsize .precisi on(); MANIPOLATORE SCOPO INPUT/OUTPUT
oct Attiva il flag oct Input e Output

resetiofl ags (fmtfl ags f) Azzera i flag speciflCl!ti in f Input e Output


Uso dei manipolatori per la formattazione dell'l/O
right Attiva il fleg right Output
Il secondo modo in cui possibile modificare i parametri di formattazione di uno
stream prevede l'uso di particolari funzioni, chiamate manipolatori, che possono seientific Attiva il flag sci enti fi e Output

essere incluse in un'espressione di 110. Nella Tabella 20.1 vengono elencati tutti i setbase (i nt base) Imposta la base numerica Output
manipolatori standard. Come si pu vedere esaminando la tabella, molti manipo-
setfi 1l(int eh) Imposta il carattere di riempimento Output
latori di I/O sono analoghi alle funzioni membro della classe ios.
Molti dei manipolatori sono stati aggiunti solo recentemente e non sono supportati setiosflags(fmtflags ~ Attiva i flag specificati in f Input e oUtput
dai compilatori meno recenti.
setprecision(int p) Imposta il numero di cifre di precisione Output

Tabella 20.1 I manipolatori del C+ +. setw(int w) Imposta l'ampiezza di campo Output

MANIPOLATORE SCOPO INPUT/OUTPUT showbase Attiva il flag showbase Output

boolalpha Attiva il flag boolalpha Input e output showpoint Attiva il flag showpoi nt Output

dee Attiva Il flag dee Input e output ski pws Attiva nflag skipws Input

endl Output del caratter~ di fine riga unitbuf Attiva il flag uni tbuf Output
e svuotamento dello stream Output
uppercase Attiva il flag uppercase Output
ends Output di un carattere nullo Output
ws Salta gli spazi vuoti iniziali Input
fixed Attiva Il flag f i xed Output

flush Svuotamento di uno slream Output

hex Attiva il flag hex Input e output


6UtpUI ____ _ Per accedere ai manipolatori che richiedono l'uso di parametri (come ad esem-
internal Attiva il flag interna 1
pio setw()) si deve includere nel programma l'header <iomanip>.
left Attiva il flag 1eft Output Ecco un esempio che illustra l'uso di alcuni manipolatori:
nobool alpha Disattiva Il flag boo 1a1 pha Input e output
#include <iostream>
noshowbase Disattiva il flag showbase Output #i ne 1ude <i omani p>
using namespace std;
noshowpoint Disattiva Il flag showpoi nt Output

noshowpos Disattiva il flag showpos Output int main()


{
noskipws Disattiva il flag ski pws.,. Input
cout hex 100 endl ;
nounitbuf . ____Disattiva il flag uni tbuf Output
cout setfill ('?') setw(lO) 2343.0;
nouppercase Disattiva il flag upperease Output
return O;
--'"--==-{segue) - -
--------- ---- - -
534 CAPITOLO 20
IL SISTEMA DI I/OC++..: LE BASI 535

Questo programma produce il seguente output:


return O;
64
??????2343
II manipolatore setiosflags() esegue la stessa operazione della funzione mem-
bro setf().
Si noti la posizione che i manipolatori occupano nella catena delle operazioni Uno dei manipolatori pi interessanti boolalpha che consente di introdurre i
di I/O. Inoltre si noti che quando un manipolatore non richiede argomenti, come valori logici come true e false invece che come valori numerici. Ad esempio:
ad esempio endl() non seguito da parentesi. Questo avviene perch all'operatore
<<viene passato l'indirizzo della funzione.
#include <iostream>
Per confronto, ecco una versione equivalente del programma precedente che using namespace std;
ottiene gli stessi risultati utilizzando le funzioni membro della classe ios:
int main()
#include <iostream>
#include <iomanip> bool b;
using namespace std;
b = true;
int mai n() cout << b << " " << bool al pha b endl;
{
cout.setf(ios::hex, ios::basefield); cout "Inserire un valore booleano: ";
cout 100 11 \n"; Il 100 in esadecimale cin >> boolalpha >> b;
cout "Questo il valore immesso: " b;
cout.fill ('?');
cout.width(lO); return O;
cout 2343. O;

return O;
Ecco quale potrebbe essere l'output del programma.

l true
Come suggerisce l'esempio, il vantaggio principale dell'uso dei manip.olatori __ _ Inserire un valore booleano: false
al posto delle funzioni membro di ios il fatto che spesso consentono di produrre Questo il valore immesso: false
codi:e pi compatto.
Il manipolatore setiosflags() consente di impostare direttamente i vari flag di
formattazione relativi a uno stream. Ad esempio, questo programma utilizza
setiosflags() per attivare i flag showbase e showpos: 20.5 Overloading di << e >>
ii nel ude <i ostream> Come si detto in precedenza, gli operatori e sono stati modificati tramite
li nel ude <i omani p> overloading in C++ per eseguire operazioni di I/O dei tipi standard. possibile
using namespace std; eseguire un ulteriore overloading di questi operatori per far loro eseguire opera-
zioni di I/O sui nuovi tipi. . :: . .
int main() Seguendo la terminologia del C++, l'operatore di output viene chiamato
{
operatore di inserimento poich inserisce i caratteri in uno stream. Analogamen-
cout setiosflags(ios: :showpos); te, l'operatore di input chiamato operatore di estrazione poich estrae i ca~
-c:oiJC<<- stiosfl ags (i os:: showbase);
ratteri da uno stream. Le funZni che eseguono l'overloading aeglioperanrrtdr- -
- cout 123 " " << hex << -123; ---- -
... inserimento ed estrazione sono generlmente chiamati inseritor.i_ed estraiton~-
--. - -~--- ---=--=------ --
--~ --- --
--,- - -
536 537

Creazione di un inseritore Questa classe memorizza il nome e il numero di telefono di una persona. Ecco
come possibile creare una funzione inseritore per oggetti di tipo phonebook.
piuttosto facile creare un inseritore per una nuova classe. Tutte le funzioni
inseritore hanno la seguente forma gene~ale: - Il Visualizza il nome e il numero telefonico
ostream &operator<<(ostream &stream, phonebook o)
ostream &operator<<(ostream &stream, tipo_classe oggetto) {
{ stream << o.name << " ";
Il corpo dell'inseritore stream << "(" << o.areacode << ") ";
return stream; stream << o.prefix << 11 - 11 << o.num << "\n";
}
return stream; Il deve restituire lo stream
Si noti che la funzione restituisce l'indirizzo di uno stream di tipo ostream (si
ricordi che ostream una classe derivata da ios che supporta l'output). Inoltre, il Ecco un breve programma che illustra l'uso della funzione inseritore per
primo parametro della funzione l'indirizzo dello stream di output. Il secondo
phonebook.
parametro l'oggetto da inserire (pu anche essere l'indirizzo dell'oggetto da
inserire). L'ultima operazione che un inseritore deve eseguire prima di terminare
#i nel ude <iostream>
la restituzione dello stream. In questo modo l'inseritore potr essere utilizzato #include <cstring>
in una lunga espressione di 110. using namespace std;
All'interno di una funzione inseritore, possibil~,inserire qualsiasi tipo di
procedura e operazione. Ovvero le operazioni svolte da un inseritore possono cl ass phonebook {
essere scelte completamente dal programmatore. Tuttavia, per conservare uno sti- public:
le di programmazione corretto, si dovranno limitare le operazioni svolte da un char name [80] ;
inseritore ali' output di informazioni su uno stream. Ad esempio, sconsigliabile int areacode;
definire come effetto collaterale dell'operazione di inserimento il calcolo di pi int prefix;
con 30 cifre decimali. int num;
Per osservare un esempio di inseritore personalizzato, si prover a creare un phonebook(char *n, int a, int p, int nm)
inseritore per oggetti di tipo phonebook:
strcpy(name, n);
areacode = a;
cl ass phonebok {
prefix = p;
public:
num = nm;
char name[SO];
}
i nt area code;
};
int prefix;
int num;
phonebook(char *n, int a, int p, int nm)
Il Visualizza il nome e il numero telefonico.
{ ostream &operator(ostream &stream, phonebook o)
{
strcpy(name, n);
stream << o.name << " ";
areacode = a;
stream << "(" <<_ Q;_. ~reacode << ") "; _
prefi x = p;"
stream << o.prefix << 11 - 11 << o.num << "\n";
num = nm;
}
}; return stream; Il deve restituire lo stream
538 CAPITOLO 20
11.--sTSTFM A o1 11 o e +-.i-~ LE sA s1 539

int main() #i nel ude <i ostream>


{ #include <cstring>
phonebook a("Teo", 111, 555, 1234); using namespace std;
phonebook b("Alice",-"312, 555, 5768);
phonebook c("To11111aso 11 , 212, 555, 9991); e1ass phonebook I
11 ora privata
cout << a b << e; cha r name [80] ;
i nt areacode;
return O; int prefix;
int num;
public:
phonebook(char *n, int a, int p, int nm)
Il programma produce il segunte output:
I
strcpy(name, n);
Teo (lll) 555-1234 areacode = a;
Alice (312) 555-5768 prefix = p;
To11111aso (212) 555-9991 num = nm;
}
Nel programma precedente, si noti che l'inseritore di phonebook non un friend ostream &operator(ostream &stream, phonebook o);
membro di phonebook. Anche se ci potrebbe sembrare a prima vista errato, il };
motivo semplice. Quando una funzione operatore di qualsiasi tipo un membro
di una classe, I' operando sinistro (passato implicitamente mediante this) l'og- Il Visualizza il nome e il numero telefonico.
getto che genera la chiamata alla funzione operatore. Inoltre, questo oggetto un ostream &operator(ostream &stream, phonebook o)
oggetto della classe di cui la funzione operatore un membro. Non vi alcun {
modo di cambiare ci. Se una funzione per l'overloading di un operatore un stream << o.name << " ";
stream << "(" << o.areacode << ") ";
membro di una classe, !'operando di sinistra deve essere un oggetto di tale classe.
stream << o.prefix << "-" << o.num << "\n";
Ma quando si esegue l'overloading degli inseritori, I' operando di sinistra uno
stream e I' operando di destra un oggetto della classe. return stream; 11 deve restituire lo stream
Pertanto, le funzioni che eseguono l'overloading degli operatori di inserimen-
to non possono essere membri della classe su cui operano. Nel programma prece-
dente le variabili name, areacode, prefix e num sono pubbliche perch devono int main()
essere accessibili da parte degli inseritori. {
Il fatto che gli inseritori non possano essere membri della classe per la quale phonebook a("Teo", 111, 555, 1234);
sono definiti sembra essere una grave svista del C++.Dato che gli inseritori non phonebook b("Al ice", 312, 555, 5768);
sono membri, come possono accedere agli elementi privati di una classe? Nel phonebook c("Tommaso", 212, 555, 9991);
programma precedente, tutti i membri sono stati resi pubblici. Tuttavia,
l'incapsulamento un componente essenziale della programmazione a oggetti. cout << a << b << e;
Richiedere che tutti i dati che devono essere prodotti in output debbano essere resi
return O;
pubblici in conflitto con questo principio. Fortunatamente, vi una soluzione a
questo problema: renaere l'inseritore friend della classe. Questo consente di la-
sciare che uno stream sia il primo argomento dell'inseritore e nel contempo con- Quando si definisce il corpo di una funzione inseritore, si deve ricordare ~i
cede alla funzione l'accesso ai membri privati della classe su cui opera. Ecco mantenerlo il pi possibile generale. Ad ese?1~io, l' inserit~re :i:ostrato nell' esem~10
l'aspetto del programma modificato tr.amutando l'inseritore in una.funzi?ne fi:iead:- - - precedente pues~ere utilizzato co_~-q~~lsias1 stream po1che 1_l_c.QEJ'O ~~!..!_a f.1_:1nzw-
IL SISTEMA DI 110 C++: LE BASI 541
540 CAPITOLO 2 O

ne invia l'output su stream ovvero lo stream che ha richiamato l'inseritore. Anche 11 Genera un riquadro.
se non sarebbe tecnicamente errato scrivere la riga: ostream &operator<<(ostream &stream, box o)
{
register int i, j;
stream << o.name << " ";

for(i=O; i<O.X; i++)


come stream <<
11
*0 ;

cout << o.name << " "; stream "\n";

questo avrebbe l'effetto di costringere a specificare cout come stream di output. for(j=l; j<o.y-1; j++) {
La versione originale funzioner con qualsiasi stream inclusi quelli connessi a file for(i=O; i<o.x; i++)
su disco. Anche se in determinate situazioni, specialmente quando si opera su if(i==O 11 i==o.x-1) stream "*";
else stream << " ";
dispositivi di output particolari, si preferir specificare precisamente il nome del-
stream << "\n";
lo stream di output, nella maggior parte dei casi questo sconsgliabile. In gene-
rale, pi flessibili saranno gli inseritori e maggiore sar il loro valore.
for(i=O; i<o.x; i++)
N9T1(:'.".~::;~~L~ L'inseritore della classe phonebookfunziona correttamente
tranne quando il valore di num qualcosa come 0034, nel qual caso gli zeri
stream <<
11
* 11
;

stream << "\n";


iniziali non vengono visualizzati. Per CQrreggere questo errore, si pu convertire
num in una stringa o impostare il carattere di riempimento a "O" e utilizzare la return stream;
funzione di formattazione width() per generare gli zeri iniziali. La soluzione
lasciata al lettore per esercizio.
int main()
Prima di esaminare gli estrattori, bene vedere un altro esempio di funzione
{
inseritore. Un inseritore non deve necessariamente gestire solo testo. Un inseritore box a{l4, 6), b(30, 7), c{40, 5);
pu essere utilizzato per produrre in output dati in qualsiasi forma. Ad esempio,
un inseritore per una classe che faccia parte di un sistema CAD potrebbe produrre cout "Ecco alcuni riquadri :\n";
in output le istruzioni per il plotter. Un altro inseritore potrebbe generare immagi- cout << a b << e;
ni grafiche. Per provare a generare in output oggetti diversi dal comune testo, si
esamini il seguente programma che disegna riquadri sullo schermo (poich il C++ return O;
non definisce una libreria di operazioni grafiche, il programma traccia un riqua-
dro utilizzando caratteri ma si liberi di utilizzare elementi grafici se il sistema ne
supporta l'uso). Il programma visualizza i seguenti riquadri:

#include <iostream> Ecco a1cuni riquadri :


using namespace std; **************
* *
cl ass box { *
int X, y; *
public: * *
box(int i, int j) {x=i; y=j;) **************
friend os.tream &opel'.'ator-<<:(.ostream &stream, box o);
}; -
---
----
542 CAPITOLO 20--
IL SISTEMA DI I/OC++: LE BASI 543

****************************** cout << "\n";


*
return stream;
* *

Si noti che sebbene questa sia una funzione di input, produce l'output neces-
******************************
sario per chiedere informazioni all'utente. Anche se lo scopo principale di un
****************************************
estrattore l'input, questo pu essere utilizzato per eseguire qualsiasi operazione
* necessaria per ottenere tale scopo. Tuttavia, come nel caso degli inseritori,
*
* consigliabile mantenere le azion_i eseguite da un estrattore direttamente correlate
**************************************** con l'input. In caso contrario si corre il rischio di perdere molto in termini di
struttura e chiarezza.
Ecco un programma che illustra l'uso dell'estrattore di phonebook:

Creazione di un estrattore #i ne 1ude <i os t ream>


#i nel ude <estri ng>
Gli estrattori sono complementari rispetto agli inseritori. La forma generale di using namespace std;
una funzione estrattore :
cl ass phonebook
istream &operator>>(istream &stream, tipo_cl~sse &oggetto) char name [SO];
{ int areacode;
Il corpo dell'estrattore int prefix;
retutn stream; int num;
} public:
phonebook () { };
phonebook(char *n, int a, int p, int nm)
Gli estrattori restituiscono l'indirizzo di uno stream di tipo istream che corri- {
sponde a uno stream di input. Anche il primo parametro deve essere l'indirizzo di strcpy(name, n);
uno stream di tipo istream. Si noti che il secondo parametro deve essere l'indiriz- areacode = a;
zo di un ogg.etto della classe per la quale definito l'estrattore. Questo consente prefix -;p;- - ----
all'operatore di input (estrattore) di modificare l'oggetto. num = nm;
Proseguendo nell'esempio della classe phonebook, ecco come possibile scri-
vere una funzione estrattore: friend ostream &operator(ostream &stream, phonebook o);
friend i stream &operator(i stream &stream, phonebook &o);
};
istream &operator(istream &stream, phonebook &o)
{
cout "Indicare il nome: ";
. Il Visualizzail nome e il numero telefonico.
stream o.name; ostream &operator<<(ostream &stream, phonebook o)
{
cout "Indicare il codice:_:'';
stream << o.name << " ";
stream o.areacode;
stream << "(" << o.areacode << ") ";
cout "Indicare il prefisso: ";
stream o.prefix; stream << o.prefix << "-" << o.num << "\n";
-c-cmr-"Indicare il numero:-";:
--stceam >:> o.num; return stream; 11 deve _rei_t!~i_re_ l~.str_eam
~}-:-::..-:-.
IL s I s TEMA-o I Il o e+ + : LE BA s I 545

Il Input del nome e del numero telefonico. nipolatori. I manipolatori personalizzati sono importanti per due motivi. Innanzi
istream &operator(istream &stream, phonebook &o) tutto, possibile raggruppare una sequenza di operazioni di I/O in un unico mani-
{
polatore. AQ esempio, non difficile trovare situazioni in cui la stessa sequenza di
cout << "Indicare il nome: "; operazioni di I/O sia richiesta frequentemente in un programma. In questi casi si
stream >> o.name;
pu utilizzare un manipolatore personalizzato, semplificando il codice sorgente
cout "Indicare il codice: ";
stream o.areacode;
ed evitando possibili errori. Un manipolatore personalizzato pu anche essere
cout "Indicare il prefisso: "; utile quando si devono eseguire operazioni di I/O su un dispositivo non standard.
stream o.prefix; Ad esempio, si potrebbe utilizzare un manipolatore per inviare codici di controllo
cout << "Indicare il numero: "; a un particolare tipo di stampante oppure a un sistema di riconoscimento ottico.
stream o.num; I manipolatori personalizzati sono una delle funzionalit del C++ che supporta
cout << "\n"; la programmazione a oggetti ma ne possono trarre beneficio anche programmi
non a oggetti. Come si vedr, i manipolatori personalizzati possono rendere pi
return stream; chiari ed efficienti i programmi che eseguono notevoli operazioni di I/O.
Vi sono due tipi principali di manipolatori: quelli che operano su stream di
input e quelli che operano su stream di output. Oltre a queste due grandi categorie,
i nt ma in()
{
vi un'ulteriore suddivisione: manipolatori che richiedono un argomento e che
phonebook a;
non ne richiedono alcuno. Le procedure necessarie per creare un manipolatore
parametrizzato variano da compilatore a compilatore e anche da una versione a
cin >> a; un'altra dello. stesso compilatore. Per questo motivo, per quanto riguarda la crea-
zione di un manipolatore parametrizzato si rimanda alla documentazione del com-
cout a; pilatore. Al contrario la creazione di manipolatori senza parametri pi semplice
e rimane costante in tutti i compilatori. Questo sar l'argomento della parte finale
return O; del capitolo.
Tutte le funzioni che realizzano manipolatori senza parametri per operazioni di
output hanno la seguente struttura:
In realt l'estrattore di phonebook non perfetto poich le istruzioni cout
sono necessarie solo se Io stream di input connesso a un dispositivo interattivo ostream &nome_manipolatore(ostream &stream)
come la console (ovvero quando Io stream di input cin). Se l'estrattore viene {
utilizzato su u.no stream connesso a un file su disco, allora le istruzioni cout non Il codice
risulteranno applicabili. Per divertimento si pu provare a sopprimere le istruzio- retum stream;
ni cout tranne quando lo stream di input fa riferimento a cin. Ad esempio si pu }
usare un'istruzione if come la seguente:
Si noti che il manipolatore restituisce l'indirizzo di uno stream di tipo ostrearn.
if(stream == cin) cout "Inserire il nome" "; Questo necessario nel caso in cui il manipolatore venga utilizzato all'interno di
un'espressione di I/O pi estesa. importante notare che anche se il manipolatore
Ora il messaggio verr visualizzato solo se il dispositivo di output lo schenno. ha come unico argomento un indirizzo allo stream su cui opera, in realt non
viene utilizzato alcun.argomento quando il manipolatore :Yiene inserito in un'9pe-
razione df output. --
Il primo semplice esempio crea un manipolatore chiamato sethex() che attiva
20.6 Creazione dUunzioni di manipolazione il flag showbase e imposta l'output esadecimale.
- - - - - ()lt!:e _all'overloading degli operatori di inserimento ed estrazione, possibile
#include <iostream>
personalizzare ulteriormente--il-sistem~d~__ll? ~:r!_c~~ando proprie funzioni-ma---- - -
#include <iomanip> - --
----546 CAPITO L0-20 IL SiSTE tX- - o"i I/ o e+ + : LE B-A-Si-547 -
------------------------------
usi ng namespace s td; cout "Valore medio " ra 567 .66 la;

//Un semplice manipolatore di output. return O;


ostream &sethex(ostream &stream)
{ .

stream.setf(ios: :showbase); Questo programma visualizza il seguente output:


stream.setf(ios: :hex, ios.: :basefield);
Valore massimo ------> 1233.23
return stream;
Valore medio ------> 567.66 <------

int main() Se devono essere utilizzati con frequenza, questi semplici manipolatori con-
{ sentono di risparmiare tempo e fatica.
cout 256 << " " << sethex << 256; L'utilizzo di un manipolatore di output particolarmente utile per inviare co-
dici a un dispositivo. Ad esempio, una stampante potrebbe accettare vari codici
return O; che cambiano le dimensioni o il font dei caratteri o che posizionano la testina di
stampa in una particolare posizione. Se queste regolazioni devono essere eseguite
frequentemente, si tratta di candidati perfetti per un manipolatore.
Questo programma visualizza 256 Ox100. Come si vede sethex utilizzata Tutte le funzioni manipolatori di input senza parametri hanno la seguente
all'interno di un'espressione di UO cos come avviene per i manipolatori standard. struttura:
I manipolatori personalizzati non devono necessariamente essere complessi
per essere utili. Ad esempio, i semplici manipolatori la() era() visualizzano rispet- istream &nome_manipolatore(istream &stream)
tivamente una freccia a sinistra e una freccia a destra: {
Il codice
#include <iostream> retum stream;
#i nel ude <i omani p>
}
using namespace std;

// Freccia a destra Un manipolatore di input riceve l'indirizzo dello stream per il quale stato
ostream__&r.a(os.tream &stream) richiamato. Questo stream dovr poi essere restituito dal manipolatore.
{ Il seguente programma crea il manipolatore di input getpass() che emette un - - - ----
stream -<< "- ... ----> n; segnale acustico e chiede una password.
return stream;
#i nel ude <i ostream>
#include <cstring>
Il Freccia a sinistra using namespace std;
ostream &la(ostream &stream)
{ //Un semplice manipolatore di input.
stream << 11 <------"; istream &getpass(istream &stream)
return stream; {_
cout <<'\a'; //-segnale acustico
cout << "Irrmettere la password: ";
int main()
{ ---returo_s_tre.am.; __
cout << "Valore massimo-~<<. ra << 1233.23 "\n";
548 CAPITOLO 20

int main() : Capitolo 21


{
cha r _p_w (80) ;
Operazioni di I/O
do { : su file in C++
ci n getpass pw;
} while (strcmp(pw, "password"));
21.1 L:header <fstream> e le classi per i file.
cout "Login completat\n"; 21.2 L:apertura e la chiusura di un file
21.3 La lettura e la scrittura di un file di testo
return O;
21.4 Le operazioni di I/O binarie
e non formattate
21.5 Altre forme della funzione get()
importante che il manipolatore restituisca lo stream. In caso contrario, non
potrebbe essere utilizzato in una serie di operazioni di input o di output. 21.6 La funzione getline()
21.7 Rilevamento della fine del file
21.8 La funzione ignare()
21.9 Le funzioni peek() e putback()
21.10 La funzione flush()
21.11 L:accesso diretto ai file
21.12 Lo stato delle operazioni di 110
21.13 Personalizzazione delle operazioni
di I/O sui file

Anche se l'approccio C++ alle operazioni di I/O costi-


tuisce un sistema integrato, l'I/O su file sufficientemente specializzato da essere
in genere considerato come un -caso-speciale, soggetto a propri limiti e metodi. In
parte, questo dovuto al fatto che, quando si parla di file, si intende in genere file
residenti su disco e i file su disco sono dotati di funzionalit e caratteristiche
specifiche che non si riscontrano per altri dispositivi. Si deve per ricordare che
l'I/O su disco un caso speciale del sistema geQerale di I/O e che la maggior parte
degli aspetti discussi in questo capitolo si applicano anche ad altri stream connes-
si ad altri tipi di dispositivi.

21.1 L'headei'- <fstram> e le clasSi per i file


Per eseguire operazioni di I/O su file, si deve includere nel programma l'header .
<fstream>. Tale header definisce numerose classi, fra le quali, ifstream, ofstream
- e fstream. Queste ~~~~derivano rispettivam~nti:; daJstr_earu, ostream e iostream.
550 CAPITOLO 21
Op E R A"ZTOlfrl) I iI O SU F I L-E-1-N C + + 551

Come si ricorder, istream, ostream e iostream derivano dalla classe ios e quindi ios::out
ifstream, ofstream e fstream. hanno accesso anche a tutte le operazioni definite da ios::trunc
ios (discusse nel precedente capitolo). Un'altra classe utilizzata per le operazioni
di JJO su file filebuf che fornisce funzionalit di I/O di basso livello su stream. Per combinare due o pi di questi valori si utilizza I' operatore OR.
Normalmente si usa filebuf indirettamente attraverso le altre classi. La modalit ios::app fa in modo che tutto l'output venga aggiunto (append)
alla fine del file. Questo valore pu essere utilizzato solo per file che accettano
operazioni di ou(put. L'inclusione di ios::ate posiziona il puntatore '.11la fine d~l
file aperto. Nonostante questo, le operazioni d.i I/O possono avvenire anche m
21.2 L'apertura e la chiusura di un file qualsiasi altro punto del file. . . . . . .
II valore ios::in specifica che il file in grado d1 fornire dati per o~~raz1om d1
In C++ si apre un file collegandolo a uno stream. Prima di poter aprire un file si input. Il valore ios::out specifica che il file in grad~ di accet~are ~au.m output.
dovr pertanto aprire uno stream. Vi sono tre tipi di stream: stream di input, stream Il valore ios::binary provoca l'apertura di un file m modi:ht bmana ~normal
di output e stream di input/output. Per creare uno stream di input, si deve dichia- mente vengono aperti in modalit binaria). Quando un fi~e viene ape.rto m moda-
rare uno stream della classe ifstream. Per creare uno stream di output lo si deve lit testo, possono avvenire varie traduzioni dei c~a~te:i, ad ~semp10 la c?nver-
dichiarare della classe ostream. Gli stream che dovranno eseguire sia operazioni sione di sequenze Carriage Return/Line Feed in cod1c1 di fi~e nga. Quando i~vece
di input che operazioni di output devono essere dichiarati della classe fstream. Ad un file viene aperto in modalit binaria, non viene esegmta alcuna traduzione.
esempio, questo. frammento di codice crea uno stream di input, uno stream di Qualsiasi file, che contenga testo formattato o semplici dati ~inari, pu ess_ere
output e uno stream in grado di eseguire operazioni sia di input che di output. aperto sia in modalit testo che in modalit binaria. L'unica differenza consiste
nella traduzione eseguita sui caratteri. .
ifstream in; 11 input Il valore ios::trunc provoca la distruzione del contenuto d1 un file ~recedente
ofstream out; 11 output avente lo stesso nome e tronca la lunghezza del file a zero. Quando si crea uno
fstream io; Il input e output stream di output con ofstream, un eventuale file preesistente verr troncato a zero.
Il seguente frammento di codice apre in output un comune file.
Dopo aver creato uno stream, un modo per associarlo a un file consiste nel-
l'uso della funzione open(). Questa funzione un membro di ognuna delle tre ofstream out;
classi di stream. Il suo prototipo :
out.open("test", ios::out);
void ifstream::open(const char *nomefile, ios::openmode modalit ios::in);
void ofstream::open(const char *nomefile, ios::openmode modalit ios::out I Ma raramente si vedr una chiamata a open(Ydfquesl>lipo, poich ~l param~
ios::trunc); tro modalit fornisce dei valori standard per ciascun tipo di stream. Per 1fstream Il
void fstream::open(cost char *nomefile, ios::openmode modalit ios::in I valore standard di modalit ios::in, per ofstream ios::out I ios::trunc e per fstream
ios::out); ios::out I ios::out. Pertanto, l'istruzione precedente avr normalmente il seguente
aspetto:
Qui, nomefile il nome del file, il quale pu includere uno specificatore di
percorso. II valore di modalit determina il modo in cui deve essere aperto il file. out.open("test"); 11 impostazione standard: normale file di output
modalit deve essere uguale a uno (o pi) di questi valori definiti da openmode
che un'enumerazione definita da ios attraverso la classe base ios_base.
lNQl.c\~~_:~~lf..:I A seconda del compi:atore imp_iegato, il par~metro-modalit
ios::app per fstream::open() potrebbe non avere l impostazwne standard m I out. Dunque
ios::ate talvolta necessario specificarli in modo esplicito.
ios::binary Se open() non ha successo e si valuta mystream in un'espressio.ne booleana il
ios::in suo valore sar uguale a fu~::Pef:ta!}~o,_p!ima di utilizzare un file~'Sl deve contro!-
- -~ - ------~
552
"CA P I T OLO 2 t' OPERAZIONI DI I/O su FILEIT<rC--,- -r 553
-"">&;~~-

lare ch~~'opei:aziono dl-~~~:bbia avuto successo. A tale scopo si pu impie- 21.3 La lettura e la scrittura di un file di testo
gare un istruzione slhlil _guente:
molto facile eseguire operazioni di lettura o scrittura su un file di test~. Si devo-
if( !mystream) I no semplicemente utilizzare gli operatori << e ~llo ste~so ~odo v1~to per le
cout "Imposs1b11e- aprf_re il file. \n"; operazioni di I/O su console, tranne per il fatto che invece di utthzzare cm e co~t,
Il gestione dell'errore - - si deve specificare uno stream precedentemente col~egato a u~ file. Ad e~emp10:
questo programma crea un breve file che contiene 11 nome e tl prezzo d1 alcum
oggetti.
Anche se perfettamente corretto aprire un file utilizzando la funzione open{),
la maggior parte delle volte questo non sar necessario poich le classi ifstream, #include <iostream>
ofstream e fstream prevedono funzioni costruttore che aprono automaticamente il #include <fstream>
file. Le funzioni costruttore hanno gi stessi parametri e gli stessi valori standard using namespace std;
della funzione open(). Pertanto, molto pi comune vedere un file aperto nel
seguente modo: int main()
{
ifstream mystream("nomefile"); Il apertura del file in input ofstream out("INVNTRY"); 11 output, comune file

Come si detto, se per qualche motivo il file non potesse essere aperto, il if(!out) {
valore della variabile associata allo stream sar false. Pertanto, che si apra il file cout "Impossibile aprire il file.\n";
utilizzando una funzione costruttore o una chiamata splicita a open(), si dovr return 1;
sempre controllare che il file sia stato effettivamente aperto andando a leggere il
valore dello stream.
out "Radio " 39.95 endl;
Per controllare se il file stato aperto con successo si usa la funzione is_open() out "Tostapane " 19. 95 endl;
che un membro di ifstream, ofstream e fstream. Tale funzione ha il seguente out "Mixer " << 24.80 endl;
prototipo:
out.close();
bool is_open(); return O;

La funzione restituisce true _se lo stream connesso a un file aperto e false in


tutti gli altri casi. Ad esempio, il seguente frammento di codice controlla se --- - Yfseguente programma legge il file creato dal programma precedente e ne
mystream aperto: visualizza il contenuto sullo schermo:

if( !mystream.is_open()) #include <iostream>


cout "File non ap.erto. \n"; lii nel ude <fstream>
11 ... using namespace std;

Per chiudere un file si utilizza la funzione membro close(). Ad esempio, per int main()
~hiud~re il file collegato a uno stream chiamato mystream si utilizza la seguente {
istruzione: ifstream in("INYNTRY"); Il input

mystrea~.clos~{_l;
if(!in) {
cout "Impossibile aprire il -file.\n";
return 1;
~-- -~fttnzio!1e_close()JJ.9n_richiede alcun parametro e n~~ r;stituis~e ;icun valore:--.:-.:-:::--__
--=.....:.-~--=---- ;_
554 CAPITOlO 21 OPE...RAZIONI DI 1/0 SU FILE IN C++ --55s------ -

char item[20]; do {
float cost; cout << 11 : 't;
gets (str);
in >> item cost; out str endl;
cout << item '<< " " << cast << "\n"; while (*str != '!');
in item cast;
cout << item <:< " " << cast << "\n"; out.close();
in item cost; return O;
cout << item << " " << cast << "\n";

in.close{);
return O;
Quando si legge un file di testo utilizzando l'operatore si deve ricordare
che avverranno alcune traduzioni di caratteri. Ad esempio, verranno omessi i ca-
ratteri corrispondenti a spazi vuoti. Per evitare la traduzione dei caratteri, si deve
aprire il file in modalit binaria e utilizzare le funzioni discusse nella prossima
In un certo senso, la lettura e la scrittura di file utilizzando gli operatori e sezione.
corrisponde nll'uso delle funzioni C fprintf() e fscanf(). Le informazioni me- Durante l'input, quando viene incontrato il codice di fine file, Io stream con-
morizzare nel file sono infatti nello stesso formato in cui vengono visualizzate nesso con il file sar uguale a false (questo fatto verr discusso nella prossima
sullo schermo.
sezione).
Quello che segue un altro esempio che mostra operazioni di I/O su disco.
Questo programtna legge una serie di stringhe immesse alla tastiera e le scrive su
di~~o. Il p:ogran1ma termina quando l'utente immette un punto esclamativo. Per
utilizzare il programma, si deve specificare sulla riga di comando il nome del file 21.4 Le operazioni di I/O binarie e non formattate
di output.
Anche se leggere e scrivere file di testo formattati molto semplice, non sempre
tinclude <iostream> questo il modo pi efficiente per gestire i file. Inoltre spesso capita di dover
ti nel.ude <fstrei\m> memorizzare dati binari non formattati e non testo. Questa sezione descrive le
using namespace std;
funzioni da impiegare.
Quando si eseguono operazioni binarie su un file, occorre assicurarsi di aprire
int main(int ar\.lc, char *argv[])
{ --- ------
il file utilizzando Io specificatore di modalit ios::binary. Anche se le funzioni non
if(argc!=V I formattate possono operare anche su file aperti in modalit testo, in questo caso
cout "USl)t output <nomefil e>\n"; possono verificarsi delle traduzioni dei caratteri. Le traduzioni dei caratteri nega-
return 1; no Io scopo delle operazioni su file binari.

ofstream out{~rgv(l]); // output, comune file Caratteri e byte

if( !out) { Prima di iniziare l'esame delle operazioni di I/O non formattato, importante
cout "l"\\lossibile aprire il file di_ output. \n"; chiarire un concetto importante. Per molti anni, le operazioni di I/O in C e C-i-+
return l; sono state "orientate ai byte". Questo dovuto al fatto che un char equivalente a
un byte ed erano disponibili solo streamai char. Ma con l'avvento dei caratteri
estesi (di tipo w_char_t) e i relativi stream, non si pu pi dire che le operazioni di
ch11r str[SO];
I/O del linguaggio C++ siamo orientate ai caratteri. Naturalmente gli stream di
c~t_ "Scr\~ura di stringhe su disco. Digitare clrar contmuano a essere stream di byte es! pue-0ntinuare-a pensare in termini di
---- ----------
~---
-555 CAPITOLO 21 OPERAZIONI DI 1/0 SU FILE IN C++ 557

byte, specialmente quando si opera su dati differenti dal tes.to. Ma l'equivalenza cout "Impossibile apri re il fi 1e.";
fra un byte e un carattere non pi garantita. return 1;
Come si detto nel Capitolo 20, tutti gli stream utilizzati in questo volume
sono stream di char (che sono di gran lunga i pi comuni). Essi semplificano
while(in) { // in uguale a O quando viene raggiunta la fine del file
anche la gestione non formattata dei file poich uno stream di char stabilisce una
in.get(ch);
corrispondenza uno-a-uno fra i byte e di caratteri a tutto vantaggio della lettura o
if(in) cout eh;
scrittura di blocchi di dati binari.

return O;
Le funzioni get() e put()

Per leggere e scrivere dati non formattati si possono utilizzare le funzioni mem-
bro geit() e put(). Si tratta di funzioni che operano su caratteri, ovvero get() legge Come si detto nella sezione precedente, quando si raggiunge la fine del file,
un carattere e put() scrive un carattere. naturalmente, se si aperto un file in mo- lo stream associato al file diviene uguale a false. Pertanto, quando viene raggiunta
dalit binaria e si opera su uno stream di char (e non di wchar_t) queste funzioni la fine del file, in diverr uguale a false provocando l'uscita dal ciclo while.
leggono e scrivono byte di dati. La funzione get() pu assumere diverse forme Vi anche un modo pi compatto per codificare il ciclo che legge e visualizza
delle quali la pi comune viene mostrata di seguito insieme alla corrispondente il file:
put().
while(in.get(ch))
cout eh;
istream &get(char &carattere);
ostream &put(char carattere);
Questo frammento di codice funziona poich get() restituisce l'indirizzo dello
stream in e alla fine del file in sar uguale a false.
La funzione get() legge un singolo carattere dallo stream chiamante e ne inse~
II programma successivo utilizza put() per scrivere sul file CHARS tutti i ca-
risce il valore in carattere. La funzione restituisce l'indirizzo dello stream. La
ratteri compresi fra O e 255. Tutti sanno che i caratter~ ~SCII occu~ano .solo I~
funzione put() scrive carattere sullo stream e restituisce l'indirizzo dello stream.
met dei valori che possibile inserire in un carattere dt tipo char. Gh altri valon
Il seguente programma visualizza con la funzione get() il contenuto di un file
di testo o binario. veno-ono normalmente chiamati caratteri estesi e includono simboli matematici e
lett:i.e accentate oppure specifiche di determinate nazioni (non tutti i sistemi pre-
--- ------#include <iostream> vedono l'uso di caratteri estesi). - ---- ------
#i nel ude <fstream>
using namespace std; #i nel ude <i ostream>
#i nel ude <fstream>
int main(int argc, char *argv[]) using namespace std;
{
char eh; int main()
{
if(argc!=2) int i;
cout "Uso: PR <nomefi l e>\n"; ofstream out("CHARS", ios: :out I ios: :binary);
return"Ti
if(!out) { _ _ __
cout "Impossibile aprire il file di output.\n";
ifstream in(argv[l], ios::in I ios::binary)_;__ return 1;
i!_U~nU_ --
--- ---- ..:.
. :__-:.___ ..:_ __
558 CAPITOLO 21-
---OPERAZIONI DI 1/0 su FILE IN e++ 559

11 sc_rive su disco tutti i caratteri ofstream outbal ("ba lance", ios: :out I ios: :binary);
for(i=O; i<256; i++) out.put((char) i);
if(!outbal) {
out.close();
cout 11 Impossibile apri re il fi 1e. \n";
return O;
return l;

Pu essere interessante osservare il contenuto di CHARS per conoscere i ca- outbal .write((unsigned char *) &ace, sizeof(struct status));
ratteri estesi disponibili sul proprio sistema. outbal .close();

Il ora rilegge il file;


le funzioni read() e write()
ifstream inbal("balance", ios::in I ios::binary);
Un altro metodo utilizzabile per leggere e scrivere blocchi di dati binari prevede
l'uso delle funzioni read() e write(). I rispettivi prototipi sono: if(!inbal) {
cout "Impossibile aprire il file.\n";
istream &read(char *buf, streamsize num); return l;
ostream &write(const char *buf, streamsize num);
inbal.read((unsigned char *) &ace, sizeof(struct status));
La funzione read() legge num caratteri dallo stream associato e li inserisce nel
buffer puntato da buf La funzione write() scrive num caratteri sullo stream asso- cout ace. name endl ;
ciato prelevandoli dal buffer puntato da buf Cme si detto in precedenza, cout "Numero di conto: 11 acc.account_num;
streamsize definito nella libreria C++ come una forma di intero. cout. precisi on (2);
Il seguente programma scrive su disco e poi rilegge una struttura. cout. setf(ios:: fixed);
cout << endl "Saldo: " acc.balance;
#include <iostream>
#i nel ude <fstream> inbal .cl ose();
#include <cstring> return O;
using namespace std;

struct status { Come si pu vedere, per leggere o scrivere l'intera struttura basta una singola
char name [80] ; chiamata a read() o write(). Non quindi necessario scrivere o leggere separatamente
float. ba lance; ogni singolo campo della struttura. Come si pu vedere da questo esempio, il
unsigned long account num;
}; - buffer pu essere costitito da qualsiasi tipo di oggetto.
;NOT.A.:.: '_~:::::'//~ Le conversioni di tipo presenti all'intemo delle chiamata a
int main()
read() e write() sono necessarie quando si opera su un buffer che non sia definito
{
come un array di caratteri. A causa della stretta verifica di tipo del C++, un
struct status ace;
pr.mtatore di un tipo non verr aut.amaticamente convertito in un puntatore di un
altro tipo.
strcpy(acc.name, "Rodolfo Trentini");
acc.balance = 1123.23; Se viene raggiunta la fine del file prima della lettura di 1111111 caratteri, read() si
--a-cc;-acrount_num = 34235678-;- - - - -- fermer e il buffer conterr i soli caratteri disponibili. Per conoscere il_numem...dL_
_c;ar~tteri etti, si pu utilizzare la funzione membro gcou.nt() il cui prototipo_.:_ __
--- --- . .
560 CAPITOLO 21 OPERAZIONI IH I/O SU FILE IN C++ 561

streamsize gcount( ); 21.5 Altre forme della funzione get()

Tale funzione restituisce il numero di caratteri letti dall'ultima operazione di Oltre alla forma mostrata precedentemente, la funzione get() viene modificata
input binario. Il seguente programma mostra un altro esempio di utilizzo di read() tramite overloading in vari modi. Di seguito vengono illustrati i prototipi delle tre
e write() e illustra l'uso di gcount(). versioni pi utilizzate:

#i nel ude <i ostream> istream &get(char *buf, streamsize num);


#i nel ude <fstream> istream &get(char *buf, streamsize num, char delim);
using namespace std; int get ( ) ;

int main{) La prima forma legge caratteri inserendoli nell'array puntato da buf ferman-
{ dosi dopo aver letto num-1 caratteri oppure finch non viene trovato il codice di
float fnum[4] (99.75, -34.4, 1776.0, 200.1}; fine riga o di fine file. Il codice nullo che conclude l'array puntato da buf verr
int i; aggiunto automaticamente da get(). Se nello stream di input viene trovato il carat-
tere di fine riga, questo non verr estratto. Rimarr nello stream fino all' operazio-
ofstream out("nurneri", ios::out I ios::binary);
i f( !out) {
ne di input successiva.
cout "Impossibile aprire il file.";
La seconda forma legge caratteri inserendoli nell'array puntato da buffer-
return 1; mandosi dopo aver letto num-1 caratteri oppure finch non viene trovato il
delimitatore, il codice di fine riga o il codice di fine file. Il codice nullo che con-
clude I'array puntato da buf verr aggiunto automaticamente da get(). Se nello
out.write((char *) &fnum, sizeof fnum); stream di input viene trovato il carattere delimitatore, questo 11011 verr estratto.
Rimarr nello stream fino all'operazione di input successiva.
out.close(); La terza versione di get() restituisce il prossimo carattere letto dallo stream.
Alla fine del file la funzione restituisce EOF. Questa forma di get() simile alla
for(i=O; i<4; i++) Il cancella l'array funzione getc() del C.
fnum[i] = O.O;

ifstream in("numeri", ios::in I ios::binary);


in.read((char *) &fnum, sizeof fnum); 21.6 La funzione getline()
Il controlla quanti byte sono stati letti Un'altra funzione membro di ogni classe stream di input che esegue operazioni di
cout in.gcount() " byte letti\n";
input geline(). Ecco i suoi prototipi:
for(i=O; i<4; i++) Il m~stra i valori letti dal file
cout fnum[i] << " "; istream &getline(char *buf, streamsize nim1);
istream &getline(char *buf, streamsize man, char delim);
in.close();
La prima forma legge caratteri inserendoli nell'array puntato da bief ferman-
return O; dosi dopo aver letto num-1 caratteri oppure finch non viene trovato il codice di
fine riga o di fine file. Il codice nullo che conclude" l'array puntato da bt!/verr
aggiunto automaticamente da getline(). Se nello stream di input viene trovato il
Questo programma scrive su disco e quindi rilegge un array di valori in virgo- carattere di fine riga, questo verr estratto ma non verr inserito nel buffer.
Ja mobile. Dopo la chiamata a read(), per determinare il numero di byte letti viene La seconda-formg-tegge-caratteri inserendoli-nell? i-ray-puntato- da b1if fer-
utilizzata gcount().-- - - - . -- - - ------ - mandosi dopo aver letto mmi-I caratteri oepur~Ji!1ch~non viene trovato il
- --- -- -, _ -~--<- -~
562 CAPITOLO 21 OPERAZIONI DI I/O SU FILE-IN C++ 563

delimitatore, il codice di fine riga o il codice di fine file. Il codice nullo che con- 21.7 Rilevamento della fine del file
clude I' array puntato da buf verr aggiunto automaticamei:ite da get(). Se nello
stream di input viene trovato il carattere delimitatore, questo verr estratto ma Per determinare il momento in cui si raggiunge la fine del file, si utilizza la fun-
non verr inserito nel buffer. zione membro eof() il cui prototipo :
Come si pu vedere, queste due versioni di getline() sono praticamente identi-
che a get(buf, num) e get(buf, num, delim). Entrambe le funzioni leggono caratteri bool eof( );
dallo stream di input e li inseriscono nell'array puntato da buffino ad aver letto
num caratteri o fino al raggiungimento del primo carattere delim. La differenza Questa funzione restituisce true quando viene raggiunta la fme del file e false
fra get() e getline() che quest'ultima legge ed elimina il delimitatore dallo stream in caso contrario.
di input. Il seguente programma utilizza eof() per visualizzare il contenuto di un file in
Ecco un programma che mostra l'uso della funzione getline(). Il programma esadecimale e in ASCII.
legge il contenuto di un file di testo una riga per volta e visualizza sullo schenno
le righe lette. /* Visualizza il contenuto del file specificato
sia in ASCII che in esadecimale. *I
Il Legge e visualizza un file di testo riga per riga.
.#include <iostream>
#include <iostream> #include <fstream>
#include <fstream> #i nel ude <cctype>
usi ng namespace s td; #include <iomanip>

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


.- .
using namespace std;

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


if(argc!=2) { {
cout "Uso: Display <nomefile>\n"; if(argc!=2) {
return 1; cout "Uso: Display <nomefile>\n";
return 1;
ifstream in(argv[l]); Il input

i f (!in) { ifstream in(argv[l], ios::in I ios::binary);


cout "Impossibile aprire il file di input.\n";
return 1; if(!in) {
cout "Impossibile aprire il file di input.\n";
return l;
char str[255];

while(in) { register int i, j;


in.getline(str, 255); Il il delimitatore standard '\n' int count = O;
-if (in) cout << str << endl; char c[l6];
}
cout. setf (i os: :uppercase);
in.close(); while(!in.eof()) {
for(i=O; i<;16 && !in,eof(); i++)
~turn O_;__ in.get{c[i]);
J_
----~~----
- -- -
--> -.
564 CAFqTOLO 21
O P E R A Z I O N I O I I / O --S-l;J- F-1 L E- I N -C ++ 565

if(i<l6) i--; Il si libera di eof


21.8 la funzione ignore()
for(j=O; j<i; j++)
La funzione membro ignore() consente di leggere e ignorare i caratteri presenti
cout setw(3) hex (int) c[j];
for(; j<l6; j++) cout " "; nello stream di input. Il suo prototipo :

cout << "\t"; istream &ignore(streamsize num=l, int_type delim=EOF);


for(j=O; j<i; j++)
if(isprint(c[j])) cout c[j); La funzione legge e ignora num caratteri (il valore standard 1) o un numero
e1se eout << " "; di caratteri inferiore nel caso in cui venga incontrato il carattere specificato da
delim (normalmente il codice EOF). Se viene incontrato il delimitatore, questo
cout endl :
non verr eliminato dallo stream di input. Qui inUype definito come un intero.
Questo programma legge il file TEST ignorando tutti i caratteri prima di uno
count++;
if(count==l6)
spazio oppure un totale di dieci caratteri. Quindi il programma visualizza la parte
count = O; rimanente del file.
cout << "Premere Invio per continuare: ":
cin.get(): #i ne1ude <i os t ream>
cout endl ; #i nel ude <fstream>
using namespace std;

int main()
in.close(); {
ifstream in("test"):
return O;
if(!in) {
cout "Impossibile aprire il file. \n":
Il programma produrr un output simile al seguente: return l;

2F 2A 20 44 69 73 70 6C 61 79 20 63 6F 6E 74 65 I* Display conte
/* Ignora fino a 10 caratteri o fino a trovare
6E 74 73 20 6F 66 20-7370 6!nl3 69 66 69 65 64 nts of specified
il primo spazio. *I
20 66 696C 65 D A 20 20 20 69 6E 20 62 6F 74 file.. in bot
in.ignore(lO, ' '):
68 20 41 53 43 49 49 20 61 6E 64 20 69 6E 20 68 h ASCII and in h
char e;
65 78 2E D A 2A 2F D A 23 69 6E 63 6C 75 64 ex *l #includ while(in) {
65 20 3C 69 6F 73 74 72 65 61 6D 2E 68 3E D A e <iostream.h> ..
in.get(c);
23 69 6E 63 6C 75 64 65 20 3C 66 73 74 72 65 61 #include <fstrea
if(in) cout e:
6D 2E 68 3E D A 23 69 6E 63 6C 75 64 65 20 3C m.h> #include <
63 74 79 70 65 2E 68 3E D A 23 69 6E 63 6C 75 ctype.h> #inclu
64 65 20 3C 69 6F 6D 61 6E 69 70 2E 68 3E D A de <iomanip.h> ..
in.close();
23 69 6E 63 6C 75 64 ~5 20 3C 73 74 64 69 6F 2E #include <stdio.
return O;
68 3E D -p;_ D A 60 61 69 6E 28 69 6E 74 20 61 h> main(int a
72 67 63 2C 20 63 68 61 72 20 2A 61 72 67 76 5B rgc, char *argv[
5D 29 D A 7B D A 20 20 69 66 28 61 72 67 63 ]) { . if(argc
21 3D 32 29 20 7B o A 20 20 20 20 63 6F 75 74 !=2) {.. cout
;--- - 20 3C 3C 20 22 55 73 61 67 65 3A 29 44 69 73 70 "Uso: Di sp
-P-!'elllere rnv;o:per continuare:
566 CAPITOLO 21 OP ERA ZIO N-1 O I I/ O SU --Fil E IN C + + 567

21.9 Le funzioni peek() e p1.1tback() istream &seekg(off_type offset, seekdir origine);


ostream &seekp(off_type offset, seekdir origine);
La funzione peek() consente di conoscere il prossimo carattere presente nello
stream di input senza eliminarlo dallo stream. Il suo prototipo : off_type un tipo definito da ios in grado di contenere il valore pi grande che
offset pu assumere. seekdir un'enumerazione, anch'essa definita in ios, che
int_type peek( ); determina il punto di riferimento.
II sistema di I/O del C++ gestisce due puntatori associati a un file. Uno il
La funzione restituisce il prossimo carattere presente nello stream o EOF nel puntatore di lettura che specifica la posizione nel file in cui avverr la prossima
caso in cui sia stata raggiunta la fine del file. Qui int_type definito come un operazione di input.L'altro il puntatore di scrittura che specifica la posizione in
intero. cui avverr la prossima operazione di output. Ogni volta che si verifica un'opera-
La funzione putback() reinserisce nello stream di input l'ultimo carattere let- zione di input o di output, l'appropriato puntatore viene automaticamente aggior-
to. Il suo prototipo : nato. L'uso delle funzioni seekg() e seekp() consente di accedere a un file in
modo non sequenziale.
istream &putback(char c); La funzione seekg() sposta il puntatore di lettura del file a offset caratteri rispet-
to all'origine specificata la quale pu corrispondere a uno dei seguenti valori:
dove c l'ultimo carattere letto.
ios::beg Inizio del file
ios::cur Posizione corrente
ios::end - Fine del ti.le
21.10 La funzione flush()
La funzione seekp() sposta il puntatore di scrittura del file a offset caratteri
Quando vengono eseguite operazioni di output, non sempre i dati vengono im- rispetto ali' origine specificata che deve corrispondere a uno dei tre valori prece--
mediatamente scritti sul dispositivo fisico connesso allo stream. In genere le in-- denti.
formazioni vengono conservate in un buffer interno fino al suo completo riempi-- Generalmente, le operazioni di I/O ad accesso diretto dovrebbero essere ese-
mento. Solo allora il contenuto del buffer verr scritto fisicamente sul disco. guite solo su fare aperti per operazioni binarie. La traduzione dei caratteri che pu
per possibile richiedere la scrittura fisica su disco anche prima che il buffer sia verificarsi nel caso dei file di testo, potrebbe alterare le richieste di posizionamento
completamente pieno. II prototipo della funzione flush() : rispetto all'effettivo contenuto del file.
II seguente programma mostra l'uso della_funzione seekp(). Il programma
ostream &flush( ); consente di modificare un determinato carattere di un file. Si deve specificare un
nome di file nella riga di comando seguito dal numero di caratteri corrispondente
Le chiamate a flush() possono essere utili quando un programma deve essere al carattere da modificare e seguito infine dal nuovo carattere. Si noti che il file
impiegato in ambienti instabili (ad esempio in casi in cui possono verificarsi ca- viene aperto in lettura e scrittura.
dute di tensione).
#i nel ude <i ostream>
NOTA ______,__ __ ___ Lo svuotamento del buffer pu essere provocato anche dalla #i nel ude <fstream>
chiusura di un file o dalla naturale fine del programma. #include <cstdlib>
using namespace std;

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


21.11 L'accesso diretto ai file { ---------
if(argc!=4) {
Nel sistema di I/O del C++, leoperaz1om d1 acesso diretto prevedono_!'uso-ddk--- -------- ---. CQll_t~~Jl: ___HANGE <nomefile> <carattere> <char>\n";----= __________ _
funzioni seekg() e seekp(). Le loro forme pi comuni sono:___ _ return l: - - - -- --
- - - ---- ---
568 CAPITOLO 21 OPERAZIONI DI I/O SU FILE IN C++ 569

in.seekg(atoi (argv[2]), ios: :beg);

fstream out(argv[l], ios::in I ios:-~out I ios::binary); while(i n.get (eh))


if{!out) { cout eh;
cout "Impossibile aprire il file.";
return l; return O;

out.seekp(atoi {argv[2]), ios: :beg);


Il seguente programma utilizza sia seekp() che seekg() per invertire i primi
out.put (*argv[3]); <num> caratteri di un file.
out.close();
#include <iostream>
return O; #include <fstream>
#include <cstdlib>
using namespace std;
Ad esempio, per cambiare il dodicesimo byte del file TEST sostituendogli
int main(int argc, char *argv[])
una Z, si deve utilizzare la seguente riga di comando:
{
if(argc!=3) {
change test 12 Z cout <<""Uso: Reverse <nomefile> <num>\n";
return l;
Il programma seguente utilizza la funzione seekg() per visualizzare il conte-
nuto di un file a partire dalla posizione specificata nella riga di comando.
fstream inout(argv[l]. ios::in I ios::out I ios::binary);
#include <iostream>
#i nel ude <fstream> if(!inout) {
#include <cstdl ib> cout "Impossibile aprire il file di input. \n";
using namespace std; return l;

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


{ - long e, i, j;
char eh; char cl, c2;
e = atol (argv[2]);
if(argc! =3)
cout "Uso: SHOW <nomefile> <posizione iniziale>\n"; for(i=O, j=e; i<j; i++, j--)
return 1; inout.seekg(i, ios::beg);
inout.get(cl);
inout.seekg(j, ios: :beg);
ifstream in(argv[l], ios: :in I ios: :binary); inout.get(c2);
if(! in) {
cout "Impossibile_ aprire il file."; i nout. seekp (i, ios: :beg);
return 1; inout.put(c2);
i nout. seekp (j, ios: :beg);
inout.put{cl);
- ---- -------
--
--~-:::----- _, __
570 CAPITOLO 21 -OPE_RAZIONllJ+.:-4-~U I-i Le 11< ..,- - . :).U___

inout.close(); 21.12 Lo stato delle operazioni di 1/0


return O;
Il sistema di I/O del C++ conserva una serie di informazioni di stato relative al
risultato di ogni operazione di I/O. Lo stato corrente del sistema di I/O contenu-
Per utilizzare il programma si deve specificare il nome del file da invertire, to in un oggetto di tipo iostate che un'enumerazione definita in 1bs che contiene
seguito dal numero di caratteri da invertire. Ad esempio, per invertire i primi 10 i seguenti membri: . "'
caratteri del file TEST si deve utilizzare il comando:
Nome Significato
reverse test 10 ios::goodbit Non impostato alcun bit di errore
ios::eofbit 1 se viene incontrata la fine del file; Oaltrimenti
Se il file contiene le seguenti parole: ios::failbit 1 quando si verifica un errore di I/O non fatale
(ammesso che esista); Oaltrimenti
Questa una prova. ios::badbit 1 quando si verifica un errore di I/O fatale;
O altrimenti
al termine dell'operazione conterr la frase:
possibile ottenere infonnazioni di stato sull 'I/O in due modi. In primo luo-
u atseuQna prova. go si pu richiamare la funzione membro rdstate(). Il suo prototipo :

iostate rdstate( );

Come conoscere la posizione corrente nel file La funzione restituisce lo stato corrente dei flag di errore. Come si pu imma-
ginare osservando l'elenco precedente, nel caso in cui non si verifichi alcun erro-
Per detenninare la posizione corrente all'interno del file si devono utilizzare le re, rdstate() restituisce goodbit. In caso contrario verr attivato un bit di errore.
funzioni: Il seguente programma illustra l'uso di rdstate(). Il programma visualizza il
contenuto di un file di testo. In caso di errore, il programma ne indica l'origine
pos_type tellg( ); utilizzando checkstatus().
pos_type tellp( );
#i nel ude <iostream>
pos_type un tipo definito da ios in grado di contenere il valore pi esteso che le #include -;.f:;tmarn"." ___ _
funzioni possono restituire. Il valore restituito da tellg() e tellp() pu essere utiliz- using namespace std;
zato come argomento per le seguenti forme di seekg() e seekp().
void checkst.atus(ifstream &in);
istream &seekg(pos_type pos);
int main(int argc, char *argv(])
ostream &seekp(pos_type pos); {
if(argc!=2) (
Queste funzioni consentono di salvare la posizione corrente nel file, eseguire cout "Uso: Display <nome fil e>\n";
altre operazioni sul file e di ripo;:arsi sulla posizione precedentemente salvata. return l;
572 CAPITOLO 21 OPERAZIONI-DI I/O SU FILE IN C++ 573

ifstream in(argv[l]); La funzione eof() stata gi discussa in precedenza. La funzione fail() restituisce il
valore logico vero quando e attivo il bit failbit. La funzione good restituisce il
i f(! in) {
valore l(!gico vero se non vi sono errori; in caso contrario restituisce il valore
cout "Impossibile aprire il file di input.\n"; logico falso.
return l;
Quando si verifica un errore, per consentire al programma ~i continuare si
dovr reinizializzare lo stato di errore impostato. A tale scopo si utilizza la funzio-
char e; ne clear() il cui prototipo :
whi 1e( in. get (e))
if (in) cout << e; void clear(iostate flags=ios::goodbit);
checkstatus(in);
Seflags uguale a goodbit (impostazione standard) vengono riportati a zero
tutti i flag di errore. Alternativamente si pu impostare jlags in modo da cancella-
checkstatus(in); // controlla lo stato finale re i soli flag di errore desiderati.
in.close();
return O;

21.13 Personalizzazione delle operazioni


void checkstatus(ifstream &in) di I/O sui file
ios::iostate i;
Nel Capitolo 17 si sono apprese le tecniche di overloading degli operatori di inse-
i = in.rdstate();
rimento ed estrazione rispetto a una data classe. In tale capitolo, si parlato solo
di operazioni di I/O da console. Ma poich tutti gli stream C++ hanno lo stesso
if(i & ios::eofbit) funzionamento, possibile utilizzare la stessa funzione di inserimento o estrazio-
cout "Raggiunta la fine del file\n"; ne modificata tramite overloading per produrre il proprio output sullo schermo o
else if(i & ios::failbit) su file. Il programma seguente rielabora l'esempio della rubrica del Capitolo 20
cout "Errore di I/O non fatale\n"; in modo da memorizzare un elenco di voci su disco. Il programma molto sem-
else if(i & ios::badbit) plice: consente di aggiungere nomi all'elenco o di visualizzare l'elenco sullo scher-
cout "Errore di I/O fatale\n"; mo. Tale programma legge e scrive i numeri telefonici tramite inseritori ed estrattori
personalizzati. Si potrebbe provare a migliorare il programma in modo da ricerca-
re un determinato numero o cancellare i null).eri non desiderati.
Questo programma restituisce sempre almeno un "errore". Al termine del ci-
clo while, l'ultima chiamata a checkstatus() indica che stata raggiunta la fine del #include <iostream>
file. Si trover la funzione checkstatus() molto utile nei propri programmi. #i nel ude <fstream>
L'altro modo in cui possibile determinare se si verificato un errore consiste #include <cstring>
nell'uso di una o pi delle seguenti funzioni: using namespace std;

bool bad( ); cl ass phonebook {


char name[BO];
bool eof( );
char areacode[4j;
bool fai!( );
char prefix[4];
bool good( ); char num[S];
public:
La funzione baci() r~stitl!is__eil valore logico vero quando attivo il bit .~dji~- ___ _ phonebook () { } ;
-------- - -'"~.

574 CAPITOLO 21 OPERAZIONI DI 1/0 SU FILE IN C++ 575

phonebook(char *n, char *a, char *p, char *nm)


{
strcpy(name, n); for(; ;)
strcpy(areacode, a); do {
strcpy(prefix, p); cout "l. I11111issione dei numeri\n";
strcpy(num, nm); cout "2. Visualizzazione dei numeri\n";
cout "3. Uscita\n";
friend ostream &operator(ostream &stream, phonebook o); cout "\nScegl iere un'opzione: ";
friend istream &operator(istream &stream, phonebook &o); cin >> e;
}; while(c<' 1' Il c>'3');

Il Visualizza il nome e il numero telefonico. switch(c) {


ostream &operator(ostream &stream, phonebook o) case 'l':
{ cin a;
stream << o.name << " "; cout << "Nuovo e 1emento: ";
stream << "(" << o.areacode << ") "; cout a; 11 vi sua 1i zzazi one
stream << o.prefix << "-"; pb a; // seri ttura su disco
stream << o.num << "\n"; break;
return stream; Il deve restituire lo stream case '2':
char eh;
pb.seekg(O, ios: :beg);
/I Input de 1 nome e de 1 numero te 1efoni co. while(!pb.eof()) {
istream &operator>>(istream &stream, phonebook &o) pb.get(ch);
{ if(!pb.eof()) cout eh;
cout << "Indicare il nome: ";
stream o.name; pb.clear(); //reinizializza eof
cout "Indicare il codice: "; cout endl ;
stream o.areacode; break;
cout "Indi care i 1 prefisso: "; case '3':
stream o.prefix; pb.close();
cout << "indicare il numero: "; return O;
stream o.num;
cout << "\n";
return stream;

Si noti che l'operatore modificato tramite overloading pu essere utilizza-


int main() to per scrivere su un file su disco o sullo schermo senza alcuna modifica. Questa
{
una delle caratteristiche pi importanti e utili dell'approccio alle operazioni di I/
phonebook a;
O seguito dal C++.
char e;

fstream pb("phone", ics: :in ios: :out I ios: :app);

if(!pb) {
--eout- "-Impossibile aprire il file della rubrica.\n";
return 1;
----- ------
: Capitolo 22

L'identificazione run-time
dei tipi e gli operatori cast

22.1 L'identificazione run-time dei tipi (RTTI)


22.2 Gli operatori di conversione cast
22.3 L'operatore dynamlc_cast

il C++ standard contiene due funzionalit che aiutano a


supportare una moderna programmazione a oggetti: l'identificazione run-time dei
tipi (abbreviata con RITI) e i nuovi operatori di cast. Nessuno di questi elementi
faceva parte delle specifiche originali del linguaggio C++ ma entrambi sono stati
aggiunti per fornire un migliore supporto al polimorfismo run-time. Il meccani-
smo RITI consente di identificare il tipo di un oggetto durante lesecuzione del
programma. Qli operatori di cast offrono un metodo di conversione pi sicuro e
controllato. Poich uno degli operatori di cast, dynamic_cast, fa riferimento diret-
tamente all'identificazione run-time dei tipi, opportuno discutere entrambi gli
argomenti nello stesso capitolo.

22.1 L'identificazione run-time dei tipi (ATTI)


L'identificazione run-time dei tipi pu per qualcuno rappresentare una novit in
quanto non presente in alcun linguaggio non polimorfico, ad esempio il C. Nei
linguaggi non polimorfici, non vi alcuna necessit di avere informazioni run-
time sui tipi poich il tipo di un oggetto gi noto in fase di compilazione (ovvero
viene specificato durante la scrittura del programma). Al contrario, nei linguaggi
polimorfici come if C++, possono esistere situ.azioni in cui il tipo di un oggetto
non noto in fase di compilazione poich l'esatta natura dell'oggetto rimane in-
determinata fino all'esecuzione del programma. Come descritto nel Capitolo 17,
il C++ implementa il polimorfismo tramite l'uso di gerarchie di classi, funzioni
virtuali e puntatori alla classe base. Poich i puntatori alla classe base possono
essere utilizzati per. puntare agli ogget!i della classe base o a qualsiasi oggetto da
essa derivato, noii sempre possibile sapere in anticipo quale tipo di oggetto sar
puntato da un puntatore alla classe base. Questa determinazione pu essere ese-
guita solo tramite l'identificazione run-time dei tipi.
Per conoscere-il tipo dhm-oggetto si utilizza typeid. Per utilizzare typeid si
deve includere il file. di intestazione <typeinfo>. - - -- - - - - ----
-- -~----=---::
CAPITOLO 22
L'IDENTIFICAZIONE RUNTIME DEI TIPI E GLI OP-E-RATORI CAST 579
578

Nonnalmente typeid viene utilizzato nel seguente modo: cout "Il tipo di i : " typeid(i) .name();
cout endl ;
cout "Il tipo di f : " typeid(f) .name();
typeid(oggetto) cout endl ;
co.ut "Il tipo di p : 11 typeid(p).name();
Qui oggetto l'oggetto di cui si vuole conoscere il tipo. Pu trattarsi di qual- cout endl ;
siasi tipo, inclusi i.tipi interni del linguaggio e le classi create dal programmatore.
typeid restituisce l'indirizzo di un oggetto di tipo type_info che descrive il tipo di cout "Il tipo di obl : 11
typeid(obl) .name(};
oggetto. cout endl ;
La classe type_info definisce i seguenti membri pubblici: cout "Il tipo di ob2 : 11
typeid(ob2) .name(};
cout << "\n\n";
bool operator==(cO!].St type_info &ob); if(typeid{i) == typeid(j)}
bool operator!=(const type_info &ob); cout "I tipi di i e j sono uguali";
bool before(const type_info &ob);
const char *name( ); if(typeid(i) != typeid(f}}
cout "I tipi di i e f sono differenti";
Per eseguire confronti fra i tipi si utilizzano gli operatori == e != modificati
tramite overloading. La funzione before() restituisce true se l'oggetto chiamante if(typeid(obl} != typeid(ob2))
precede l'oggetto utilizzato come parametro nell'ordine di raccolta. Questa fun- cout "I tipi di obl e ob2 sono differenti";
zione ha principalmente un utilizzo interno. Il valore che restituisce non ha niente
a che vedere con l'ereditariet e le gerarchie di classi. La funzione name() restitu- return O;
isce un puntatore al nome del tipo.
Ecco un semplice esempio che utilizza typeid.
Ecco l'output prodotto dal programma:
11 Un semp 1i ce esempi o che usa typei d.
lii nel ude <i ostream> Il tipo di i : int
lii nel ude <typeinfo> Il tipo di f : float
using nainespace std; Il tipo di p : char *
Il tipo di obl : class myclassl
class myclassl Il tipo di ob2 : cl ass mycl ass2
Il -
}; I tipi di i e j sono ugual i
I tipi di i e f sono differenti
cl ass mycl ass2 I tipi di obl e ob2 sono differenti
Il
}; L'utilizzo pi importante di typeid si verifica quando tale operatore viene ap-
plicato attraverso un puntatore a una classe base polimorfica. In questo caso, typeid
int main() restituisce automaticamente il tipo dell'oggetto effettivamente puntato, che pu
{ essere un oggetto appartenente anaclasse base oppure un oggetto da essa deriva-
int i, j; to. Si ricord_i che~e_untatore a una classe base pu puntare a oggetti della classe
float f; base oppure a uno qualsiasi degli oggetti da essa derivati. Pertanto, utilizzando
char *p; typeid possibile determinare run-time il tipo dell'oggetto puntato dal puntatore
myclassl obl;
alla classe base.. Questo principi? ~~i~ostrato dal seguente i;rro.graroma.
myclass2 ob2;
L I oE N T I F I e A z I o N E R u N . T I M E o EI T I p I E GL I o p E RAT o RI eAST- 581
580 CAPITOLO 22

Il Un esempio che usa typeid su una gerarchia di classi polimorfica. p punta a un oggetto di tipo class Mammal
p punta a un oggetto di tipo class Cat
#include <iostream>
p punta a un oggetto di ti po cl ass Pl atypus
#i nel ude <typei nfo>
using namespace std;
Come si detto, quando typeid viene applicato a un puntatore alla classe base
class Mammal { di un tipo polimorfico, il tipo dell'oggetto puntato viene determinato run-time,
public: come indicato dall'output prodotto dal programma.
virtual bool lays_eggs() { return false; } Il Mammal polimorfica In ogni caso, quando typeid viene applicato a un puntatore a una gerarchia di
Il ... classi non polimorfica, allora si ottiene il tipo base del puntatore. Questo significa
}; che non viene fatto alcuno sforzo per determinare l'oggetto cui il puntatore sta
effettivamente puntando. Ad esempio si provi a trasformare in commento la paro-
class Cat: public Mammal { la riservata virtual che precede la funzione lays_eggs() in Mamma! e a compilare
public:
ed eseguire il programma. Verr visualizzato il seguente output.
Il
};
p punta a un oggetto di tipo class Mammal
class Platypus: public Mammal { p punta a un oggetto di tipo cl ass Mammal
public: p punta a un oggetto di tipo class Mammal
bool lays_eggs() { return true;
Il Poich. Mammal non pi una classe polimorfica, il tipo dell'oggetto sar
}; Mamma! poich questo il tipo del puntatore.
Dato che typeid viene in genere applicato a un puntatore deindirizzato (ovve-
int main() ro un puntatore cui viene applicato l'operatore *), stata creata una speciale esten-
{ sione per gestire la situazione in cui il puntatore deindirizzato nullo. In questo
Mamma 1 *p, AnyMamma 1 ; caso, typeid lancia l'eccezione bad_typeid.
Cat cat;
Gli indirizzi di un oggetto di una gerarchia polimorfica di classe funzionano
Pl atypus pl atypus;
come i puntatori. Quando si applica typeid a un indirizzo di un oggetto di una
p = &AnyMamma 1; classe polimorfica, typeid restituisce il tipo dell'oggetto cui si sta facendo effetti-
cout "p punta a un oggetto di tipo "; vamente riferimento, il quale pu essere un tipo derivato. La circostanza in cui si
cout ~< typeid(*p) .name{) endl; utilizza pi frequentemente questa funzionalit quando gii oggetii"vengono pas-
sati alle funzioni per indirizzo. Ad esempio, nel seguente programma, la funzione
p = &cat; WhatMammal() dichiara un parametro indirizzo a oggetti di tipo Mamma!. Questo
cout "p punta a un oggetto di ti po "; significa che a WhatMammal() possono essere passati riferimenti a oggetti di tipo
cout typeid{*p).name() endl; Mamma! o di qualsiasi classe derivata da Mamma!. Quando a questo parametro
viene applicato l'operatore typeid, viene restituito il tipo dell'oggetto effettiva-
p = &platypus; mente passato.
cout "p punta a un oggetto di ti po ";
cout typeid(*p) .name() endl;
Il Usa typeid con un indirizzo.
#include <iostream>
return O;
#include <typeinfo>
.using namespace. std;

Ecco l'output prodotto dal programma: -class Mammal


public:
CAPITOLO 22 L'ID ENTI F I eA z I o NE R u N. TIME DE I TI pI E GLI op ERA T O.R I eA sT 583
582

-virtual bool lays_eggs() { return false; ) Il Mammal polimorfica Ad esempio, la seguente istruzione perfettamente lecita:
Il
}; cout typeid(int) .name();

class Cat: public Mamma1 { L'uso principale di questa forma di typeid consiste nell'ottenere un oggetto di
public: tipo type_info che descrive il tipo specificato e che possa essere utilizzato in un 'istru-
Il zione di confronto fra tipi. Ad esempio, questa forma di typeid indica che i gatti
);
non amano l'acqua:
class Platypus: public Mammal {
public: void WhatMammal (Mammal &ob)
bool. lays_eggs() { return true; {
cout "ob l 'indirizzo di un oggetto di ti po ";
Il cout typeid(ob) .name() endl;
};
if(typeid(ob) :: typeid(Cat))
Il Illustra l'uso di typeid con un parametro di tipo indirizzo. cout << "I gatti non amano l'acqua.";
void WhatMammal (Mammal &ob)
{
cout "ob l'indirizzo di un oggetto di tipo ";
cout typeid(ob).name() endl; Una semplice applicazione dell'identificazione
run-time dei tipi

int main() Il seguente programma suggerisce le potenzialit del sistema RTII. Nel program-
{ ma, la funzione factory() crea istanze di vari tipi di oggetti derivati dalla classe
Mamma 1 AnyManma 1; Mammaf (una funzione che produce oggetti viene spesso chiamata "fabbrica di
Cat cat; oggetti" o "object factory"). II tipo specifico dell'oggetto creato viene determina-
Platypus platypus; to dal risultato di una chiamata a rand(), il generatore di numeri casuali del C++.
Pertanto non vi alcun modo di sapere in anticipo quale tipo di oggetto verr
WhatManma 1 (AnyManma 1) ; genera~o.:..~~?gramma crea 1Ooggetti e conta il numero di mammiferi. Poich
WhatManma 1.( cat); una chiamata a factory() pu generare qualsiasi tipo di mammifero, il programma
WhatMammal (platypus); determina il tipo dell'oggetto facendo affidamento su typeid.
return O;
Il Illustra l'identificazione run-time dei tipi.
#i nel ude <i ostream>
using namespace std;
Ecco l'output prodotto dal programma:
class Mammal {
ob l'indirizzo di un oggetto di tipo class Mammal publ ic:
ob l'indirizzo di un oggetto di tipo .class Cat virtual bool lays_eggs() {..feturn false; } Il Mammal polimorfica
ob 1 'indirizzo di un oggetto di ti po cl ass Pl atypus Il
h
Vi anche una seconda forma di typeid che accetta come argomento il nome
di un tipo. Ecco l'uso <!1..9.l:!~ta forma: ___ . class Cat: public Mammal {
Qublic: - - --
L'IDENTIFICAZIONE RUN-TIME DEI 'UPI E GLI OPERATORI CAST 585
584 CAPITOLO 22

cout << " Cani: " << d << endl;


Il cout << " Gatti: " <<-e endl;
}; cout << " Ornitorinchi: " << p << endl;

class Platypus: public Marrmal { return O;


publ ic:
bool lays_eggs() { return true;
Il Ecco l'output del programma:
};

class Dog: public Marrmal { L'oggetto di tipo class Platypus


publ ic: L'oggetto di ti po cl ass Pl atypus
L'oggetto di ti po cl ass Cat
Il L'oggetto di tipo class Cat
};
L'oggetto di ti po cl ass Pl atypus
11 Una "fabbrica" di oggetti derivati da Marrmal. L'oggetto di tipo class Cat
Manmal *factory{) L'oggetto di tipo class Dog
{ . L'oggetto di tipo class Dog
switch(rand{) % 3 ) { L'oggetto di tipo class Cat
case O: return new Dog; L'oggetto di ti po cl ass Pl atypus
case 1: return new Cat;
case 2: return new Pl atypus; Ani mal i generati:
Cani: 2
return O; Gatti: 4
Ornitorinchi : 4

int main{)
{ typeid pu essere applicato anche a classi tempiste
Marrmal *ptr; 11 puntatore alla cl asse base
int i; L'operatore typeid pu essere applicato anche a classi template. II tipo di un og-
int c=O, d=O, p=O; getto che un'istanza di una classe template in parte detenninato dai valori
utilizzati per i suoi dati generici nel momento in cui l'oggetto viene istanziato.
11 genera e conta gli oggetti Due istanze della stessa classe template che sono create utilizzando dati differenti
for(i=O; i<lO; i++) { rappresentano pertanto tipi differenti. Ecco un semplice esempio:
ptr = factory(); 11 generate an object
Il Uso di typeid con i template.
cout "L'oggetto di tipo " typeid(*ptr) .name();
#include <iostream>
cout endl ; usi ng namespace std;

11 conteggi o template <class T> class myclass


if(typeid(*ptr) == typeid(Dog)) d++; T a;
if(typeid(*ptr) == typeid(Cat)) c++; public:
if(typeid(*ptr) == typeid(Platypus)) p++; myc 1as s (T i ) a =i;
il
};
cout endl ;
cout "Ani mal i generatfi\n".i ::::::-:::::-:-.:-: -::::- _
-----586 CAPITO_l.Q __22 L'IDENTIFICAZIONE RUN-TIME DEI TIPI E GLI OPERA TORICA~ 587

int main() 22.2 Gli operatri di conversione cast


{
myclass<int> ol(lO), o2(9); Il linguaggio C++ definisce cinque operatori di cast. Il primo rappresentato
mycl ass<dou~le> o3(7 .2);_ dalla fonna tradizionale di conversione cast ereditata dal linguaggio C. Gli altri
quattro sono stati aggiunti pochi anni fa. Tali operatori sono dynamic_cast,
cout "Il tipo di ol ";
const_cast, reinterpreLcast e static_cast. Questi operatori offrono un maggiore
c'out typeid(ol) .name(} endl;
controllo sulle conversioni di tipo.
cout "Il tipo di o2 ";
cout typeid(o2) .name() endl;

cout << "Il tipo di o3 ":


22.3 L'operatore dynamic_cast
cout typeid(o3) .name() endl;
dynamic_cast forse l'operatore di cast pi importante. Tale operatore esegue
cout endl ; una conversione run-time che verifica la validit della conversione. Se al momen-
to dell'esecuzione di dynamic_cast la conversione errata, allora l'operatore
if(typeid(ol) == typeid(o2)) dynamic_cast non ha successo. Ecco la fonna generale di dynamic_cast:
cout "ol e 02 hanno 1o stesso ti po\n";
dynamic_cast<tipo-destinazione> (espressione)
if(typei.d(ol) == typeid{o3))
cout "Errore\n";
Qui tipo-destinazione specifica il tipo di destinazione della conversione cast
else
ed espressione l'espressione che viene convertita nel nuovo tipo. Il tipo di desti-
cout "ol e o3 hanno tipi differenti\n";
nazione deve essere un puntatore o un indirizzo e lespressione convertita deve
return O; fornire un puntatore o un indirizzo. Pertanto dynamic_cast pu essere utilizzato
per convertire un tipo di puntatore in un altro o un tipo di indirizzo in un altro.
Lo scopo di dynamic_cast quello di eseguire conversioni su tipi polimorfici.
Ecco loutput prodotto dal programma. Ad esempio, date due classi polimorfiche chiamate B e D, con D derivata da B, un
dynamic_cast pu sempre convertire un puntatore D* in un puntatore B*. Questo
Il tipo di obl class myclass<int> dovuto al fatto che un puntatore alla classe base pu sempre puntare a un oggetto
Il t-ipo--di -ob2- class mvclass<int> derivato. Ma dynamic_cast pu convertire un puntatore D* in un puntatore B* solo- - - -----
Il tipo di Q.3 class my~l ass<doubl e> se l'oggetto puntato effettivamente un oggetto di tipo D. In generale dynamic_cast
ha successo se il puntatore (o l'indirizzo) convertito un puntatore (o un indiriz-
ol e o2 hanno lo stesso tipo zo) a un oggetto del tipo di destinazione o un oggetto derivato da tale tipo. In tutti
ol e o3 hanno tipi differenti gli altri casi la conversione non avr successo. Se. la conversione non ha successo,
quando la conversione riguarda i puntatori, dynamic_cast produce il valore null
. Come si pu vedere. anche se i due oggetti appartengono alla stessa classe mentre quando riguarda gli indirizzi viene lanciata un'eccezione bad_cast.
template, se i loro dati parametrizzati non corrispondono, essi non rappresentano Ecco un semplice esempio. Si supponga che Base sia una classe polimorfica
tipi equivalenti. Nel programma, o1 di tipo myclass<int> e o3 di tipo e che Derived derivi da Base.
mycl.ass<double>. Pertamo_essi rappresentano tipi differenti.
L'identificazione run-rime dei tipi non una funzionalit che possa risultare Base *bp, b_ob;
utile in molti programn_ Tuttavia, quando si impiegano tipi polimorfici, consen- Deriv,ed *dp, d_ob;
te di sapere quale tipo di oggetto si sta impiegando in una determinata situazione.
--bp .= &d::..eb-:-H -i-1-puntatore alla classe base punta a un oggetto Derived
588 CAPITOLO 22 L'IDENTIFl-CAZIONE RUNTIME oErTIPI E GLI OPERATORI CAST 589

dp = dynamic cast<Derived *> (bp); Il Conversione nel tipo derivato cout endl ;
if(dp) cout :;-< "Cast -K";
bp = dynamic cast<Base *> (&d_ob);
Qui la conversione dal puntatore alla classe base bp in un puntatore alla classe if(bp) { -
derivata dp funziona perch bp sta in effetti puntando ad un oaaetto Derived. cout "Conversione cast da Derived * a Base * - OK. \n";
Pertanto questo frammento di codice esegue la conversione. Ma ~el successivo
0
bp->f();
frammento, la conversione non ha successo poich bp punta a un oggetto Base ed else
cout << "Errore\n";
vietato convertire un oggetto base in un oggetto dcrivuto.
cout endl ;
bp = &bob; Il il puntatore alla classe base punta a un oggetto Base
dp = dy;ami e_cast<Deri ved *> (bp); 11 errore bp = dynami e cas t<Base *> (&b_ob) ;
if(!dp) cout "Cast Fails"; if(bp) { -
cout "Conversione cast da Base * a Base * - OK. \n";
In questo frammento di codice la conversione non ha successo. bp->f();
Il seguente programma mostra le varie situazioni che possono essere gestite else
con dynamic_cast. cout << "Errore\n";

Il Illustra l'uso di dynamic_cast. cout endl ;


l nel ude <i ostream>
using namespace std; dp = dynamiC cast<Derived *> (&b_ob);
if(dp) -
cl ass Base { cout "Errore\n";
public: else
virtual void f() { cout "All'interno di base\n"; } cout "Conversione cast da Base * a Derived * - Errore. \n";
Il cout endl ;
};

class Drived : public Base { bp = &d_ob; 11 bp punta a un oggetto Derived


publ ic: dp = dynamic_cast<Derived *> (bp);
void f() { 'cout "All'interno di Derived\n"; ) if(dp) { - - ------
cout "Conversione cast di bp in un Derived * - OK\n"
" un errore poich bp in realt punta\n"
"a un oggetto Derived. \n";
i nt mai n() dp->f();
{ else
Base *bp, b_ob; cout "Errore\n";
Derived *dp, d_ob;
cout endl ;
dp = dynamic_cast<Derived *> (&d_ob);
if(dp) { bp = &b_ob; Il bp punta a un oggetto Base
cout "Conversione cast da Derived * a Derived * _ OK. \n"; dp = dynami c cast<Deri ved *> (bp);
dp->f(); jf(dp) -
els.e cout << "Errore";
cout .'.'._~r_o:e\n"; else {
_ ~ut-::-<< -"Ora~ onverte bp in un Deriv'eCf'*'\r" <<
- ------ -
----
- -----=-.=------~ -~

590 CAPITOLO 22 . L'IDENTIFICAZIONE RUNTIME DEI TIPI E GLI OPERATORI CAST 591

" un errore poich in realt .!lp \n" Sostituzione di typeid con dynamic_cast
punta a un oggetto di tipo Base. \n";
_p.operatore dynamic_cast pu in alcuni casi essere utilizzato al posto di typeid.
Ad esempio, si supponga ancora che Base sia una classe base polimorfica da cui
cout endl ; deriva Derived. Il seguente frammento di codice assegna a dp l'indirizzo dell' og-
getto puntato da bp se e solo se l'oggetto veramente di tipo Derived.
dp ~ &d _ob; 11 dp punta a un oggetto Deri ved
bp = dynamic_cast<Base *> (dp); Base *bp;
if(bp) { Oerived *dp;
cout "Conversione cast di dp in Base * - OK. \n";
bp->f();
Il
if(typeid(*bp) == typeid(Derived)) dp = (Derived *) bp.;
else
cout "Error\n"; In questo caso, per eseguire la conversione viene utilizzata un'operazione cast
tradizionale. Tale operazione sicura grazie al fatto che l'istruzione if controlla
return O;
l'accettabilit del cast impiegando typeid prima di eseguire la conversione. Ma
esiste un modo migliore per svolgere l'operazione: sostituire gli operatori typeid e
!'.istruzione if con la seguente istruzione contenente dynamic_cast.
Ecco l'output prodotto dal programma.

Conversione cast da Derived * a Derived * - OK.


dp = dynamic_:a~t<Derived *> (bp);
All'interno di Derived
Poich dynamic_cast ha successo solo se l'oggetto da convertire un oggetto
Conversione cast da Derived * a Base * - OK. del tipo di destinazione o un oggetto di un tipo da esso derivato, dopo I' esecuzio-
All'interno di Derivd ne di questa istruzione dp conterr nullo un puntatore a un oggetto di tipo Derived.
Dato che dynamic_cast ha successo solo se la conversione cast consentita, in
Conversione cast da Base * a Base * - OK. queste situazioni pu semplificare la logica del programma. Il seguente program-
All'interno di Base ma illustra il modo in cui dynamic_cast pu essere utilizzato per sostituire typeid.
L'operazione viene eseguita due volte, la prima con typeid e la seconda con
Conversione cast da Base * a Derived * - Errore. dynamic_cast.
Conversione cast di bp in un Derived * OK
poi ch bp n realt punta
Il Usa dynamic_cast al posto di typeid.
#include <iostream>
a un oggetto Deri ved.
#include <typeinfo>
All'interno di Derived
using namespace std;
Ora converte bp in un Deri ved *
cl ass Base {
un errore poich bp in realt punta
public:
a un oggetto di tipo Base.
virtual void f() {}
};
Conversione cast di dp in Base * - OK.
A11 'interno di Oeri ved
class Deriv.ed_~_public Base
publi'c:
void derivedOnly() {
-592 CAPITOLO 22 --L~IDENTIFICAZIONE RUN-TIME DEI TIPI E GLI OPERATORI CAST --593

cout " un oggetto di ti po Deri ved. \n"; Come si pu vedere, l'uso di dynamic_cast semplifica la logica necessaria per
} convertire un puntatore alla classe base in un puntatore alla-classe derivata. Ecco
}; l'output del programma:
int main()
Conversione cast da Base a Derived fallita.
{
un oggetto di tipo Derived.
Base *bp, b_ob;
Conversione cast da Base a Derived fallita.
Derived *dp, d_ob; un oggetto di tipo Derived.

I I ************************************
11 usa typei d Uso di dynamic_cast con classi template
11 ************************************
bp = &b_ob; L'operatore dynamic_cast pu essere utilizzato anche con le classi template. Ad
if(typeid{*bp) == typeid(Derived))
esempio,
dp = (Derived *) bp;
dp->deri vedOn 1y O;
Il Illustra l'uso di dynamic_cast su classi template.
else #include <icistream>
cout "Conversione cast da Base a Derived fallita.\n"; using namespace std;

bp = &d_ob; template <class T> class Num {


i f(typei d {*bp) == typei d (Deri ved)) protected:
dp = (Derived *) bp; T val;
dp->derivedOnly(); public:
Num(T x) { val = x; }
else vi rtua 1 T getva 1 O { return va 1 ; )
cout "Errore, 1a conversione cast dovrebbe funzionare! \n"; Il
};
Il ************************************
Il usa dynamic_cast template <class T> class SqrNum public Num<T> {
public:
I I ******~*****************************
bp = &b_ob; SqrNum{T x) : Num<T>{x) { }
dp = dynamic cast<Oerived *> (bp); T getval O { return val * val;
if{dp) dp->d;rivedOnly(); );
else
cout "Conversione cast da Base a Derived fallita.\n"; int main()
{
bp = &d_ob; Num<int> *bp, numint_ob(2):
dp = dynamic_cast<Derived *> (bp);. SqrNum<int> *dp, sqrint_ob(3);
if{dp) dp->derivedOnly(); Num<double> numDouble_ob{3.3);
els~ =--
cout "Errore, la conversione cast dovrebbe funzionare!\n": bp = dynami e cast<Num<i nt> *> (&sqrint ob);
if(bp) { - -
return O; cout "Conversione cast da SqrNm<int>* a Num<int>* - OK. \n";
cout "Il valore " bp->getval O -endl;
} -~else ____ _
. ____:..=::.::_ - - - - :... __ :_ . -~-:-----~
594 CAPITOLO 22 L'IDENTIFICAZION!;__RUN-TIME DEI TIPI E GLI OPERATOR-r-e-Asr-----595

cout << "Errore\n"; L'operatore const_cast

cout endl; L'operatore const_cast utilizzato per rimuovere esplicitamente lattributo const
e/o volatile in una conversione cast. Il tipo di destinazione deve essere lo stesso del
dp = dynamic_cast<SqrNum<int> *> (&numint ob); tipo di origine ad eccezione dell'eliminazione degli attributi const e/o volatile.
if{dp) Normalmente l'operatore const_cast viene utilizzato per rimuovere l'attributo
cout << "Errore\n"; const. Ecco la forma generale di const_cast.
else {
cout "Conversione cast da Num<int>* a SqrNum<int>* non consentita. \n";
cout "Non si pu eseguire una conversione cast di un puntatore alla const_cast<tipo> (espressione)
cl asse base\n";
cout "in un puntatore a una classe derivata. \n"; Qui, tipo specifica il tipo di destinazione della conversione ed espressione
l'espressione da convertire nel nuovo tipo. Il seguente programma illustra l'utiliz-
cout endl ; zo di const_cast.

bp = dynamic_cast<Num<int> *> (&numDouble_ob); Il Illustra l'uso di const_cast.


if(bp) #i nel ude <i ostream>
cout << "Errore\n"; using namespace std;
else
cout "Conversione cast impossibile da Num<double>* a Num<int>*.\n"; void sqrva-1 (const int *val)
cout "Sono due tipi differenti. \n"; {
i nt *p;
return O;
Il elimina l'attributo const.
p = const_cast<int *> (val);
Ecco l'output del programma:
*p =*val **val; Il ora modifica l'oggetto tramite v
Conversione cast da SqrNum<int>* a Num<int>* - OK.
Il valore 9
int main()
Conversione cast da Nu-m<int>* a Sqr-Nliiii<iff>*non consentita. {
Non si pu eseguire una conversione cast di un puntatore alla classe base int X = 10;
in un puntatore a una classe derivata.
cout "x prima della chiamata: " x endl;
Conversione cast impossibile da Num<double>* a Num<int>*. sqrval (&x);
Sono due ti pi differenti. cout "x dopo la chiamata: " x endl;

Ci che questo esempio intende illustrare il fatto che non possibile utiliz- return O;
zare dynamic_cast per convertire un puntatore a un tipo di istanziazione template
in un puntatore a un altro.tipo di istanza. Si ricordi: l'esatto tipo di un oggetto di
una classe template determinato dal tipo dei dati utilizzati per creare l'istanza Ecco di seguito l'output prodotto da questo programma:
dehemplate. Pertanto Num<double> e Num<int> sono due tipi differenti.
x prima della chiamata: 10
....Lf1.9.Q.Q_li!_ chiamata: 100

- - + - ------
596 CAPITOLO 22 L'IDENTIFICAZIONE RUN-TIME DEI TIPT""E-GTIOP ..ERATORI CAST- 597

Come si pu vedere, x stato modificato da sqrval() _anche se il parametro di static_cast<tipo> (espressione)


sqrval() stato specificato come un puntatore const.
L'operatore const_cast pu essere utilizzato anche per eliminare l'attributo Qui, tipo specifica il tipo di destinazione della classe ed espressione l' espres-
const da un indirizzo. Ad esempio ecco una rielaborazione del programma prece- sione da convertire nel nuovo tipo.
dente che prevede un passaggio del valore tramite un indirizzo const. L'operatore static_cast sostituisce in generale la conversione cast tradiziona-
le. Tale operatore non fa altro che eseguire una conversione cast non polimorfica.
Il Usa const_cast su un indirizzo const. Ad esempio, il seguente programma converte un valore int in un double.
#include <iostream>
using namespace std; "'.
~--.
Il Usa static_cast.
#include <iostream>
void sqrval (const int &val) using namespace std;
{
11 elimina l'attributo const su val int main()
const_cast<int &> (val) = val * val; {
int i;

int main() for(i=O; i<lO; i++)


{ cout static_cast<double> (i) I 3 " ";
int X = 10;
return O; .
cout "x prima della chiamata: " x endl;
sqrval (x);
cout "x dopo la chiamata: " << x << endl;
l'operatore reinterpret_cast
return O;
L'operatore reinterpret_cast converte un tipo in un tipo radicalmente differente.
Ad esempio pu trasformare un puntatore in un intero e viceversa. Pu essere
Questo programma produce lo stesso output del precedente. Anche questo utilizzato anche per eseguire conversioni fra tipi inerentemente incompatibili. La
programma funziona per il solo motivo che const_cast rimuove temporaneamen- sua forma generale :
te l'attributo const da val, consentendo dunque di utilizzarlo per assegnare un
nuovo valore all'argomento chiamante (in questo caso x). reinterpret_cast<tipo> (espressione)
Occorre per notare che l'uso di const_cast per eliminare l'attributo const
pu essere pericoloso. Dunque opportuno impiegarlo con cura. Qui tipo specifica di tipo di destinazione della conversione e espressione
Un ultima annotazione: solo const_cast pu rimuovere l'attributo const. Tale l'espressione da convertire nel nuovo tipo.
operazione non pu invece essere eseguita n con dynamic_cast, n con static_cast L'uso dell'operatore reinterpret_cast illustrato dal seguente esempio.
e neppure con reinterpret_cast.
11 Un esempio che usa reinterpret_cast.
#include <iostream>
l'operatore static_cast using namespace std;

L'operatore static_cast esegue una conversione non polimorfica. Tale operatore i,nt main()
pu essere utilizzato per ogni conversione standard. Non verr eseguita alcuna
verifica run-time. La sua forma generale : - -- - --
598 _.b,PITOLO 22

int i; : Capitolo 23
char *p = "Questa una stringa";
Namespace, f~nzioni
i = rei nterpret cast<i nt> (p); 11 conversione cast di un puntatore in un : di conversione e altri
intero -
argomenti avanzati
cout i;
23.1 I namespace
return O;
23.2 Lo spazio dei nomi std
23.3 Creazione di funzioni di conversione
Qui reinterpreLcast converte il puntatore p in un intero. Questa conversione 23.4 Funzioni membro const e mutable
rappresenta una modifica radicale di tipo e dunque un buon esempo di utilizzo 23.5 Funzioni membro volatile
di reinterpret_cast.
23.6 Costruttori espliciti
23.7 Uso della parola riservata asm
23.8 Specifiche di linking
23.9 Operazioni di I/O su array
23.10 Uso di array dinamici
23.11 Uso di I/O binario con stream basati
su array
23.12 Riepilogo delle differenze esistenti
fra Ce C++

Ouesto capitolo descrive l'uso degli spazi dei nomi


(namespace) e altre funzionalit avanzate fra le quali le funzioni di conversione,.-.
i costruttori espliciti, le funzioni membro const e volatile, la parola riservata asm
e le specifiche di linking. Il capitolo termina con una discussione sulle operazioni
di VO basate su array e un riepilogo delle differenze fra C e C++.

23.1 I namespace
I namespace (spazi dei nomi) sono stati brevemente introdotti in precedenza. Si
tratta di un'aggiunta relativamente recente al linguaggio C+:i-. Il loro scopo
quello di localizzare il nome degli identificatori per evitare collisioni fra i nomi .
.. L'ambiente di programmazione C++ ha visto un'esplosione di nomi di variabili,
_!!inzioni ~ classL Prima dell'invenzione di namespace, tutti questi nomi erano a
__caccia di un proprio angolino nello.spazio dei nomi globale e questo portava alla
p._al?cita di vari conflitti. Ad esempio, se il programma definhli una funzione chia-
600 CAPITOLO 23 ...!) I'\ IVI C. .Jr,.... I,,, t. 1 o '-' '' ._ 1 o,;''

mata abs(), questa poteva (a seconda dei valori utilizzati per i parametri) sovrapporsi i nt 1owerbound;
alla funzione om~mima della libreria standard, poich entrambi i nomi si sarebbe-
ro trovati nello stesso spazio dei nomi. Potevano sorgere collisioni fra nomi quan- cl ass counter
do un programma utilizzava due o pi librerie prodotte da terzi. In questo caso era int count;
public:
possibile che un nome definito da una libreria entrasse in conflitto con lo stesso
counter(int n)
nome definito dall'altra libreria. La situazione poteva essere particolarmente if(n <= upperbound) count n;
problematica nel caso dei nomi delle classi. Ad esempio, se il programma defini- e1se count = upperbound;
sce una classe chiamata ThreeDCircle e una libreria utilizzata dal programma de-
finisce una classe aventelo stesso nome, questo porterebbe alla nascita di un
conflitto. void reset(int n)
La creazione della parola riservata namespace ha lo scopo di rispondere a if(n <= upperbound) count n;
questi problemi. Per il fatto che localizza al proprio interno la visibilit dei nomi,
uno spazio dei nomi consente di utilizzare lo stesso nome in contesti differenti
senza che questo provochi conflitti. Probabilmente chi si pi avvantaggiato nel- int run()
l'utilizzo di namespace la libreria standard del C++.Prima di namespace, l'in- if(count > lowerbound) return count--;
tera libreria C++ era definita nello spazio dei nomi globale (che ovviamente era else return lowerbound;
l'unico disponibile). Dall'aggiunta della parola riservata namespace, la libreria
};
C++ definita all'interno di un proprio namespace chiamato std che riduce la
possibilit che sorgano collisioni fra i nomi. Si pu anche creare un proprio spazio
dei nomi in modo da localizzare la visibilit dei noffi.i che si pensa possano provo-
Qui, upperbound, lowerbound e la classe counter fanno parte del campo d' azio-
care conflitti. Questo particolarmente importante quando si creano librerie di
classi o di funzioni. ne definito dallo spazio dei nomi CounterNamespace.
All'interno di uno spazio dei nomi, gli identificatori dichiarati al suo interno
possono essere utilizzati direttamente, senza qualificarli con il nome del namespace
Le basi degli spazi di nomi stesso. Ad esempio, all'interno di CounterNamespace, la funzione run() pu far
riferimento direttamente a lowerbound nell'istruzione seguente:
La parola riservata namespace consente di partizionare lo spazio globale di nomi
creando una regione di dichiarazioni. In pratica uno spazio dei nomi definisce un if(count > lowerbound) return count--;
campo di visibilit. Ecco la forma generale della parola riservata namespace:
Ma poich nam_e.sf2ac~ J:l.e_finisce un campo di visibilit, per far riferimento a
namespace nome oggetti dichiarati in un uno spazio dei nomi differente occorre utilizzare l'opera-
Il dichiarazioni tore di risoluzione del campo di azione.
Ad esempio, per assegnare il valore 1O a upperbound da parte di codice che si
Tutto ci che definito in un'istruzione namespace all'interno del campo trova all'esterno di CounterNamespace, si deve utilizzare la seguente istruzione:
d'azione di tale namespace.
Ecco un esempio di namespace che localizza i nomi utilizzati per implemen- CounterNameSpace: :upperbound = 10;
tare una semplice classe di conto alla rovescia. Nello spazio dei nomi sono defini-
te la classe counter che implementa il contatore e le variabili upperbound e Oppure, per dichiarare un oggetto di tipo counter dall'esterno di
lowerbound che contengono i limiti superiore e inferiore che si applicano a tutti i Counterf:!amespace, si utilizza un'istruz_i.Qne del seguente tipo:
contatori.
CounterNameSpace: :counter ob;
nfill'lespace.....Co.unterNameSpace
int upperbound;
---~
--~---
-------
------
602 CAPITOLO 23 NAMESPACE, FUNZIONI DI CO-NVERSIONE ... 603

In generale, per accedere a un membro di uno spazio dei nomi dall'esterno di CounterNameSpace: :counter ob2(20);
tale spazio dei nomi, occorre far precedere al nome del membro il nome dello
spazio dei nomi seguito dall'operatore di risoluzione del campo d'azione. do {
Ecco un programma che mostra l'uso di CounterNamespace. i = ob2.run();
cout << i << 11 11 ;
} whi le( i > CounterNameSpace:: lowerbound);
Il Illustra l'uso di un namespace.
cout endl ;
#include <iostream>
using namespace std;
ob2. reset(lOO);
CounterNameSpace:: 1owerbound = 90;
namespace. CounterNameSpace
do {
int upperbound;
i = ob2. run () ;
int lowerbound;
cout << i << " "; -
} whil e(i > CounterNameSpace:: 1owerbound);
cl ass counter {
int count;
return O;
public:
counter(int n)
i f ( n <= upperbound) count n;
else count = upperbound; Si noti che la dichiarazione di un oggetto counter e i riferimenti a upperbound
e lowerbound sono qualificati da CounterNamespace. Tuttavia, una volta che
stato dichiarato un oggetto di tipo counter, non necessario qualificarlo ulterior-
void reset(int n) { mente o qualificare i suoi membri. Pertanto ob1 .run() pu essere richiamata diret-
if(n <= upperbound) count n; tamente in quanto il suo spazio dei nomi gi stato risolto.

int run() L'istruzione using


if(count > lowerbound) return count--;
else return l owerbound; Come si pu immaginare, se il programma include numerosi riferimenti ai mem-
bri di uno spazio dei nomi, il fatto di dover specificare il nome dello spazio dei
}; nomi e l'operatore di risoluzione del campo d'azione ad ogni riferimento, pu
diventare fastidiosa. Per risolvere questo problema stata introdotta la parola
riservata using. Ecco la forma generale della parola riservata using:
int main()
{
CounterNameSpace: :upperbound = 100; using namespace nome;
CounterNameSpace: : 1owerbound = O; using nome::membro;

CounterNameSpace:: counter obl (10); Nella prima forma, nome specifica il nome del namespace cui si vuole acce-
int i; dere. Tutti i membri definiti all'interno dello spazio di nomi specificato diventano
quindi immediatamente visibili (in pratica entrano a far parte dello spazio dei
do { nomi corrente) e possono essere utilizzati senza alcuna qualifica. La seconda for-
i = obl. run(); II).a rende. visibile un solo membro dello spazio dei nomi. Ad esempio, supponen-
cout << i << 11 11 ;
do uno spazio dei nomi CounterNames~e _ome quello illustrato in precedenza,
----t-wlii-1-e (i :. CounterNameSpace: : 1owerbound) ;
cout endl ; - - --- -
possibile utilizzare le seguenti~~!_fl!~ioni _i:_sing. --- ------
604 CAPITOLO 23 NAMESPACE, FUNZIONI DI CONVERSIONE ... 605

using CounterNameSpace: :lowerbound; 11 diventa visibile solo lowerbound CounterNameSpace: :lowerbound = O;


lowerbound = 10; Il OK perch lowerbound visibile
CounterNameSpace:: counter obl (10) ;-
using namespace CounterNameSpace; // diventano visibili tutti i membri i nt i;
upperbound = 100; Il OK perch ora sono visibili tutti i membri
do {
Il seguente progranuna illustra l'uso di using rielaborando l'esempio del con- i = obl.run();
cout << i << " ";
tatore sviluppato nella sezione precedente.
} whil e(i > CounterNameSpace:: 1owerbound);
cout endl ;
Il Illustra l'uso di using.
#i nel ude <iostream> 11 ora usa tutto CounterNameSpace
usi ng namespace std; using namespace CounterNameSpace;
namespace CounterNameSpace counter ob2(20);
i nt upperbound;
int lowerbound; do {
i = ob2. run();
cl ass counter cout.<< i<< 11 11 ;
int count; while(i > lowerbound);
public: cout endl ;
counter( i nt n)
if(n <= upperbound) count n; ob2.reset(100);
else count = upperbound; lowerbound = 90;
do {
i = ob2. run () ;
void reset(int n) cout << i << " ";
if(n <= upperbound) count n; while(i > lowerbound);

return O;
int run() {
H(count > lowerbound) return count--;
else return lowerbound;
Questo programma illustra un altro argomento molto importante: utilizzando
}; uno spazio dei nomi non si chiude lo spazio dei nomi corrente. Quando si richia-
ma uno spazio dei nomi, tutti i nomi in esso contenuti vengono aggiunti a quelli
dello spazio dei nomi attualmente attivo. Pertanto, prima della fine del program-
int main() ma, sia std che CounterNamespace saranno stati aggiunti allo spazio dei nomi
{ globale.
11 usa sol o upperbound di CounterNameSpace
usiiig CounterNameSpace: :upperbound;
Spazi dei nomi senza nome
11 ora non necessari o qual i fi care upperbound
.....J!PP~i:bound _:__100; __ _ . _ Vi anche un tipo particolare di spazio deinomi,...senza.nome, che consentculi ... _
.crear~jqe_!!tifi,catori
univoci all'interno di un file.Q!!.~pazi ~~i nomi senza nome
. --'-=-- _ JL ~a anc~~ _!!.ecessario qualificare lowerbound ecc. sono anche detti spazi dei nomi anonimi. Ecco la loro forma.generai~_~:
606 CAPITOLO 23 N-AMESPACE, FUNZIONI DI CONVERSTONE':-:-:-- -507

namespace Alcune opzioni degli spazi dei nomi


Il dichiarazioni
Possono esistere pi dichiarazioni di spazi di nomi aventi lo stesso nome. Questo
Gli spazi dei nomi senza nome consentono di definire identificatori univoci consente di suddividere uno spazio dei nomi su pi file o anche in pi aree dello
noti solo nel campo d'azione di un determinato file. Questo significache all'inter- ;. stesso file. Ad esempio:
no del file che contiene lo spazio dei nomi anonimo, i membri di tale spazio dei
nomi possono essere utilizzati dirett~ente, senza alcuna qualifica mentre al- #include <iostrearn>
i' esterno del file gli identificatori risulteranno invisibili. using namespace std;
Gli spazi dei nomi senza nome eliminano la necessit di utilizzare il modifi-
namespace NS
catore di classe static. Come descritto nel Capitolo 2, un modo per restringere il int i;
campo di visibilit di un nome globale al solo file in cui dichiarato consiste
nell'impiegare la parola riservata static: Ad esempio, si considerino i due file
seguenti che fanno parte dello stesso programma. Il
Primo file Secondo file namespace NS
int j;
static int k; extern i nt k;
void fl() { void f2() {
k = 99; Il OK k = 10; Il errore int main()
} {
NS: :i = NS: :j = 10;
Poich k definita in Primo file, pu essere utilizzata in Primo file. In Secondo
file, k specificato come extern e questo significa che non definito, anche se ne Il fa riferimento in particolare a NS
sono noti il nome e il tipo. Quando questi due file vengono linkati, un tentativo di cout << N::i * N::j "\n";
utilizzare k all'interno di Secondo file provoca un errore poich k non definito.
Il usa il namespace NS
Facendo precedere a k in Primo file il qualificatore static, il suo campo di visibilit usi ng namespace NS;
viene ridotto unicamente al file e la variabile non risulter visibile in Secondo file.
Se il linguaggio C++ consente comunque di utilizzare dichiarazioni globali statiche, cout << i * j;
si pu ottenere lo stesso effetto utilizzand_o_uno spazio dei nomi senza nome. Ad
esempio: return O;

Primo file Secondo file


Questo programma produce il seguente output:
namespace extern i nt k;
int k; void f2() { 100
} k = 10; 11 errore 100
void fl() { {
k = 99; Il OK
} Qui, NS stato suddivis.o in due parti. Queste due parti costituiscono insieme
lo spazio dei nomi NS.
Uno spazio dei nomi deve essere dichiarato all'esterno di ogni altro campo-di --
. Anche in questo caso k utilizzabile solo in Primo file. preferibile utilizzare
visibili!. Questo significa che non possibile dichiarare spazi di nomi contenuti,
__ _ spazi d_.!_,nomi anonimi piuttosto che il qualificatore static.
__ -~d ese~pio, all'intemocff un ~~E:'?ne.-vi-una"sola eccezione: un~-s~zio de.i _
---- ---~.~- -
608 CAPITOLO 23
----- -
N A M E s p A e E ' F u N Zl-O-NT-!TI- e o N V E R s I o N E...- . 609

nomi pu trovarsi all'interno di un altro spazio dei nomi. Si consideri il seguente si pu far riferimento direttamente a NS2 poich l'istruzione using porta NS1 nel
programma: - campo di visibilit.
In genere non necessario creare spazi di nomi per programmi di piccole o
#include <iostream> medie dimensioni. Se invece si devono creare librerie riutilizzabili e si vuole ga-
using namespace std; rantire la pi ampia trasportabilit, opportuno considerare l'inserimento del co-
dice in uno spazio dei nomi.
namespace NSl {
int i;
namespace NS2 Il un namespace nidificato
int j; 23.2 Lo spazio dei nomi std
Lo standard del linguaggio C++ definisce l'intera libreria standard come uno spa-
zio dei nomi chiamato std. Questo il motivo per il quale la maggior parte dei
int main()
{
programmi di questo volume include la seguente istruzione:
NSl: :i = 19;
Il NS2::j = 10; Errore, NS2 non visibile using namespace std;
NSl::NS2::j = 10; Il questo va bene
Questa provoca l'inserimento dello spazio dei nomi std nello spazio dei nomi
cout Nl::i 11 11
NNS1::NS2::j "\n"; corrente, dando accesso diretto al nome delle funzioni e delle classi definite nella
libreria standard, senza dover ripetere continuamente la qualifica std::.
Il usa NSl Naturalmente possibile qualificare esplicitamente ogni nome con std:: se. si
usi ng namespace NSl; desidera. Ad esempio il seguente programma non inserisce la libreria standard
nello spazio dei nomi globale.
I* Ora che NSl visibile,
si pu utilizzare NS2 per far riferimento a j. *I
cout <<i * N2::j;
Il Usa la qualifica esplicita di std::
#include <iostream>
return O;
int main()
{
int val;
Questo programma produce il seguente output:
std: :cout << "Introdurre un numero: ";
19 10
190 std: :cin val;

Qui lo spazio dei nomi NS2 contenuto nello spazio dei nomi NS1. Pertanto, std: :cout << "Ecco il numero: ";
all'inizio del programma quando si fa riferimento a j occorre qualificarla con std: :cout std: :hex val;
entr~bi gli spazi dei nomi (NS1 e NS2). NS2 da solo non sufficiente. Dopo
l'istruzione: - return O;

using namespa~e NSl;


Qui cout, cin e il manipolatore hex vengono qualificati esplicitamente con il
loro spazio dei nomi. Questo significa che per scrivere sullo standard output si
- - - -- - - - -
610 CA P I T O LO 2 3 NAMESPACE, FUNZIONI DI CONVERSIONE ... 611

deve specificare std::cout; per leggere dallo standard input si deve u~are std::cin e 23.3 Creazione di funzioni di conversione
per far riferimento al manipolatore esadecimale .si deve usare std::hex.
Un motivo che pu spingere a non portare la libreria standard C++ nello spa- In alcune situazioni, si vuole usare un oggetto di una classe in un'espressione che
zio dei nomi globale si verifica quando il programma utilizza la libreria in modo prevede altri tipi di dati. In genere per eseguire questa operazione possono essere
molto limitato. Quando invece il programma contiene centinaia di riferimenti a utilizzate delle funzioni operatore modificate tramite overloading. Tuttavia, in
nomi contenuti nella libreria, opportuno includere std nello spazio dei nomi altri casi, ci che si desidera una semplice conversione di tipo dalla classe al tipo
corrente. di destinazione. Per gestire questi casi, il linguaggio C++ consente di creare fun-
Se si usano solo pochi nomi della libreria standard, pi semplice specificare zioni di conversione personalizzate. Urta funzione di conversione converte la clas-
un'istruzione using per ciascun nome. Il vantaggio di questo approccio il fatto se in un tipo compatibile con quello del resto dell'espressione. Ecco la forma
che si pu continuare a utilizzare tali nomi senza la qualifica std:: e senza portare generale di una funzione di conversione:
l'intera libreria standard nello spazio dei nomi globale. Ad esempio:
operator tipo( ) retum valore;
Il Porta solo alcuni nomi nel namespace globale
#i nel ude <i ostream> Qui, tipo il tipo di destinazione al quale si deve convertire la classe e valore
il valore della classe dopo la conversione. Le funzioni di conversione restitui-
11 ottiene l'accesso a cout, cin ed hex scono dati di tipo tipo e non possibile utilizzare altri specificatori di tipo. Inoltre
using std: :cout;
non pqssibile includere parametri. Le funzioni di conversione sono ereditate e
using std: :cin;
using std: :hex;
possono essere virtuali.
La seguente funzione di conversione utilizza la classe stack sviluppata nel
int main() Capitolo 11. Si supponga di voler utilizzare gli oggetti di tipo stack all'interno di
{ un'espressione intera. Inoltre si supponga che il valore di un oggetto stack utiliz-
int val; zato in un'espressione intera sia il numero di valori attualmente contenuti nello
stack (questa situazione pu essere utile se, ad esempio, si usano oggetti stack in
cout << "Introdurre un numero: "; una simulazione e si deve monitorare la rapidit con la quale lo stack si riempie).
Un modo per risolvere questa situazione consiste nel convertire un oggetto di tipo
cin val; stack in un intero che rappresenti il numero di elementi contenuti nello stack. Per
cout << "Ecco il numero: "; ottenere questo risultato, si pu usare una funzione di conversione avepte il s!':- -
----co.uf hex val;
guente aspetto:
return- O;
operator i nt () { return tos; }

Qui, cin, cout ed hex pos.sono essere utilizzati direttamente mentre gli altri
Ecco un programma che illustra il funzionamento delle funzioni di conversione:
nomi dello spazio dei nomi std non sono stati inseriti nello spazio dei nomi cor-
rente.
#include <iostream>
Come si detto, la libreria C++ originale stata definita nello spazio dei nomi using namespace std;
globale. Se si deve eseguire la conversione di vecchi programmi C++, si deve inclu-
dere un' istruzione-using namespace std oppure qualificare ogni riferimento a un const int -SIZE=lOO;
membro della libreria con std::. Questo particolarmente importante se si stanno
sostituendo vecchi file header .H con. le nuove versioni di tali file. Si ricordi che i . Il crea la classe stack
vecchi header .H inseriscono il proprio contenuto nell~pazio dei nomi_globale; i cl ass stack (
nuovi file.11~!!4-~i_Il_~ris~o!lo il loro contenuto nello spazio d:_i ~~n:1i st~:- _ _int Sfcl<[SIZE];
int tos;
..public:
612 CAPITOLO 23 NAMESPACE, FUNZIONI DI CONVERSIONE ... 613

stack(} { tos=O; } Come si pu vedere dal programma, quando un oggetto stack viene utilizzato
void push(int i); in un'espressione intera, come j::stck, all'oggetto viene applicata la funzione di
int pop(void); conversione. In questo specifico caso, la funzione di conversione restituisce il
operator int(} { return tos; } // conversione di stack in int valore 20. La funzione di conversione viene chiamata anche quando stck viene
};
sottratto da SIZE.
Ecco un altro esempio di una funzione di conversione. Questo programma
void stack: :push(int i)
{
crea una classe chiamata pwr() che conserva e calcola il risultato di alcuni numeri
if(tos==SIZE) { elevati a una potenza. Il risultato viene memorizzato come double. Fornendo una
cout "Stack esaurito. \n"; funzione di conversione nel tipo double e restituendo il risultato, si possono utiliz-
return; zare oggetti di tipo pwr nelle espressioni che impiegano altri valori double.
- }_
stck[tos) = i; #i nel ude <i ostream>
tos++; using namespace std;

cl ass pwr {
int stack: :pop() double b;
{ int e;
if(tos==O) { double val;
cout << "Underflow dello stack. \n"; public:
return O; pwr(double base, int exp);
pwr operator+(pwr o)
tos--; double base;
return stck[tos]; int exp;
base = b + o.b;
exp = e + o.~;
int main()
{ pwr temp(base, exp);
stack stck; re tu rn temp;
int i, j;
operator double() { return val; } // converte in double
for(i=O;-i<20; i++) stck.push(i); };

j = stck; // converte in i nt pwr: :pwr(double base, int exp)


{
cout j elementi nello stack. \n"; b = base;
e = exp;
cout SIZE - stck " spazi disponibili. \n"; val = l;
return O; if(exp==O) return;
for( ; exp>O; exp-) val = val * b;

Il programma produce il seguente output:


i~t main()
20 elementi nellOStack. {
80 spazi -disponi bi Ji. _pw.r_x(4.0,.2);
--- -- -
-------~
-- - - double a; -
NAMESPACE, FUNZIONI DI _G.ONVERSIONE ... 615
614 C A P I T O LO 2 3

a= x; Il converte in double Per specificare che una funzione membro const, si utilizza la forma illustra-
cout << x + 100.2; Il converte x in double e somma 100.2 ta nel seguente esempio.
cout << -"\n";
class X {
pwr y(3.3, 3), z(O, O); int some_var;
public:
z =x + y;
11 nessuna conversione int fl() const; Il funzione membro const
a = z; Il converte in double };
cout a;
Come si pu vedere, la parola riservata const segue la dichiarazione dei para-
return O; metri della funzione.
Si dichiara una funzione membro come const per evitare che modifichi l'og-
getto che la richiama. Ad esempio, si consideri il seguente programma.
Ecco l'output del programma.
I*
116.2 Illustra l'uso delle funzioni membro const.
20730.7 Questo programma non accettato dal compilatore.
*I
Come si pu vedere, quando x viene utilizzata nell'espressione x+100.2, per #i nel ude <i ostream>
produrre il valore double viene utilizzata la funzione di conversione. Si noti anche using namespace std;
che nell'espressione x+y non viene applicata la funzione di conversione in quanto
l'espressione considera solo oggetti di tipo pwr. class Demo
I
Come si pu immaginare dagli esempi precedenti, vi sono molte situazioni in int i;
public:
cui conveniente creare una funzione di conversione per una classe. Spesso le
int geti() const {
funzioni di conversione offrono una sintassi pi naturale quando si lavora con
1 oggetti appartenenti a una classe e oggetti di un tipo standard C++.In particolare,
nel caso della classe pwr, la disponibilit della conversione in double consente di
return i ; /I ok

utilizzare gli oggetti di tale classe nelle normali espressioni matematiche e questo void seti (int x) const
semplifica la programmazione e la comprensibilit del programma. - - _____ _ i = x; Il errore!
1 al!.che possibile creare funzioni di conversione differenti a seconda delle
esigenze. Ad esempio si possono definire funzioni di conversione in double o in };
long. Verr automaticamente applicata l'una o l'altra funzione a seconda del tipo
di ciascuna espressione. int main()
{
Demo ob;

23.4 Funzioni membro const e mutable ob.seti (1900):


cout ob.geti ();
Le funzioni membro di una classe possono essere dichiarate con-st e in questo
caso this verr trattato come un puntatq~~- const. Pertanto tali funzioni non potran- return O;
no modificare l'oggetto che le richiama. Inoltre un oggetto const non pu richia- ' }
_ _ _m.,,are una funzione membro non-const. Tuttavia, una funzione membro const pu
essere richiamata da oggetti eonst o 11on-cons_t_.______ _
616 CAPITOLO 2 3 NAMESPACE, FUNZIONI DI CONVERSIONE ...
~~~~~~~~~~~~'--~~~~~~~~~~~~~~~
617--

Questo programma non pu essere compilato poich seti() dichiarata const. Qui, i specificata come mutable e dunque pu essere modificata dalla fun-
Questo significa che non pu modificare l'oggetto che l'ha richiamata. Poich zione seti(). Al contrario, j che non mutable non pu essere modificata da setj().
tenta di modificare i, il programma interrotto. Al contrario, poich geti() non
tenta di modificare i, perfettamente accettabile.
Talvolta si desidera che solo uno o pi membri di una classe possano essere
modificati da una funzione const ma che la funzione non possa modificare tutti 23.5 Funzioni membro volatile
gli altri membri. Si pu ottenere questo effetto utilizzando la parola riservata
mutable che consente di ignorare la dichiarazione const. Questo significa che un Le funzioni membro di una classe possono essere dichiarate volatile e questo fa in
membro mutable pu essere modificato anche da una funzione membro const. Ad modo che il puntatore this venga trattato come un puntatore volatile. Per specifica-
esempio: re una funzione membro come volatile, si deve utilizzare la forma illustrata nel
seguente esempio:
Il Illustra l'uso di mutable.
#include <iostream> class X {
using namespace std; public:
void f2{int a) volatile; Il funzione membro volatile
class Demo { };
mutable int i;
int j;
public:
int geti() const { 23.6 Costruttori espliciti
return i ; 11 ok
. Come si detto nel Capitolo 12, ogni volta si ha un costruttore che richiede un
solo argomento, per inizializzare l'oggetto si pu usare ob(x) oppure ob =x. Il
void seti (int x) const motivo che quando si crea un costruttore che accetta un argomento si sta anche
i =x; 11 ora ~ OK. creando implicitamente una conversione dal tipo dell'argomento al tipo della classe.
Ma talvolta si desidera che questa conversione automatica non abbia luogo. Per
questo motivo il linguaggio C++ definisce la parola riservata explicit. Per com-
I* La funzione seguente non pu essere compilata. prendere il suo effetto, si consideri il seguente programma.
voi d setj (i nt x) const {
i
- j =x; I ERRORE!------
#i nel ude <i ostream>
using namespace std;
*I
};
cl ass mycl ass
int a;
int main()
public:
{
myclass(int x) { a = x; }
Demo ob;
i nt geta () { return a; }
};
ob. seti (1900);
cour ob. geti () .;
int main()
{
return O;
myclass ob = 4; Il convertita automaticamente in myclass(4)

cout ob :9.~t11 ()-;


--=--.:~--- --

618 CAPITOLO 23 NAMESPACE, FUNZIONI DI CONVERSIONE ... 619

return O; - 23. 7 Uso della parola riservata asm


Anche se il linguaggio C++ molto flessibile e potente, vi sono alcune situazioni
Qui il costruttore di myclass accetta un parametro. Si faccia attenzione al molto specializzate che il linguaggio non in grado di gestire. Ad esempio il
modo in cui ob dichiarata in main(). L'istruzione: linguaggio C++ non contiene istruzioni in grado di disattivare gli interrupt. Per
gestire queste situazioni, il linguaggio offre una sorta di via di fuga che consente
m;1class ob = 4; Il convertita automaticamente in myclass(4) di impiegare direttamente il codice assembler bypassando completamente il com-
pilatore C++.Questa via di fuga rappresentata dall'istruzione asm. Con asm
viene automaticamente convertita in una chiamata al costruttore di myclass dove possibile inserire il codice assembler direttamente all'interno del programma C++.
l'argomento 4. Pertanto l'istruzione precedente viene gestita dal compilatore Questo codice assembler verr compilato senza alcuna modifica e diverr una
come se fosse scritta nel seguente modo: parte integrante del codice del programma nel punto esatto in cui stata inserita la
parola riservata asm.
m-1class ob(4}; Ecco la forma generale dell'istruzione asm:

Se non si vuole che avvenga questa conversione implicita, basta utilizzare lo asm ("op-code")
i,pecificatore esplicito il quale pu essere applicato solo a costruttori. II costruttore
explicit verr utilizzato solo quando l'inizializzazione utilizza la normale sintassi Dove op-code l'istruzione assembler che deve essere inserita nel program-
dei costruttori mentre non verr impiegato per eseguire conversioni automatiche. ma. Mlt~ compilatori consentono di impiegare anche le seguenti forme di asm:
Ad esempio, dichiarando come explicit il costruttore di myclass, non verr utiliz-
:r.ata la conversione automatica. Ecco un esempio di myclass() dichiarata in modo asm istruzione ;
explicit. asm istruzione newline
asm
'inc 1ude <i os t ream> sequenza di istruzioni
U':.ing namespace std;
Qui, istruzione e un'istruzione assembler. Data la natura di asm (che stretta-
cl ass mycl ass
int a;
mente legata all'implementazione) per informazioni sul suo uso opportuno con-
p11bl ic: sultare la documentazione del compilatore.
expl ic mycl-ass(int x) a = X; }
Attualmente, il Microsoft Visual C++ utilizza a tale scopo la parola riservata
i nt -geta () { return a; } __asm che ha un funzionamento simile ad asm.
); Ecco un esempio semplice e sicuro d'uso della parola riservata asm:

Ora saranno consentiti solo costruttori nella forma: #include <iostream>


using namespace std;
my~l ass ob(4);
int main()
{
mentre istruzioni come la seguente: asm int 5; 11 genera. l'.jntertupt 5

__ myr._l_ass ob = 4; 11 Errore return O;

'>aranno errate.
--Se utilizzato in DQS~questo programma genera un'istruzione INT 5 che pro-
vocaTa stainp d~to)itenuto dello schermo.
620 CAPITOLO 23 NAMESPACE, FUNZIONI DI CONVEASINE-... 621

~ Per impiegare l'istruzione asm _necessario conoscere appro- Tramite questa forma possibile specificare pi di una funzione:
fonditamente il funzionamento del linguaggio assembler. Chiunque non sia parti-
colarmente esperto in tale linguaggio non dovrebbe usare questa parola riservata extem "linguaggio" {
per evitare di incorrere in gravi errori. prototipi
}

23.8 Specifiche di 1-inking


23.9 Operazioni di I/() su array
In C++ possibile specificare il modo in cui una funzione viene linkata nel pro-
gramma. Normalmente le funzioni vengono linkate secondo lo stile C++. Le spe- Oltre alle operazioni di I/O su console e su file, il sistema di I/O a stream del C++
cifiche di linking consentono di linkare una funzione utilizzando le forme previ- consente anche di eseguire operazioni di I/O su array. Le operazioni di I/O su
ste da altri tipi di linguaggi. La forma generale dello specificatore di linking : array utilizzano array di caratteri come dispositivo di input, dispositivo di output
o entrambi. Le operazioni di I/O su array vengono eseguite tramite normali stream
extem "linguaggio" prototipo-di-funzione C++. In pratica, alle operazioni di I/O su array si applica tutto ci che gi si sa
sulle operazioni di I/OC++. L'unica differenza consiste nel fatto che il dispositi-
Dove linguaggio indica il linguaggio desiderato. Tutti i compilatori C++ vo collegato allo stream un array di caratteri. Gli stream collegati ad array di
supportano il linking C e C++. Alcuni consentono anche di utilizzare gli caratteri vengono normalmente chiamati stream char . Per utilizzare nei pro-
specificatori Fortran, Pascal o BASIC (a tale proposito opportuno consultare la grammi le operazioni di I/O su array, si deve includere il file <strstream>.
documentazione del compilatore). .
'.ff.QTA.':;'" -~_,_:;;;::z3 Le classi per stream basati su caratteri descritte in questa se-
#include <iostream> zione sono sconsigliate dallo standard per il C++.Anche se rimangono valide, ne
using namespace std; sconsigliato l'uso per realizzare nuovo codice. Questa breve discussione stata
inclusa sono perch tali operazioni sono state ampiamente utilizzate.
extern "C" voi d myCfunc ();

int main() Classi basate su array


{
myCfunc(); . Le classi di I/O su array sono istrstream, ostrstream ~- strstr~_a,m, Queste classi
sono utilizzate per creare rispettivamente stream di input, di output e di input/
return O; output. Inoltre la classe istrstream deriva da istream, la classe ostrstream deriva
ostream e la classe strstream deriva da iostream. Pertanto tutte le classi basate su
array derivano indirettamente da ios ed hanno accesso alle stesse funzioni mem-
Il Questa verr linkata come una funzione C. bro delle normali classi di I/O.
voi d myCfunc ()
{
cout "Questa verr linkata come una funzione C. \n"; Creazione di uno stream di output su array

Per eseguire un'operazione di output su un array, si deve collegare tale array a uno
stream utilizzando questo costruttore ostrstream:
!Bml"i.h:1~ La parola riservata extern una parte integrante delle speci-
fiche di lnking. Inoltre le speijiche di linking devono essere globali, ovvero non
ostrstream ostr(char *buf, streamsize size, openmode mode=ios::out);
possono essere utilizzate all'interno ai una]Unzzone.
~- -

622 CAPITOLO 23 NAMESPACE, FUNZIONI DI CONVERSIONE ... 623

Qui, buf un puntatore all'array e verr utilizzato per raccogliere i caratteri Questo programma chiude manualmente l' array utilizzando il manipolatore
scritti sullo stack ostr. Le dimensioni dell'array vengono passate tramite il para- ends. Il fatto che l'array venga automaticamente chiuso con un NULL o meno
metro size. L'impostazione predefinita prevede che lo stream venga aperto per dipende dall'implementazione, dunque sempre opportuno eseguire una chiusu-
operazioni di normale output ma possibile applicare tramite OR varie opzioni ra manuale dell'array.
per creare la modalit desiderata. Ad esempio si pu includere ios::app per fare in Si pu determinare il numero di caratteri inviati in output all'arresto richia-
modo che l'output venga scritto alla fine delle informazioni contenute nell'array. mando la funzione membro pcount(). Tale funzione ha il seguente prototipo:
Nella maggior parte dei casi viene impiegata l'impostazione predefinita.
Dopo aver aperto uno stream di output su array, tutto l'output inviato allo streamsize pcount( );
stream verr inserito nell'array. Tuttavia non possibile scrivere oltre i limiti
dell'array. Un tentativo in questo senso provocher un errore. Il numero restituito da pcount() include anche il carattere di chiusura NULL,
Ecco un semplice programma che dimostra il funzionamento di uno stream di sempre che questo esista.
output su array. Il seguente programma illustra l'uso di pcount(). Il programma mostra che
outs contiene 18 caratteri: 17 pi il carattere nullo.
#i nel ude <strstream>
#i nel ude <i ostream> #i nel ude <strstream>
usi ng namespaee std; #include <iostream>
using namespace std;
int main()
{ int main()
ehar str[80]; {
char str[80];
ostrstream outs(str, sizeof(str));
ostrstream outs (str, si zeof (str));
outs "Operazioni di IIO su array in C++. ";
outs << 1024 << hex " "; outs "abcdefg ";
outs.setf(ios: :showbase); outs 27 " " << 890.23;
outs 100 ' ' 99. 789 ends;
outs ends; Il chiude la stringa con il carattere nullo
cout << str; Il visualizza la stringa sulla console c--Ollt-"<-outs. pcount(); 11 numero di caratteri contenuti in outs

return o; cout << " " << str;

return O;
Questo programma visualizza il seguente risultato:

Operazioni di IIO su array in C++. 1024 Ox64 99.789


Uso di un array di input
Si deve tenere in considerazione che outs uno stream come un altro; dunque
ha le stesse funzionalit~di ogni altro tipo di stream di cui si parlato in preceden- Per collegare imo stream di-input a un array, si usa il seguente costruttore istrstream:
za. L'unica differenza consiste nel fatto che il dispositivo collegato . un ar:ray di
caratteri. Poich outs uno stream, possibile utilizzare i manipolatori hex ed ". istrstream istr(const char *buj);
----ends. Sono disponibilianche_Ie..funzionLmembro di ostream, come ad esempio
_s_etf(). - - - -- - Qui, buf un puntatore all'array che verr utilizzato come origine dei caratteri
- _, __ - -- _og'!i volta che verr eseguit dell'input dallo stream istr. Il cori.ten..ilto de-11' array __ -
----
624 CAPITOLO 23 NAMESPACE, FUNZIONI DI CONVERSIONE ... 625

puntato da buf deve essere chiuso dal carattere nullo. Ciononostante il carattere del contenuto di un array di testo. Quando viene raggiunta la fine dell'array (cor-
nullo finale non viene mai letto dall' array. rispondente alla fine del file), ins sar falso.
Ecco un programma di esempio che utilizza per l'input una stringa.
/* Questo programma i 11 ustra la lettura del contenuto
#i ne 1ude <iostream> di un array contenente testo. */
#include <strstream> #include <iostream>
using namespace std; #i nel ude <strstream>
using namespace std;
int main()
{ int main()
char s[]. = "10 Hello Ox75 42.73 OK"; {
char sO = "10.23 questo testo ?!\n";
istrstream ins(s);
istrstream ins(s);
int i;
char str[BO]; char eh;
float f;
/* 1egge e vi sua 1i zza i 1 contenuto
//legge: 10 Hello di un .array di testo. */
i ns i;
i ns >> str; ins.unsetf(ios::skipws); //non salta gli spazi
cout << i << " " << str << endl; while (ins) // false al raggiungimento della fine dell 'array
ins eh;
//legge Ox75 42.73 OK cout eh;
i ns hex i ;
ins f;
i ns str; return O;

cout << hex << i << " " << f << " " << str;

return O; Stream di input/output su array

Per creare uno stream basato su array che esegua operazioni di input e output, si
Se si vuole utilizzare come input solo una parte di una stringa, si deve utilizza- utilizza la funzione costruttore strstream:
re la seguente forma del costruttore istrstream:
strstream iostr(char *biif, streamsize size, openmode mode= ios::in I ios::out);
istrstream istr(const char *buf, streamsize size);
Qui bzef punta alla stringa che verr utilizzata per le operazioni di I/O. Il valore
In questo caso verranno utilizzati solo i primi #ze elementi dell'array_puntato di size specifica le dimensioni dell'array. Il va!ore di mode deten11.ina la modalit
da buf Questa stringa non deve essere chiusa dal carattere nullo poich il valore di funzionamento dello stream iostr. Per normali operazioni di I/O, mode sar
di si::.e a determinare le sue dimensioni. iios::in I ios::out. Per operazioni di input, l'array deve essere chiuso dal carattere
Gli stream collegati ad aree di memoria si comportano esattamente come quelli nullo.
collegati ad altri dispositivi. Ad esempio, il seguente programma illustra la lettura Ecco un prngranmra c:he utilizza un array per eseguire operazioni di input e
output. ____ _
-----.:....:..=-.:..":;-..
-----
---
626 NAMESPACE, FUNZIONI ].I CONVERSIONE ... 627

Il Esegue sia l'input che l'output. Questa funzione "congela" l' array e restituisce un puntatore ad esso. Il puntatore
#i nel ude <i os t ream> restituito da str() consente di accedere all'array dinamico come se fosse una strin-
#include <strstream>
ga. Dopo che un arry dinamico viene congelato, non pu pi essere utilizzato per
using namespace std;
operazioni dioutput a meno che non venga "scongelato". Pertanto opportuno
int main()
non congelare l'array mentre gli vengono inviati caratteri.
{ Ecco un programma che utilizza un array dinamico.
char iostr[SO];
#include <strstream>
strstream strio(iostr, sizeof(iostr), ios::in I ios::out); #include <iostream>
using namespace std;
int a, b;
char str[80]; int main()
{'
strio "10 20 test "; char *p:
strio a >> b >> str;
cout << a << n Il << b << Il Il << str << endl: ostrstream outs; Il alloca dinamicamente un array

return O; outs "Operazioni di I/O su array in C++ ";


outs << -10 << hex << " ";
outs.setf(ios: :showbase):
outs << 100-<< ends;
Questo programma scrive sull'array e poi ne rilegge il contenuto.
p = outs.str(): /I Congela il buffer dinamico
/I e restituisce un puntatore ad esso.
23.10 Uso di array dinamici
cout p;
Negli esempi precedenti, quando si collegato uno stream a un array di output, al
return O;
costruttore di ostrstream venivano passati l'array e le sue dimensioni. Questo
approccio funziona quando si conosce il numero massimo di caratteri che verran-
no inviati all'array. Ma cosa accade se non si conoscono le dirn:ens'mni esatte che
Le operazioni di I/O su array ciinamici possono essere utilizzate anche con la
dovr assumere l'array di output? La soluzione a questo problema rappresentata
classe strstream che consente di eseguire simultaneamente operazioni di input e
dalla seconda forma del costruttore di os_trstream:
output.
Per congelare o scongelare un array dinamico si richiama la funzione freeze()
ostrstream( );
il cui prototipo :
Quando si usa questo costruttore, ostrstream crea e gestisce un array allocato void freeze(bool action = true);
in modo dinamico le cui dimensioni crescono automaticamente in modo da acco-
gliere l'output inviato.
Se action true, l'array viene congelato mentre se_ false, l'array vi~ne
- Per accedere all' array allocato dinamicamente si deve usare una seconda fun- scongelato. ----
zione, chiamata str(), che ha_ il seguente prototipo:

_ char *str( );
NAMESPACE, FUNZIONI DI CONVERSIONt:-:-.-----S2S
628 CAPITOLO 23

23.11 Uso di I/O binario con stream basati su array _ me. Ecco le differenze pi importanti.
In C++ le variabili locali possono essere dichiarate in qualsiasi punto di un
Come si detto, le operazioni di I/O basate su array offrono tutte le funzionalit blocco. In C devono essere dichiarate all'inizio di un blocco, prima di ogni istru-
delle normali operazioni di 1/0. Pertanto gli array collegati a stream possono con- zione "attiva".
tenere anche Informazioni binarie. Per determinare la fine dell'array quando si In C, una funzione dichiarata nel seguente modo:
leggono informazioni binarie si deve utilizzare la funzione eof(). Ad esempio, il
seguente programma mostra come leggere il contenuto di un qualsiasi array (bi- int f();
nario o di testo) utilizzando la funzione get().
non dice nulla sui parametri della funzione. Quando fra le parentesi non viene
#include <iostream> specificato nulla, in C significa che non viene stabilito nulla rispetto ai parametri
.#include <strstream> della funzione. Potrebbero esservi parametri oppure no. In C++ una dichiarazione
using namespace std; di funzione come questa significa che la funzion non ha parametri. Pertanto in
C++ queste due dichiarazioni sono equivalenti:
int main()
{ int f();
char *p = "questo testo";
int f(void);
i strstream i ns (p);
In C++, void in un elenco di parametri opzionale. Molti programmatori C++
char eh;
includono void per indicare a chiunque legger il programma che una funzione
Il legge e visualizza informazioni binarie non usa parametri; tuttavia la presenza di void, tecnicamente, non necessaria.
while (!ins.eof()) { In C++ tutte le funzioni devono avere un prototipo. In C questo non un
ins.get(ch); requisito (anche se buona abitudine specificare tutti i prototipi anche in un pro-
cout hex (int) eh '; gramma C).
Una differenza piccola ma potenzialmente importante fra C e C++ il fatto
che in C una costante carattere viene trasformata automaticamente in un intero. In
return O; C++ questo non avviene.
In C non un errore dichiarare pi volte una variabile globale, anche se questa
non considerata una buona pratica di programmazione. In C++ questo rappre-
In questo esempio, i valori formati da \1\2\3 e cos via sono valori non senta un errore.
stampabili. In C un identificatore deve avere almeno 31 caratteri significativi. In C++ tutti
Per produrre in output caratteri binari si usa la funzione put(). Per leggere i caratteri sono significativi. Tuttavia, per motivi pratici, opportuno evitare di
buffer di dati binari si usa la funzione membro read(). Per scrivere buffer di dati impiegare identificatori cos lunghi.
binari si usa la funzione write(). In e possibile (anche se raro) richiamare main() dal programma. Il linguag-
gio C++ non consente questa operazione.
In C non si pu richiedere l'indirizzo di una variabile register. In C++ questo
consentito.
-~23.12 Riepilogo delle-differenze esistenti fra C e C++ ~ In C, quando non viene indicato alcuno specificatore di tipo nelle esrensioni
di dichiarazione, viene utilizzato per default il tipo ir:i~:_Questa regola non viene
Lo standard C++ fondamentalmente un'estensione dello standard Ce pratica-
pi applicata in C++ (e probabilmente verr eliminata anche dalle prossime ver-
mente ogni programma C anche un programma "C++. Tuttavia esistono alcune
_.i_Qne delC "'""),_._ _
differenze che sono state l'argomento della Parte l e della Parte 2 drquesto-volu-- -
-~---
--=---- - -:

: Capitolo 24

Introduzione
: alla libreria STL

24.1 Introduzione all'uso della libreria STL


24.2 ~e classi container
24.3 Funzionamento generale
24.4 I vettori
24.5 Le liste
24.6 Le mappe
24.7 Gli algoritmi
24.8 Uso degli oggetti funzione
24.9 La classe string
24.10 Commenti finali sulla libreria STL

:::=uesto capitolo introduce un argomento che da molti


viene considerato la pi importante aggiunta al linguaggio C++ degli ultimi anni:
la libreria STL (Standard Template Library). La creazione della libreria STL
stato uno degli sforzi pi ingenti svoltisi durante la standardizzazione del C++.
Tale libreria fornisce classi template di utilizzo generale, funzioni che implemen-
tano algoritmi e strutture dati di uso comune comprendendo, ad esempio, il sup-
porto di vettori, liste, code e stack. Inoltre la libreria STL definisce varie routine
di accesso a tali elementi. Poich la libreria STL si basa su classi template, gli
algoritmi e le strutture dati possonq essere applicate praticamente a ogni tipo di
dati. La libreria STL un elemento software complesso che utilizza alcune delle
funzionalit pi avanzate del linguaggio C++. Per comprendere e utilizzare al
meglio la libreria STL dunque opportuno onoscere al meglio il linguaggio C++
e soprattutto il funzionamento dei puntatori, degli indirizzi e dei template. Fran-
camente, la sintassi template che descrive la libreria pu inizialmente intimorire
(anche se il tutto sembra molto pi complicato di quanto non sia in realt). Anche
se in questo capit9l9 non vengono introdotti argomenti pi complessi di quelli
trattati in precedenza, non ci si deve sorprendere o scoraggiare se inizialmente si
trova che 1' argomento sembra essere confuso. Basta pazientare e studiare-gli esempi
' evitando che la sintassi poco familiare distragga dalla semplicit di base della
libreria.
- - Lo sco.po..di questo caprtofo quello di presentare una panora.iiilcilaella libre,__ __ .... - -
- _ria-STEpifttenio dalla filosofia progettuale, dall'organizzazione, dagli elem~nti -- - - -
632 CAPITOLO 24 IN T R 0-tHJ-Z-l..Q NE ALLA LI B JH El I A S TL 633

di base e dalle tecniche di programmazione. Poich la libreria STL molto estesa, del contenuto dei container. Molti algoritmi operano su un intervallo di elementi
non possibile discuterne in un capitolo tutte le funzionalit. Per questo motivo la di un container.
Parte quarta rappresenta una guida di riferimento completa alla libreria.
Questo capitolo descrive anche una delle nuove classi pi importanti del C++:
Gli iteratori
la classe string. Tale classe definisce un tipo di dati che consente di lavorare con
stringhe di caratteri come con qualsiasi altro tipo di dati, ovvero tramite gli opera- Gli iteratori sono oggetti strettamente imparentati con i puntatori. Essi danno la
tori. La classe string strettamente correlata alla libreria STL.
possibilit di attraversare il contenuto di un container un po' come un puntatore
consente di attraversare un array. Vi sono cinque tipi di iteratori.

24.1 Introduzione all'uso della libreria STL ITERATORE ACCESSI CONSENTITI


Accesso diretto Memorizzazione e lettura dei valori. ~accesso agli elementi pu avvenire in modo diretto.
Anche se la libreria STL molto estesa e la sua sintassi pu inizialmente spaven-
tare, molto facile utilizzarla se si comprende il modo in cui costruita e quali Bidirezionale Memorizzazione e lettura dei valori. Spostamento In avanti e all'Indietro.
sono gli elementi da essa impiegati. Pertanto, prima di offrire degli esempi di
Avanti Memorizzazione e lettura dei valori. Spostamento solo in avanti.
codice, opportuno presentare una panoramica della libreria STL.
--- Il nucleo della libreria STL costituito da tre elementi: container, algoritmi e Input Lettura ma non memorizzazione dei valori. Spostamento solo in avanti.
iteratori. Questi elementi collaborano fra loro per fornire soluzioni pronte all'uso
Output Memorizzazione ma non lettura dei valori. Spostamento solo in avanti.
per vari problemi di programmazione.

In generale, un iteratore che ha maggiori capacit di accesso pu essere utiliz-


I container zato in luogo di uno che dotato di minori capacit di accesso. Ad esempio, al
posto di un iteratore di input si pu utilizzare un iteratore in avanti.
I container sono oggetti che contengono altri oggetti; la libreria offre container di Gli iteratori vengono gestiti come i puntatori. Dunque possibile incremen-
vari tipi. Ad esempio, la classe vector definisce un array dinamico, deque crea una tarli e decrementarli. Agli iteratori si pu applicare l'operatore *. Gli iteratori
coda a doppio concatenamento e list fornisce una lista lineare. Questi container vengono dichiarati utilizzando il tipo iterator definito dai vari container.
sono chiamati container sequenziali poich nella terminologia STL una sequenza La libreria STL supporta anche gli iteratori inversi. Gli iteratori inversi sono
una lista lineare. Oltre ai container di base, la libreria definisce anche dei container iteratori bidirezionali o ad accesso diretto che si muovono lungo una sequenza in
associativi che consentono di ricercare in modo efficiente un valore sulla base di direzione inversa. Pertanto, se un iteratore inverso _punta. aj_l~_~ne di una sequenza,
una chiave. Ad esempio, un container map fornisce l'accesso a valori tramite incrementando tale iteratore si far in modo che esso punti al penultimo elemento.
chiavi univoche. Pertanto una mappa memorizza coppie chiave/valore e consente Quando parla dei vari tipi di iteratori nelle descrizioni dei template, questo
di trovare un valore sulla base della sua chiave. volume usa i seguenti termini.
Ogni classe container definisce una serie di funzioni specifiche per il container
stesso. Ad esempio, un container per liste include le funzioni di inserimento, can-
cellazione e unione degli elementi. Un container stack include le funzioni di inse- TERMINE DESCRIZIONE
rimento (push) ed estrazione (pop) dei valori. Bi Iter lteratore bidirezionale

Forlter lteratore in avanti


Gli algoritmi In Iter lteratore di input

Gli algoritmi operano su container. Essi rappresentano il mezzo tramite il quale Out Iter - -lteratore di output

possibile manipolare il contnuto dei container. Fra le funzionalit offerte dagli Rand Iter lteratore di accesso diretto
_____ al~ori_t~i vi dell'inizializzazione, Y-ordinamento, la-ricerca e la trasforffiaiion~-=-=-~-
- - - -___:_- .. __ _
634 6-A-?- H 0-U;)._2 4 - - ....,,.,.--- -=---::::.::-
-~--ULIUl'lt. ttl..L.h 1..1onc.~.:i1L. 0'30

Altri elementi del libreria STL


Due entit presenti nella libreria STL sono i binder e i negatori. Un binder
Oltre ai container, agli algoritmi e agli iteratori, la libreria STL si basa su molti collega un argomento a un oggetto funzione. Un negatore restituisce il comple-
mento di un predicato.
altri componenti standard. Fra i principali vi sono gli allocatori, i predicati, le
funzioni di confronto e ogni oggetto funzione. Un ultimo termine da introdurre adattatore. In termini STL, un adattatore
trasforma una cosa in un'altra. Ad esempio, il coniillner queue (che crea una coda
Per ogni container definito un allocatore. Gli allocatori gestiscono
l'allocazione della memoria per un container. L'allocatore standard un oggetto standard) un adattatore per il container deque.
della classe allocator ma per applicazioni particolari possibile definire allocatori
specifici. Per la maggior parte degli usi si impiega 1' allocatore standard.
Molti degli algoritmi e container usano tipi speciali di funzioni chiamati pre- 24.2 Le classi container
dicati. Vi sono due tipi di predicati: unari e binari. Un predicato unario accetta un
argomento mentre un predicato binario ne accetta due. Queste funzioni restitui- Come si detto, i container sono oggetti STL che memorizzano pi dati. La Ta-
scono un risultato true o false. Le condizioni che provocano la restituzione di un bella 24.1 elenca i container definiti dalla libreria STL. In tale tabella sono anche
valore true o false sono definite dal programmatore. Nella parte rimanente del indicati i file header necessari per utilizzare ciascun container; Anche la classe
capitolo, quando necessario utilizzare una funzione predicato unaria, si fa riferi- string, che gestisce stringhe di caratteri, un container ma verr discusso pi
mento al tipo UnPred. Quando invece necessario un predicato binario, verr avanti in questo stesso capitolo.
utilizzato il tipo BinPred. In un predicato binario, gli argomenti sono sempre in Poich il nome del tipo generico nella dichiarazione di una classe template
ordine (primo, secondo). Per predicati unari e binari gli argomenti contengono arbitrario, le classi container dichiarano delle versioni di questi tipi tramite typedef.
valori appartenenti al tipo degli oggetti contenuti nel container. In questo modo il nome dei tipi assume un significato concreto.
Alcuni algoritmi e alcune classi usano un tipo particolare di predicato binario
che confronta due elementi. Le funzioni di confronto restituiscono true se il pri-
mo argomento minore del secondo. Le funzioni di confronto saranno identifica- Tabella 24.1 I container definiti dalla libreria STL.
te dal tipo Comp.
Oltre agli header richiesti dalle varie classi STL, la libreria standard C++ .CONTAINER DESCRIZIONE HEADER RICHIESTO
include i file h.eader <utility> e <functional> che forniscono il supporto per la libre- bitset Un gruppo di bit ~bitset>
ria STL. Ad esempio, la classe template pair che contiene una coppia di valori
deque Una coda a doppio concatenamento <deque>
definita in <Utility>. Si prover a utilizzare pair pi avanti in questo stesso capitolo.
I template contenuti in <functional> aiutano a costruire oggetti che definisco- list Una lista lineare <li st>
no operator(). Si tratta di oggetti funzione che possono essere utilizzati al posto map Una mappa chiave/valore in cui a una chiave <map>
dei puntatori a funzione. In <functional> sono dichiarati vari oggetti funzione. ----------- associato un solo valore
Eccone alcuni;_
multimap Una mappa chiave/valore in cui a una chiave possono <list>
essere associati due o pi valori
plus minus multiplies divides modulus
negate equal_to multi set Un Insieme in cui ogni elemento <set>
not_equal_to greater greater_equal non necessariamente univoco
less less_equal logical_and logical_or logical_not
pri ority_queue Una coda a priorit <queue>

L'oggetto funzione probabilmente pi utilizzato less che determina quando queue Una coda <queue>
un oggetto minore di un altro. Gli oggetti funzione possono essere utilizzati al
set Un Insieme In cui ogni elemento univoco <set>
posto dei puntatori a funzione negli algoritmi STL descritti pi avanti. Utilizzan-
do oggetti funzione al posto dei puntatori a funzione, la libreria STL in grado di stack Uno stack <stack>
generare codice pi efficiente. - - ---- -
veotor Un array dinamico <vector>

--- ---- --
- --- ------
-------w--- -;-
-535- ---e A P- no Lo 2 4 I ti.T R O O U Z I O N E A L L A L I B R E R I A S TL :__-637-. - --

Ecco alcuni dei nomi pi utilizzati fra quelli definiti. Uno dei modi pi comuni per accedere agli elementi di un container a:tra:
verso un iteratore. I container sequenziali e associativi forniscono le funzioni
size_type Un tipo di intero membro begin() ed end() che restituiscono gli iteratori all'inizio e alla fin~ del
reference L'indirizzo di un elemento container. Questi iteratoti sono molto utili per accedere al contenuto del contamer.
const_reference L'indirizzo cconst di un elemento Ad esempio per scorrere un container si pu ottenere un iteratore al suo inizio con
iterator Un iteratore begin() e poi incrementare l'iteratore finch il suo valore non raggiun~~ e~~().
const_iterator Un iteratore const I container associativi forniscono la funzione find() che consente d1 mdlVldua-
reverse_iterator Un iteratore inverso re un elemento del container a partire dalla sua chiave. Dato che i container asso-
const_reverse_iterator Un iteratore const inverso ciativi associano a una chiave il suo valore, find() rappresenta il modo in cui ven-
value_type Il tipo del valore contenuto in un container gono nonnalmente individuati gli elementi conten~ti in ~n ~ontainer as~ociativo.
allocator_type Il tipo dell'allocatore Poich vector un array dinamico, supporta la smtass1 d1 accesso agh elemen-
key_type Il tipo di una chiave ti tramite indicizzazione.
key_compare II tipo di una funzione che confronta due chiavi . Dopo aver creato il container co~tenente informazioni, questo potr esser~
value_compare II tipo di una funzione che confronta due valori manipolato tramite uno o pi algoritmi. Gli algoritmi non solo consentono d1
modificare il contenuto di un container in un modo predefinito ma consentono
anche di trasfonnare un tipo di sequenza in un altro.
Nelle prossime sezioni si imparer ad applicare queste tecniche gener_ali a tr~
24.3 Funzionamento generale diversi container: vector, list e map. Se si impara il funzionamento d1 questi
container, no~ vi dovrebbero essere problemi a impiegare anche gli altri.
Anche se le operazioni interne svolte dalla libreria _STL sono molto sofisticate,
l'uso della libreria piuttosto semplice. Innanzitutto si deve decidere il tipo di
container che si vuole usare. Ognuno di essi offre vantaggi e costringe a scendere
a compromessi. Ad esempio, un vettore ottimo quando necessario usare una 24.4 I vettori
specie di array con accesso diretto e quando non vengono eseguite troppe opera-
zioni di inserimento o cancellazione. Una lista offre agevoli operazioni di inseri- vector probabilmente il container generico pi utilizzato. La classe vector supp?rta
mento e cancellazione al prezzo di una certa lentezza. Una mappa fornisce un un array dinamico. Si tratta di un array le cui dimensioni cresc~no aut~mat1c~
container associativo ma questo ha un costo in termini elaborativi. mente. Come si sa, in C++ le dimensioni di un array sono fissate m fase di compi-
Dopo aver scelto un container, per aggiungervi elementi, per accedere o mo- lazione. Anche se questo di gran lunga il modo pi efficiente per implementare
dificare tali elementi o per cancellare elem.e11tLsi _de:vono usare le sue funzioni gli array, anche il pi restrittivo in quanto le ~imensioni_ d_en'.ar:ay n~n possono
membro. Ad eccezione di bitset, un container cresce automaticamente e si riduce essere modificate run-time in modo da adattarsi alle condmom d1 funzionamento
quando glielementi vengono rimossi. del programma. Un vettore risolve questo problema alloca~do la memoria ~gni
Gli elementi possono essere aggiunti e rimossi da un container in vari modi. volta che viene richiesta. Nonostante il fatto che i vettori abbiano una natura dma-
Ad esempio sia i container sequenziali (vector, list e deque) che i container asso- mica, possibile utilizzarli impiegando la normale notazione dgli array.
ciativi (map, multimap, set e multiset) forniscono una funzione membro chiamata Ecco come specificata la classe vector:
insert() che inserisce elementi in un container e una funzione e rase() che rimuove
gli elementi da un container. I container sequenziali forniscono anche le funzioni template <class T, class Allocator = allocator<T>>class vector
push_back() e push_front() che aggiungono un elemento rispettivamente alla fine
o all'inizio di un contairier. Queste funzioni _sono probabilmente il modo pi co- Qui, T il tipo di dati memorizza!o e Allocatore spe~ifica I' allocatore c?e,
mune con il quale vengono aggiunti elementi a un container sequ~-;;ziale. Per ri- normalmente, l' allocatore standard: La classe vector ha i seguenti costruttori:
muovere .singoJLelementi da un container sequenziale si usano le funzioni
pop_front() e pop_back() che, rispettivamente, rimuovono elementi dall'inizio o ' explicit vector(const Allocator &a= Allocator() );
dalla fine del container. - explicit vector(size_type num;-const T &val-=-T-(-k-
-const Allocator ~q_=:: Allocator( ));
- -- ---~--- -- c.-
-----=-==...-.:-_

638 CAPITOLO 24
INTRODUZIONE ALLA LIBRERIA STL 639

vector(const vector<T, Allocator> &oh); La funzione push_back() inserisce un valore alla fine del vettore. Se necessa-
template <Class lnlter> vector(lnlter start, Initer end, rio, la lunghezza del vettore viene incrementata in modo da contenere il nuovo
const Allocator &a =Allocator( )); elemento. Utilizzando la funzione insert() possibile inserire gli elementi nella
parte mediana del vettore. Un vettore pu anche essere inizializzato. In ogni caso,
La prima forma costruisce un vettore vuoto. La seconda forma costruisce un una volta che un vettore contiene degli elemnti, diventa possibile eseguire acces-
vettore contenente num elementi con il valore val. Per val si pu indicare.uri valo- si o modifiche utilizzando l'operatore [ ). Per rimuovere elementi da un vettore si
re standard. La terza forma costruisce un vettore che contiene gli stessi elementi usa la funzione erase().
di oh. La quarta forma costruisce un vettore che contiene gli elementi dell'inter-
vallo specificato dagli iteratori start ed end. Tabella 24.2 Alcune delle funzioni membro pi utili definite da vector.
Ogni oggetto che verr memorizzato in un vettore deve definire un costruttore
standard. Inoltre deve definire le operazioni < e ==. Alcuni compilatori possono MEMBRO DESCRIZIONE
chiedere che vengano definiti anche altri operatori di confronto. Poich si tratta di reference back ( ) ; Restituisce rindirizzo dell'ultimo elemento di un vettore.
un dettaglio implementativo, per avere informazioni precise si deve consultare la const_reference back( ) const;
documentazione del compilatore. Tutti i tipi interni soddisfano automaticamente iterator begin( ); Restituisce un lteratore al primo elemento di un vettore.
questi requisiti. const_iterator begin( ) const;
Anche se la sintassi template pu sembrare piuttosto complessa, non diffici- void clear( ) ; Rimuove tutti gli elementi dal vettore.
le dichiarare un vettore. Ecco alcuni esempi:
bool empty( .) const; Restituisce true se il vettore chiamante vuoto,
altrimenti restituisce false.
vector<i nt> iv; 11 crea un vettore di irit di 1unghezza zero
vector<char> cv(S); Il crea un vettore di char di 5 elementi itera tor end( ) ; Restituisce un ileratore alla fine del vettore.
vector<char> cv(S, 'x'); Il inizializza un vettore di char di 5 elementi const_iterator end( ) const;
vector<int> iv2(iv); Il crea un vettore di int da un altro vettore di int iterator erase(iterator i); Rimuove l'elemento puntato da i. Restituisce un lteratore
all'elemento successivo a quello rimosso.
Per la classe vector sono definiti i seguenti operatori di confronto: iterator erase(iterator start, iterator end); Rimuove gli elementi compresi fra start ed end. Resbluisce
un iteratore a1relemento che segue rultimo elemento rimosso.
:=, <, <=, !=, >, >=
reference front( ) ; Restituisce l'indirizzo del primo elemento di un vettore.
const_reference front( ) const;
Per vector. definito anche l'operatore di specificazione-delrindice. Questo
iterator insert(iterator i, const T &val); Inserisce val immediatamente prima delrelemento
consente di accedere agli elementi di un vettore utilizzando la normale notazione specificato da I. Restituisce un lteratore an elemento.
per gli arraY. -
void insert(iterator i, size_type num, Inserisce num copie di val immediatamente prima
La Tabella 24.2 mostra molte delle funzioni membro definite da vector. La const T & val) dell'elemento specificato da i.
Parte quarta di questo volume contiene un elenco completo delle classi STL. Fra
le funzioni membro pi utilizzate vi sono size(), begin(), end{), push_back(), insert{) template <class lnlter> Inserisce la sequenza definita da start ed end
void insert (i terator i, lnlter start, immediatamente prima dell'elemento specificalo da i.
ed erase{). La funzione size{) restituisce le dimensioni di un vettore. Questa fun- In Iter end);
zione utile poich consente di determinare run-time le dimensioni di un vettore.
reference operator[ ](size type i) const; Restituisce l'indirizzo dell'elemento specificato da I.
Si ricordi che le dimensioni di un vettore possono crescere a seconda delle esigen- const reference operator[ J (size type i)
_ze e pertanto devono essere determinate durante !-:esecuzione e non durante la coiist; -
compilazione.
void pop_back( ); Rimuove l'ultimo elemento di un vettore.
La funzione begin{) restituisce-un iteratore all'inizio del vettore. La funzione
end{) restituisce un iteratore alla fine del vettore. Come si detto gli iteratori sono vod push_back(const T &val_);_ __ Aggiunge alla fine del vettore un elemento con il valore
specificato dnat- -- . ---- - -
- - --- -simili ai puntatori e g~~~ alle f!:_l~ioni begin{) ed end{) si pu ottenere con -~Pi~ _. ___ _
- dit un iteratore all'inizio o alla fine del .Y:ettore;- --=-=-- size_type size( ) const; ~-- _Re.stiltJisc.,e l!"nufllero di_ elementi CO!J~IP_in un vettore.
640 CAPITOLO 24 I N T R G-Q -U-Z~ ON E -A L LA L I B R E R I A S T L - 641 -::

Ecco un breve esempio che illustra le operazioni di base di un vettore. Ecco l'output del programma:

II Il 1ustra 1 'uso di un vettore. Dimensioni = 10


iinclude <iostream> Contenuto =
ii ne 1ude <vector> a be de f g h i
ii ne 1ude <cctype>
using namespace std; Espansione de1 vettore
Attuali dimensioni = 20
int main{) Contenuto =
a b e d e f g h i j k 1 mn o p q r s t
vector<char> v(lO); Il crea un vettore di lunghezza 10
int i; Contenuto modificato =
AB C OE F GH I J K L MN O P Q R S T
Il visualizza le dimensioni originali di v
cout "Dimensioni = " v.size() endl; Si osservi attentamente questo programma. In main() viene creato un vettore
di caratteri chiamato v con capacit iniziale pari a 10. Pertanto inizialmente v
Il assegna un valore agli elementi di un vettore contiene 1Oelementi. Questo confermato dalla chiamata alla funzione membro
for(i:=O; i<lO; i++) v[1] i + 'a'; size(). Successivamente questi 1Oelementi vengono inizializzati con i caratteri da
"a" a ''j" e quindi viene visualizzato il contenuto di v. Si noti che viene impiegata
11 visualizza il contenuto de 1 vettore la normale notazione per gli array. Quindi alla fine del vettore v vengono aggiunti
cout << "Contenuto =";
altri 10 elemnti utilizzando la funzione push_back(). Questo provoca una cresci-
for(i=O; i<v.size(); i++) cout v[i] " ";
cout "\n\n";
ta div e come si pu vedere dall'output, le sue dimensioni raggiungono il valore
20. Infine il valore degli elementi di v viene modificato utilizzando la normale
cout << "Espansione del vettore\n"; notazione per array.
I* inserisce nuovi elementi alla fine del vettore, Questo programma ha anche un altro punto interessante. Si noti che i cicli che
il qua 1e cresce in modo appropriato *I visualizzano il contenuto div usano come limite il valore v.size(). Un vantaggio
for(i=O; i<lO; i++) v.push_back(i + 10 + 'a'); dei vettori rispetto agli array il fatto che possibile conoscere le loro dimensioni
correnti. Come si pu immaginare questa possibilit utile in molte situazioni.
Il vtsua.lliza_L~_attuali dimensioni di v
cout "Attuali dimensioni = " v.size() endl;
Accesso a un vettore tramite un iteratore
11 visualizza il contenuto del vettore
cout "Contenuto =\n"; Come si sa, gli array e i puntatori sono strettamente correlati in C++.Per accedere
for(i=O; i<v.size(); i++) cot v[i] " "; a un array si pu utilizzare il suo indice oppure un puntatore. Analogamente in
cout "\n\n"; STL vi una stretta correlazione fra vettori e iteratori. Si pu accedere ai membri
di un vettore utilizzando l'indice oppure un iteratore. Questa possibilit illustra-
11 cambia il contenuto del vettore
ta dal seguente esempio:
for(i=O; i<v.size(); i++) v[i] toupper(v[i]);
cout "Contenuto modi fi cat.o. =\n";
for(i=O; i<v.size(); i++) cout v[i] " "; LI Accede agli elementi of un vettore tramite un iteratore.
cout endl ; - #"i nel ude <i ostream;
#include <vector>
return O; ili nel ude <cctype>
______} -----us~ng namespace__s.td; _
642 CAPITOLO 24 -f N T R O D U Z I O.!~LE A L L A L I BH~--b-+ L 643

int main() Ecco l'output del programma:


{
vector<char> v (10); 11 crea un vettore di lunghezza 10 Contenuto originale =
vect>.!:'."char>:: itera tor p; 11 crea un iteratore a b c d e f g h i j
int i;
Contenuto modificato =
11 assegna un valore agli elementi del vettore ABC DE F GHI J
p = v.begin();
i = O;
while(p != v.end())
Nel programma, si noti il modo in cui viene dichiarato l'iteratore p. Il tipo
*p=i+'a'; iterator definito dalle classi container. Pertanto per ottenere un iteratore per un
p++; determinato container si deve utilizzare una dichiarazione simile a quella illustra-
i++; ta nell'esempio: basta qualificare iterator con il nome del container. Nel program-
ma, p inizializzato in modo da puntare all'inizio del vettore grazie alla funzione
membro begin(). Questo iteratore pu quindi essere utilizzato per accedere al vet-
Il visualizza il contenuto del vettore tore un elemento alla volta incrementandolo a piacere. Questo processo analogo
cout "Contenuto originale =\n"; al modo in cui si usa un puntatore per accedre agli elementi di un array. Per
p = v.begin(); determinare il momento in cui viene raggiunta la fine dell'array si impiega la
while(p != v.end()) { funzione membro end(). Questa funzione restituisce un iteratore all'elemento suc-
cout << *p << " 11 ; cessivo rispetto all'ultimo elemento del vettore. Pertanto, quando p uguale a
p++;
end(), stata ~aggiunta la fine dell'array.
cout "\n\n";
Inserimento e cancellazione degli elementi in un vettore
11 cambi a il contenuto del vettore
p = v.begin();
La funzione insert() consente di inserire elementi all'interno del vettore. Per ri-
while(p != v.end())
*p = toupper(*p);
muovere elementi si usa erase(). Il seguente programma mostra l'uso di queste
p++; due funzioni:

11 Il 1ustra l'uso di i nsert ed erase.


11 vi sua 1i zza i 1 contenuto del vettore #include <iostream>
cout ''.Contenuto modificato =\n"; #include <vector>
p = v.begin(); using namespace std;
while(p != v.end()) {
cout << *p << 11 11 ; i nt mai n ()
p++; {
vector<char> v (10);
cout endl ; vector<char> v2;
char str[] "<Vector>";
return O; int i;

Il inizializza v
---;for(i=O; i<lO; i++) v[i] i+ 'a';

--- - ~--
------~-
INTRODUZIONE ALLA LIBRERIA- STL 645

Il copia 1n vZ.1 caratteri contenuti in str Dimensioni dopo l'inserimento delle X= 20


for(i-0; striJ ~ 1++} v2.p~sh_back(str(i]); Contenuto dopo l'inserimento =
,~ .. ,,~... . '
., .-'i~""i;!'"':i;~-, . - abXXXXXXXXXXcdEL}ghi j
Il vhuaH~zaA' contenuto originale del vettore
cout cintenuto originale di v:\n"; Dimensioni dopo la cancellazione= 10
for(fO; 1<v,size(}; 1++) cout v(i] " "; Contenuto dopo 1a cancel 1azione =
. cout "\n\n; abedef g hi j
vector<char>: :iterator p. = v.begin(); Dimensioni dopo l'inserimento di v2 =18
li+= 2; J/
punta al terzo .elemento Contenuto dopo l 'inserimento =
a b< e c t o r > e d e f g h i
Il inserisce 10 'X' in v
v. insert(p, 10, x);
Questo programma mostra due forme di insert(). La prima volta che viene
Il visualizza il contenuto dopo l'inserimento utilizzata, la funzione inserisce in v 1O "X". La seconda volta inserisce in v il
cout "Dimensioni dopo l'inserimento delle X " << v.size() endl; contenuto di un secondo vettore, v2. Questo secondo uso il pi interessante. Tuie
cout "Contenuto dopo l'inserimento =\n"; forma richiede come argomenti tre iteratori. Il primo specifica il punto di inseri-
for(i=O; i<v.size(); i++) cout v[i] " "; mento nel container chiamante. Gli ultimi due puntano all'inizio e alla fi,ne della
cout << "\n\n"; sequenza da inserire.

11 rimozione degli elementi


p = v.begin(); Memorizzazione di oggetti in un vettore
P += 2; 11 punta al terzo elemento
v.erase(p, p+lO); Il rimuove i 10 elementi successivi I precedenti esempi si occupavano dell'inserimento in un vettore di elementi ap-
partenenti a tipi standard, ma anche possibile creare vettori di oggetti apparte-
Il visualizza il contenuto dopo la cancellazione nenti a classi. I vettori possono contenere ogni tipo di oggetti, compresi quelli
cout "Dimensioni dopo la cancellazione=" v.size() endl; delle classi del programma. Ecco un esempio che utilizza un vettore per memo-
cout "Contenuto dopo la cancellazione =\n";
rizzare oggetti che contengono delle misurazioni di temperatura eseguite nell'ar-
for(i=O; i<v.size(); i++) cout v[i] " ";
cout << "\n\n";
co di una settimana. Si noti che DailyTemp definisce il costruttore standard e che
vengono fomite le versioni modificate tramite overloading degli operatori < e ==.
Il Inserisce v2 in v Si ricordi che a seconda del modo in cui il compilatore implementa la libreria
v.insert{p, v2.begin(), v2.end()); STL, possono essere definiti anche altri operatori di confronto.
cout "Dimensioni dopo l'inserimento di v2 =";
cout v.size() endl; Il memorizza un oggetto di tipo class in un vettore.
cout contenuto dopo l'inserimento =\n"; #include <iostream>
for{i=O; i<v.size(); i++) cout v[i] 11 " ; lii nel ude <vector>
cout endl; #i nel ude <cstdl i b>
using namespace std;
return O;
,;lass DailyTemp
int temp;
Questo programma produce il seguente .output~ public:
ailyTemp() tmp = O;
Con-1!!.ou~o originale di v: DailyTemp{int x) { temp = x; } -
.~-::"Tb ~~..! f g h i j
646 CAPITOlO 24 INTRODUZIONE AlTACifiREfffA STL 647

DailyTemp &operator=(int x) Temperature in gradi Centigradi:


ternp = x; return *thi s: 21 25 17 21 31 17 25

I vettori offrono grandi potenzialit, sicurezza e flessibilit ma sono meno effi-


double get_temp() { return temp; } cienti dei normali array. Pertanto, per i compiti di programmazione pi semplici, in
}; genere si preferisce utilizzare normali array; tuttavia vi sono situazioni in cui i van-
taggi dei vettori ripagano di gran lunga i loro costi in termini di efficienza.
bool operator<(DailyTemp a, DailyTemp b)
{
return a.get_temp{) < b.get_temp();
24.5 Le liste
bool operator"=(DailyTemp a, DailyTemp b)
La classe list supporta una lista lineare bidirezionale. A differenza di un vettore
{
return a. get_ temp () == b. get_ temp () ;
che supporta accessi diretti, una lista offre solo accessi sequenziali. Poich le liste
sono bidirezionali, l'accesso pu avvenire in entrambi i sensi di scorrimento.
Una lista ha la seguente specifica template:
int main()
{ template <class T, class Allocator =allocator<T>> class list
vector<DailyTemp> v;
int i; Qui, T il tipo dei dati memorizzati nella lista. L' allocatore specificato da
Alfocator (che normalmente l'allocatore standard). Ecco i suoi costruttori:
for(i=O; i<7; i++)
v.push_back(Dai lyTemp(60 + rand{)%30));
explicit list(const Allocator &a= Allocator() );
cout "Temperature Farenheit:\n"; explicit list(size_type num, const T &val =T ( ),
for(i=O; i<v.size(); i++) const Allocator &a= Allocator( ));
cout v[i] .get_temp() " "; list(const Iist<T, Allocator> &oh);
template <class Inlter>list(lnlter start, Inlter end,
cout endl ; const Allocator &a = Allocator( ));

Il Conversione da grai:!TTarerihe-it a gradi Centigradi La prima forma costruisce una lista vuota. La seconda forma costruisce una
for(i=O; i<v.size(); i++) lista contenente num elementi di valore valore e consente anche di utilizzare valo-
v[i] = (v[i] .get_temp()-32) * 519 ;
ri standard. La terza forma costruisce una lista che contiene gli stessi elementi di
ob. La quarta forma costruisce una lista che contiene gli elementi nell'intervallo
cout "Temperature in gradi Centigradi :\n";
for(i=O; i<v.size(); i++) specificato dagli iteratori start ed end.
cout v[i) .get_temp() " 11 ; Per list sono definiti i seguenti operatori di confronto:

return O; :;;:, <, <=, !=, >, >=

La Tabella 24.3" mostra alcune delle funzioni membro pi utilizzate di list.


Ecco l'output del programma: Come nel caso dei vettori, per inserire elementi nella lista si. lisala.fiiiizione
push_back(). Per inserire elementi al'inizio della lista si usa push_front(). Un
Temperature Farenheit: -- . elemento pu anche esseremseritofn-mezzo alla lista con io.se.r.t(). Per_unire due
71 77 64 70 89 64 78 - liste si usa RJi~() e pednserfre un lista in un'altra si usa merge(). - - - - - -
-----
---------------

648 CAPITOLO 24 649

Tabella 24.3 Alc_une delle funzioni membro pi utili~ate di list. Tabella 24.3 Alcune delle funzioni membro pi utilizzate di list. (canfinua)
MEMBRO DESCRIZIONE MEMBRO DESCRIZIONE
reference back ( ) ; Restituisce findirizzo dell'ultimo elemento della lista. size_type size( ) const; Restituisce il numero di elementi contenuti nella fista.
const_reference back( ) const;
void sort( ) ; Ordina la Usta. La seconda forma ordina la lista con la funzione di
iterator begin( ); Restituisce un ileratore al primo elemento della lista. tempi ate <cl ass Comp> confronto In per determinare quando un elemento minore di un
const_iterator begin( ) const; void sort(Comp cmpfn); altro.
void clear( ) ; Rimuove tutti gli elementi dalla lista. void splice(iterator i, li contenuto di ob viene inserito nella lista chiamante
1i st<T, All ocator> &ob); nella posizione puntata da I. Dopo roperazione, ob vuoto.
bool empty( ) const; Reslltuisce true se la lista chiamante vuota, altrimenti
restituisce false. void splice(iterator i, Celemento puntato da el viene rimosso dalla lista ob e
1ist<T, Allocator> &ob, memorizzato nella lista chiamante nella posizione puntata da I.
i tera tor end ( ) ; Restituisce un ileratore alla fine della lista. iterator el);
const_iterator end( ) const;
void splice(iterator i, Cintervallo definito da start ed end viene rimosso da ob e
iterator erase(iterator i); Rimuove l'elemento puntato da ;, Restituisce un iteratore list<T, Allocator> &08, inserito nella lista chiamante a partire dalla posizione puntata da I.
all'elemento successivo a quello rimosso. ITERATOR start, iterator end);

iterator erase(iterator start, Rimuove gli elementi nell'intervallo compreso fra start ed end.
iterator end); Restituisce un iteratore all'elemento che segue l'ultimo elemento
rimosso.
I dati che dovranno essere contenuti nella lista devono definire un costruttore
reference front ( ) ; Restituisce l'indirizzo del primo elemento della lista.
const_reference front( ) const;
standard. Inoltre devono definire i vari operatori di confronto. Al momento attua-
le, i requisiti per gli oggetti che verranno memorizzati in una lista variano da
iterator i nsert (i tera tor i, const T &val); Inserisce val immediatamente prima dell'elemento specificato da i. compilatore a compilatore dunque opportuno controllarne la documentazione.
Restituisce un iteratore alrelemento.
Ecco un semplice esempio d'uso di una lista.
void insert(iterator i, size_type num, Inserisce num copie di val immediatamente prima dell'elemento
const T &val) specificato da i.
11 Elementi di base delle liste.
template <class Inlter> Inserisce fa sequenza definita da start ed end immediatamente #include <iostream>
void insert (iterator f, prima dell'elemento specificato da i. #i nel ude <li st>
In Iter star~, Inlter end);
using namespace std;
void merge(list<T, Allocator> &ob); Unisce fa lista ordinata contenuta in ob alfa lista ordinata
templ ate <cl ass Comp> chiamante. Il risultato ordinato. Dopo l'unione, la lista contenuta int main()
voi d me!:lle (<1 i st<T, A11 ocator> &ob, in ob vuota. Nella seconda forma, pu essere specificata una {
Comp cmpfn) ; funzione di confronto che determina quando un elemento minore
di un altro. list<int> lst; Il crea una lista vuota
int i;
void pop_back( ) ; Rimuove l'ultimo elemento della lista.

void pop_front( ); Rimuove il primo elemento della lista.


for(i=O; i<lO; i++) lst.push_back(i);

void push_back(const T &val); Aggiunge alla fine della lista un elemento con il valore specificato cout "Dimensioni = " lst.size() endl;
da val.

void push_front(const T .&val); Aggiunge alla fine della lista un elemento con Il valore specificato cout << "Contenuto = ";
da val. -= list<int>: :iterator p lst.begin..0;
while(p != ~~nd())
void remove(const T &val); Rimuove dalla lista gli elementi con nvalore val.
cout *p << " ";
void reverse( ); __tn_~rteJaJlsta chiamante. p++;

-- (segue) cout << "\n\n";


----- --------
INTRODUZIONE ALLA LIBRERIA STL 651
650 CAPITOLO 24

Il cambia il contenuto della lista La funzione end()


p = lst.begin();
Ora giunto il momento di enfatizzare un attributo inaspettato della funzione
while(p != lst.end())
*p = *p + 100;
container end(). La funzione end() non restituisce un puntatore all'ultimo ele-
p++; mento del container ma all'elemento successivo. Pertanto l'ultimo elemento del
container puntato da end() - 1. Questo consente di scrivere algoritmi molto effi-
cienti che attraversano tutti gli elementi di un container incluso l'ultimo. Quando
cout << "Contenuto modificato = "; l'iteratore avr raggiunto lo stesso valore di end(), si sapr che sono stati attra~e:
p = lst.begin(); sati tutti gli elementi. Occorre tenere conto di ci in quanto non una car_atte~st1-
while(p != lst.end()) ca intuitiva di end(). Ad esempio, si consideri il seguente programma che v1sual1zza
cout << *p << 11 11 ; una lista prima in avanti e poi all'indietro.
p++;

11 Uso di end O.
#include <iostream>
return O;
lii nel ude <li st>
using namespace std;

Ecco l'output prodotto dal programma: int mainO'


{
Dimensioni = 10 list<int> lst; Il crea una lista vuota
Contenuto = O 1 2 3 4 5 6 7 8 9 int i;

Contenuto modificato = 100 101 102 103 104 105 106 107 108 109 for(i=O; i<lO; i++) lst.push_back(i);

Questo programma crea una lista di interi. Innanzitutto viene creato un ogget- cout "Li sta visualizzata in avanti =\n";
to list vuoto. Poi nella lista vengono inseriti dieci interi. L'operazione viene ese- list<int>::iterator p = lst.begin();
guita con la funzione push_back() che inserisce ogni nuovo valore alla fine della while(p != lst.end()) {
lista esistente. Poi vengono visualizzati sia le dimensioni che il contenuto della cout << *p << 11 11 ;
p++;
lista. La lista viene visualizzata tramite il seguente frammento di codice:
cout << "\n\n";
list<int>::iterator p lst.begin();
while(p != lst.end()) { cout "Lista visualizzata all'indietro =";
cout << *p " "; p = lst.end();
p++; while(p != lst.begin())
p--; 11 decrementa il puntatore
cout << *p << " ";
Qui l'iteratore p viene inizializzato per puntare all'inizio della lista. Ad ogni
ciclo p viene incrementato e dunque punta all'eleinento successivo. Il ciclo termi-
na quando p punta alla fine della lista. Questo frammento di codice praticamen- return O;
te identico a quello utilizzato per il vettore. I cicli come questo sono molto comu- )
ni nel codice STL e il fatto che gli stessi costrnttori possano essere utilizzati pe_i:_ ___.
accede_r~_a___onta_iner di tipo differente fa parte dell'!J~~t_e_!_lza ~~Ila libreria STL.
---------"""
----:::.-::.....=.....-- ~-.

INTRODUZIONE ALLA LIBRERIA STL 653


652 CAPITOLO 24

Ecco-l'output prodotto dal programma: cout << 11 \n\n";

cout "Contenuto di l st2: ;


Lista visualizzata in avanti =
p = lst2.begin();
ol 2345 6 78 9 while(p != lst2.end())
cout << *p << 11 11 ;
Lfsta visualizzata all'indietro = p++;
9a16s4 3 2 i o
Il codice che visualizza la lista in avanti dovrebbe essere gi noto. Ma si fac- return O;
cia nttenzione al codice che visualizza la lista in ordine inverso. Innanzitutto
all'iteratore p viene assegnata con end() la posizione finale della lista. Poich
end() restituisce un iteratore che punta all'oggetto successivo all'ultimo memo- Ecco l'output prodotto dal programma:
rizznto nella lista, necessario decrementare p. Questo il motivo per cui p viene
decrementato prima dell'istruzione cout contenuta nel ciclo. Si ricordi: end() non Contenuto di lstl:
restituisce un puntatore all'ultimo oggetto della lista ma all'oggetto successivo. o1 2 3 4 56 78 9

Contenuto di l st2:
Le funzioni push_front() e push_back() 9 8 7 6 5 4 3_2 1 o
Si pu costruire una lista aggiungendo elementi da entrambe le estremit.. Finora Poich lst2 viene costruita inserendo gli elementi nella parte frontale, viene
gli elementi erano stati aggiunti solo alla fine, impiegando push_back(). Per ag- prodotta una lista in ordine inverso rispetto a lst1 nella quale gli elementi veniva-
giungere elementi all'inizio della lista si usa push_front(). Ad _esempio: no inseriti via via alla fine.
I* 1>1 fferenze fra
PUSh back () e push front() *I Ordinamento di una lista
#includ; <iostream> -
#include <list> Una lista pu essere ordinata richiamando la funzione membro sort(). Il seguente
using nair.espace std; . programma C-fea-una-lista di interi casuali e quindi la ordina.
int llllaln() _
( Il Ordinamento di una lista.
lht<1nt> lstl, lst2; #include <iostream>
11\t I; lii nel ude <1 i st>
lii nel ude <est dli b>
fo.t(f-O; 1<10; i++) lstl.push back(i); using namespace std;
f~l.1-0; i<lO; i++) lst2.push)ront(i);
int main()
H\\<fnt>:: iterator p; {
-,,1 i st<i nt l st;
~"Contenuto di lstl:\n"; int i;
P_,., hU,begin(); .
'Ht(p I lstl.end()) { 11 crea una li sta di interi casual i
/t'OQt e< .. p << ; for(iO; i<lO; i++)
.. :....~if-t - .-:-:: :::::}st.push_back(rand())';-= - - -
--~. ---- -----
654 CAPITOLO 24 IN T R O O U ZIO N E--A L LA LIBRERIA S TL 655

cout "Contenuto originale =\n"; int main()


list<int>::iterator p = lst.begin(); {
while(p != lst.end(}) { list<int>.J.stl, lst2;
cout << *p << 11 11 ; int i;
p++;
for(i=O; i<lO; i+2) lstl.push back(i);
cout endl endl ; for(i=l; i<ll; i+2) lst2.push)ack(i);

11 ordina la l i sta cout "Contenuto di l stl: \n";


lst.sort(); list<int>::iterator p = lstl.begin();
while(p != lstl.end{)) {
cout "Contenuto ordinato =\n"; cout << *p << " ";
p = l st.begin(); p++;
while(p != lst.end()}
cout << *p << " "; cout endl endl ;
p++;
cout "Contenuto di l st2: \n";
p = lst2.begin();{
return O; while(p != lst2.end())
cout << *p _<< " ";
p++;
Ecco un ipotetico output prodotto dal programma:
cout << endl << endl;
Contenuto originale =
41 18467 6334 26500 19169 15724 11478 29358 26962 24464 Il unisce le due liste
lstl.merge(lst2);
Contenuto ordinato = if(lst2.empty())
cout "Ora l st2 vuota";
41 6334 11478 15724 18467 19169 24464 26500 26962 29358
cout "Contenuto di l stl dopo merge: \n";
p = lstl.begin();
while(p != lstl.end())'{
Unione di due liste
cout << *p << ;
p++;
Una lista ordinata pu essere unita a un'altra. II risultato una lista ordinata che
contiene gli oggetti presenti nelle due liste di partenza. La nuova lista occuper la
lista chiamante mentre la seconda lista rimarr vuota. II seguente esempio unisce return O;
due liste. La prima lista contiene i numeri pari compresi fra Oe 9 e la seconda lista
contiene i numeri dispari (sempre compresi fra O e 9). L'unione delle due liste
produce la sequenza O I 2 3 4 5 6 7 8 9. Ecco l'output prodotto dal programma:
Il Unone di due liste. Contenuto di l stl:
#include <iostream> ci 2 '4 6 8
#include <list>
------using namespace std;
Contenuto di lst2:
1 3 5 7 9
I N T A 6DUTI ON IO -A L L A L I B A E A I A S-LL----657 -
656 CAPITOLO 24

Ora 1st2 vuota friend bool operator==(const myclass &ol,


Contenuto di 1stl dopo merge: const myclass &o2);
o1 2 3 4 5 6 7 8 9 friend bool operator!=(const myclass &ol,
const myclass &o2);
);
Un'ultima nota su questo esempio; si noti l'uso della funzione empty(). Tale
funzione restituisce true se il container chiamante vuoto. Poich empty() rimuo- bool operator<(const myclass &ol, const myclass &o2)
ve tutti gli elementi dalla seconda lista, dopo il completamento dell'unione essa {
risulter vuota, come si pu notare dall'output del programma. return ol.sum < o2.sum;

Memorizzazione di oggetti in una lista bool operator>(const mycl ass &ol, const mycl ass &o2)
{
Ecco un esempio che utilizza una lista per memorizzare oggetti di tipo myclass. Si return ol.sum > o2.sum;
noti che gli operatori <, >, !=e == sono modificati 'tramite overloading per operare su
oggetti di tipo myclass. Questi sono gli operatori richiesti dal compilatore Microsoft
Visual C++ (il compilatore impiegato per eseguire il test degli esempi STL presen- bool operator==(const myclass &ol, const myclass &o2)
tati in questo capitolo). Altri compilatori potrebbero richiedere l' overloading anche }
di altri operatori. La libreria STL utilizza queste funzioni per determinare l'ordina- return ol. sum == o2. sum;
mento e l'uguaglianza degli oggetti del container. Anche se la lista non un container
ordinato, comunque necessario avere la possibilit di confrontare gli elementi
bool operator!=(const myclass &ol, const myclass &o2)
durante le operazioni di ricerca, ordinamento o unione.
{
return ol.sum != o2.sum;
Il Memorizzazione di oggetti in una lista.
lti nel ude <i ostream>
#i nel ude <li st>
int main()
lti nel ude <estri ng>
{
using namespace std;
int i;
cl ass myd-ass-~f-
Il crea la prima lista
i nt a, b~
li st<mycl ass> l stl;
int sum;
for(i=O; i<lO; i++) 1stl.push_back(mycl ass(i, i));
publ i e:
myclass() { a = b = O; }
cout << "Prima lista = ";
myclass(int i, int j) {
list<myclass>::iterator p = lstl.begin();
a = i;
while(p != lstl.end()) {
b = j;
cout p->getsum() " ";
sum = a + b;
p++;
int getsum() { return sum;-}
cout endl ;
friend bool operator<(const myclass &ol,
Il
crea la seconda lista
const mycl ass &o2);
list<myclass> lst2;
friend bool operator>(const myclass &ol, - - . for(l'=o;-i-<lO;- i++) l st2.push_back(mycl ass(i*2, i*3));
-~-=--const myclass &o2);
658 CAPITOLO 24 INTRODOZi-DNE ALLA LIBRERIA STL 659

cout "Seconda 1i sta = "; Come si detto, una mappa pu contenere solo chiavi univoche, non con-
p = lst2.begin(); -
sentito creare chiavi duplicate. Per creare una mappa che consente l'impiego di
whtle(p != lst2.end()) {
cout << p->getsum() << " 11 ;
chiavi multiple si deve utilizzare una multi-mappa.
p++;
Il container map ha la seguente specifica template.

cout endl ; template <class Key, class T, class Comp less<Key>,


class Allocator = allocator<T>> class map
Il ora unisce lstl e lst2
lstl.merge(lst2); Qui Key il tipo di dati delle chiavi, T il tipo di dati dei valori memorizzati
(ovvero mappati) e Comp una funzione che confronta le due chiavi. Nonnal-
Il visualizza il risultato mente si tratta della funzione oggetto di servizio standard less(). Allocator
cout "Risultate dell'unione = ";
l'allocatore (che nonnalmente allocator).
p = lstl.begin();
while(p != lstl.end()) {
Una mappa ha i seguenti costruttori:
cout << p->getsum() << " 11 ;
p++; explicit map(const Comp &cmpfn = Comp( ),
const Allocator &a = Allocator( ) );
map(const map<Key, T, Comp, Allocator> &ob);
return O; template <class Inlter> map(lnlter start, lnlter end,
const Comp &cmpfn =Comp( ), const Allocator &a =
Allocator( ));
Il programma crea due liste di oggetti myclass e poi ne visualizza il contenu-
to. Quindi unisce le due liste e visualizza il risultato. Ecco l'output prodotto dal La prima fonna costruisce una mappa vuota. La seconda fonna costruisce una
programma: mappa che contiene gli stessi elementi di ob. La terza fonna costruisce una mappa
che contiene gli elementi dell'intervallo specificato dagli iteratori start ed end. La
Prima lista = O 2 4 6 B 10 12 14 16 18 funzione specificata da cmpfn, se presente, detennina lordinamento della mappa.
Seconda lista = O 5 10 15 20 25 30 35 40 45 In generale, ogni oggetto utilizzato come chiave deve definire un costruttore
Risultato dell'unione = O O 2 4 5 6 8 10 10 12 14 15 16 18 20 25 30 35 40 45 standard per eseguire l'overloading di tutti gli operatori di confronto necessari.
Per map sono definiti i seguenti operatori di confronto.

24.6 Le mappe ==, <, <=, !=, >, >=

La classe map supporta un container associativo in cui viene realizzata una map- La Tabella 24.4 mostra numerose funzioqi membro di map. Nelle descrizioni,
pa fra chiavi univoche e valori. In pratica una chiave semplicemente un nome key_type il tipo della chiave e value_type rappresenta pair<Key, T>.
assegnato a un valore. Successivamente diventa possibile recuperare il valore uti- Le coppie chiave/valore vengono memorizzate in una rriappa come oggetti di
lizzando la relativa chiave. Pertanto in generale, una mappa un elenco di coppie tipo pair che ha la seguente specificazione template.
chiaYe/valore. La potenza della mappa legata al fatto che consente di ricercare
un valore sulla base della sua chiave. Ad esempio si potrebbe definire una mappa template <class Ktype, class type> struct pair {-
che utilizza come chiave il nome di una persona e memorizza come valore il typedef Ktype first_type; Il type of key
numero di telefono di quella persona. I container associativi stanno diventando ---+ypedef Vtype second_type; Il type of value
Ktype first; 11 contains the key
sempre pi.Y_popolari.neLcampo della programmlJ.zione. _ _ -- _
Vtype second; 11 contains the value

- - - ---- ---- --
660 CAPITOLO 24

11 costruttori Si pu costruire una coppia utilizzando uno dei costruttori di pair oppure
pai r(); make_pairO, che costituisce un oggetto pair sulla base del tipo degli.oggetti utilizza-
pai r{const Ktype &k, const Vtype &v); ti come parametri. make_pair() una funzione generica che ha il seguente prototipo.
template<class , class > pair(const<A, B> &ob);
template <class Ktype, class Vtype>
pair<Ktype, Vtype> make_pair(const Ktype &k, const Vtype &v);
Come si pu dedurre dai commenti, il valore della chiave contiene la chiave
stessa e il valore di value contiene il valore associato a tale chiave. Come si pu vedere, make_pair() restituisce un oggetto pair costituito dai va-
lori dei tipi specificati da Ktype e Vtype. Il vantaggio di make_pair() il fatto che
il tipo degli oggetti memorizzati viene determinato automaticamente dal compi-
Tabella 24.4 le funzioni pi utilizzate della .container map. latore e dunque non deve essere specificato esplicitamente dal programmatore.
Il seguente programma illustra gli elementi di base dell'uso di una mappa. In
MEMBRO DESCRIZIONE
particolare vengono utilizzate coppie chiave/valore che mostrano il mappaggio
iterator begin( ); Restituisce un iteratore al prtmo elemento della mappa. fra le lettere maiuscole e il corrispondente codice ASCII. Pertanto la chiave un
const_iterator begin( ) const;
carattere e il valore un intero. Le coppie chiave/valore memorizzate sono:
void clear( ); Rimuove tutti gli elementi dalla mappa.

size_typ: count(canst key_type &k) const; Restituisce ii numero di volte che ksi presenta nella mappa A 65
(1 o zero). . B 66
bool emp:y( ) const; Restituisce true se la mappa chiamante vuota, altrimenti c 67
restituisce false.
e cosl via. Dopo aver memorizzarlo le coppie, viene richiesta una chiave (ovvero
i tera tor :nd ( ) ; Restituisce un Jteratore alla fine della mappa.
const_iterator end( ) const; una lettera compresa fra A e Z) e quindi viene visualizzato il codice ASCII di tale
voi d era;e(iterator i);
lettera.
Rimuove relemento puntato da I.

void erase(iterator start, i tera tor end); Rimuove gli elementi nell'intervallo compreso fra start ed end. 11 Uso delle mappe.
.#include <iostream>
size_typ: erase(canst key_type &k) Rimuove dalla mappa gli elementi la cui chiave k.
#i nel ude <map>
iterator find(const key_type &k); Restituisce un iteratore alla chiave specificata. Se la chiave non . -~:_!ng namespace std;
const_iterator find(const key_type &k) viene trovata, allora viene restituito un iteratore alla fine della
const; _ mappa.
int main()
iterator insert(iterator i, Inserisce val al posto o dopo l'elemento specificato da /. {
const value_type &val); Restituisce un iteratore all'elemento. map<char, i nt> m;
template <class lnlter> Inserisce un intervallo di elementi. int i;
voi~ insert(Inlter start, Inlter end)
Il inserisce le coppie nella mappa
pair<ite~ator, bool> Inserisce val nella mappa chiamante. Restituisce un Jteratore for(i=O; i<26; i++) {
inse"t(const value_type &val); all'elemento. t:elemento viene inserito solo se non esiste ancora
nella mappa. Se l'elemento viene inserito, restituisce, pair<iterator, m.insert(pair<char, int>('A'+i, 65+i));
true>, altrimenti restituisce pair<iterator, false>.

referen=e operator[ ] {const key_type &i) Restituisce rindirizzo dell'elemento specificato da I. Se questo
elemento non esiste viene inserito.--- char eh;
11
cout "Introdurre 1a chi ave: ;

.. Restituisce ii numero di elementi attualmente presenti nella lista. cin eh;


662 CAPITOLO 24 - IN T R O O UZIO NE ALLA LIBRERIA Slr---663 - -

map<char, int:S:-:!iterat0-r p; 11 Usa una mappa per creare una rubrica telefonica.
#include <ostream>
Il trova il valore sulla base della chiave immessa #include <map>
p = m. find(ch); #include <cstring>
if{p != m.end()) usi ng namespace std;
cout "I 1 suo va 1ore ASCII " p->second;
else class name {
cout "La chi ave non presente nella mappa. \n"; char str[40];
publ ic:
return O; name() { strcpy(str, 1111 ) ; }
name{char *s) { strcpy(str, s); }
char *get{) { return str; }
Si noti l'uso della classe template pair per costruire le coppie chiave/valore. Il
};
tipo di dati specificato da pair deve corrispondere a quello della mappa in cui sono
state inserite le coppie. 11 Definisce l 'operatore "minore di".
Dopo aver inizializzato la mappa con le chiavi e i valori, possibile ricercare bool operator<(name a, name b)
un valore partendo dalla sua chiave, utilizzando la funzione find(fche restituisce {
un iteratore all'elemento corrispondente oppure (nel caso la chiave non venga return strcmp(a.get(), b.get()) < O;
trovata) alla fine della mappa. Quando la chiave viene trovata, il valore associato
alla chiave contenuto nel membro second di pair. .
Nell'esempio, le coppie chiave/valore venivano costruite in modo esplicito, cl ass phoneNum {
utilizzando pair<char, int>. Anche se questo approccio non ha nulla di sbagliato, char str[80];
spesso pi facile utilizzare make_pair() che costruisce un oggetto pair sulla base public:
del tipo dei dati utilizzati come parametri. Ad esempio, dato il programma prece- phoneNum() { strcmp(str, 1111 ) ; }
dente, anche questa riga di codice inserisce in m coppie chiave/valore. phoneNum(char *s) { strcpy(str, s); }
char *get() { return str; }
};
m. insert(make_pai r( (char) {'A' +i), 65+i));

Qui la conversione cast in char necessaria per eliminare la conversione auto- int main()
matica in int che viene eseguita quando i viene sommato a "A". Altrimenti la {
determinazione del tipo automatica. map<name, phoneNum> directory;

Il inserisce nella mappa nomi e numeri


Memorizzazione di oggetti in una mappa di ree tory. i nsert (pai r<name, phoneNum>(name("Tommaso"),
phoneNum("555-4533")));
Come per tutti gli altri container, anche una mappa pu essere utilizzata per me- directory. i nsert (pai r<name, phoneNum>(name{"Cri sti na"),
morizzare oggetti appartenenti a classi create dall'utente. Ad esempio, il seguente phoneNum("555-9678")));
programma crea un semplice elenco tele~onico. In pratica crea una mappa di nomi directory. insert(pai r<name, phoneNum>(name("Gi ovanni "),
e dei rispettivi numeri telefonfd. A tale scopo crea due classl"chiamate name e phoneNum( "555-8195")));
number.Dato che una mappa gestisce un elenco ordinato di chiavi, il programma directory. insert{pair<name, phoneNum>(name("Rachele"),
phoneNum("555-0809")));
definisce anche I' operatore <per oggetti di tipo name. In generale si deve definire
l'operatore< per tutte le classi che verranno utilizzate come chiave (alcuni com-
11 dato n- nome, trova n-numero_
-pilatorf-possono E!::!:_i~de!~ ~~- ~efl_nizione anche di altri.operatori di confronto).
- char str[80];
664 CAPITOLO 24
I N T R O O U Z I 0-N- E A L L A L I B R E R I A S TL 665

cout << "Introdurre un nome: ";


ci n str;
template <class Inlter, class T>
size_t count(lnlter start, Inlter end, const T &val);
map<name, phoneNum>: :itera tor p; template <class Inlter, class UnPred>
size_t count_if(lnlter start, Inlter end, UnPredpfa);
p = directory.find(name(str));
if(p != directory.end())
cout "Numero telefonico: " p->second.get(); Tabella 24.5 Gli algoritmi della libreria STL.
else
cout << "Nome non presente. \n"; ALGORITMO uso
adj acent_fi nd Ricerca elementi adiacenti in una sequenza e restituisce un iteratore alla prima corrispondenza.
return O;
binary_search Esegue una ricerca binaria su una sequenza ordinata.

copy Copia una sequenza.


Ecco un esempio d'uso del programma:
copy_backward Come copy() tranne per il fatto che prima sposta g6 elementi dalla fine della sequenza.

Introdurre un nome: Rachele count Restituisce il numero di elementi della sequenza.


Numero tele fonico: 555-0809.
count_i f Restituisce il numero di elementi della sequenza che soddisfano un predicato.

equal Determina se due intervalli coincidono.


Nel programma, ogni voce della mappa un array di caratteri contenente una
stringa chiusa dal carattere nullo. Pi avanti nel capitolo si vedr un modo pi equa 1_range Restituisce un intervallo in cui un elemento pu essere Inserito In una sequenza senza alterar-
semplice per scrivere questo programma impiegando il tipo standard string. ne rordine.

fill e fill_n Riempie un intervallo con il valore specificato.

find Ricerca in un intervallo un valore e restituisce un iteratore alla prima occorrenza delfelemento.
24. 7 Gli algoritmi
find_end Ricerca in un intervallo una sottosequenza. Restituisce un ileratore alla fine della sottosequenza
all'interno dellintervallo.
Mentre i container forniscono il supporto per le proprie operazioni di base, gli
find_first_of Trova in una sequenza il prtmo elemento che corrisponde a un elemento di un intervallo.
algoritmi forniscono azioni pi estese o complesse. Inoltre essi consentono di
lavorare contemporaneamente su due tipi differenti. Per av"'feaccessoagli algoritmi find_if Ricerca in un intervallo un elemento per il quale un predicato unario definito dall'utente resti-
STL si deve includere nel programma l'header <algorithm>. tuisce trua.
La libreria STL definisce un gran numero di algoritmi che sono riepilogati for_each Applica una funzione a un Intervallo di elementi.
nella Tabella 24.5. Tutti gli algoritmi sono funzioni template. Questo significa che
generate e generate_n Assegna agli elementi di un intervallo 1. valori prodotti da una funzione di generazione.
possono essere applicati a ogni tipo di container. Gli algoritmi STL verranno trat-
tati nella Parte quarta di questo volume; le prossime sezioni mostrano solo alcuni includes Determina se una sequenza include tutti gli elementi di un'altra sequenza.
esempi del loro uso.
inplace_merge Unisce un intervallo con un altro intervallo. Entrambi gli intervalli devono essere ordinati in senso
ascendente. La sequenza risultante ordinata.

Conteggio iter_swap Scambia i valori puntati dai due iteratori lornili _col!1_9 .argomenti.

lexicographical_compare Confronta alfabeticamente due sequenze.


Una delle operazioni piilseniplici che possibile eseguire su una sequenza il
conteggio. Per fare ci si usa count() o count_if(). La loro forma generale la Trova il primo punto della sequenza che non sia inferiore a un determinato valore.
--seguente:
(segue]
- - - - ---- -- -- ----- -
I N T-R-0-0-U Z LO NE . A L L A L I B R E R I A S T L - 667

Tabella 24.5 Gli algoritmi della libreria STL. (conflnua} Tabella 24.5 Gli algoritmi della libreria STL. (conffnuaJ

ALGORITMO uso ALGORITMO uso


make_heap set_sy11111etri c_di fference Produce una sequenza che contiene la differenza simmetrica fra due insiemi ordinati.
Costruisce uno heap da una sequenza.
Produce una sequenza che contiene l'unione di due insiemi ordinati.
max Restituisce il maggiore di due valori. set_union

max_element Restituisce un lteratore all'elemento maggiore di un intervallo. sort Ordina un Intervallo.

merge sort_heap Ordina uno heap in un determinato intervallo.


Unisce due sequenze ordinate, inserendo il risultato In una terza sequenza.
Crea una sequenza In modo che tutti gli elementi per I quali un predicato restituisce true prece-
m1n Restituisce il minore di due valori. stabl e_parti ti on
dano quelli per cui il predicato restituisce false. li partizionamento stabie. Viene pertanto con-
servato l'ordinamento relativo della sequenza.
min_element Restituisce un iteratore all'elemento minore di un Intervallo.
Ordina un intervallo. ~ordinamento stabile. Pertanto gli elementi uguali non verranno spostati.
mismatch Trova la prima differenza fra gli elementi di due sequenze. Restituisce gli iteratori ai due stable_sort
elementi.
swap Scambia due valori.
next_pennutati on Costruisce la successiva permutazione di una sequenza.
swap _ranges Scambia gli elementi di un intervallo.
nth_el ement Dispone una sequenza in modo che tutti gli elementi minori dell'elemento E si trovino prima di Applica una funzione a un intervallo di elementi e memorizza il risultato in una nuova sequenza.
tale elemento e che tutti gli elementi maggiori di E seguano tale elemento. transform

partial_sort Ordina un intervallo. unique e unique_copy Elimina gli elementi duplicati da un intervallo.

Trova l'ultimo punto della sequenza che non sia maggiore di un determinato valore.
parti al _sort_ copy Ordina un Intervallo e poi copia il numero di elementi necessari per riempire la sequenza upper_bound
risultante.

partiti on Dispone una sequenza in modo che tutti gli elementi per i quali un predicato restituisce true
precedano quelli per cui il predicato restituisce false. L'algoritmo count() restituisce il numero di elementi della sequenza compresi
pop_heap Scambia il primo elemento con il penultimo e poi ricostruisce lo heap. fra start ed end e corrispondenti a val. L'algoritmo count_if() restituisce il numero
di elementi della sequenza compresi fra start ed end per i quali il predicato unario
prev_permutati on Costruisce la permutazione precedente di una sequenza.
pfn restituisce il valore booleano true.
push_heap Inserisce un elemento alla fine dello heap. Il seguente programma illustra l'uso di count().
random_shuffle Casualizza una sequenza.
Il Illustra l'uso di count().
remove, remove_i f, Rimuovono elementi dall'intervallo specificato. lii nel ude <i ostream>
remove_copy e"l'emove_copy_if .
#include <vector>
rep l ace, rep l ace_copy, Sostituisce gli elementi di un intervallo. lii nel ude <cstdl i b>
replace_if e replace_copy_if #include <algorithm>
reverse e reverse_copy Inverte l'ordine di un intervallo.
using namespace std;

rotate e rotate_copy Ruota a sinistra gli elementi in un intervallo. int main()


search
{
Ricerca una sottosequenza in una sequenza.
vector<bool> v;
search~n Ricerca in una sequenza il numero specificato di elementi simili. int i;
set_difference Produce una sequenza che contiene la differenza fra due insiemi ordinati. for(i=O; i < 10; i++) {
set_ i ntersecti on Produce una sequenza che contiene l'Intersezione di due insiemi ordinati. if(rand() % 2) v.push_back(true);
etse-v-;push.::back(fa 1se);
-1--
668 CAPITOLO 24
INTRODUZTUNE ALLA LIBRERIA- STL 669

cout << "Sequenza =\n";


for(i=l; i < 20; i++) v .push_back(i);
for(i=O; i<v .size(); f++)
cout boolalpha v[i] 11 ";
cout << "Sequenza =\n";
coi.if endl ;
for(i=O; i<v.size(); i++)
cout << v[i] << " ";
i = count(v.begin(), v.end(), true); cout endl;
cout i " elementi true. \n";
i = count_if(v.begin(), v.end(), dividesBy3);
return O;
cout i " numeri sono divisibili per 3.\n";

return O;
Il programma visualizza il seguente output:

Sequenza = Questo programma produce il seguente output:


true true false false true false false false false false
3 elementi true.
Sequenza =
1 2 3 4 5 6 7 B 9 10 11 12 13 14 15 16 17 18 19
. Il programma i?iz~a creando un vettore costituito da valori true e false genera- 6 numeri sono di vis i bili per 3.
ti casualmente. Qumd1 usa count() per conteggiare il numero di valori true.
Il se~ente programma illustra l'uso di count_if(). Viene creato un vettore con- Si deve notare il modo in cui realizzato il predicato unario divides8y3. Tutti
tenente 1num~ri da 1 a I?. Q~indi con~a. i valori divisibili per 3. A tale scopo viene i predicati unari ricevono come parametro un oggetto dello stesso tipo di quello
creato un predicato unano chiamato d1v1des8y3() che restituisce true quando l' ar- contenuto nel container sul quale il predicato sta operando. Il predicato deve resti-
gomento divisibile per 3. tuire un risultato true o false sulla base del valore dell'oggetto.

11 I11 ustra l 'uso di count_ if () .


#i nel ude <iostream> Rimozione e sostituzione degli elementi
#i nel ude <vector>
#i nel ude <a 1gori thm> Talvolta utile generare una nuova sequenza che costituita da un numero ben
usi ng namespace std; deciso di elementi tratti da una sequenza originaria. Un algoritmo di questo tipo
remove_copy(). Ecco la ~'\lg_fQ~~_generale:
I* Questo iL.un predicato unari o che determina
se il numero divisibile per 3. *I
bool dividesBy3(int i)
template <class Initer, class Outlter, class T>
{ Outlter remove_copy(lnlter start, Inlter end,
if( (i%3) == O) return true; Outlter result, const T &val);

return false; L'algoritmo remove_copy() copia gli elementi dall'intervallo specificato, ri-
muovendo quelli uguali a val. II risultato viene inserito nella sequenza puntata da
result e quindi viene restituito un iteratore che punta alla fine del risultato. Le
int main() dimensioni del container_ di destinazione devono essere sufficienti per contenere
{ il risultato. -< -
vector<int> v;
Per copiare una sequenza in un'altra sostituendo un elemento con un altro, si
int i;
usa replace_copy().

----- --- ----- - -


--- ------~--

670 CAPITOLO 24 INTRODUZIONE ALLA LIBRERIA STL 671

La sua forma generale : cout endl ;

template <class Jnlter, class Outlter, class T> 11 sostituisce gli spazi con il carattere due punti
Outlter replace_copy(Inlter start, Inlter end, replace_copy(v.begin(), v.end(), v2.begin(), ' ', ':');
Outlter result, const T &old, const T &new);
cout "Risultato dopo la sostituzione degli spazi con caratteri di due-
L'algoritmo replace_copy() copia gli elementi dall'intervallo specificato, so- punti =\n";
stituendo gli elementi uguali a old con new. Il risultato viene inserito nella se- for(i=O; i<v2.size(); i++) cout v2[i];
quenza puntata da result e quindi viene restituito un iteratore alla fine del risulta- cout endl endl ;
to. Le dimensioni del container di destinazione devono essere sufficienti per con-
tenere il risultato. return O;
Il seguente programma illustra l'uso di remove_copy() e replace_copy(). In
particolare il programma crea una sequenza di caratteri. Quindi rimuove dalla
sequenza tutti gli spazi e li sostituisce con il carattere":". Ecco l'output prodotto dal programma:

Il Illustra 1 'uso di remove_copy e replace_copy. Sequenza di input =


#include <iostream> Programmare in STL fantastico.
#i nel ude <vector> Risultato dopo 1a rimozione degli spazi
#include <algorithm> ProgrammareinSTLfantastico.
using namespace std;
Sequenza di input =
int main() Programmare in STL fantastico.
{ Risultato dopo la sostituzione degli spazi con caratteri di due-punti
char str[] ="Programmare in STL fantastico."; Programmare: in: STL: : fantastico.
vector<char> v, v2(30);
int i;
Inversione di una sequenza
for(i=O; str[i]; i++) v.push_back(str[i]);
Uno degli algoritmi pi utilizzati reverseH-.ehe-inverte una sequenza. La sua
Il**** !_llustra l'uso di remove_copy **** forma generale :
cout "Sequenza di input =\n";
for(i=O; i<v.size(); i++) cout v[i];
template <class Biiter> void reverse(Bilter start, Bilter end);
cout endl ;

11 rimozione degli spazi L'algoritmo reverse() inverte l'ordine dell'intervallo degli elementi conmpresi
remove_copy(v.begin(), v.end(), v2.begin(), ' '); fra start ed end. Il seguente programma illustra l'uso di reverse():

cout "Risultato dopo la rimozione degli spazi =\n"; Il Illustra l'uso di reverse.
for(i=O; i<v2.size(); i++) cout v2[i]; #include <iostream>
cout endl endl ; #include <vector>
#i nel ude-<argortthm>
Il **** ora illustra l'uso di replace_copy **** using riamespace std;
cout "Sequenza di input =\n"; --
for(i=O; i<v.size(); i++) cout -v[i]; intmffn\r- -
- --- . ---- -- - -- -
672 CAPITOLO 24 INTRODUZIONE ALLA LIBRERIA STL 673

seconda sequenza. Entrambe le versioni restituiscono un iteratore che punta alla


vector<int> v; fine della sequenza prodotta.
int i; Il seguente programma utilizza una semplice funzione di trasformazione chia-
mata reciprocai() che crea il reciproco di un numero. Si noti che la sequenza risul-
for(i=O; i<lO; i++) v.push_back(i);
tante viene memorizzata nella stessa lista contenente la sequenza originale.
cout "Situazione iniziale = ";
for(i=O; i<v.size(); i++) cout v[i] " "; 11 Uso dell'algoritmo transfonn.
cout endl; #include <iostream>
#i nel ude <1 i st>
reverse(v.begin(), v.end()); #i ne 1ude <a 1gori thm>
using namespace std;
cout "Dopo 1 'inversione = ";
for(i=O; i<v.size(); i++) cout v[i] " "; Il Una semplice funzione di trasfonnazione.
double reciprocal (double i) {
return O; return 1.0li; Il restituisce il reciproco

int main()
Ecco l'output prodotto dal programma:
{

Situazione iniziale = O 1 2 3 4 5 6 7 8 9 1i st<doubl e> val s;


Dopo 1 'inversione = 9 8 7 6 5 4 3 2 1 O int i;

Il inserisce i valori in una lista


Trasformazione di una sequenza for(i=l; i<lO; i++) vals.push_back((double)i);

Uno degli algoritmi pi interessanti transform() che modifica ciascun elemento cout "Contenuto originale di vals =\n";
di un intervallo sulla base di una funzione fornita a parte. L'algoritmo transform() 1ist<doub1 e>:: itera tor p = va 1s. begi n ();
ha le due forme generali seguenti: while(p != vals.end()) {
cout << *p << " ";
template <class Initer, class Outlter, class Fune) p++;
Outlter transform(lnlter start, Initer end, Outlter result,
Fune unaryfunc);
template <class Initerl, class lniter2, class Outlter, class Fune) cout endl ;
Outlter transform(lnlterl start], Inlterl endl, Inlter2 start2,
Outlter result, Fune binaryfunc); Il trasfonna vals
p = transfonn(vals.begin(), vals.end(),
val s.begin(), reciprocal);
L'algoritmo transform() applica una funzione a un intervallo di elementi e
memorizza il risultato in result. Nella prima forma, l'intervallo specificato da -'". cout "contenuto trasfonnato di vals =\n";
start ed end. unaryfunc la funzione da applicare. Questa funzione riceve come p = vals.begin();
parametro il valore di un elemento e deve restituire la sua trasforriiazione. Nella while(p != vals.end())
seconda forma, la trasformazione viene applicata utilizzando una funzione opera- cout << *p << " ";
tore binaria che_rk.eve come primo parametro il valore dell'.ele.mento della se- p++;
~ __-_ ---:.:.:..'l!:!enza-c:~t:_deveess~~trasformato e come secondo parametro n elememo-della-:--::-::- r=--='----- ----
____ _,.

-fiiffR O OUZIO NE A LIA LIBA E.A I A-S-f.L- --675-


674 CAPITOLO 24 -----

return O; less_equal logical_and logical_or

Ed ecco gli oggetti funzione unari:


Ecco l'output prodotto dal programma:
logical_not negate
Contenuto origina 1e di va 1s =
123456789 Gli oggetti funzione svolgono le operazioni specificate dal loro nome. L'uni-
Contenuto trasformato di val s = co oggetto funzione il cui nome poco chiaro negate che inverte il segno del suo
1 0.5 0.333333 0.25 0.2 0.166667 0.142857 0.125 0.111111 argomento.
Gli oggetti funzione fomiti sono classi template che eseguono l'overloading
Come si pu vedere, ogni valore di vals stato trasformato nel suo reciproco. di operator(), che restituisce il risultato dell'operazione specificata sul tipo di dati
selezionato. Ad esempio, per richiamare l'oggetto funzione binario plus() si usa la
seguente forma:
24.8 Uso degli oggetti funzione
plus<float>{}
Come si detto all'inizio del capitolo, la libreria STL supporta (e utilizza pesante-
mente) gli oggetti funzione. Come si ricorder gli oggetti funzione sono semplice- Gli oggetti funzione fomiti usano l'header <functional>.
mente classi che definiscono operator(). La libreria STL fornisce numerosi oggetti Si pu partire da un semplice esempio. Il seguente programma usa l'algoritmo
funzione gi pronti, ad esempio less, minus e cos via..Inoltre consente di definire i transform"o (descritto nella sezione precedente) e l'oggetto funzione negate() per
propri oggetti funzione. Per la verit la descrizione di tutti gli elementi riguardanti invertire il segno di una lista di valori.
la creazione e l'uso degli oggetti funzione non rientra negli scopi di questo volume.
Ma fortunatamente, come si visto negli esempi precedenti, possibile utilizzare la 11 Usa un oggetto funzione unario.
libreria STL senza dover creare neppure un oggetto funzione. Tuttavia, poich gli #i nel ude <i ostream>
oggetti funzione sono un ingrediente fondamentale della libreria STL, importante #i nel ude <li st>
#include <functional>
quantomeno sapere a grandi linee il loro funzionamento.
#include <algorithm>
using namespace std;
Oggetti funzione unari e binari
int main{}
{
Cos come.esistono predicati unari e binari, esistono oggetti funzione unari e bi-
1i st<doubl e> val s;
nari. Un oggetto funzione unario richiede un argomento mentre un oggetto fun-
int i;
zione binario ne richiede due. Si deve utilizzare un oggetto del tipo richiesto. Ad
esempio, se un algoritmo si attende un oggetto funzione binario, occorre passargli Il inserisce i valori in una lista
un oggetto funzione binario. for(i=l; i<lO; i++) vals.push_back((double)i);

cout "Contenuto originale di vals =\n";


Uso degli oggetti funzione forniti list<double>::iterator p = vals.begin{};
while(p != vals.end()) {
La libreria STL fornisce u~'ricco ass~rtimento di oggetti fu~~ione. Ecco gli og- cout << *p << 11 11 ;
getti funzione binari forniti: p++;

plus minus multiplies divides modulus --c-our< endl ;


- - equillj:o _ .::::!lQt=eql!l!l. JQ. greater grealer.:.:.equal less
676 CAPITOLO 24 INTRODUZIONE ALLA LIBRERIA STL 677

Il usa l'oggetto funzione negate Il inserisce i valori in una lista


p = transform(vals.begin(), vals.end(), for{i=lO; i<lOO; i+lO) vals.push back{{double)i);
val s.begin(), for{i=l; i<lO; i++) divisors.push_back{3.0);
negate<double>()); Il richiama 1 'oggetto funzione
cout "Contenuto originale di vals =\n";
cout "Contenuto negato di val s =\n"; list<double>::iterator p = vals.begin();
p = val s.begin(); while{p != vals.end()) {
while(p != vals.end()) cout << *p << 11 11 ;
cout << *p << 11 11 ; p++;
p++;

cout endl ;
return O;
11 trasforma val s
p = transform(vals.begin(), vals.end(),
Il programma produce il seguente output: divisors.begin(), vals.begin(),
divides<double>()); Il richiama l'oggetto funzione
Contenuto o~inale di vals
cout "Contenuto di vals diviso per 3 =\n";
123 456 789
p = valS.b.egin();
Contenuto negato di val s =
while(p != vals.end())
-1 -2 -3 -4 -5 -6 -7 -8 -9
cout << *p << " 11 ;
p++;
Nel programma, si noti il modo in cui viene richiamata negate(). Poich vals
. una lista di valori double, negate() viene richiamata utilizzando negate<double>().
L'algoritmo transform() richiama automaticamente negate() per ciascun elemen- return O;
to della sequenza. Pertanto l'unico parametro che negate() riceve come argomen-
to un elemento della sequenza.
Il prossimo programma illustra l'uso dell'oggetto funzione binario divides(). Ecco l'output del programma:
Questo programma crea due liste di valori double e poi divide l'uno per l'altro.
Questo pr~grainma utilizza la forma binaria dell'algoritmo transform(). Contenuto origina 1e di va 1s =
10 20 30 40 so 60 70 80 90
Il Uso di un oggetto funzione binario. Contenuto di vals diviso per 3 =
#i nel ude <iostream> 3.33333 6.66667 10 13.3333 16.6667 20 23.3333 26.6667 30
#i nel ude <li st>
#i nel ude <functional > In questo caso, l'oggetto funzione binario divides() divide gli elementi della
#include <algorthm> prima sequenza con i corrispondenti elementi della seconda sequenza. Pertanto
usi ng namespace std;
divides() riceve gli argomenti nel seguente ordine:
int mafif()
{ divi des (first, second)
list<double> vals;
list<double> dvisors; Questo ordine pu essere generalizzato. Ogni volta che viene utilizzato un
int i; - - oggetto funzione binario, i suoi argomenti vengono ordinati in questo modo.

- --- - -----
--- - -----
678 CAPITOLO 24

Creazione di un oggetto funzione int main()


{
Oltre a utilizzare gli oggetti funzione forniti, anche possibile crearne di nuovi. A list<double> vals;
tale scopo si deve semplicemente creare una classe cheesegue l' overloading della int i;
funzione operator(). Tuttavia, per ottenere la massima flessibilit opportuno uti-
lizzare come classe base dell'oggetto funzione una delle seguenti classi definite
Il inserisce i valori in una lista
for(i=l; i<lO; i++) vals.push_back((double)i);
dalla libreria STL.
cout << "Contenuto originale di vals =\n";
template <class Argument, class Result> struct unary function list<double>: :iterator p = vals.begin();
typedef Argument argument_type; - while{p != vals.end()) {
typedef Re su 1t resu 1t type; cout << *p << " ";
); - p++;

templ ate <cl ass Argumentl, cl ass Argument2, cl ass Result> cout endl ;
struct binary_function {
typedef Argumentl first argument type; Il usa 1 'oggetto funzione reciprocal
typedef Argument2 second argument type; p = transform(val s.begin(), val s .end(),
typedef Resul t resul t type; - vals.begin(),
); - reciprocal ()); Il richiama 1 'oggetto funzione

Queste classi template forniscono nomi di tipi concreti per i tipi generici uti- cout << "Contenuto trasformato di val s =\n";
lizzati dall'oggetto funzione. Anche se questo tecnicamente solo una comodit, p = vals.begin();
si tratta di elementi molto utilizzati nella creazione di oggetti funzione. while(p != vals.end())
II seguente programma mostra la creazione di un oggetto funzione cout << *p << Il 0 ;
personalizzato. Il programma converte in un oggetto funzione la funzione p++;
reciprocai() (utilizzata per illustrare in precedenza il funzionamento dell'algoritmo
transform()).
return O;

LI _ci:e_a _J !.P.gg_etto funzione reci procal.


#include <iostream>
Si devono notare due aspetti importanti di reciprocai(). Innanzitutto eredita la
#include <list>
classe base unary_function(). Questo le d accesso ai tipi argument_type e
#include <functional>
#include <algorithm>
result_type. In secondo luogo definisce operator() in modo che restituisca il reci-
using namespace std; proco del suo argomento. In generale, per creare un oggetto funzione, basta eredi-
tare dalla classe base corretta ed eseguire l'overloading di operator(). Dunque in
Il Un semplice oggetto funzione. realt tutto molto semplice.
class reciprocal: unary function<double, double> {
public: -
resul t_type operator{J-(argument_type i) Uso dei binder
{
return (result_type) 1.0li; Il restituisce il reciproco Quando si usa un oggetto funzione binariq,_~ppssiq.ile collegare un valore a uno
) dei suoi argomenti. Questa possibilit pu essere utile in molte situazioni. Ad
}; esempio-si-potrebbe voler rimuovere da una sequenza tutti gli elementi maggiori
--~ _dhm determinato valore, ad esempio 8:-A-tale s_copo occorre un-~~~o per colle-
INTROUULIU1~c
680 CAPITOLO 24

gare il numero 8 all'operando di destra dell'oggetto funzione greater(). Pertanto int main()
{
si vuole che greater() esegua il confronto:
1i st<i nt> 1st;
11 st<int>: :i tera tor p, endp;
val > 8
int i;
per ciascun elemento della sequenza. Per ottenere questo risultato, la libreria STL
fornisce il meccanismo dei binder. for(i=l; i < 20; i++) lst.push_back(i);
Esistono due binder: bind2nd() e bind1st(). Essi hanno la seguente forma
generale: cout "Sequenza originale:\n";
p = lst.begin();
bindlst(binfanc_obj, value) while(p != lst.end())
bind2nd(binfanc_obj, value) cout << *p << " ";
p++;
Qui, binfunc_obj un oggetto funzion~ binario. bind1 st() restituisce un ogget- cout endl ;
to funzione unario il cui operando di sinistra di binfunc_obj collegato a value.
bind2ndQ restituisce un oggetto funzione unario il cui operando di destra di endp = remove_if(lst.begin(), lst.end(),
binfunc_obj collegato a value. bind2nd() il binder pi comunemente utilizzato. bind2nd(greater<int>O, 8));
In entrambi casi il risultato di un binder un oggetto funzione unario che impiega
la connessione al valore specificato. Per illustrare I 'uso di un binder, verr utilizza cout "Sequenza risultante:\n";
l'algoritmo remove_if(). Tale algoritmo rimuove da una sequenza gli elementi p = lst.begin();
sulla base del risultato di un predicato. Ecco il suo prototipo: while(p != endp) {
cout << *p << 11 " ;
template <class Forlter, class UnPred> p++;
Foriter remove_if(Forlter start, Foriter end, UnPred fune);

return O;
L'algoritmo rimuove dalla sequenza gli elementi definiti da start ed end nel
caso in cui il predicato unario definito dafanc sia true. L'algoritmo restituisce un
puntatore alla nuova fine della sequenza, che riflette cos la cancellazione degli Ecco l'output prodotto dal programma:
elementi.
Il seguente programma.rimuove da una sequenza tutti i valori maggiori di 8.
Sequenza origina1e:
Poich il predicato richiesto da remove_if() unario, non si pu semplicemente 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
utilizzare l'oggetto funzione greater() cosl come poich greater() un oggetto Sequenza risultante:
binario.Al contrario si deve collegare il valore 8 al secondo parametro di greater() 12345678
impiegando il binder bind2nd() nel modo illustrato dal programma.
Si consiglia di sperimentare l'uso di questo program~a, pro~~n~o .con oggetti
11 Illustra l'uso di bind2nd(). funzione differenti e collegando valori differenti. Come si scopnra, i bmder esten-
#include <iostream> dono le potenzialit della libreria STL in vari modi. . . . .
#include <list> Un'ultimaannotazione: strettamente conne_sso ai bmder vi un ogg~tt~ chia-
#include <functional> mato negatore. I negatori sono not1 () e not2() e restituiscon? la negazione (ov-
#include <algorithm>
-vero il complemento) di ci che viene fornito come predicato. Ecco la loro
using namespace std;
forma generale:

---a. - - - - -
682 CAPITOLO 24 I N T R O O U Z I O N E A l LA LI 8-R E R I A S Tl 683

notl (unary_predicate) Come si pu vedere dai commenti, in C++ non possibile utilizzare l'opera-
not2(binary_predicate) tore di assegnamento per dare un nuovo valore a un array di caratteri (ad eccezio-
ne solo dell' inizializzazio_!!e) n possibile utilizzare l'operatore+ per concatenate
Ad esempio, se si sostituisce la riga: due stringhe. Queste operazioni devono essere scritte utilizzando apposite funzio-
ni della libreria:
endp = remove_if(lst.begin(), lst.end(),
notl (bi nd2nd (greater<i nt>(}, 8))); strcpy(sl, "Alfa");
strcpy(s2, "Beta");
nel programma precedente, verranno rimossi da 1st tutti gli elementi che non strcpy(s3, sl);
strcat(s3, s2);
sono maggiori di 8.
Poich gli array di caratteri chiusi dal carattere nullo non sono tecnicamente
tipi, non possibile applicargli gli operatori C++.Questo complica anche la pi
24.9 la classe string' rudimentale operazione sulle stringhe. Soprattutto l'impossibilit di operare su
stringhe chiuse dal carattere nullo con gli operatori C++ standard che ha portato
Come si sa, il linguaggio C++ non supporta direttamente un tipo per le stringhe. _ allo sviluppo di una classe per stringhe standard. Si ricordi che quando si defini-
Tuttavia fornisce due modi per gestire le stringhe: innanzitutto si pu usare un sce una classe in C++, si definisce a tutti gli effetti un nuovo tipo di dati che pu
normale array di caratteri chiuso dal carattere nullo, che ormai dovrebbe essere essere integrato completamente nell'ambiente C++.Questo significa ovviamente
familiare. In genere questo tipo di array chiamato "stringa C". Il secondo modo che gli operatori possono essere modificati in modo da operare sulla nuova classe.
costituito dalla classe string; quest'ultimo sar l'approccio che verr esaminato Pertanto, aggingendo una classe standard per stringhe diventa possibile gestire
ora. le stringhe come si fa con ogni altro tipo di dati: tramite gli operatori.
In .realt la classe string una specializzazione di una classe template pi Vi anche un altro motivo che ha spinto a realizzare una classe standard per le
generale chiamata basic_string. Infatti esistono due specializzazioni di basic_string: stringhe: la sicurezza. Nelle mani di un programmatore inesperto o poco accorto,
string che supporta stringhe di caratteri da 8 bit e wstring che supporta stringhe di molto facile superare i limiti di un array contenente una stringa chiusa dal carat-
caratteri estesi. Poich in programmazione vengono utilizzati prevalentemente tere nullo. Ad esempio, si consideri la funzione strcpy() che esegue la copia di una
caratteri a 8 bit, in questa sezione verr esaminata solo la classe string. stringa. Questa funzione non controlla in alcun modo se vengono superati i limiti
Prim di parlare della classe string importante comprendere perch fa parte dell'array di destinazione. Se l'array di origine contiene pi caratteri di quanti
della libreria C++.Le classi standard non sono state aggiunte al linguaggio C++ possano essere inseriti nell'array di destinazione, pu nascere un errore del pro-
in modo casuale. Infatti ogni nuova aggiunta stata accompagnata da grandYO- ------ gramma o un blocco di sistema. Come si vedr, la classe standard per le stringhe
scussioni e dibattiti. Poich il linguaggio C++ conteneva gi un certo supporto per previene questo errore.
le stringhe nella forma degli array di caratteri chiusi dal carattere nullo, a prima Nell'analisi finale, vi sono tre motivi che hanno spinto a includere nello standard
vista potrebbe sembrare che l'inclusione della classe string rappresenti un'ecce- una classe per le stringhe: l'uniformit (ora la stringa un tipo), la comodit (si
zione a questa regola. La verit niolto diversa, ecco perch: le stringhe chiuse possono usare gli operatori standard del linguaggio) e la sicurezza (non possono
dal carattere nullo non possono essere manipolate da nessuno degli operatori essere superati i limiti dell'array). Si deve tenere in considerazione che non vi
standard del linguaggio C++ n possono entrare nelle normali espressioni C++. alcun motivo che costringa ad abbandonare completamente le normali stringhe
Ad esempio, si consideri il seguente frammento di codice: chiuse dal carattere nullo che rappresentano tuttora il modo pi efficiente per
implementare le stringhe. Tuttavia, quando la velocit non un problema, la pos-
char s1[80] ,-s2[80], s3[80]; sibilit di usare la nuova classe string d accesso a un modo pi sicuro e integrato
di gestire le stringhe.
sl = "Alfa"; Il non si pu fare Anche se tradizionalmente questo argomento non collegato alla libreri.a STL,
s2 = "Beta"; Il non si pu fare la classe string non altro che un nuovo container definito dal C++. Questo
s-;s---..-s1-..--si; Il errore, non consentito ~ignifica che supporta gli algoritmi d_:~ri~ti ne~a sezione precedente. Tuttavlalf- --- -
684- CAPITOLO 2 4 INTRODUZIONE ALLA LIBRERIA STL 685

stringhe offrono anche altre funzionalit. Per avere accesso alla classe string si Questi operatori consentono di utilizzare gli oggetti stringa nelle normali
deve includere nel programma l'header <string>. espressioni ed evitano di dover richiamare funzioni come strcopy() o strc~t() .. In
La classe string molto molto estesa ed dotata di numerosi costruttori e generale possibile utilizzare nelle espressioni gli oggetti string e le normali stnn-
funzioni membro. Inoltre molte funzioni membro sono dotate di varie forme cre- ghe chiuse dal carattere nullo. Ad esempio a -n oggetto string si pu assegnare
ate tramite overloading. Per questo motivo non possibile esaminare in un capito- una stringa chiusa dal carattere nullo.
lo l'intero contenuto di string. Dunque si parler semplicemente delle sue caratte- L'operatore + concatena due oggetti string oppure un oggetto string e una
ristiche pi utilizzate. Dopo aver compreso il funzionamento della classe string, stringa C. Dunque sono consentite le seguenti varianti:
non sar difficile esplorare tutte le altre funzionalit di cui essa dotata.
La classe string supporta numerosi costruttori. Ecco i prototipi dei tre costruttori string + string
pi utilizzati: stringa + stringae
stringa e + string
string( );
string(const char *str); L'operatore + pu essere utilizzato anche per concatenate un carattere alla
string(const string &str); fine di una stringa.
La classe string definisce la costante npos uguale a -1. Questa costante rap-
La prima forma crea un oggetto string vuoto. La seconda forma crea un ogget- presenta la lunghezza massima di una stringa.
to string utilizzando come base una stringa chiusa dal carattere nullo puntata da La classe per stringhe C++ semplifica straordinariamente la gestione delle
str. Questa forma fornisce una conversione da una stringa chiusa dal carattere stringhe. Ad esempio, utilizzando oggetti string possibile impiegare l'operatore
nullo alla classe string. La terza forma crea una stringa partire da un'altra stringa. di assegnamento per assegnare a una stringa una stringa quotata, l'operator~+ per
Per gli oggetti string sono definiti numerosi operatori fra i quali: concatenate le stringhe e gli operatori di confronto per confrontare le stnnghe.
Queste operazioni sono illustrate dal seguente programma:
OPERATORE SIGNIFICATO
Il Un breve esempio d'uso delle stringhe.
Assegnamento #include <iostream>
#i nel ude <stri ng>
Concatenamento
usi ng namespace std;
+= Concatenamento e assegnamento
int main()
Uguaglianza
-- 1---
I= Disuguaglianza string strl("Al fa");
string str2("Beta");
Minore di string str3{"0mega");
<= Minore o uguale a stri ng str4;

Maggiore di 11 assegna una stringa


>= Maggiore o uguale a str4 = strl;
cout << strl << 11 \n" << str3 << 11 \n";
Indici

Output
11 concatenadue stringhe
str4 = strl + str2;
Input cout << str4 << "\n";

Il concatena una stringa e una stringa C


str4--;--=St-r1-+- 11 -::: "+ str3;
686 CAPITOLO 24

cout << str4 << "\n."; dimensionati automaticamente per contenere la stringa fornita. Pertanto, quando
si assegnano o concatenano le stringhe, le dimensioni della stringa di destinazio-
/I confronta le stringhe ne cresceranno automaticamente per accogliere la nuova stringa. Dunque non
if(str3 > strl) cout "str3 > strl\n"; possibile superare i limiti di una stringa. Questo aspetto dinamico degli oggetti
if(str3 == strl+str2)
string una delle caratteristiche che la distinguono rispetto alle stringhe C che
cout "str3 == strl+str2\n";
sono soggette a errori dovuti al superamento dei limiti dell'array.
/* A un oggetto stringa pu essere assegnata
anche una normale stringa. */
Alcune delle funzioni membro di string
strl = "Questa una stringa chiusa dal carattere nullo. \n";
cout strl;
Mentre gli operatori consentono di eseguire sulle stringhe le operazioni pi sem-
Il crea un oggetto stringa a partire da un altro oggetto stringa plici, quando occorre eseguire operazioni pi complesse vengono impiegate ap-
string str5(strl); posite funzioni membro. La classe string contiene un numero elevatissimo di fun-
. cout strS; zioni membro e dunque non possibile descriverle tutte. In questo capitolo ver-
ranno esaminate solo le pi comuni.
Il legge una stringa
cout "Introdurre una stringa: "; Manipolazione di base delle stringhe
cin strS; Per assegnare una stringa a un'altra si usa la funzione assign(). Ecco due delle sue
cout strS; forme:

return O; string &assign(const string &strob, size_type start, size_type man);


string &assign(const char *str, size_type man);
Il programma produce il seguente output: Nella prima forma, al!' oggetto chiamante vengono assegnati mcm caratteri della
stringa strob a partire dall'indice specificato da start. Nella seconda forma, all'og-
Alfa
getto chiamante vengono assegnati i primi num caratteri della stringa C str. Entram-
Omega
be le funzioni restituiscono l'indirizzo dell'oggetto chiamante. Naturalmente molto
AlfaBeta
Al fa - Omega
pi comodo utilizzare= per assegnare una stringa a un'altra. La funzione assign()
str3 > strl viene impiegata solamente per assegnare una parte di una stringa.
Questa una stringa chiusa dal carattere nullo. La funzione membro append() aggiunge a una stringa una parte di un'altra
Questa una stringa chiusa dal carattere nullo. stringa. Ecco due delle sue forme:
Introdurre una stringa: STL
STL string &append(const string &strob, size_type start, size_type num);
string &append(const char *str, size_type num);
Si noti la facilit di gestione della stringa. Ad esempio, per concatenare le
stringhe si usa l'operatore+ e per confrontare due stringhe si usa>. Per ottenere Qui, all'oggetto chiamante vengono aggiunti num caratteri tratti da strob a
queste stesse operazioni con le stringhe C, sarebbe necessario ricorrere alle fun- partire dall'indice specificato da start. Nella seconda forma all'oggetto chiaman-
zioni strcat() e strcmp(). Po~ch gli oggetti string del linguaggio C++ possono te vengono aggiunti i primi num caratteri della stringa C str. In entrambi i casi
essere utilizzati tranquillamente insieme alle normali stringhe C, non vi alcuno viene restituito l'indirizzo dell'oggetto chiamante. Naturalmente molto pi fa-
svaritaggio a impiegarle nel programma, anzi si otterranno notevoli vantaggi. cile utilizzare l'operatore+ per aggiungere un'intera stringa a un'altra e dunque si
Vi un'ultima cosa da notare nel programma precedente: le dimensioni delle usa la funzione append() solo quando sI deve aggiungere una parte della stringa. -- - - -
_s!r_ing~-~.QP. vengono spec~f~cate. Gli oggetti di -~iI:<:_>_~~i~g- p~~~ono essere -Per-inserire o sostituire i caratteri di una_.'\.tringa.sLusa!JO le funzioni insert() e
replace(). Ecco i loro prototipi pi utiliz?'.ati: - - ---
- - - - - - -----
--_BB_B~_C.:._A_P_IT~O.:....::.L~0-'-2_4 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _~ - - - - - -INTRODUZIONE -A-l-b-A--b-1 BRE RIA S TL ~S9

string &insert(size_type start, const string &strob); Il illustra l'uso di erase()


string &insert(size_type start, const string &strob, cout "Rimuove 34 caratteri da strl:\n";
size:_type insStart, size_type num); strl.erase(6, 34);
string &replace(size_type start, size_type num, const siring &strob); cout strl << 11 \n\n";
string &replace(size_type start, size_type orgNum, const string &strob,
size_type replaceStart, size_type replaceNum); Il illustra l'uso di replace
cout "Sostitu sce 8 caratteri di strl con str2: \n";
strl.replace(7, 8, str2);
La prima forma di insert() inserisce strob nella stringa chiamante all'indice cout << strl << endl;
specificato da start. La seconda forma di insert() inserisce nella stringa chiamante
all'indice specificato da start num carattere tratti dalla stringa strob a partire da return O;
i11sStart.
La prima forma di replace() sostituisce num caratteri dalla stringa chiamante
u partire da start con il contenuto della stringa strob. La seconda forma sostituisce Ecco l'output prodotto da questo programma:
orgNum caratteri a partire da start con replaceNum caratteri della stringa specifi-
c11ta da strob partendo da replaceStart. In entrambi i casi viene restituito l 'indiriz- Le stringhe iniziali
zo dell'oggetto chiamante. strl: La potenza delle stringhe C++.
Per rimuovere caratteri da una stringa si usa erase(). Ecco una delle sue forme str2: Le potenzialit della libreria STL
pi comuni:
Inserisce s'tr2 in strl:
string &erase(size_type start= O, size_type nun;,, npos); La potle potenzialit della libreria STLenza delle stringhe C++.

Rimuove 34 caratteri da strl:


Tale funzione rimuove dalla stringa chiamante num caratteri a partire da srart. La potenza delle stringhe C++.
L:1 funzione restituisce l'indirizzo della stringa chiamante.
Il seguente programma illustra l'uso delle funzioni insert(), erase() e replace(). Sostitu sce 8 caratteri di strl con str2:
La poteLe potenzialit della libreria STLe stringhe C++.
Il Illustra l'uso di insert(), erase() e replace().
#include <iostream> Ricerca di una stringa
li nel ude <string> La classe string fornisce numerose funzioni membro che consentono di eseguire
-------
1.lS i ng namespace std; ricerche all'interno di una stringa. Fra di esse vi sono find() e rfirrd{). Ecco i proto-
tipi delle pi comuni versioni di queste funzioni:
1tlt main()
{
size_type find(const string &strob, size_type start=O) const;
string strl("La potenza delle stringhe C++.");
string str2("L~ potenzialit della libreria STL"); size_type rfind(const string &strob, size_type start=npos) const;

cout "Le stringhe iniziali\n"; Partendo da start, find() ricerca nella stringa chiamante la prima occorrenza
<:out "strl: " << strl endl; della stringa contenuta in strob. Se la trova restituisce l'indice in cui stata trovata
<:out_::.:'_ "str2: " << str2 "\n\n"; la stringa. Se invece la stringa non viene trovata, restituisce npos. rtind() I' oppo-
-sto di find(). A partire da start ricerca all'inverso nella stringa chiamante la prima
Il illustra l'uso di insert() occorrenza della stringa contenuta in strob (ovvero ricerca l'ultima occorrenza di
<:out "Inserisce str2 . in strl:\n"; strob nella stringa chiamante). Se latrova, rtind() restituisce l'indice in cui stata
strl. insert(6, str2);
_trg\.'._ata la corrispondenza, altrimenti restituisce npos.
__ ~E~:__~<__s_t~l __<~ "\n\n"; Ecco un breve esempiO-Che u_tilizza find() e rtindQ_:_
- ---- - -~-----~-
--------
690 CAPITOLO 2..4 - I N T R o D u ZTOfrrA L LA L I B R E R I A s TL- -691

#i nel ude <i ostream> return O;


#i nel ude <stri ng>
using namespace std;
Ecco l'output prodotto dal programma:
int main()
{
Trovato in O
int i;
La stringa rimanente :
string sl =
Veloce di mente, forte di corpo, puro di cuore
"Veloce di mente, forte di corpo, puro di cuore";
string s2;
Trovato in 18
La stringa rimanente :
i = sl.find("veloce"};
forte di corpo, puro di cuore
if(i!=string::npos) {.
cout << "Trovato in " << i << endl ;
Trovato in 34
cout "La stringa rimanente :\n";
La stringa rimanente :
s2.assign(sl, i, sl.size()};
puro di cuore
cout s2;
Trovato in 39
cout << "\n\n";
La stringa rimanente :
di cuore
i = sl. find("forte");
if(i!=string::npos) { Confronto fra stringhe
cout "Trovato in " << i << endl;
Per confrontare l'intero contenuto di una stringa con il contenuto di un'altra, in
cout "La stringa rimanente :\n";
s2.assign(sl, i, sl.size());
genere vengono impiegati gli operatori relazionali descritti in precedenza. Ma se
cout s2; si deve confrontare solo una porzione di una stringa, necessario impiegare la
funzione membro compare(), descritta di seguito:
cout << "\n\n";
int compare(size_type start, size_type num, const string &strob) const;
i = sl.find("puro"};
if(i!=string::npos) { . Qu!_ il con_(r.2_~to viene eseguito fra num caratteri tratti da strob a partire da
cout << "Trovato in " << i << endl; start e la stringa chiamante. Se la stringa chiamante minore, compare() restitu-
cout << "La stringa rimanente : \n";
isce un valore minore di O. Se la stringa chiamante maggiore di strob, restituisce
s2.assign(sl, i, sl.size(});
cout s2;
un valore maggiore di O. Se strob e la stringa chiamante sono uguali, compare()
restituisce il valore O.
cout "\n\n";
Come ottenere una stringa chiusa dal carattere nullo
11 trova 1 'ultimo "di"
Anche se gli oggetti string sono estremamente utili, in alcuni casi occorre ottenere
i = sl.rfind("di"); la versione C di una stringa. Ad. esempio, si .potrebbe utilizzare un oggetto string
if{i !=string: :npos) { per costruire il nome di un file. Tuttavia, quando si apre un file, occorre specifica-
cout "Trovato in " << i << endl; re un puntatore a una normare stringa C. Per risolvere questo problema, si pu
cout "La stringa rimanente :\n"; utilizzare la funzione membro c_str(). Ecco il suo prototipo:
s2.assign(sl, i, sl.size(}};
ut s2; const char *c_str( ) const;
-- -- }
- -------------

692 I N T R O D U Z.1.0 N E A L L A L I B R E R I A S TL 693

Questa funzione restituisce un puntatore alla versione chiusa dal carattere nullo eout << *p++;
della stringa contenuta nell'oggetto string chiamante. La stringa chiusa dal carat- cout endl ;
tere nullo non deve essere modificata. Inoltre .non si pu garantire che sia valida
dopo che sull'oggetto string sono state eseguite altre operazioni.
return O;

Le stringhe sono container


Ecco l'output del programma:
La classe string risponde a tutti i requisiti dei container. Pertanto supporta tutte le
funzioni dei container, come begin(), end() e size(). Inoltre supporta gli iteratori. La gestione delle stringhe in C++ semplice
Pertanto un oggetto string pu e.ssre manipolato anche tramite algoritmi STL. La gestione delle stringhe in C++ semplice
Ecco un semplice esempio: Vi sono 4 'i' in strl
LA GESTIONE DELLE STRINGHE IN C++ SEMPLICE
11 1e stringhe come conta i ner.
#include <iostream>
#include <string> Inserimento di stringhe in altri container
#include <algorithm>
using namespace std; Anche se string un container, gli oggetti di tipo string vengono comunemente
memorizzati in altri container STL, come ad esempio le mappe o le liste. Ad
int main() esempio,' ecco un modo migliore per scrivere il programma per rubriche telefoni-
{ che descritto in precedenza. In questo caso al posto delle stringhe C .vengono
string strl("La gestione delle stringhe in C++ semplice"); utilizzati oggetti di tipo string.
string: :iterator p;
int i;
11 Usa una mappa di stringhe per creare una rubrica telefonica.
#include <iosfream>
Il usa size()
#i nel ude <map>
for(i=O; i<strl.size(); i++)
#include <string>
cout strl[i];
using namespace std;
cout endl ;

int main()
11 usa un i teratore
{
p = strl.begin();
map<string, string> directory;
while{p != strl.end())
cout *p++;
directory. i nsert (pai r<stri ng, stri ng>("Tommaso", "555-4533"));
cout endl ;
directory. i nsert (pai r<string, stri ng>("Cri stina", "555-9678"));
directory. i nsert{pai r<stri ng, stri ng>("Giovanni ", "555-8195"));
11 usa 1 'a 1go ritmo count ()
directory. i nsert (pai r<stri ng, stri ng>("Rachel e", "555-0809"));
i= count(strl.begin(), strl.end(), 'i');
cout << "Vi sono " << i << " 'i' in strl\n";
string s;
cout << "Introdurre un nome: ";
Il usa transform() per trasformare la stringa in lettere maiuscole
cin s;
transform(strl.begin(), strl.end(), strl.begin(),
toupper) ;. - - - -
map<string, string>:: iterator p;
--- ___ p = strl.begin();
while{p != st.rJ._g_ncj_QJ__
p = directory.fJnd(s);
- --- ~
-~---- ---
694 CAPITOLO 24

if(p !=directory.end()} Parte terza


cout << "Numero telefonico: " << p->seconcl;
else -:- LA LIBRERIA
cout << "Nome non presente. \n":
: DI FUNZIONI STANDARD
return O;

24.1 O Commenti finali sulla libreria STL


.
La libreria STL diventata un elemento fondamentale del linguaggio C++. Dun- ,
que ora molte operazioni di programmazione vengono normalmente svolte im- H C++ definisce due tipi di librerie. La prima la me-
piegando tale libreria. STL combina ottime doti di potenzialit e flessibilit e, moria delle funzioni standard. Questa libreria costituita da funzioni indipenden-
seppure la sua sintassi sia uh po' complessa, il suo uso molto semplice. Nessun ti di utilizzo generale che non fanno parte di nessuna classe. Il C++ eredita la
programmatore C++ pu ignorare la libreria STL poich essa giocher in futuro libreria di funzioni dal C. La seconda la libreria di classi per la programmazione
un ruolo sempre pi importante nella realizzazione dei programmi. a oggetti. La. Parte terza di questo volume costituisce una guida di riferimento per
la libreria di funzioni standard. La Parte quarta descrive la libreria delle classi.
La libreria delle funzioni pu essere suddivisa nelle seguenti categorie:
funzioni di I/O;
funzioni per la gestione di stringhe e caratteri;
funzioni matematiche;
funzioni per lora, la data e la localizzazione;
funzioni di allocazione dinamica della memoria;
funzioni varie;
funzioni per caratteri estesi
L'ultima categoria stata aggiunta al C standard nel 1995 e successivamen-
te stata incorporata nel C++. Essa contiene gli equivalenti a caratteri estesi
(wchar_t) per molte funzioni di libreria. Onestamente l'uso della libreria per
caratteri estesi molto limitato e il linguaggio C++ fornisce un modo migliore
per gestire ambienti a caratteri estesi ma per completezza l'argomento verr
descritto nel Capitolo 31.
Un'ultima annotazione: tutti i compilatori forniscono molte pi funzioni ri-
spetto a quelle definite dallo standard sia per il C che per il C++. li} ge!_lere queste
funzioni aggiuntive si occupano dell'interfacciamento con il sistema operativo e
di altre operazioni relative all'ambiente. Per informazioni si rimanda alla docu-
mentazione del compilatore.

---- -------.
: Capitolo 25

Le funzioni di I/O
: basate sul e

Ouesto capitolo descrive le funzioni di I/O tipiche del


linguaggio C. Queste funzioni sono definite sia nel C standard che nel C++ standard.
Anche se quando si deve realizzare un nuovo programma in genere si utilizza il
sistema di I/O a oggetti C++, in realt nulla impedisce di utilizzare in un program-
ma C++ le funzioni di I/O C, quando questo sembri appropriato. Le funzioni
contenute in questo capitolo sono state specificate innanzitutto dallo standard ANSI
per il C e vengono generalmente chiamate "il sistema di I/O ANSI C".
L'header associato alle funzioni di 1/0 C <stdio> (un programma C deve
utilizzare il file header <Stdio.h> ). Questo header definisce varie macro e vari tipi
utilizzati dal file system. Il tipo pi importante FILE, utilizzato per dichiarare un
puntatore a file. Gli altri tipi sono size_t e fpos_t. Il tipo size_t (corrispondente in
genere a qualche forma di intero senza segno) definisce un oggetto in grado di
contenere le dimensioni del pi grande file consentito dal sistema operativo. Il
tipo fpos_t definisce un oggetto che pu contenere tutte le informazioni necessa-
rie per specificare in modo univoco ogni posizione all'interno di un file. La macro
pi utilizzata fra quelle definite nei file header EOF il cui valore indica la fine
del file.
Molte delle funzioni di I/O in caso di errore impostano la variabile intera
globale standard ermo. Il programma pu co~trollare questa variabile per ottene-
re informazioni pi approfondite sull'errore. I valori che possono essere contenuti
in ermo dipendono dall'implementazione.
Per una panoramica sul sistema di I/O C, consultare i Capitoli 8 e 9 nella Parte
prima.
.N:OTA"'. _:_:-:: Questo capitolo descrive le funzioni di !IO a caratteri. Si trat-
ta di funzioni originariamente definite per il Ce C++ standard_e_sono anche
quelle di gran lunga pi utilizzate. Nel 1995, a questo gruppo sono state aggiunte
numerose-funzioni pel'--caratteri-estesi (wchar_t) per verranno descritte breve-
mente nel Capitolo H,- - - - - -- - ___ _
- - - - -------
-~- - -- -
698 CAPITOLO 25 ---c-F--P-UNZIONI DI 1./0-BASATE SUL C 699

clearerr() La funzione feof() particolarmente utile quando si opera su file binari poich
l'indicatore di fine file anche un intero binario valido. opportuno eseguire
#include <cstdio>
chiamate esplicite a feof() piuttosto che controllare il valore restituito da getc();
voi d clearerr(FILE *stream);
nel caso di file binari, questo l'unico modo per garantire con sicurezza se stata
raggiunta la fine del file.
~ funzione clearerr() esegue il reset (ovvero imposta a O) del flag per errori Le funzioni correlate sono clearerr(), ferror(), perror(), putc() e getc().
associato allo stream. Viene anche eseguito il reset dell'indicatore di fine file.
I flag di errore degli stream vengono inizialmente impostati a Oda una chia-
mata eseguita con successo alla funzione fopen(). Quando si verifica un errore i ferror()
flag rimangono attivi fino a un'esplicita chiamata a clearerr() oppure fino all'es~
cuzione di rewind(). #include <cstdio>
Gli errori su file possono verificarsi per varie ragioni, molte delle quali dipen- int ferror(FILE *stream);
dono dal sistema. L'esatta natura dell'errore pu essere determinata richiamando
perrorQ che visualizza l'errore verificatosi. La funzione ferror() controlla se si verificato un errore sul file associato allo
Le funzioni correlate sono feof(), ferror() e perror(). stream specificato. Se il valore restituito O, significa che non si verificato alcun
errore, mentre un valore diverso da Oindica che si verificato un errore.
I flag di errore associati a stream rimangono attivi finch il file non viene
fclose() chiuso o finch non viene richiamata la funzione clearerr().
Per.determinare l'esatta natura dell'errore si usa la funzione perror().
#include <cstdio>
int fclose(FILE *stream);
Le funzioni correlate sono clearerr ), feof() e perror().

La fun~ione fclose() chiude il file associato a stream e ne vuota il buffer. Dopo fflush()
<wer eseguito fclose(), stream non pi connesso al file e ogni buffer allocato
dinamicamente viene deallocato. #include <cstdio>
s: fclose() ha successo, restituisce il valore O, altrimenti restituisce EOF. Il int fflush(FILE *stream);
tentauvo di chiudere un file gi chiuso produce un errore. Anche la rimozione del
dispositivo di memorizzazione prima di chiudere un file genera un errore, cos Se stream associato a un file aperto in scrittura, una chiamata a fflush() pro-
~"Ome la man.canza di spazio su disco. voca la scrittura fisica sul file del contenuto del buffer di output. Se stream punta
Le fuEzioni correlate sono fopen(), freopen() e fflush(). a un file di input, il contenuto del bufferinpufviene cancellato. In entrambi i
casi, il file rimane aperto.
Se il valore restituito zero, I' operazione stata completata con successo;
feofQ
EOF indica che si verificato un errore di scrittura.
#include <cstdio>
I buffer vengono svuotati automaticamente alla normale terminazione del pro-
nt feof(FILE *stream); gramma oppure quando sono completamente pieni. Anche la chiusura del file
provoca lo svuotamento del suo buffer.
Le funzioni correlate sono fclose(), fopen(), fread(), fwrite(), getc() e putc().
La fun~ion~ feof() controlla la posizione dell'indicatore di posizione del file
~rdetermmare se stata raggiunta la fine del file associato allostream. Se l'indi-
~-atore_ di posizione del file alla fine del file, viene restituito un valore diverso da fgetc()
O, altn.menti viene restituito il valore O.
Dopo ~h~ stata r~ggiuntaJa.fine_del file, tutte le suc_c_essive op~razioni di _ #include <cstdio>
lettura rest1tu1scono EOF finc~_!}~!!_ verr_ richiamata rewind() o finch.la posizio- int fgetc (FILE *stream);
----- -
1~ corrente all'interno del.file. non verr.spostata con_f~e-ek().-:~-- --- ____ _
_7o_o_ _:_C_A_P_T_T~O~L~0.......::.2~5_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _~_ -~-
LE FUNZIONI DI 1/0 BASATE SUL C--701---

La funzione fgetc() restituisce il prossimo carattere contenuto nello stream di fopen()


input a partire dalla posizi~ne corrente e incrementa l'indicatore di posizione del
file. Il carattere viene letto come un unsigned char che viene convertito in un #include <cstdio>
intero. FILE *fopen(const char *fname, const char *mode);
Quando viene raggiunta la fine del file, fgetc() restituisce EOF. Ma poich
EOF un valore intero valido, quando si lavora su file binari, per controllare se La funzione fopen() apre un file il cui nome puntato da name e restitu~sce lo
stata raggiunta la fine del file si deve usare feof(). fgetc() restituisce EOF anche se stream che gli viene associato. Le operazioni consentite sul file sono definite dal
incontra un errore. Per controllare se si sono verificati errori quando si opera su valore di mode.
un file binario, si deve usare ferrar(). I valori consentiti per mode sono elencati nella Tabella 25.1. Il nome del file
Le funzioni correlate sono fputc(), getc(), putc() e fopen(). deve essere una stringa di caratteri contenente un nome di file valido cosl come
definita dal sistema operativo e pu includere il percorso (sempre che l'ambiente
supporti questa possibilit).
fgetpos{)
Se fopen() riesce ad aprire il file spec.ificato, restituisce un puntatore FILE. Se
ii nel ude <estdi o> il file non pu essere aperto, restituisce un puntatore nullo.
int fgetpos(FILE *stream, fpos_t *position); Come si pu vedere nella tabella, un file pu essere aperto i~ m?d~it testo ?
in modalit binaria. In modalit testo si verificano alcune traduz1om dei carat~en.
Ad esempio i codici di fine riga possono essere convertiti in sequenze camage
La funzione fgetpos() memorizza il valore corrente del!' indicatore di posizio-
Retum / Linee Feed. Sui file binari tali traduzioni non avvengono.
ne del file nel!' oggetto puntato da position: L'oggetto puntato da position deve
essere di tipo fpos_t. Il valore cos memorizzato pu ~ssere utile solo nelle suc-
cessive chiamate a fsetpos(). . Tabella 25.1 l valori consentiti per il parametro mode di fopen().
Se si verifica un errore, fgetpos() restituisce un valore diverso da O, altrimenti MODALIT SIGNIFICATO
restituisce O. .,. Apre un file di testo in lettura.
Le funzioni correlate sono fsetpos(), fseek() e ftell().
'w' Crea un file di testo in scrittura.

a Aggiunta a un file di testo.


fgets()
'rb' Apre un file binario in lettura.
#i nel ude <est di o>
char *fgets(char. *str, int num, FILE *streom); 'wb' Crea un file binario in scrittura.

'ab' Aggiunta a un file binario.


La funzione fgets() legge fino a num - I caratteri da stream e li inserisce
r+" Apre un file di testo in lettura/scrittura.
nell'array di caratteri puntato da str. I caratteri vengono letti fino a che non viene
ricevuto un codice di fine riga o EOF o finch non viene raggiunto il limite spe- 'w+' Crea un file di testo in lettura/scrittura.
cificato. Dopo la lettura dei caratteri, nell'array viene inserito un codice nullo "a+" Apre un file di testo in lettura/scrittura.
immediatamente dopo l'ultimo carattere letto. Il carattere di fine riga verr man-
tenuto ed entrer a far parte dell'array puntato da str. "rb+' o 'r+b" Apre un file binario in lettura/scrittura.

In caso di successo, fgets() restituisce str; in caso di fallimento restituisce un 'wb+. o 'w+b' Crea un me binario in lettura/scrittura.
puntatore nu!Jo. Se si verifica u11 errore di lettura, il contenuto dell'array puntato
'ab+" o 'a+b" Apre un file binario in lettura/scrittura.
da str sar hicteterrninato. Poich il puntatore nullo viene restituito sia quando si
verifica un errore sia alla fine del file, per determinare ci che- accaduto si deve
usare feof() o ferrar().
Le funzioni correlate-sono-fputs(), fgetc(), gets() <:...P~s(_).__ _
_, ___ __ ___ _
..
702 CAPITO L 0-2 5 LE FUNZIONI DI I/O BASATE SUL C 703

Il seguente frammento di codice mostra il metodo corretto per aprire un file: fputc()

FILE *fp; #include <cstdio>


int fputc(int eh, FILE *stream);
if ((fp = fopen("test", "w"))==NULL) {
printf("Impossibile aprire il file.\n"); La funzione fputc() scrive il carattere eh nello stream specificato e alla posi-
exit(l); zione specificata e poi fa avanzare l'indicatore di posizione del file. Anche se, per
motivi storici, eh dichiarato come un int, fputc() lo converte in un unsigned char.
Poich al momento della chiamata tutti i caratteri specificati come argomenti ven-
Questo metodo rileva eventuali errori di apertura di un file, ad esempio I' aper- gono promossi a interi, in genere come argomenti vengono utilizzati dei caratteri.
tura di un disco protetto in scrittura o completamente pieno. Per indicare un errore Quando invece viene usato un intero, i byte di ordine superiore verranno sempli-
viene utilizzato NULL poich nessun puntatore a file avr mai tale valore. cemente ignorati.
Se si usa fopen() per aprire un file .in output, un eventuale file preesistente fputc() restituisce il valore del carattere scritto. Quando si verifica un errore,
avente tale nome verr cancellato e sostituito. Se non esiste un file con tale nome, restituisce EOF. Quando i file sono aperti per operazioni binarie, EOF pu essere
verr creato. L'apertura in lettura di un file richiede naturalmente che il file esista. anche un carattere valido e per sapere se si effettivamente verificato un errore si
Se non esiste verr restituito un errore. Per aggiungere dati alla fine di un file si deve utilizzare la funzione ferrar().
deve usare la modalit "a". Se il file non esiste verr creato. . Le funzioni correlate sono fgetc(), fopen(), fprintf(), fread() e fwrite().
Quando si accede a un file aperto per operazioni di lettura e scrittura, non si
pu far seguire a un'operazione di output un'operazione di input senza prima
richiamare fflush(), fseek(), fsetpos() o rewind(). Inoltre non si pu far seguire a fputs()
un'operazione di input un'operazione di output senza una chiamata alle funzioni
#i nel ude <cstdi o>
appena menzionate. int fputs(const char *str, FILE *stream);
Le funzioni correlate sono fclose(}, fread(}, fwrite(), putc() e getc().
La funzione fputs() scrive sullo stream specificato il contenuto della stringa
fprintf{) puntata da str. Il terminatore nullo non viene scritto.
In caso di successo la funzione fputs() restituisce un valore non negativo men-
#include <cstdio> tre in caso di errore restituisce EOF. Se lo stream aperto in modalit testo, pos-
int fprintf(FILE *stream, const char *format, .. ); sono verificarsi alcune traduzioni sui caratteri. Questo significa che potrebbe non
esservi una corrispondenza uno-a-uno fra la stringa e il file. Se invece lo stream
La funzione fprintf() produce in output in stream il valore degli elementi con- viene aperto in modalit binaria, non verr applicata alcuna traduzione e vi sar
tenuti nella lista degli argomenti e nel modo specificato dalla stringaformat. Il una corrispondenza uno-a-uno fra la stringa e il file.
valore restituito il numero di caratteri effettivamente prodotti in output. Se si Le funzioni correlate sono fgets(), gets(), puts(), fprintf() e fscanf().
verifica un errore, il valore restituito sar negativo.
Gli argomenti possano andare da Oa un numero massimo definito dal sistema.
Le operazioni della stringa e dei comandi di controllo del formato sono iden- fread()
tiche a quelle della funzione printf(); per una descrizione approfondita, si consulti
#i nel ude <cstdi o>
tale funzione. size t fread(void *buf, sizt. size, size_t count,
Le funzioni correlate sono printf(} e fscanf(): - FILE *stream);

--.::==:_ - - -

~--
704

La funzione fread() restituisce count oggetti, ognuno dei quali avr dimensio- La funzione fseanf() restituisce il numero di argomenti cui stato assegnato
ni pari a size byte; le informazioni vengono lette da stream e inseriti nell'array un valore. Questo numerQ non include i campi saltati. Quando viene restituito il
puntato da buf L'indicatore di posizione del file viene fatto avanzare del numero valore EOF significa che prima del primo assegnamento si verificato un errore.
di caratteri letti. Le-funzioni correlate sono seanf() e fprintf().
La funzione fread() restituisce il numero di elementi effettivamente letti. Se
stato letto un numero di elementi inferiore a quelli richiesti nella chiamata, vuol
dire che si verifiato un errore oppure che stata raggiunta la fine del file. Per fseek()
determinare ci che accaduto si usa feof() o ferror().
Selo stream viene aperto per operazioni di testo, possono verificarsi alcune #i nel ude <cstdi o>
traduzioni dei caratteri come la conversione di sequenze Carriage retum I Linee int fseek(FILE *stream, long offset, int origin);
Feed in codici di fine riga.
Le funzioni correlate sono fwrite(), fopen(), fscanf(), fgetc() e getc(). La funzione fseek() imposta l'indicatore di posizione del file associato allo
stream secondo il valore di offset e di origin. Il suo scopo quello di supportare
operazioni di I/O ad accesso diretto. Il valore offset il numero di byte rispetto a
freopen() origin in cui occorre portarsi. Il valore di origin deve essere una delle seguenti
macro (definite in <cstdio> ):
#i nel ude <cstdi o>
FILE *freopen (const char *fname, const char *mode,
FILE *stream); NOME SIGNIFICATO
SEEK_SET Posizionamento rispetto all'inizio del file.
La funzione freopen() associa uno stream esistent a un altro file. Il nome del
SEEK_CUR Posizionamento rispetto alla posizione corrente.
nuovo file puntato da fname e la modalit di accesso puntata da mode; lo
stream da riassegnare puntato da stream. La stringa mode usa lo stesso formato SEEK_ENO Posizionamento rispetto alla fine del file.
di fopen(); una discussione pi approfondita si trova nella descrizione di fopen().
Quando viene richiamata, freopen() cerca innanzitutto di chiudere il file cui Quando viene restituito il valore O, fseek() ha avuto successo. Un valore di-
attualmente associato lo stream. Se il tentativo di chiusura non ha successo, la verso da O indica che si verificato un errore.
funzione freopen() continua comunque a tentare di aprire l'altro file. Si pu usare fseek() per spostare l'indicatore di posizione in qualsiasi punto
In caso di successo la funzione freopen() restituisce un puntatore allo stream del file, anche oltre la fine. Al contrario un errore tentare di posizionare l'indica-
mentre in caso contrario restituisce un puntatore nullo. tore prima dell'inizio del file.
freopen() viene principalmente utilizzata per redirigereTfile iffiniti dal siste- La funzione fseek() cancella il flag di fine file associato allo stream specifica-
ma (stdin, stdout e stderr) in qualche altro file. to. Inoltre annulla ogni eventuale ungete() eseguita sullo stesso stream (vedere la
Le funzioni correlate sono fopen() e fclose(). funzione ungete{)).
Le funzioni correlate sono ftell{), rewind{), fopen(), fgetpos() e fsetpos().
fscanf{)

#include <cstdio> fsetpos()


int fscanf(FILE *stream, const char *format, ... );
llinclude <cstdio>
int fsetpos(F!LE *stream, const fpos_t *position);
La funzione fscanf() funziona esattament~ come scanf() tranne per il fatto che
legge le informazionidallo stream specificato invece che da stdin. Per informa- La funzione fsetpos() sposta l'indicatore di posizione del file nel punto speci-
zioni consuhare la dscrizione di scanf(). ficato dall'oggetto puntato da position. Questo valore deve essere stato ottenuto

--- ------
706 CA P I T CL O 2 5 LtFlJ N Z I O WI D I I I O B A S A T E_..$_ U L C -_707

in precedenza con una chiamata a fgetpos(). Dopo aver eseguito fsetpos(), l'indi- getc()
catore di fine file viene reinizializzato. Inoltre viene annullato ogni precedente
chiamata a ungete(). _ #include <cstdio>
Se fsetpos() non ha successo, restituisce un valore diverso da O. In caso di int getc(FILE *stream);
successo restituisce O.
Le funzioni correlate sono fgetpos(), fseek() e ftell(). La funzione getc() restituisce il carattere successivo tratto dallo stream di input
e incrementa l'indicatore di posizione del file. Il carattere viene letto come un
unsigned char che viene convertito in un intero. Quando viene raggiunta la fine
ttellO del file, getc() restituisce EOF. Ma poich EOF un valore intero valido, quando
si lavora su file binari necessario controllare che non sia stata raggiunta la fine
#include <cstdio> del file tramite la funzione feof(). getc() restituisce EOF anche quando si verifica
long ftell (FILE *stream); un errore. Quando si lavora su file binari, per controllare quando si verificato un
errore si deve usare ferror().
La funzione ftell() restituisce il valore corrente dell'indicatore di posizione del Le funzioni getc() e fgetc() sono identiche e nella maggior parte delle
file nello stream specificato. Nel caso di stream binari, il valore la posizione (in implementazioni la funzione getc() definita come una semplice macro nel modo
termini di byte) in cui posizionato l'indicatqre rispetto all'inizio del file. Per seguente:
stream di testo, questo valore non ha significato se non come argomento di fseek(),
poich le traduzioni apportate al contenuto del file (ad esempio la trasformazione #define getc(fp) fgetc(fp)
dei codici di fine riga in Carriage Retum I Line Feed) pu alterare le dimensioni
apparenti del file. Questo fa in modo che alla macro getc() sia sostituita la funzione fgetc().
La funzione ftell() restituisce in caso d'errore il valore -1. Se lo stream inca- Le funzioni correlate sono fputc(), fgetc(), putc() e fopen().
pace di eseguire posizionamenti diretti (ad esempio nel caso di un modem) il
valore restituito indefinito.
Le funzioni correlate sono fseek() e fgetpos(). getchar()

#includ <cstdio>
fwrite{) int getchar(void);

-~1~1ud~_~stdio> La funzione getchar() restituisce il prossimo carattere contenuto in stdin. Il


size_t fwrite(const void *buf, size_t size, carattere viene letto come un unsigned char che viene convertito in un intero, - -- ---
size_t count, FILE *stream); Se viene raggiunta la fine del file, getchar() restituisce EOF. Ma poich EOF
un valore intero valido, quando si lavora su file binari si deve controllare ~e ~tata
La funzione fwrite() scrive nello stream num oggetti ognuno dei quali ha lun- raggiunta la fine del file utilizzando la funzione feof(). Anc~e qua_ndo s1 venfica
ghezza pari a size byte tratti dall'array di caratteri puntato da buf. L'indicatore di un errore getchar() restituisce EOF. Per controllare quando s1 venficato un erro-
posizione del file viene fatto avanzare del numero di caratteri scritti. re, nel caso di file binari si deve usare la funzione ferror().
La funzione fwrite() restituisce il numero di elementi effettivamente scritti Spesso la funzione getchar() implementata come una macro.
che, se la funzione ha successo, sar uguale al numero di oggetti richiesti. Quando Le funzioni correlate sono fputc(), fgetc(), putc() e fopen().
viene scritto un numero di elementi inferiore rispetto a quelli richiesti, si verifi-
to un errore. Per gli Sfream di testo, possono verificarsi varie traduzioni di ca-
ratteri ma queste non avranno effetto sul valore restituito. gets{)
Le funzioni correlate sono fread(), fscanf(), getc() e fgetc().
#include <cstdio>
char"'ge"ts{-char _*str);
708 CAPITOLO 25 LE FUNZIONI DI 1/0 BASATE SUL C 709

La funzione gets() legge i caratteri da stdin e Ii inserisce nell'array di caratteri Se gli argomenti sono insufficienti rispetto agli specificatoci di formato, l'output
puntato da str. I caratteri vengono letti fino al codice newline successivo o fino sar indefinito. Se vi sono pi argomenti che specificatori, gli argomenti in ecces-
alla fine del file. II carattere di fine riga non entra a far parte della stringa ma viene so vengono eliminati. Gli specificatori di formato sono elencati nella Tabella 25.2.
trasformato nel codice nullo di fine stringa. La funzione printf() restituisce il numero di caratteri effettivamente stampati.
In caso di successo, gets() restituisce str e in caso di fallimento restituisce un Quando viene restituito un numero negativo, significa che si verificato un errore.
puntatore nullo. Se si verifica un errore, il contenuto dell' array puntato da str sar Ai codici di formato possono essere associati dei modificatori che specificano
indeterminato. Poich viene restituito \!Il puntatore nullo sia in caso di errore che la larghezza del campo, la precisione e lallineamento. Un intero posizionato fra il
in caso .di raggiungimento della fine del file, per determinare cosa accaduto si segno % e il codice di formattazione costituisce uno specificatore di larghezza
deve usare feof() o ferror(). minima del campo. Questo ha l'effetto di inserire degli spazi o il numero "O" per
, Non v~ alcu~ modo per limitare il numero di caratteri letti da gets() e dunque garantire la larghezza minima del campo. Se la stringa o il numero maggiore del
e necessano fare m modo che non vengano superati i limiti dell'array puntato da minimo specificato, verr stampato completamente. Normalmente lo spazio vuo-
str. to sar occupato dal carattere spazio. Se si vuole utilizzare il numero "O", si deve
Le funzioni correlate sono fputs(), fgetc(), fgets() e puts(). posizionare uno "O" prima dello specificatore di larghezza del campo. Ad esem-

perror()
Tabella 25.2 Gli specificatori di formato di printf().
#include <cstdio>
void perror( const char *str); CODICE FORMATO
%e Carattere
La funzione perror() mappa il valore della variabile globale ermo in una strin-
%d Intero decimale con segno
ga e scrive la stringa in stderr. Se il valore di str non nullo, verr scritto, seguito
da un due punti e da un messaggio definito dall'implementazione. %i Intero decimale con segno

%e Notazione scientifica (e minuscola)


printf() {;:: %E Notazione scientifica (E maiuscola)
o;&
"C
#include <cstdio> "' %f Numero in virgola mobile
:.-
int printf(const . .cha.L:'f.armat, ) ; <~
%g Use il formato pi breve fra %e (minuscola) o 'lof
~-
~,

:lr'
,,'\. %G Usa il formato pi breve fra %E (maiuscola) o %1
. La funzione printf() scrive su stdout la lista degli argomenti specificata dalla
strmga puntata da formato. ~ %o Ottale senza segno
g;~
La stringa puntata da formato costituita da due tipi di elementi. II primo j
~ %s Stringa di caratteri
composto dai caratteri che verranno stampati sullo schermo. Il secondo tipo con- 'i.
tiene gli specificatori di formato che definiscono il modo in cui vengono visualizzati i
~e
%u Intero decimale senza segno
gli a:gomenti. Uno specificatore di formato inizia con il segno percentuale ed ~ %x Esadecimale senza segno (lettere minuscole)
seguito dal codice di formato. Vi deve essere esattamente Io stesso numero di "'0
;:;~

argome~ti e Io stesso numero di specificatori di formato e gli specificatori di %X Esadecimale senza segno (lettere maiuscole)
formato e gli argomenti devono essere ordinati allo stesso modo. Ad esempio, la %p visualizza un puntatore
~cguente istruzione visualizza la frase "Hi c IO there!".
%n t.:argomento associato un puntatore a un Intero nel quale viene lnserilo il numero di caratteri scritti
finora
prfntf("Hi %e %d %s", 'e', 10, "there!");
. %% _-stampa il segno% -
710 CA P I T O LO 2 5 LE F tt N Z !-o N I DI I/ O BA..S A T E SU L C---=+1-l--

pio il codice %05d far precedere ai numeri di meno di 5 cifre una serie di carat- tiene cifre decimali. Prima dei codici di formattazione x e X, il codice# provoca la
teri "O" in modo che la larghezza totale -del campo sia pari a 5. visualizzazione del numero esadecimale con il prefisso Ox. Prima del codice di
Il significato del modificatore di precisione dipende dal codice di formattazione formattazione o, il modificatore # provoca la visualizzazione di un valore ottale
che viene modificatO.Per aggiungere un modificatore di precisione si deve posi- con il prefisso O. II modificatore # pu essere applicato solo a questi specificatori
zionare un punto decimale seguito dal valore della precisione, dopo lo specificatore di formato. Gli specificatori di larghezza minima del campo e di precisione pos-
di larghezza del campo. Per i formati e, E e F il modificatore di precisione deter- sono essere fomiti anche come argomenti di printf() oltre che come costanti. A tale
mina il numero di cifre decimali visualizzate. Ad esempio il codice %10.4f scopo si deve utilizzare come segnaposto il carattere *. Quando viene esaminata la
visualizza un numero in 10 spazi con 4 cifre decimali. Quanto il modificatore di stringa di formattazione, la funzione printf() assegna ad ogni asterisco l'argomen-
precisione viene applicato al codice di formattazione g o G, determina il numero to nell'ordine in cui si presenta.
massimo di cifre significative visualizzate. Quando viene applicato agli interi, il Le funzioni correlate sono scanf() e fprintf().
modificatore di precisione specifica il numero minimo di cifre da visualizzare. Se
necessario verranno aggiunti caratteri "O" iniziali.
Normalmente l'output .allineato a desfra: se la larghezza del campo mag- putc()
giore rispetto ai dati stampati, i dati vengono accostati al bordo destro del campo.
llinclude <cstdio>
Per richiedere I' allineamento a sinistra dei dati basta inserire il segno - appena
int putc(int eh, FILE *stream);
dopo il segno%. Ad esempio, il codice %-10.2f allinea a sinistra un numero in
virgola mobile con due cifre decimali in un campo che si estende per 10 caratteri.
La funzione putc() scrive sullo stream il carattere contenuto nel byte meno
Vi sono due modificatori di formato che consentono a printf() di visualizzare
significativo di eh. Poich al momento della chiamata gli argomenti a caratteri
interi short e long. Questi modificatori possono essere applicati agli specificatori
vengono promossi in interi, si pu tranquillamente utilizzare caratteri come argo-
d, i, o, u ex. Il modificatore I (la lettera elle) dice a printf() che deve essere stampa-
menti di putc().
to un tipo long. Ad esempio, %Id chiede la visualizzazione di un intero long. II
La funzione putc() restituisce i caratteri scritti con successo oppure EOF in
modificatore h chiede a printf() di visualizzare un intero short. Pertanto il codice
caso di errore. Se lo stream di output stato aperto in modalit binaria, EOF un
%hu indica dati di tipo short unsigned int.
valore valido per eh. Questo significa che si deve usare ferrar() per detenninare se
Se si usa un moderno compilatore che supporta i caratteri estesi, introdotti nel
si verificato un errore.
1995, allora si pu usare il modificatore I con lo specificatore e per indicare l'uso
Le funzioni correlate sono fgetc(}, fputc(), getchar() e putchar().
di caratteri estesi di tipo wchar_t. Si pu utilizzare il modificatore I anche con il
comando di formattazione s per indicare una stringa di caratteri estesi.
Il modificatore L pu precedere i comandi per numeri in virgola mobile e, f e putchar()
g e indica che deve essere stampato un long double.
Il comando %n chiede di inserire in una variabile intera (il cui puntatore lii nel ude <cstdi o>
specificato nell'elenco degli argomenti) il numero di caratteri che sono stati scrit- int putchar(int eh);
ti fino al momento in cui stato incontrato tale codice. Ad esempio, questo fram-
mento di codice visualizza il numero 18 dopo la frase "Questa una prova": La funzione putchar() scrive il carattere contenuto nel byte meno significativo
di eh su stdout. In pratica la funzione equivalente a putc(ch, stdout). Poich al
int i; momento della chiamata gli argomenti a caratteri vengono promossi in interi, si
pu tranquillamente utilizzare caratteri come argomenti di putchar().
printf("Questa una prova%n", &i); La funzione putchar() restituisce i caratteri scritti con successo oppure EOF in
printf("%d", i); caso di errore. Se lo stream di output st~to aperto in modalit binaria, EOF un
valore valido per eh. Questo significa che si deve usare ferror() per determinare se
Se utilizzato con alcuni codici di formattazione di printf(), il carattere# ha un si verificato un errore.
. significato speciale. Quando il carattere# precede un codice g, G, f, e o E, signifi- L funzione correlata putc().
ca che richiesto l'inserimentoaelp-unt<Ydecimale, anchese-il m~mero-non-con~ -
712 CAPITOLO 25 LE FUNZIONI DI 1/0 BASATE SUL c-1n--

putsO scanf()

llinclude <cstdio> #i nel ude <cstdi o>


int puts{const char *str); int scanf{const char *format, ... };

La funzione puts() scrive la stringa puntata da str sul dispositivo di output La funzione scanf() una routine di input di carattere generale che legge in-
standard. Il terminatore nullo viene tradotto nel codice di fine riga. formazioni dallo stream stdin e le memorizza nelle variabili puntate dalla lista
La funzione puts() restituisce un valore non negativo oppure EOF in caso di degli argomenti. Pu leggere tutti i tipi di dati standard e convertirli automatica-
insuccesso. mente nel formato interno corretto.
Le funzioni correlate sono putc(), gets() e printf(). La stringa controllo puntata da format costituita da tre classificazioni di
caratteri:

remove() specificatoci di formato;


caratteri di spaziatura;
#include <cstdio> caratteri non di spaziatura.
int remove{const char *fname);
Gli specificatoci di formato di input iniziano con il segno % e dicono a scanf()
La funzione remove() cancella il file specificato dajname. In caso di successo quale tipo di dati deve essere letto. Gli specificatoci di formato sono elencati nella
restituisce Omentre in caso di errore restituisce un valore diverso da zero. Tabella 25.3.
Una funzione correlata rename().
Tabella 25.3 Gli specificatori di formato di scanfO.
rename() CODICE SIGNIFICATO

llinclude <cstdio> %e Legge un singolo carattere.


int rename(const char *oldfname, const char *newfname); %d Legge un intero decimale.

%i Legge un intero.
La funzione rename() cambia il nome del file specificato da oldfname asse-
gnandogli il nome newfname. Il nuovo nome non deve essere gi presente nella %e Legge un numero in virgola mobile.
stessa directory.
%f Legge un numero in virgola mobile.
In ca:so di successo la funzione restituisce il valore Omentre in caso di errore
restituisce il valore O. %9 Legge un numero in virgola mobile.
Una funzione correlata remove(). %o Legge un numero ottale.

%s . Legge una stringa.


rewind()
%-x~~~~~~~Leg~ge_u_n_nu_me_ro_esa~d_ec1_m_a1_e.~~~~~~~~~~~~~~~"'---
lii nel ude <cstdi o> %p Legge un puntatore.
void .r~wi_nd{FILE *stream) ;_
%n Riceve un valore Intero uguale al numero di caratteri scritti finora.

La funzione rewind() porta l'indicatore di posizione-del-file all'inizio dello %u Legge un intero senza segno.
stream specificato. Inoltre cancella i flag di fine file di errore associati a stream.
%[ l Scansione di un insieme di 'caratteri.
Non restituisceal.Cili'fVlore .
.--_Una ~unzione correlata fseek(). Legge un segno percentuale.
------ - -~- --
714 CA P I T O LO -2-5 LE FUNZIONI DI I/O BASATE SUL C 715

Ad esempio %s legge una stringa e o/od legge un intero. La stringa di dato l'input 10/20 inserisce il valore 1Oin x, ignora il segno di divisione e assegna_
formattazione viene letta da sinistra a destra e gli specificatori vengono associati il valore 20 a y.
uno dopo l'altro agli elementi che costituiscono la lista degli argomenti. I comandi di formattazione possono specificare un modif~atore che indica la
Per leggere un intero si deve specificare il codice I (la lettera elle) davanti allo lunghezza massima di un campo. Si tratta di un numero intero posizionato fra il
specificatore di formato. Per leggere un intero short si deve porre una h davanti segno % e il codice di formattazione per limitare il numero di caratteri letti per
allo specificatore di formato. Questi modificatori possono essere utilizzati nei ciascun campo. Ad esempio, se nella variabile address non si vogliono leggere
codici di formattazione d, i, o, u e x. pi di 20 caratteri, si deve utilizzare la seguente forma:
Normalmente gli specificatori f, e e g chiedono a scanf() di assegnare i dati a
un float. Se si pone la I davanti a uno di questi specificatori, scanf() assegna i dati scanf("%20s", address);
a un double. La lettera L dice a scanf() che la variabile che riceve i dati un long
double. Se Io stream di input maggiore di 20 caratteri, una successiva chiamata di
Se si usa un moderno compilatore che supporta i caratteri estesi, introdotti nel input inizier da dove questa chiamata terminata. L'input di un campo pu ter-
1995, allora si pu usare il modificatore I con lo specificatore e per indicare l'uso minare prima del raggiungimento della lunghezza massima del campo quando
di caratteri estesi di tipo wchar_t. Si pu utilizzare il modificatore I anche con il viene inco~trato uno spazio vuoto. In questo caso scanf() si porta sul campo suc-
comando di formattazione s per indicare una stringa di caratteri estesi. La lettera cessivo.
I pu essere utilizzata anche per modificare uno scanset in modo da richiedere Anche se gli spazi, i codici di tabulazione e i codici di fine riga sono utilizzati
luso di caratteri estesi. come separatori fra i campi, quando si legge un singolo carattere questi codici
Un carattere di spaziatura nella stringa di formattazione fa in modo che scanf() verranno letti.come qualsiasi altro carattere. Ad esempio, se lo stream di input
salti uno o pi spazi nella stringa di input. Un carattere di spaziatura pu essere un contiene x y,
vero e proprio carattere di spazio, un carattere di tabulazione o un carattere di fine
riga. In pratica un carattere vuoto nella screen di controllo fa in modo che scanf() scanf("%c%c%c", &a, &b, &e);
legga senza memorizzare un qualsiasi numero (anche O) di caratteri di spaziatura
fino al successivo carattere non di spaziatura. restituisce il carattere x in a, uno spazio in b e il carattere y in c.
Un carattere non di spaziatura nello stream di formattazione fa in modo che Attenzione: ogni altro carattere della stringa di controllo (inclusi gli spazi, i
scanf() legga e scarichi il carattere corrispondente. Ad esempio il codice %d,%d caratteri di tabulazione e i codici di fine riga) vengono utilizzati per individuare
fa in modo che scanf() legga un intero, legga ed elimini una virgola e infine legga ed eliminare i caratteri dallo stream di input. Dunque verranno eliminati tutti i
un altro intero. Se il carattere specificato non viene trovato, scanf() ha termine. caratteri corrispondenti. Ad esempio, dato lo stream di input 1Ot20,
Tutte le variabili utilizzate per ricevere valori tramite scanf() devono essere
passate per indirizzo. Questo significa che tutti gli argomenti devono essere - - - --
--~

scanf("%dt%d", &x, &y);


puntatori. -
Gli elementi di input d'evono essere separati da uno spazio, un codice di
inserisce IO in x e 20 in y. La t viene eliminata per la presenza della lettera t nella
tabulazione o dal carattere di fine riga. I segni di punteggiatura come le virgole i
stringa di controllo.
punti e virgola e cos via non contano come separatori. Questo significa che:
Un'altra funzionalit di scanf() Io scanset. Uno scanset definisce l'insieme
di caratteri che verranno letti da scanf() e assegnati al corrispondente array di
scanf("%d%d", &r, &e);
caratteri. Uno scanset viene definito inserendo i caratteri da considerare fra pa-
rentesi quadre. La parentesi quadra ?perta deve essere preceduta dal segno %. Ad
accetter un input 1o 20 ma non un input 10,20. esempio, _il seguente scanset ch~e a scanf() di leggere i soli caratteri A, B e C:
Un asterisco posizionato:dopo il segno% e prima del codice di formattazione
consente di leggere i dati del tipo specificato ma sopprime l'assegnamento: Per- _ %[ABC]
tanto il comando:
Quando viene utilizzato uno scanset, scanf() continua la legger-e i caratteri-e- -
scanf("%d%*c%d", &x, &y);
__aj inserirli nel corrispondente array di caratteri finch non viene incontrato un
---- -------
716 L E F u N zl 0-N i iITl7 o B A sA TE s u L -e-. 717

carattere non presente nello scanset. La variabile corrispondente deve essere un sono definite da size mentre mode determina la modalit di bufferizzazione. Se
puntatore a un array di caratteri. All'uscita di scanf{), l'array conterr una stringa buf nullo, setvbuf() alloca un proprio buffer.
chiusa dal carattere nullo e contenente i caratteri letti. Per mode si possono usare i valori _IOFBF, _IONBF e _IOLBF. Tali valori sono
Si pu anche specificare uno scanset inverso quando il primo carattere dello definiti-in <CStdio>. Quando mode uguale a _IOFBF, viene eseguita una
scanset ''- Questo significa ch scanf() accetter tutti i caratteri non definiti dallo bufferizzazione completa. Se mode _IOLBF, lo stream verr bufferizzato riga
scanset. per riga, ovvero il buffer verr svuotato ogni volta che nello stream di output
Per specificare un intervallo basta utilizzare un trattino. Ad esempio, la se- viene scritto il codice di fine riga; per gli stream di input, una richiesta di input
guente istruzione chiede a scanf{) di accettare tutti i caratteri fra A e Z. legger tutti i caratteri fino al codice di fine riga. In entrambi i casi il buffer sar
comunque svuotato una volta raggiunta la massima capacit. Quando mode
%(A?: _IONBF, non viene eseguita alcuna bufferizzazione.
Il valore di size deve essere maggiore di O.
importante ricordare che uno scanset distingue fra lettere maiuscole e mi- In caso di successo la funzione setvbuf{) restituisce Omentre in caso di errore
nuscole. Pertanto se si vogliono leggere lettere maiuscole e minuscole occorre restituisce un valore diverso da zero.
specificarle esplicitamente. Una funzione correlata setbuf{).
La funzione scanf{) restituisce un .numero equivalente al numero di campi cui
stato assegnato un valore. Questo numero non comprende i campi letti ma non
assegnati a causa della presenza del modificatore *. Se prima della lettura del sprintf()
primll campo si verificato un errore, la funzione restituisce EOF.
#i nel ude <cstdi o>
Le funzioni correlate sono printf{) e fscanf{).
int sprintf(har *buf, const char *formot, . ) ;

setpufQ La funzione sprintf() identica a printf{) tranne per il fatto che l'output viene
inserito nel!' array puntato da buf invece di essere scritto sullo schermo. Per infor-
#incl~~e <cstdio> mazioni consultare printf().
void setbuf(FILE *streom, char *buf); II valore restituito uguale al numero di caratteri effettivamente inseriti
nell'array.
La funzione setbuf{) consente di specificare il buffer per Io stream oppure, Le funzioni correlate sono printf() e fsprintf{).
quan3o buf nullo, consente di disattivare la bufferizzazione. Se deve essere spe-
citk,i.m un buffer definito dal programmatore, questo deve essere lungo BUFSIZ
carmteri. BUFSIZ definito in <cstdio>. ----- --- sscanf()
La funzione setbuf{) non restituisce alcun valore.
#include <cstdio>
Le funzioni correlate sono fopen{), fclose{) e setvbuf().
int sscanf(const char *buf, const char *format .. ) ;

setv-bufO La funzione sscanf() identica a scanf() tranne per il fatto che i dati vengono
letti dall'array puntato da buf invece che da stdin. Per informazioni consultare
#inc..,de <cstdio> scanf().
int ~oetvbuf(FILE *streom, char *buf, int mode, size_t size); II valore restituito uguale al numero delle variabili cui stato assegnato un
valore. Questo numero non include i campi saltati p~r l'uso del modific!!tore *.Il
La funzione setvbuf{) consente al programmatore di specificare il buffer, le valore Osignifica che non stato assegnato il valore -d alcun campo mentre EOF
s~e ilimensioni e la sua modalit-di funzionamento. Per bufferizzare le operazioni indica che prima del primo assegnamento si verificato un errore.
d1 UO verr utilizzato I'array di caratteri puntato da buf. Le dimensioni del buffer Le funzioni correlate sono scanf() e fscanf().
-718 CAPITOLO 25 LE FUNZIONI DI I/O BASAfTsu'Lc 719

tmpfile() Non si pu eseguire ungete() su EOF.


Una chiamata a ungete() cancella il flag di fine file associato allo stream spe-
#include <cstdio>
cificato. Il valore dell'indicatore di posizione del file per uno stream di testo
FILE *tmpfile(void);
indefinito finch non vngono letti tutti i caratteri reinseriti e allora sar uguale al
valore che aveva prima della chiamata a ungete(). Per gli stream binari, ogni chia-
La funzione tmpfile() apre in aggiornamento un file temporaneo e restituisce mata a ungete() decrementata l'indicatore di posizione del file.
un puntatore allo stream. La funzione utilizza automaticamente un nome di file La funzione restituisce il carattere eh in caso di successo e EOF in caso di
univoco per evitare conflitti con i file preesistenti. errore.
In caso di fallimento la funzione tmpfile() restituisce un puntatore nullo, altri-
Una funzione correlata getc().
menti restituisce un puntatore allo stream.
Il file temporaneo creato da tmpfile() viene rimosso automaticamente alla chiu-
sura del file o al termine del programma. vprintf(), vfprintf() e vsprintf()
Una funzione correlata tmpnam().

#i nel ude <cstdarg>


tmpnam{) #include <cstdio>
int vprintf(char *formot, va_list org_ptr);
#include <cstdio> int vfprintf(FILE *streom, const char *format,
char *tmpnam(char *nome); . va_list org_ptr);
int vsprintf(char *buf, const char *formot,
La funzione tmpnam() genera un nome di file univoco e lo memorizza nell' array va_list org_ptr);
puntato da name. Lo scopo principale di tmpnam() quello di generare un nome
di file temporaneo diverso rispetto ad ogni altro file contenuto nella directory Le funzioni vprintf(), vtprintf() e vsprintf() hanno funzionalit equivalenti, ri-
corrente. spettivamente, a printf(), fprintf() e sprintf() tranne per il fatto che la lista degli
La funzione pu essere richiamata fino a TMP_MAX volte. TMP_MAX defi- argomenti sostituita da un puntatore. Questo puntatore deve essere tipo va_list,
nito in <CStdio> ed equivale ad almeno 25. Ogni volta che viene richiamata, definito nell'header <CStarg> (O nel file header C <Stdarg.h>).
tmpnam() genera un nuovo nome di file temporaneo. Le funzioni correlate sono va_arg(), va_start() e va_end().
In caso di successo viene restituito un puntatore a name, altrimenti viene re-
stituito un puntatore nullo.
- -Unlrfmizione correlata tmpfile().

ungete()

lfinclude <cstdio>
int ungetc(int eh, FILE *stream);

La funzione ungete() reinserisce nello stream il carattere specificato dal byte


qi ordine inferiore di eh.. Questo carattere verr ottenuto dalla successiva opera-
zione di lettura dallo stream. Una chiamata a fflush() o fseek() annulla un'opera-
zione ungete() ed elimina il carattere.
In genere consentito il reinserimento di un solo carattere mentre alcune
implementazioni consentono di reinserire nella stringa-un mllg1Purnumero
caratteri. - ----
- - --=----=-- .~. .-

Capitolo 26

Lefunzioni per stringhe


e caratteri

!
l..a libreria di funzioni standard contiene una ricca va-
riet di funzioni per la manipolazione delle stringhe e dei caratteri. Le funzioni
per stringhe operano su un array di caratteri chiuso dal carattere nullo e richiedo-
no l'impiego dell'header <Cstring>. Le funzioni per caratteri usano l'header
<CCtype>. I programmi e devono usare i file header <String.h> e <Ctype.h>.
Poich I linguaggio C/C++ non prevede verifiche sul raggiungimento dei li-
miti per le operazioni sugli array, sar responsabilit del programmatore evitare
di fuoriuscire dai limiti stabiliti per gli array. Se si dimentica questa precauzione,
si provocher molto probabilmente il blocco del programma.
In C/C++ un carattere stampabile un carattere che pu essere visualizzato
su un terminale. In genere si tratta dei caratteri compresi fra Io spazio (Ox20) e il
carattere tilde (OxFE). I caratteri di controllo hanno un valore compreso fra Oe
Ox!F pi il codice DEL (Ox7F).
Per motivi storici, i parametri delle funzioni a caratteri sono interi ma viene
utilizzato solo il byte di ordine inferiore; le funzioni per caratteri convertono auto-
maticamente i loro argomenti in unsigned char. Tuttavia anche possibile richia-
mare queste funzioni con argomenti char in quanto la chiamata a funzione pro-
muove automaticamente i caratteri in interi.
L'header <cstring> definisce il tipo sizt?_t che in pratica la stessa cosa di
unsigned.
Questo capitolo descrive le sole funzioni che operano su caratteri di tipo char.
Si tratta delle funzioni originariamente definite dal C e dal C++ standard e sono
anche quelle pi ampiamente utilizzate e supportate. Le funzioni per caratteri
estesi che operano su caratteri di tipo wchar_t verranno discusse nel Capit<?l~31.

isalnum()

- --~ - - - ---
722 CAPITOLO 26 -
----~~----~==-'"= _______
La funzione isalnum() restituisce un numero diverso da Oquando il suo argo- isgraph()
mento una lettera alfabetica o una cifra. Se il carattere non alfanumerico,
restituisce O. _5:
_#include <cctype>
Le funzioni correlate sono isalpha(), iscntrl(), isdigit(), isgraph(), isprint(), int isgraph(int eh};
ispunct() e isspace().
La funzione isgraph() restituisce un valore diverso da zero se eh un carattere
stampabile diverso da uno spazio, altrimenti restituisce O. In generale i caratteri
isalpha() stampabili sono quelli compresi fra Ox21 e Ox7E.
Le funzioni correlate sono isalnum(), isalpha(), iscntrl(), isdigit(), isprint(),
#i nel ude <cctype>
ispunct() e isspace().
int isalpha(int eh);

La funzione isalpha() restituisce un valore diverso da zero se eh una lettera islower()


dell'alfabeto, altrimenti restituisce il valore O. Cosa si debba intendere per lettera
dell'alfabeto dipende da lingua a lingua ma in genere si tratta delle lettere maiu- #include <cctype>
scole e minuscole comprese fra A e Z. int islower(int eh};
Le funzioni correlate sono isalnum(), iscntrl(), isdigit(), isgraph(), isprint(),
ispunct() e isspace(). La funzione islower() restituisce un valore diverso da zero se eh una lettera ,
altrimenti restituisce O.
Una funzione correlata isupper().
iscntrl()

#include <cctype> isprint()


int iscntrl(int eh);
lii nel ude <cctype>
La funzione iscntrl() restituisce un valore diverso da Ose eh compreso fra O int isprint(int eh);
e OxlF o se uguale a Ox7F (DEL); altrimenti restituisce il valore O.
Le funzioni correlate sono isalnum(), isalpha(), isdigit(), isgraph(), isprint(), La funzione isprint() restituisce un valore diverso da zero se eh un carattere
ispunct() e isspace(). stampabile, incluso Io spazio, altrimenti restituisce il valore O. I caratteri stampabili
si trovano spesso nell'intervallo fra Ox20 e Ox7E.
Le funzioni correlate sono isalnum(), isalpha(), iscntrl(), isdigit(), isgraph(),
isdigit() ispunct() e isspace().
#i nel ude <cctype>
int isdigit(int eh); ispunct()

La funzione isdigit() restituisce un valore diverso da zero se eh una cifra, #include <cctype>
ovvero se un carattere compreso fra Oe 9. Altrimenti restituisce il valore O. int ispunct(int eh};
Le funzioni correlate sono isalnum(), isalpha(), iscntrl(), isgraph(), isprint(),
ispunct() e isspac~_(). -- La funzione ispunct() restituisce un valore diverso da O se eh un segno di
punteggiatura, altrirne_nti restituisce O. Con "segno di punteggiatura" si intendono
tutti i caratteri stampabili che non sono n alfanumerici n spazi.

- __:_--.::_ -~ ..
724 CAPITOLO 26
--- -- - LE FUNZIONl_PER STRINGHE E CARATTERI 725

Le funzioni correlate sono isalnum(), isalpha(), iscntrl(), isdigit(), isgraph() e La funzione memchr() ricerca nell'array puntato da buffer la prima occorren-
isspace().
za di eh nei primi eount caratteri.
La funzione memchr() restituisce un puntatore alla prima occorrenza di eh in
isspace() buffer; se eh non viene trovato restituisce un puntatore nullo.
Le funzioni correlate sono memcpy() e isspace().
#i nel ude <cctype>
int isspace(int eh);
memcmp()
La funzione isspace() restituisce un valore diverso da zero se eh uno spazio, #i nel ude <est ring>
una tabulazione orizzontale, una tabulazione verticale, un codice di avanzamento int memcmp(const void *bufl, const void *buf2, size_t eount);
pagina, un codice Carriage Retum o un carattere di fine riga; altrimenti restituisce O.
Le funzioni correlate sono isalnum(), isalpha(), iscntrl(), isdigit(), isgraph() e La funzione memcmp() confronta i primi count caratteri degli array puntati da
ispunct().
bufl e buj2.
La funzione memcmp() restituisce un intero da interpretare nel seguente modo:
isupper()
VALORE SIGNIFICATO
#i nel ude <cctype>
int isupper(int eh); Minore di O bufl minore di buf2

O(zero) buf1 uguale a buf2


La funzione isupper() restituisce un valore diverso da O se eh una lettera
Maggiore di O buf1 maggiore di buf2
maiuscola, altrimenti restituisce O.
Una funzione correlata islower().
Le funzioni correlate sono memchr(), memcpy() e strcmp().

isxdigit()
memcpy()
#i nel ude <cctype>
int isxdigit(int eh); #i nel ude <estri ng>
void *memcpy(void *to, const void *from, size_t.counzj;____ _
La funzTone isxdigit() restituisce per valore diverso da zero se eh una cifra
esadecimale, altrimenti restituisce O. Una cifra esadecimale si trova negli inter- La funzione memcpy() copia eount caratteri dall' array puntato dafrom all' array
valli A-F, a-f e 0-9. puntato da to. Se gli array si sovrappongono, il comportamento di memcpy()
Le funzioni correlate sono isalnum(), isalpha(), iscntrl(), isdigit(), isgraph(), indefinito. _
ispunct() e isspace(). La funzione memcpy() restituisce un puntatore a to.
Una funzione correlata memmove().
memchr()
memmove()
#include <cstring>
void *memchr(const void *buffer, int eh, size_t count); #include <cstri.n~--
void *memmove(void *to, const void *from, size_t coun.t);
726_ - CAPlTOLO 26

La funzione memmove() copia count caratteri dall'array puntato da from


Se non viene trovata una corrispondenza la funzione restituisce un puntatore
all'array puntato da to. Se gli array si sovrappongono, la copia verr eseguita
nullo.
correttamente, inserendo il contenuto corretto in to ma modificando anche from.
Le funzioni correlate sono strpbrk(), strspn(), strstr() e strtok().
La funzione memmove() restituisce un puntatore a to.
Una funzione correlata memcpy().
strcmp()
memset() #i nel ude <estri ng>
int strcmp(const char *strl, const char *str2);
#i nel ude <estri ng>
void *memset(void *buf, int eh, size_t count);
La funzione strcmp() confronta in modo lessicale due stringhe e restituisce un
intero sulla base del risultato, come indicato nella seguente tabella.
La funzione memset() copia il byte di ordine inferiore di eh nei primi count
caratteri dell'array puntato da buf e restituisce buf
L'uso pi comune di memset() consiste nell'inizializzazione di una regione di VALORE SIGNIFICATO
memoria con un valore ben noto. Minore di o stri minore di str2
Le funzioni correlate sono memcmp(), memcpy() e memmove().
O(zero) stri uguale a str2

Maggiore di O str1 maggiore di str2


strcat()

#include <estring> Le funzioni correlate sono strchr(), strcpy() e strcmp().


ehar *streat(char *stri, const ehar *str2);

La funzione strcat(} concatena una copia di str2 a strl e chiude strl con un strcoll()
carattere nullo. Il terminatore nullo che originariamente chiudeva strl viene
#i nel ude <estri ng>
sovrascritto dal primo carattere di str2. La stringa str2 non viene modificata int streoll (const char *strl, eonst char *str2);
dalr operazione. Se gli array si sovrappongono, il comportamento di strcat(}
indefinito.
--- - - ----- La funzione strcat(} restituisce strl. La funzione strcoll() confronta la stringa puntata da strl con quella puntata da
str2. Il confronto viene eseguito secondo quanto stabilito nella funzione-setlocale()
Si ri_ordi che non vengono eseguite verifiche dei limiti e dunque responsa-
(per informazioni consultare la parte relativa a tale funzione).
bilit del programmatore assicurarsi che str1 abbia dimensioni sufficienti per con-
La funzione strcoll() restituisce un intero interpretabile nel seguente modo:
tenere sia il suo originale contenuto che il contenuto di str2.
Le funzioni correlate sono strchr(}, strcmp() e strcpy().
VALORE SIGNIFICATO

strchr() Minore di O stri minore di str2

o(zero) stri uguale a str2


#i nel ude <estri ng>
ehar *strehr(const ehar *str, int eh); Maggior!! di o stri maggiore di str2

La funzione strchr(} restituisce un puntatore alla prima occorrenza del byte di Le funzioni correlate sono niemcmp{) e strcmp().
ordine inferiore di eh nella stringa puntata da str,_ ---- -

----- ---------
----~-:--- - ---
728 CAPITOLO 26 LE FUNZIONI PER STRINGHE E CARATTERI 729

strcpy() - strncat()

#i nel ud <cstring> #include <cstring>


char *strcpy(char *strl, cnst char *str2); char *strncat(char *strl, const char *str2, size_t count);

La funzione strcpy() viene utilizzata per copiare in stri il contenuto di str2. La funzione strncat() concatena non pi di count caratteri della stringa punta-
str2 deve essere un puntatore a una stringa chiusa dal carattere nullo. La funzione ta da str2 alla stringa puntata d stri e chiude stri con il carattere nullo. Il
strcpy() restituisce un puntatore a stri. tenninatore nullo che originariamente chiudeva stri viene sovrascritto dal primo
Se stri e str2 si sovrappongono, il comportamento di strcpy() indefinito. carattere di str2. La stringa str2 non viene modificata dal!' operazione. Se le strin-
Le funzioni correlate sono memcpy(), strchr(), strcmp() e strncmp(). ghe si sovrappongono, il comportamento indefinito.
La funzione strncat() restituisce stri.
Si ricordi che non vengono eseguite verifiche sui limiti e dunque responsa-
strcspn() bilit del programmatore assicurarsi che le dimensioni di stri siano sufficienti per
contenere il suo originale contenuto e anche str2.
#include <cstring>
Le funzioni correlate sono strcat(), strnchr(), strncmp() e strncpy().
size_t strcspn(const char *strl, const char *str2);

La funzione strcspn() restituisce la lunghezza della sottostringa iniziale della strncmp()


stringa puntata da str1 che composta dai soli caratteri non contenuti nella stringa
puntata da str2. In altre parole, strcspn() restituisce l'indice del primo carattere #include <cstring>
della stringa puntata da stri che corrisponde a uno qualsiasi dei caratteri contenu- int strncmp(const char *strl, const char *str2, size_t count);
ti nella stringa puntata da str2.
Le funzioni correlate sono strrchr(), strpbrk(), strstr() e strtok(). La funzione strncmp() confronta in modo lessicale non pi di count caratteri
di due stringhe e restituisce un intero sulla base del risultato, come indicato nella
seguente tabella.
strerror{)

#i nel ude <est ring> VALORE SIGNIFICATO


char *strerror(int errnum);
Minore di o -- --srrr emrnore di str2

La funzione strerror(} restituisce un puntatore a una stringa definita O(zero) strt uguale a str2
dall'implementazione associata al valore di errnum. In nessun caso si deve modi- Maggiore di o str1 maggiore di sl/2
ficare la stringa.
Se nelle stringhe vi sono meno di count caratteri, il confronto termina quando
strlen() viene incontrato il primo carattere nullo.
Le funzioni correlate sono strcmp(), strnchr() e strncpy().
#Include <cstring>
size_t strlen(const char *str);
strncpy{)
La funzione strlen() restituisce la lunghezza della stringa chiusa dal carattere
#include <cstring>
nullo puntata da~tr. Il terminatore nullo non viene contato.
char *strncpy(char *strl, const char *str2, size_t count);
-- ~~~-zion~~orrefate sono memcpy-O~~tchr(), strcmp(fe-strncmp().
------ -----::---~---:._
730 CAPITOLO 26 LE FUNZIONI PER STRINGHE E CARATTERI 731

La funzione strncpy() copia fino a count caratteri dalla stringa puntata da str2 nella stringa puntata da str2. -
alla stringa puntata da stri. str2 deve essere un puntatore a una stringa chiusa dal Le fun~oni correlate sono strpbrk(), strrchr{), strstr() e strtok().
carattere nullo.
Se stri e str2 si sovrappongono, il comportamento di strncpy() indefinito.
Se la stringa puntata da str2 contiene meno di cou~t caratteri, alla fine della strstr()
stringa stri verranno aggiunti dei caratteri nulli fino a copiare count caratteri
Al contrario, se la stringa p1:1ntata da str2 pi lunga di count caratteri, la
#i nel ude <estri ng>
char *strstr(const char *strl, eonst char *str2);
stringa risultante puntata da stri sar chiusa dal carattere nullo.
La funzione strncpy() restituisce un puntatore a stri.
La funzione strstr() restituisce un puntatore alla prima occorrenza della strin-
Le funzioni correlate sono memcpy(), strchr(), strncat() e strncmp().
ga puntata da str2 all'interno della stringa puntata da strl. Se non viene trovata
alcuna corrispondenza, la funzione strstr() restituisce un puntatore nullo.
strpbrk() Le funzioni correlate sono strchr(), strcspn(), strpbrk(), strspn(), strtok() e
strrchr().
#include <cstring>
char *strpbrk(const char *strl, const char *str2);
strtok()
La funzione strpbrk() restituisce un puntatore al primo carattere della stringa
puntata da stri che corrisponde a un qualsiasi carattere della stringa puntata da #includ ~cstring>
char *strtok(char *strl, const char *str2);
str2. I terminatori nulli non vengono inclusi. Se non vi alcuna corrispondenza la
funzione restituisce un puntatore nullo.
Le funzioni correlate sono strspn(), strrchr(), strstr() e strtok(). La funzione strtok() restituisce un puntatore al token successivo nella stringa
puntata da stri. i caratteri che compongono la stringa puntata da str2 sono i
delimitatori che separano i token. Se non vi sono token vie~e restituito un puntatore
strrchr() nullo.
Per trasformare una stringa in token la prima chiamata a strtok() deve fare in
#include <cstring> modo che stri punti alla stringa da trasformare in token. Le chiamate successive
char *strrchr(const char *str, int eh); devono usare per strl un puntatore nullo. In questo modo possibile ridurre in
token l'intera stringa.
La funzione strrchr() restituisce un puntatore all'ultima occorrenza del byte di possibile usare delimitatori differenti per ciascuna chiamata a strtok().
ordine inferiore di c/z nella stringa puntata da str. Se non viene trovata alcuna Le funzioni correlate sono strchr(), strcspn(), strpbrk(), strrchr{) e strspn{).
corrispondenza, strrchr() restituisce un puntatore nullo.
Le funzioni correlate sono strpbrk(), strspn(), strstr() e strtok().
strxfrm()

strspn() #i nel ude <estri ng>


size_t strxfrm(char *strl, const char *str2, size_t count);
#include <cstring>
si~e_t strspn(const char *strl, const char *str2); La funzione strxfrm() trasforma i primi count caratteri. della stringa puntata da
str2 in modo che possano essere utilizzati dalla funzione strcmp() e inserisce il
La funzione strspn() restituisce la lunghezza della scittostringa iniziale della risultata nella stringa puntata da stri. Dopo la trasformazione, i risultati di strcmp()
___ ____stringa puntata da stri che composta dai soli caratteri contenuti nella stringa su stri e strcoll() 'SUila stnnga ongiifale puntata da sri':r5aranno1rreIUkt- -
puntata da str2. In altre parole,--strspn{) res.tituisce_l'indice del primo carattre'-'----- La funzione strxfrm()restituisce la lunghezza dclhtstringa trasformata.
della stringa puntata stri che non corrisponde ad aiCUrii'dei caratteri contenuti Una funzione cbrrelata strCOll():- -- -- =-=:.c.:.:: -- -- -
tolower() Capitolo 27
#i nel ude <cctype>
int tolower(int eh);
Le funzioni matematiche

La funz~one tolower() restituisce l'equivalente minuscolo di eh (se eh un


lettera); altrimenti eh viene restituito cos come . a
Una funzione correlata toupper().

toupper()

#i ne 1ude <cctype>
int toupper(int eh);
La libreria delle funzioni standard contiene numerose
La funz~one toupper() restituisce l'equivalente maiuscolo di eh (se eh una funzioni matematiche che rientrano nelle seguenti categorie:
lettera); altnmenti eh viene restituito cos come . funzioni trigonometriche;
Una funzione correlata tolower().
funzioni iperboliche;
funzioni esponenziali e logaritmiche;
funzioni varie
Tutte le funzioni matematiche richiedono l'impiego dell'header <cmath> (i
programmi e devono usare il file header <math.h> ). Oltre a dichiarare le funzioni
matematiche, questo header definisce la macro HUGE_VAL. Anche le macro EDOM
ed ERANGE sono. molto impiegate dalle funzioni matematiche. Queste macro
sono definite nell 'header <cermo> (o nel file header <ermo.h> ). Se un argomento
di una funzione matematica non nel dominio richiesto, viene restituito un valore
definito dall'implementazione e alla variabile intera globale ermo viene assegna-
to il valore EDOM. Se una routine produce un risultato troppo esteso per essere
rappresentato, si verifica un overflow. In questo caso la routine restituisce - - -- -
HUGE_VAL e a ermo viene assegnato ERANGE. Se si verifica un underflow, la
funzione restituisce il valore Oe assegna a ermo il valore ERANGE.
Tutti gli angoli sono espressi in radianti.
Originariamente le funzioni matematiche erano specificate in modo da opera-
re su valori di tipo double ma il C++ standard ha aggiunto delle versioni modifica-
te tramite overloading in grado di accettare esplicitamente valori di tipo float e
long double. Per il resto l'operativit delle funzioni non risulta modificata.

acos()

#i nel ude <cmath>


,.. ---fl oat _acos{-Hoat-arg);

---=-:: l
"""--

~--_:''
734 C A P I T O b-0--2--7--- 735

double acos(double arg); Le funzioni correlate sono asin(), acos(), atan(), tan(), cos(), sin(), sinh(), cosh()
long double acos(long double arg); ..e tanh().

La funzione acos() restituisce l'arco coseno di arg. L'argomento di acos()


deve essere compreso tra -1 e l; altrimenti si verifica un errore di dominio. ceil()
Le funzioni correlate sono asin(), atan(), atan2(), sin(), cos(), tan(), sinh(),
#include <cmath>
cosh() e tanh().
float ceil(float num);
double ceil (double num);
long double ceil (long double num);
asin()

iinclude <cmath> La funzione ceil() restituisce il pi piccolo intero (rappresentato come un valore
float asinffloat arg); in virgola mobile) non minore di num. Ad esempio, dato 1.02, ceil() restituis!=e
double asin(doubl e arg): 2.0. Dato -1.02, ceil() restituisce -1.
long double asin(long double arg); Le funzioni correlate sono floor() e fmod().

La funzione asin() restituisce l'arco seno di arg. L'argomento di asin() deve


essere compreso tra -1e1; altrimenti si verifica un errore di dominio. cos()
Le funzioni correlate sono acos(), atan(), atan2(), sin(), cos(), tan(), sinh(),
#i ne 1ude <cma th>
cosh() e tanh().
float cos(float arg);
double cos(double arg);
long double cos(long double arg);
atan()

#include <cmath> La funzione cos() restituisce il coseno di arg. II valore di arg deve essere
float atan(float arg); espresso in radianti.
double atan(double arg); Le funzioni correlate sono asin(), acos(), atan2(), atan(), tan(), sin(), sinh(),
long double atan(long double arg); cos() e tanh().

La funzione atan() restituisce_[ ar.co_ tangente di arg.


Le funzioni correlate sono asin(), acos(), atan2(), tan(), cos(), sin(), sinh(), cosh()
cosh() e tanh().
#i nel ude <cmath>
float cosh(float arg);
atan2() double cosh(double arg);
long double cosh(long double arg);
#include <cmath>
float atan2(float y, float x); La funzione cosh() restituisce il coseno iperbolico di arg.
double atan2(double y, double x); Le funzioni correlate sono asin(), acos(), atan2(), atan(), tan(), sin(), cosh() e tanh().
long double atan2(.long double y, long double x);

----La funzione atan2() restituisce l'arco tangente di ylx. Per calcolare il quadran-
te del valore restituito utilizza il segno dei suoi argomenti.
#include <cmath>
float_ exp(float arg);
736 CAPITOLO 27
I E F Il N Z-1 ON I MATE M ATI C H E 737

double exp(double arg); frexp()


. long double exp(long double arg);
#i nel ude <cmath>
La funzione exp() restituisce il logaritmo natuale in base e elevato alla poten- float frexp{float num, int *exp);
za arg. double frexp(double num, int *exp);
Una funzione correlata log( ). long double frexp(long double num, int *exp);

La funzione frexp() decompone il numero num in una mantissa compresa fra


fabsQ 0.5 e 1 e un esponente intero tale che num =mantissa * 2<XP. La mantissa viene
restituita dalla funzione e lesponente viene memorizzato nella variabile puntata
#include <cmath>
daexp.
float fabs(float num);
double fabs(double num);
Una funzione correlata ldexp().
long double fabs{long double num);
ldexp()
La funzione fabs() restituisce il valore assoluto di num.
- Una funzione correlata abs(). #include <cmath>
float ldexp(float num, int exp);
double ldexp{double num, int exp);
floor() long doubJe ldexp(long double num, int exp);

#include <cmath>
float floor(float num}:
La funzione ldexp() restituisce il valore di num * 2"-'P Nel caso di overflow
double floor(double num);
viene restituito il valore HUGE_VAL.
long double floor(long double num); Le funzioni correlate sono frexp() e modf().

La funzione floor() restituisce il massimo intero (rappresentato come un valo- log()


re in virgola mobile) non maggiore di num. Ad esempio, dato 1.02, floor() restitu-
isce 1.0. Dato -1.02, floor() restituisce -2.0. #i nel ude <cmath>
Le funzioni correlate sono fceil() e fmod(). float log(float num);
doubl e log (doubl e num);
long double log(long double num);
fmod()
La funzione log( ) restituisce il logaritmo natulae di num. Se num negativo si
#i ne 1ude <cma th>
verifica un errore d dominio mentre se l'argomento O si verifica un errore di
float fmod(float x, float y);
double fmod(double x, double y);
intervallo.
long double fmod(long double x, long double y);
Una funzione correlata log10().

La funzione fmod() restituisce il resto di xly. log10()


Le funzioni correlate sono ceil(), floor() e fabs().
#include <cmath>---
'float logl0(1'1oat num);

---- ---- -
LE FUNZIONI MATEMATICHE 739
rTOLO 27

La funzione sin() restituisce the sine di arg. II valore di arg deve essere espres-
so in radianti.
Le funzioni correlate sono asin(), acos(), atan2(), atan(), tan(), cos(), sinh(),
funzione log 1O() restituisce il logaritmo in base 1Odi num. Se num nega- cosh() e tanh() .
.si verifica un errore di dominio mentre se l'argomento Osi verifica un errore
''intervallo.
Una funzione correlata log( ). sinh()

#i nel ude <cma th>


float sinh(float arg);
double sinh(double arg);
#i nel ude <cmath>
long double sinh(long double arg);
float modf(float num, float *i);
double modf(double num, double *t); La funzione sinh() restituisce il seno iperbolico di arg.
long double modf(long double num, long double *i); Le funzioni correlate sono asin(), acos(), atan2(), atan(), tan(), cos(), tanh(),
cosh() e sin().
La funzione modf()decompone num nelle sue parti intera e frazionale. Resti-
tuisce la parte frazionale e inserisce la parte intera nella variabile puntata da i.
Le fupzioni correlate sono frexp() e ldexp(). sqrt{)

#i nel ude <cmath>


pow() float sqrt(float num);
double sqrt(double num);
#include <cmath> long double sqrt(long double num);
float pow(float base, float exp);
float pow(float base, int exp); La funzione sqrt() restituisce la radice quadrata di num. Se viene richiamata
double pow(double base, double exp); con un argomento negativo, si verifica un errore di dominio.
double pow(double base, int exp); Le funzioni correlate sono exp(), log() e pow().
long double pow(long double base, long double exp);
long double pow(long double base, int exp);

La funzione pow() restituisce base elevata alla potenza exp (base""P). Se base
uguale a O e exp minore o uguale a O si verifica un errore di dominio. Questo #i nel ude <cmath>
accade anche se base negativa e exp non un intero. Un overflow produce un float tan(float arg);
errore di intervallo. double tan(double arg);
long double tan(long double arg);
Le funzioni correlate sono exp(), log( ) e sqrt().
La fupzione tan() restituisce la tangente arg. Il valore di arg deve essere espresso
sin() in radianti.
Le funzioni correlate sono acos(), asin(), atan(), atan2(), cos(), sin(), sinh(),
ti nel ude <cmath> cosh() e tanh().
float sin(ffoat arg);
aouble sin(double arg);
----1ong double sinfhlng-dquble-arg);----- -

- - - - --------
tanh()
Capitolo 28
#include <cmath>
float tanh(float arg); Le funzioni per le date, le
double tanh(tlouble arg);
long double tanh(long double arg);
: ore e la localizzazione

La funz~on~ tanh() restituisce la tangente iperbolica di arg.


. Le funz10m correlate sono acos(), asin(), atan(), atan2() cos() sin() cosh()
smh() e tan(). ' ' '

La libreria delle funzioni standard definisce numerose


funzioni che si occupano di date e ore. Inoltre definisce funzioni che gestiscono
tutte le informazioni geopolitiche associate a un programma.
Le funzioni per date e ore richiedono l'uso dell'header <Ctime> (un program-
ma C deve usare il file header <time.h>). Questo header definisce tre tipi correlati
con il tempo:. clock_t, time_t e tm. I tipi clock_t e time_t consentono di rappresen-
tare l'ora e la data di sistema sotto forma di interi (la cosiddetta ora di calenda-
rio). La struttura tm contiene la data e l'ora suddivise nei rispettivi elementi ed
definita nel seguente modo:

struct tm {
int tm_sec; /*secondi, 0-61 */
int tm_min; /*minuti, 0-59 */
int tm_hour; /* ore, 0-23 */
int tm_mday; /* giorno del mese, 1-31 */
int tm_mon; /* mese da Gennaio, 0-11 */
int tm_year; /* anno dal 1900 */
int tm_wday; /*giorni da Domenica, 0-6 */
int tm_yday; /*giorni dal primo Gennaio, 0-365 */
i nt tm_i sdst /* indi catare
ora legale */

Il valore di tm_isdst positivo quando attiva lora legale, O se non attiva


l'ora legale e negativa se non sono disponibili informazioni. Questa forma del-
l'ora e della data chiamata ora suddivisa.
Inoltre <Ctime> definisce la macro CLOCKS_PER_SEC che equivale al nu-
mero di impulsi di sistema al secondo.
- Le funzioni-per la gestione delle_in:formazionig~QP.9!itiche d'ambiente ri-
chiedono l' he.ader ~locale> (un programmae d_eYe usare il file header <locale.h>). __ _ -- _ .
742 CAPITO ru-213----
LE DATE, LE ORE CCA ICJCIIT1zzATIONE--m--

asctime() dato un puntatore ali' ora di calendario. L'ora di calendario viene in genere ottenu-
ta tramite una chiamata a time(). -
#include <ctime> II buffer utilizzato da ctime() per contenere la stringa di output formattato un
char *asctime(const struct tm *ptr); array di caratteri allocato in modo statico che ~iene sovrascritta o~ volta che
viene richiamata la funzione ctime(). Per salvare 11 contenuto della stnnga occorre
La funzione asctime() restituisce un puntatore a una stringa che contiene le copiarla altrove.
informazioni memorizzate nella struttura puntata da ptr convertite nella seguente Le funzioni correlate sono localtime(), gmtime{}, time() e asctime().
forma:

giorno mese data ore:minuti:secondi anno \n\O dlfftirne()

#include <ctime>
Ad esempio: double difftime(time_t time2, time_t tmel);

Wed Jun 19 12:05:34 1999 La funzione difftime() restituisce la differenza in secondi fra timel e time2,
ovvero time2 - time 1.
Il puntatore a struttura passato a asctime() viene in genere ottenuto da localtime() Le funzioni correlate sono localtime(), gmtime(), time(), asctime().
ogmtime().
Il buffer utilizzato da asctime() per contenere la stringa di output formattato
un array di caratteri allocato in modo statico che viene sovrascritta ogni volta che grntirne()
viene richiamata la funzione asctime(). Per salvare il contenuto della stringa oc-
corre copiarla altrove. #include <ctime>
Le funzioni correlate sono localtime(), gmtime(), time() e ctime(). struct tm *gmtime(const time_t *time);

La funzione gmtime() restituisce un puntato~e alla forma suddivisa di t~me in


clock() una struttura tm. L'ora rappresentata in termini di tempo UTC (Coordmated
Universal Time) che corrisponde in pratica all'ora mediana di Greenwich. Il valo-
ffinclude <ctime> re time in genere ottenuto tramite una chiamata a time(). Se il sistema non supporta
clock_t clock(void); l'ora UTC viene restituito il valore NULL
La struttura usata da gmtime() per contenere lora ,cos suddivisa viene all~cata
La funzione clock() restituisce un valore che approssima la durata dell'esecu- in modo statico ed sovrascritta ogni volta che viene richiamata la funzione.
zione del programma. Per trasformare il valore in secondi, occorre dividerlo per Dunque per salvare il contenuto della struttura occorre copiarla altrove.
CLOCKS_PER_SEC. Se l'ora non disponibile viene restituito il valore -1. Le funzioni correlate sono localtime(), time() e asctime().
Le funzioni correlate sono time(), asctime() e ctime().

localeconv()
ctime()
#include <clocale>
#include <ctime> st ruct 1conv *loca1econv (voi d);
chr *ctime(const time_t *time);
La funzione localeconv() restituisce un puntatore a una struttura di tipo l~onv
La funzione ctime() restituist:eln:puntatore a una stringa nella forma: che 'contiene varie informazioni geopolitiche d'ambiente relative alla fonnattaz10ne
dei valori numerici.J.,a struttura lc.o.nv_ organizzata nel segllente modo:____ __
giorno mese anno ore:minuti:secondi
- - - - - anno
------ -....:..=.'~ :_ __:.;_
744 CAPITOLO 2tf- - -LE FUNZIONI PER LE DATE, LE ORE E LA LOCALIZZAZIONE-74S-

struct lconv { La funzione localeconv() restituisce un puntatore alla struttura lconv. Non si
char *decimal_point; /* carattere di punto decimale deve modificare il contenuto di questa struttura. Per informazioni specifiche sulla
per va 1ori non monetari */ struttura lconv, consultare la documentazione del compilatore.
char *thousands_sep; /* separatore delle migliaia La funzione correlata setlocale().
per valori non monetari */
char *grouping; /* specifica i 1 raggruppamento per
valori non monetari */ localtime()
char "int_curr_symbol; /* simbolo di valuta internazionale */
char *currency_symbol; /* simbolo di valuta locale */ #include <ctime>
char "'mon_decimal_point; /* carattere di punto decimale per struct tm *localtime(const time_t *time);
valori monetari */
char "'mon_thousands_sep; /* separatore delle migliaia per
La funzione localtime() restituisce un puntatore a una forma suddivisa di time
valori monetari */
char *mon_groupi ng; /* specifica i 1 raggruppamento per
in una struttura tm. L'ora rappresentata in termini locali. Il valore time in
valori monetari */ genere ottenuto tramite una chiamata a time().
char *positive_sign; /* indicatore di valori positivi per La struttura usata da localtime() per contenere l'ora cos suddivisa viene allocata
valori monetari */ in modo statico ed sovrascritta ogni volta che viene richiamata la funzione.
char *negative_sign; /* indicatore di valori negativi per Dunque per salvare il contenuto della struttura occorre copiarla altrove.
val ori monetari */ Le funzioni correlate sono gmtime(), time() e asctime().
char int_frac_digits; /* numero di cifre visualizzate a
destra del punto decimale per
valori monetari visualizzati con mktime()
il formato internazionale */
char frac_digits; /* numero di cifre visualizzate a #include <ctime>
destra del punto decimale per time_t mktime(struct tm *time);
valori monetari visualizzati con
il formato 1ocal e */ La funzione mktime() restituisce un'ora di calendario equivalente all'ora sud-
char p_cs_precedes; /* 1 se il simbolo della valuta precede divisa presente nella struttura puntata da time. Gli elementi tm_wday e tm_yday
un valore positivo, O se il simbolo vengono impostati dalla funzione e dunque non devono essere definiti al momen-
della valuta segue il valore*/
to della chiamata. Se mktime() non pu rappresentare le informazioni come un'ora
/* 1 se il simbolo della valuta
separato dal valore con uno spazio,
valida, restituisce -1.
O altrimenti */ Le funzioni correlate sono time(), gmtime(), asctime() e ctime().
char n_cs_precedes; /* 1 se il simbolo della valuta precede
un valore negativo, O se il simbolo
della valuta segue il valore*/ setlocale()
char n_sep_by_space; /* 1 se il simbolo della valuta
#include <clocale>
separato da un valore negativo
char *setlocale{int type, const char *locale);
con uno spazio, O se il
simbolo della valuta segue il valore*/
-, char p_sfgn_posn; /* iililca la posizione del L~ fnzione setlocale.() consente di interrogare o impostare alcuni parametri
simbolo di valore positivo*/ relativi all'ambiente geopolitico di esecuzione del programma. Se locale null,
char n_sign_posn; /* indica la posizione del . setlpcale() restituisce un puntatore alla stringa di localizzazione corrente. Altri-
simbolo di valore negativo*/ menti setlocale() tenta di utilizzare la stringa specificata da locale per impostare i
parametri localinel modo spcificato da type. Per informazioni sulle stringhe di
-1ocalizzarione, consultare. la documentazione del compilatore. _::::-=:---.-::: ~-:
j~,...:..==-:.._...:
746 CAPITOLO 28 LE FU N ZIO N 1-fl--R-t.-E DATE, LE O_R_E_ E L LOCALIZZA ZIO N E 747

AI momento della chiamata, type deve essere una delle seguenti macro: COMANDO SOSTITUZIONE

LC_ALL %a Giorno della settimana abbreviato


LC_CO[lATE %A Giorno della settimana completo
LC_CTYPE
LC_MONETARY %b Nome del mese abbreviato
LC_NUMERIC %8 Nome del mese completo
LC_TIME
%e Stringa standard per la data e l'ora
LC_ALL fa riferimento a tutte le categorie di localizzazione. LC_COLLATE %d Giorno del mese (131)
riguarda le operazioni della funzione strcoll(). LC_CTYPE modifica l'operativit
%H Ora (0-23)
delle funzioni per caratteri. LC_MONETARY determina il formato monetario.
LC_NUMERIC cambia il carattere di punto decimale per le funzioni di input e %! Ora(112)
output formattato. Infine LC_TIME determina il comportamento della funzione
%j Giorno dell'anno (1386)
strftime(). La funzione setlocale() restituisce un puntatore una stringa associata al
parametro type. %m Mese (1-12)
Le funzioni correlate sono localeconv(), time(), strcoll() e strftime().
%M Minuto (0-59)

%p Equivalente locale di AM o PM
strftime()
%5 Secondo (059)
#i ne 1ude <et i me> %U Settimana (053); il primo giorno domenica
size_t strftime(ehar *str, size_t maxsize, eonst char *fmt,
eonst struet tm *time); %w Giorno della settimana (06); domenica = O

%W Settimana (0-53); il primo giorno lunedl


La funzione strftime() inserisce nella stringa puntata da str le informazioni
%x Stringa standard per la data
relative alla data e all'ora e anche altre informazioni utilizzando comandi di
formattazione contenuti nella stringa puntata dafmt impiegando 1' ora specificata %X Stringa standard per l'ora
in time. In str verr inserito un massimo di maxsize caratteri.
%y Anno senza secolo .(0-99) - _ --
La funzione strftime() si comporta un po' come sprintf() in quanto riconosce
una serie di comandi di formattazione che iniziano con il segno di percentuale %Y Anno con secolo (in decimale)
(%) e inserisce l'output formattato in una stringa. I comandi di formattazione
%Z Nome fuso orario
vengono utilizzati per specificare il modo in cui le informazioni devono essere
rappresentate in str. Ogni altro carattere presente nella stringa di formattazione %% Segno di percentuale
verr inserito in str senza alcuna modifica. La data e l'ora fanno riferimento al-
l'ora locale. Nella seguente tabella sono elencati i comandi di formattazione. Si Le funzioni correlate sono time(), localtime() e gmtime().
noti che la maggior parte dei comandi distingue fra lettere maiuscole e minuscole.
La funzione strftime() restituisce il numero di caratteri inseriti nella stringa
puntata da str oppure O se si verifica un errore. time()

--tinciude <ctime>
time_t time(time_t *time);

-=-=..._:__---- ,_
748 CA P I T O LO 2 8

La funzione time() restituisce l'ora di calendario corrente del sistema.. Se il : Capitolo 29


sistema non supporta lora, restituisce -1.
La funzione time() pu essere richiamata con un puntatore nullo oppure con Le_ funzioni di allocazione
un puntatore a una variabile di tipo time_t. Nel secondo caso alla variabile verr
assegnata anche l'ora di calendario. : dinamica della memoria
Le funzioni correlate sono localtime(), gmtime(), strftime() e ctime().

Onesto capitolo descrive le funzioni di allocazione di-


namica della memoria, cos come vengono ereditate dal linguaggio C. Il nucleo di
tali funzioni costituito da malloc() e free(). Ogni volta che si richiama malloc(),
viene allocata una porzione della memoria libera. Ogni volta che si richiama free()
la memoi:ia viene restituita al sistema. La regione da cui viene tratta l'area di
memoria si chiama heap. I prototipi delle funzioni di allocazione dinamica si
trovano in <Cstdlib>. Un programma e deve usare il file header <Stdlib.h>.
Tutti i compilatori C++ includono almeno le seguenti funzioni di allocazione
dinamica della memoria: calloc(), malloc(), free() e realloc(). Quasi certamente il
compilatore fornir molte varianti di queste funzioni in modo da rispondere a
varie esigenze e da adattarsi alle caratteristiche dell'ambiente operativo. A tale
proposito si rimanda alla documentazione del compilatore.
Anche se il linguaggio C++ supporta le funzioni di allocazione dinamica del-
la memoria qui descritte, in genere esse non vengono utilizzate nei programmi
C++. Questo dovuto al fatto che il linguaggio C++ definisce appositi operatori
di allocazione dinamica della memoria chiamati new e delete. Questi presentano
notevoli vantaggi rispetto alle funzioni di allocazione dinamica della memoria.
Innanzitutto new alloca automaticamente la quantit di memoria corretta per il
tipo di dati specificato. In secondo luogo restituisce un puntatore del tipo corretto.
Infine sia new che delete possono essere modificati tramite overloading. Visti
questi vantaggi rispetto alle funzioni di allocazione dinamica della memoria del
C, si consiglia di utilizzare gli operatori al posto delle funzioni.

calloc()

lii nel ude <cstdl i b>


~oid *calloc(size_t num, size_t size);

--- ----
-~
75! CAf>HO LO 29 LE FUNZIONI DI ALLOCAZIONE DINAMICA DELLA MEMORIA 751

La funzione callec() alloca un'area di memoria le cui dimensioni sono uguali pu essere maggiore o minore del precedente. realloc() restituisce un puntatore al
a .iium * size. Pertanto calloc() alloca un'area di memoria sufficiente per un array nuovo blocco di memoria poich un aumento delle dimensioni pu richiedere che
dl num oggetti di dimensioni size. La funzione calloc() restituisce un puntatore al realloc() sposti l'intero blocco. In questo caso il contenuto del vecchio blocco
prilno byte della regione allocata. Se la memoria insufficiente per soddisfare la viene copiato nel nuovo blocco senza alcuna perdita di informazioni.
richiesta, viene restituito un puntatore nullo. sempre importante verificare che Se ptr nullo, realloc() alloca semplicemente size byte di memoria e restitui-
il valore restituito non sia nullo prima di tentare di usarlo. sce un puntatore a tale area. Se size O, la memoria puntata da ptr viene liberata.
Le funzioni correlate sono free(), malloc() e realloc(). Se la memoria disponibile nello heap insufficiente per allocare size byte, la
funzione restituisce un puntatore nullo e il blocco originale rimane non modificato.
Le funzioni correlate sono free(), malloc() e calloc().
free()

#i~cl ude <cstdl i b>


vctd free(void *ptr);

La funzione free() restituisce all'heap la memoria puntata daptr. L'area tome-


rb. a~ essere disponibile per ogni successiva operazione di allocazione.
E fondamentale che free() venga richiamata con il puntatore precedentemente
aiocato utilizzando una delle funzioni di allocazione dinamica (calloc() o malloc() ).
L"aso di un puntatore non valido provoca con ogni probabilit la distruzione del
n:eccanismo di gestione della memoria e pertanto un blocco del sistema.
Le funzioni correlate sono calloc(), malloc() e realloc().

malloc()

#1cl ude <cstdl i b>


vctd *malloc(size_t size);

La funzione rnalloc() restituisce un puntatore al primo byte di una regione di


rremoria di dimensioni pari a size allocata nello heap. Se la memoria insuffi-
ci:!nte per soddisfare la richiesta, malloc() restituisce un puntatore nullo. sempre
ir:portante verificare che il valore restituito non sia nullo prima di tentare di uti-
li:zare il puntatore. Il tentativo di utilizzare un puntatore NULL provoca general-
rrente un blocco del sistema.
Le funzioni correlate sono free(), realloc() e calloc().

realloc()

1o1clude <cstdl ib>


v='.d *realloc(void *ptr, size_t size);

La funzione realloc() camrualedfmnsioni dell'area di memoria precedente- .


- _::r._nte ~l~ta.~:a ptr in-mo~~da ottenere uno spazio pari a size.
Il vaforedi ~~--=- - -- - ---- ------ ---
. Capitolo 30

Le funzioni di servizio
i

. a libreria delle funzioni standard definisce numerose


funzioni che forniscono vari tipi di servizi. Fra di esse vi sono funzioni di conver-
sione, funzioni per l'elaborazione di liste di argomenti di lunghezza variabile,
funzioni di ordinamento e ricerca e funzioni per la generazione di numeri casuali.
La maggior parte delle funzioni descritte in questo capitolo richiede l'uso
dell'header <Cstdlib> (un programma C dovr utilizzare il file header <stdlib.h>).
In questo header sono definiti div_t e ldiv_t che sono i tipi dei valori restituiti
rispettivamente da div() e !div(). Inoltre viene definito il tipo size_t che il tipo del
valore unsigned restituito da sizeof. Inoltre sono definite le seguenti macro:

MACRO SIGNIFICATO
NULL Un puntatore nullo

RANO_MAX Il valore massimo che pu essere restituito dalla funzione rand().

EXIT_FAILURE Il valore restituito al processo chiamante quando il programma terminato senza successo.

EXIT_ SUCCESS Il valore restituito al processo chiamante se il programma terminato con successo.

Per le funzioni che impiegano un header diverso da <cstdio> stato indicato


l'header da includere.

abort()

#i nel ude <cstdl i b>


voi d abort (voi d);

-ba-funzione abort() provo.ca.l.'.immedia~_}!_~.~r~ a_nC?_rmale del program-


ma__Qeneralmente i buffer non ven~no .svuotati su file. In alcuni ambie.n!i, _ _
754 CAPITOLO 30 LE FUNZIONI DI SERVIZIO 755

abort() restituisce al processo chiamante (in genere il sistema operativo) un va- La funzione atexit() fa in-modo che alla normale terminazione del programma
lore definito dall'implementazione per indicare che il programma terminato venga richiamata la funzione puntata dafunc. Se la funzione registrata con suc-
con un insuccesso. cesso come funzione di chiusura del programma, la funzione atexit() restituisce il
Le funzioni correlate sono exit() e atexit(). valore O, altrimenti restituisce un valore diverso da O.
Si possono definire almeno 32 funzioni di chiusura del programma, le quali
verranno richiamate in ordine inverso rispetto alla loro definizione.
absO Le funzioni correlate sono exit() e abort().
#include <cstdlib>
int abs(int num); atof()
long abs(long num);
double abs(double num); #i nel ude <cstdl i b>
double atof(const char *str);
La funzione abs(} restituisce il valore assoluto di num. La versione long di
abs() uguale a labs(}. La versione double di abs() uguale a fabs(). La funzione atof() converte la stringa puntata da str in un valore double. La
Una funzione correlata labs(). stringa deve contenere un numero in virgola mobile valido. Se questo non avvie-
ne, il valore restituito indefinito.
Il nut~ero pu essere concluso da qualsiasi carattere che non possa costituire
assert()
un numero in virgola mobile. Ad esempio pu trattarsi di uno spazio vuoto, di un
#i nel ude <cassert>
segno di punteggiatura (ad eccezione del punto) o di un carattere (diverso da e o
void assert(int exp); E). Questo significa che se atof() viene richiamata sulla stringa "100.00SALVE",
verr restituito il valore 100.00.
La macro assert(), definita nell'header <cassert> scrive le informazioni di Le funzioni correlate sono atoi() e atol().
errore su stderr e quindi chiude l'esecuzione del programma se l'espressione exp
restituisce O. Altrimenti assert() non fa nulla. Anche se l'output definito atoi()
dal!' implementazione, la maggior parte dei compilatori usa un messaggio simile
al seguente: #include <cstdl ib>
int atei (const char *str);
Assert_!.o~ failed: <expression>, file <file>, line <linenum>
La funzione atoi() converte la stringa puntata da str in un valore int. La stringa
La macro assert() viene generalmente utilizzata per verificare che il program- deve contenere un numero intero valido. Se questo non avviene, il valore restitu-
ma stia operando in modo corretto e l'espressione organizzata in modo da forni- ito indefinito; tuttavia molte implementazioni restituiscono il valore O.
re true solo quando non si verificato alcun errore. Il numero pu essere concluso da qualsiasi carattere che non possa costituire
Non necessario rimuovere le istruzioni assert() dal codice sorgente dopo il un numero intero. Ad esempio pu trattarsi di uno spazio vuoto, di un segno di
debug del programma: basta definire la macro NDEBUG e tutte le macro assert() punteggiatura o di un carattere. Questo significa che se atoi() viene richiamata
verranno ignorate. sulla stringa "123.23", verr restituito il valore 123 e ".23" verr ignorato.
Una funzione correlata abort(). Le funzioni correlate sono atof() e atol().

atexit() tol()
#i nel ude <cstdl ib> -- - - - - #include <cstdlib>
int atexit(void (*func)(void)); long atol(const-char-*str); - ---...;;..;;;__-____ _
-----156-- e A p I T o Lo_ 3. o --TE"TlfN ZIO N I DI SERV-~~~--(51-

La funzione atol() converte la stringa puntata da str in un valore long. La strin- div()
ga deve contenere un numero long valido. Se questo non avviene, il valore restitu-=
ito indefinito; tuttavia molte implementazioni restituiscono il valore O. #i nel ude <cstdl i b>
Il numero pu essere concluso da qualsiasi carattere chnon possa costituire div t div(int numerator, int denominotor);
ldi~_t div(long numerator, long denominator);
un numero intero. Ad esempio pu trattarsi di uno spazio vuoto, di un segno di
punteggiatura o di un carattere. Questo significa che se atol() viene richiamata
sulla stringa "123.23", verr restituito il valore l23L e ".23" verr ignorato. La versione int della funzione div() restituisce il quoziente e per il restt' ,id-
Le funzioni correlate sono atof() e atoi(). i' operazione numeratore I denominatore in una struttura di tipo div_t. La ven;,,ne
long di div() ha le stesse caratteristiche della funzione ldiv().
La struttura div_t contiene quanto meno i due campi seguenti:
bsearch()
int quot; /* quoziente */
#include <cstdlib> i nt rem; /* resto */
void *bsearch(const void *key, const void *buf,
size t num, size t size, La struttura ldiv_t contiene quanto meno i due campi seguenti:
int C*compare)(c~nst void *, const void *));

long quot; /* quoziente */


La funzione bsearch() esegue una ricerca binaria sull'array ordinato puntato long rem; /* resto */
da buf e restituisce un puntatore al primo membro che corrisponde alla chiave
puntata da key. num indica il numero di elementi dell'_{UTay mentre size indica le Una funzione correlata ldiv().
dimensioni in byte di ciascun elemento.
Per confrontare un elemento dell'array con la chiave si usa la funzione punta-
ta da compare(). La funzione compare() deve avere la seguente forma: exit()

intftmc_name(const void *argl, const void *arg2); #i ne 1ude <es tdl i b>
void exit(int exit_code);
Inoltre la funzione deve restituire un valore secondo le regole descritte dalla
seguente tabella. La funzione exit() provoca l'immediata chiusura (normale) del prognmuna.
Al processo chiamante (normalmente il sistema operativo) vien~ passato il rn~~:
exit_code, sempre che l'ambiente operativo supporti l'operazione. Per l'Oll\ -
____ ----
CONFRONTO VALORE RESTITUITO zione, se il valore di exit_code O o EXIT_S~CCESS si presume che il pro~m~~
argt minore di arg2 Minore di O ma sia terminato naturalmente. Un valore diverso da O oppure EXIT_FAILU
argt uguale ad arg2
indica un errore definito dall'implementazione.
Le funzioni correlate sono atexit() e abort().
arg 1 maggiore di arg2 Maggiore di O

getenv()
L'array deve essere ordinato in senso ascendente e l'indirizzo pi basso deve
contenere l'elemento inferiore.
#i nel ude <estdl i b>
Se I'array non contiene la Chiave, bsearch() restituisc~ un puntatore nullo. - ~har *getenv(eonst char *name);
Una funzione correlata qsort().
. .. .. . d.1111hknte
La funzione getenv() restituisce un puntatore ali e m1ormaz1om
---associate-alla-stringa puntata da name all~in~rno de~l~abella delle infon11:11.1om
758 CAPITOLO 30 --[E FUNZIONI DI SERVIZIO 759

d'ambiente definite dall'implementazione. La stringa restituita non deve mai es- La funzione longjmp{) esegue il reset dello stack allo stato descritto da envbuf
sere modificata dal programma. che deve essere stato impostato tramite una chiamata a setjmp(). Questo fa in
L'~_biente di un programma pu includere percorsi e dispositivi. L'esatta
modo che l'esecuzione del programma riprenda dall'istruzione che seguiva la.
natura di questi dati definita dall'implementazione: Per informazioni opportu- chiamata a setjmp(). Pertanto il computer viene "ingannato" poich riterr di non
no consultare la documentazione del compilatore. aver mai lasciato la funzione che ha richiamato setjmp(). In pratica la funzione
Se viene eseguita una chiamata a getenv() con un argomento non corrispon- longjmp() esegue un salto nel tempo e nello spazio (di memoria) a un punto del
dente ai dati d'ambiente, getenv() restituisce un puntatore nullo. programma senza seguire il normale processo di uscita dalle funzioni.
Una funzione correlata system{). Il buffer envbuf di tipo jmp_buf, definito nell'header <csetjmp>. Prima di
richiamare longjmp(), il buffer deve essere stato impostato tramite una chiamata a
labsO setjmp().
Il valore di status diviene il valore restituito da longjmp() e pu essere interro-
#include <cstdlib> gato per determinare il luogo di provenienza del salto. L'unico valore non consen-
1ong 1abs (1 ong num) ; tito O.
L'uso pi comune di longjmp() consiste nell'uscire da un insieme molto pro-
La funzione labs() restituisce il valore assoluto di num. fondo di routine quando si verifica un errore.
Una funzione correlata abs{). Una funzione correlata setjmp().

ldivO mblenO.

#include <cstdlib> #i nel ude <cstdl i b>


ldiv_t ldiv(long numerator, long denominator); int mblen(const char *str, size_t size);

La funzione ldiv() restituisce il quoziente e il resto della divisione numeratore La funzione mblen() restituisce la lunghezza in byte di un carattere multibyte
I denominatore. puntato da str. Vengono esaminati solo i primi size caratteri. In caso di errore la
La struttura ldiv_t contiene quanto meno i due campi seguenti: funzione restituisce -1.
Se str nulla e i caratteri multibyte hanno una codifica che dipende dallo
long quot; /* quoziente */ stato, la funzione restituisce un valore diverso da O. In caso contrario restituisce O.
l ong rem; /* resto */ Le funzioni correlate s-onombtowc() e wctomb{).

Una funzione correlata div().


mbstowcs()

longjmp() #include <cstdlib>


size_t mbstowcs(wchar_t *out, const char *in, size_t size);
#i nel ude <csetjmp>
void longjmp(jmp_buf envbuf, int status); La funzione mbstowcs() converte la stringa multibyte puntata da in in una
stringa di caratteri estesi e. inserisce il risultato @ll'array puntato da out. I~ out
La funzione longjmp() fa in modo che l'esec'z{one del program~a riprenda verranno memorizzati solo size byte.
dal punto dell'ultima chiamata a setjmp(). Queste due funzioni forniscono un mezzo La funzione mbstowcs() restituisce il numero di caratteri multibyte convertiti.
per saltre fuori dalle funzioni. Si noti che necessario impiegare l'header ' In caso di errore la funzione restituisce -1.
<csetjmp>.--- -- ----- Le funzioni correlate sono wcstombs{) e mbtowc().
- ---- --~---~
760 CAPITOLO 30
LE FUNZIONI-DI SERVIZIO 761

mbtowc()
raise()
#include <cstdl ib>
#include <csignal>
int mbtowc(wchar_C"out, const char *in, size_t stze};
int raise(int signal);

La funzione mbtowc() converte il carattere multibyte contenuto nell'array La funzione raise() invia al programma in esecuzione il segnale specificato da
puntato da in nell'equivalente a caratteri estesi e inserisce il risultato nell'oggetto signal. In caso di successo restituisce il valore O e in caso contrario restituisce un
puntato da out. Verranno esaminati solo size caratteri. valore diverso da O. La funzione utilizza l'header <Csignaf>.
Questa funzione restituisce il numero inserito in out. in caso di errore la fun- Il C++ standard definisce i seguenti segnali. Naturalmente il compilatore
zione restituisce -1. Se in nullo e i caratteri multibyte hanno dipendenze di stato libero di fornire anche altri segnali.
allora mbtowc() restituisce' un valore diverso da O; altrimenti la funzione restitui~
sce il valore O.
Le funzioni ~orrelate sono mblen(), wctomb(). MACRO SIGNIFICATO
SIGABRT Errore terminazione

qsort() SIGFPE Errore virgola mobile

SIGILL Istruzione errata


#i nel ude <cstdl i b>
void qsort(void *buf, size t num, size t stze t:utente ha premuto ClRl-C
int (*compare} (const void *
cons~ voi_d *)};
SIGINT

SIGSEGV Accesso illegale alla memorta

. La funzio.ne qsort() ordina I' array puntato da buf utilizzando lalgoritmo S!GTERM Programma terminato
Qmckso:t (sviluppato da Hoare). Il Quicksort il miglior algoritmo di ordina-
mento d1 carattere generale. Al termine dell'algoritmo l'array sar ordinato. II Una funzione correlata signal().
n~mero di elementi dell'array specificato da num e le dimensioni in byte di
ciascun elemento sono descritte da size.
Per confrontare un elemento dell' array con la chiave si usa compare. campa re rand()
deve avere la seguente forma: .
#i nel ude <cstdl i b>
int rand(void);
intfimc_name(const void *argl, const void *arg2);

La funzione rand() genera una sequenza di numeri pseudocasuali. Ad ogni


Il codice restituito deve seguire le regole contenute nella tabella.
chiamata, rand() restituisce un intero compreso fra O e RAND_MAX.
Una funzione correlata srand().
CONFRONTO VALORE RESTITUITO
arg1 miwe di arg2 Minore di O
setjmp{) .
arg1 uguale ad arg2
#i nel ude <csetjmp>
arg1 maggiore di arg2 Maggiore di O
int setjmp(jmp_buf envbuf);

L'array viene ordinato in senso ascendente e dunque l'indirizzo pi basso La: funzione setjmp() salva in buf il contenuto dello stack di sistema in modo
conterr l'elemento inferiore; - --- - ---- - che successivamente possa essere utilizzato da longjmp(). La funzione usa l'header
Una funzione correlata bsearch(). _____ _ <csetjmp>. - -- _ =-::-:-:--- - ::::-; -== - -
LE FUNZIONI DI SERVIZIO -753 ---
762 - CAPITOLO 30

Alla chiamata la funzione setjmp() restituisce O. Per durante l'esecuzione La funzione correlata rand().
longjm~O. passa un ~go~ento a setjmp() ed questo valore (sempre diverse da 0)
che sara il valore d1 setimp() dopo una chiamata a longjmp(). Per informazioni
strtod()
consultare la descrizione della funzione longjmp().
Una funzione correlata longjmp(). #include <cstdlib>
double strtod(const char *start, char **end);

signal() La funzione strtod() converte una stringa che rappresenta un numero (start) in
#i nel ude <csignal>
un double e quindi restituisce il risultato.
La funzione strtod() opera nel seguente modo. Innanzitutto salta eventuali
void (*signal(int sgnal, void (*func)(int)}) (int);
spazi vuoti contenuti nella stringa puntata da start. Poi legge ogni car.attere che
costituisce il numero. La lettura dei caratteri termina quando viene nlevato un
La funzione signal() registra la funzione puntata da fune come gestore del carattere che non pu far parte di un n1:1mero in virgola mobile. ~ trattar~i ~i un~
segnale specificato da signal. Pertanto quando il programma ricever il secrnale spazio vuoto, di un segno di punteggiatura (a part~ il punto) e d1 car.atten d:v~rs1
signal, richiamer la funzione puntata dafunc. "' da e o E. Al termine end punta all'eventuale parte nmanente della stringa ongma-
Il valore difunc pu essere l'indirizzo di una funzione di gestione dei segnali ria. Questo significa che se strtod() viene richiamata sul sulla stringa "100.00
o una delle seguenti macro definite in <Csignal>. elementi'', restituir il valore 100.00 mentre end punter allo spazio che precede
la parola "elementi". . . . . .
MACRO SIGNIFICATO Se non viene eseguita alcuna conversione, la funzione rest1tmsce O. Se s1 ven-
fica un overflow, strtod() restituisce HUGE_VAL (o -HUGE_VAL per indicare un
SIG_DFL Usa il sistema di segnalazione standard
overflow negativo) e imposta a ERANGE la variabile globale ermo per.in~icar~
S!G_!GN Ignora il segnale un errore relativo all'intervallo. Se si verifica un underflow, strtod() restltmsce il
valore Oe alla variabile globale ermo viene assegnato il valore ERANGE.
s: vi~ne utilizzato per indirizzo di funzione, alla ricezione del segnale verr Una funzione correlata atof().
eseguito 11 gestore specificato.
In caso di successo, signal() restituisce l'indirizzo della funzione definita in
precedenza per il segnale specificato. In caso di errore viene restituito SIG ERR strtol()
(definito in <csignal>). -
--Jinclude <cstdl ib>
Una funzione correlata raise().
long strtol (const char *start, char **end,
int radix);
srand()
La funzione strtol() converte una stringa che rappresenta un numero (start) in
#include <cstdlib> un long e quindi restituisce il risultato. La bse del numero determinata da ra~i~.
void srand{unsigned seed); Se radix zero, la base determinata dalle regole che determinano le carattenstl-
che della costante.
La funzio~e srand() definisce un punto iniziale per la sequenza generata da La funzione strtol() opera nel seguente modo. Innanzitutto salta eventuali sp~-
rand(), la fu_n~1one che restituiscll'.. numeri pseudocasuali. zi vuoti contenuti nella stringa puntata da start. Poi legge ogni carattere che costi-
. srand() viene generalmente utilizzata per consentire a pi programmi di uti- tuisce il numero. La lettura dei caratteri termina quando viene rilevato un caratte-
li~zare s~quenze differenti di numeri pseudocasuali specificando punti di avvio re che non pu far parte di un numero long. Pu trattarsi di uno spazio vuoto, d un
d1fferent1. Nel con.tempo si pu anche usare srand() per generare ripetutamente la segno di punteggiatura e di caratteri. Al termine end punta all'e:entu~le .parte
stessa sequenza. d1 numeri-pseudocasuali, richiamando lo stesso seme prima di ril!la!l~nte della stringa originaria. Questo significa clre-se-strtol() viene nch1ama---- - _
iniziare la se_q!Jenz:- - - --- - - - - --- --
----=---.---~ --
764 CA P I T O LO 3 O -- LE FUNZIONI DI SERVIZIO 765

ta sul sul~~ stringa" 100.00 elementi", restituir il valore 1OOL mentre end punter Se system() viene richiamata con un puntatore nullo, allora restituisce un va-
allo spaz10 che precede la parola "elementi". lore diverso da O se l'interprete dei comandi presente e O altrimenti (alcuni
Se il risultato non rappresentato da un intero long, la funzione restituisce programmi C++ vengono eseguiti in sistemi dedicati che non US!!IO siste~ ope-
~O~G_MAX o LONG_~IN e imposta a ERANGE la variabile globale ermo per rativi ed interpreti di comandi dunque non si pu sempre presumere che sia pre-
md1care un errore relativo all'intervallo. Se non si verifica nessuna conversione sente un interprete dei comandi). Il valore restituito da system() definito
strtol() restituisce il valore O. dall'implementazione. Comunque quando il comando viene :segui~o c?n su.cces:
Una funzione correlata atol(). so la funzione restituisce generalmente il valore O mentre m tutu gh alta casi
restituisce un valore diverso da O.
Una funzione correlata exit().
strtoul{)

#i nel ude <cstdl i b>


va_arg(), va_start() e va_end()
unsigned long strtoul (const char *start, char **end,
int radix);
#i nel ude <cstdarg>
type va_arg(va_list argptr, type);
. La funzione strtoul() converte una stringa che rappresenta un numero (start) void va end(va list argptr);
m un unsigned long e quindi restituisce il risultato. La base del numero determi- voi d va=start (;a_l i st argptr, las(_parm);
nata da radir:. Se radix zero, la base determinata dalle regole che determinano
le caratteristiche della costante. Se radix viene specificata deve essere compresa Le macrova_arg(), va_start{) e va_end{) collaborano per consentire il passag-
fra 2 e 36. gio a una funzione di un numero variabile di argomenti. L'esempio pi ~omune .di
La funzione strtoul() opera nel seguente modo. Innanzitutto salta eventuali una funzione che accetta un numero variabile di argomenti printf{). Il tipo va_hst
spazi vuoti contenuti nella stringa puntata da start. Poi legge oani carattere che definito da <cstdarg>.
costituisce il numero. La lettura dei caratteri termina quando :iene rilevato un La procedura generale per creare una funzione che pu accettare un numero
carattere che non pu far parte di un numero unsigned long. Pu trattarsi di uno variabile di araomenti la seguente. La funzione deve avere almeno un parametro
sp~io vuoto, di un ~egno di punteggiatura e di caratteri. Al termine end punta noto (ma pu"'anche averne di pi) il quale deve precedere la lis~a dei param~tri
ali eventuale parte nmanente della stringa originaria. Questo significa che se variabili. L'ultimo parametro noto si chiama last_parm. Il nome d1 last_parm vie-
strtoul() viene richiamata sul sulla stringa "100.00 elementi", restituir il valore ne utilizzato come secondo parametro in una chiamata a va_start{). Prima di poter
lOOL mentre end punter allo spazio che precede la parola "elementi". accedere ai parametri variabili, occorre inizializzare il puntatore agli argomenti
Se il risultato n.on rappresentato da un intero unsigned long, la funzione argptrtramite---u-na-c-hiamata ava_start{). Successivamente i parametri veng?no
r:stituisce ULO~G_MAX e imposta a ERANGE la variabile globale ermo per in- restituiti tramite chiamate a va_arg() dove type il tipo del parametro successivo.
dicare un errore relativo all'intervallo. Se non si verifica nessuna conversione Infine, una volta che tutti i parametri sono stati letti e prima di uscire dalla funzio-
strtol() restituisce il valore O. ne, una chiamata a va_end() assicura che lo stack venga ripristinato in modo cor-
Una funzione correlata strtol(). retto. Se si dimentica di richiamare va_end() pr?babile che si verifichi un blocco
del programma.
Una funzione correlata vprintf{).
system{)

#i nel ude <cstdl i b>


~cstomps()
int system(const char *str);
#include <cstdlib>
La funzione system() invia la stringa puntata da strcome comando per l'inter- size_t wcstombs(char *out, const wchar_t *in, size_t size); _
-- pretLdfilQOlandi del sistema Qperativo. __ _

----------
---- ----
766 e A PTT-ot-o-so - .
___ .-:::--:----
La i:unzione wcstombs() converte l'array di caratteri estesi puntato da in nel : Capitolo 31
suo equ1v~~nte m~ltib!t~ e ~nserisce i.I risultato nell'array puntato da out. Vengo-
no convert1t1 solo 1 pruru szze byte d1 in. La conversione tennina quando viene Le funzioni per caratteri
incontrato il tenninatore nullo.
In caso di successo, wcstombs() restituisce il numero di byte convertiti. In : estesi
caso di insuccesso restituisce -1.
Le funzioni correlate sono wctomb() e mbstowcs(). 31.1 Le funzioni di classificazione
per caratteri estesi
31.2 Le funzioni di I/O per caratteri estesi
wctomb()
31.3 Funzioni per stringhe di caratteri estesi
#include <cstdlib> 31.4 Funzioni di conversione per stringhe
int wctomb(char *out, wchar_t in); di caratteri estesi
31.5 Funzioni per array di caratteri estesi
~a funz~one .wct~m~() converte il carattere esteso in nel suo equivalente 31.6 Funzioni per la conversione di caratteri
multzbyte e msensce ti nsultato nell'oggetto puntato da out. L'array puntato da multibyte ed estesi
out deve essere lungo MB_CUR_MAX caratteri.
In caso di successo, wctomb() restituisce il numero di byte contenuti nel carat-
tere multibyte. In caso di insuccesso restituisce -I.
. ~e out nullo, allora wctomb() restituisce un valore diverso da O nel caso in f\el 1995, al C standard stata aggiunta una serie di
cm 11 caratt~re 1_1lultibyte abbia stabilito delle dipendenze _e Oin caso contrario. funzioni per. caratteri estesi che successivamente sono state adottate dal C++
Le funz1om correlate sono wcstombs() e mbtowc(). standard. Le funzioni per caratteri estesi operano su caratteri di tipo wchar_t che
occupano 16 bit. Queste funzioni sono in genere analoghe alle equh'3.lenti funzio-
ni per char. Ad esempio, la funzione iswspace() la versione a carattere estesi di
isspace(). In generale le funzioni per caratteri estesi usano lo stesso nome delle
equivalenti funzioni per char, con l'aggiunta della lettera "w'.
Le funzioni per caratteri estesi usano due header: <cwchar> e <cwctype>.
Sono supportati anche il file header e <Wchar.h> e <Wctype.h>.
L'header <cwctype> definisce i tipi Wint_t, wctrans_t e wctype_t. Moire delle
funzioni per caratteri estesi ricevono come parametro un carattere esteso. Il tipo
di questo parametro wint_t. Tale parametro in grado di contenere un carattere
esteso. L'uso del tipo wint_t nelle funzioni per caratteri estesi simile all'uso di int
nelle funzioni per char. I tipi wctrans_t e wctype_t sono dedicati a oggetti utilizza-
ti rispettivamente per rappresentare un mappaggio dei caratteri (myero una cradu
zione) e la classificazione di un carattere. L'indicatore di fine file per caratteri
estesi definito come WEOF.
Oltre a definire win_t, l'header <cwchar> definisce il tipo mstate_t che descri-
ve un oggetto che contiene lo stato di una conversione da multib)1e a caratteri
estesi. L'header <cwchar> definisce anche le macro NULL, WEIF. WCHAR_MAX e
WCHAR_MIN. Le ultime due macro definiscono il valore massinio e minimo che
RU essere contenuto in un oggetto di tipo wchar_t.
Anche se il supP-orto che I!!..libreria delle funzioni standard offre per i caraneri
estesi piuttosto ~gi~ in r:_alt queste funzioni vengono utilizzate raramente.
- ----- - - ---~ ->----~
--- ---- -----------------

-----
768 CAPITO tO 31

Oltre alle funzioni elencate nella Tabella 31. l, <CWctype> definisce le seguen-
Un 1?otivo il fatto che il sistema di JJO e le librerie di classi del C++ standard
ti funzioni che forniscono un metodo aperto per la classificazione dei caratteri.
forniscono un supporto per caratteri normali ed estesi tramite l'impiego di class
tem~lat~. Inoltre, non vi un grande interesse nella realizzazione di programm~
che Impiegano caratteri estesi. Naturalmente la situazione potrebbe cambiare nel wctype_t wctype(const char *attr); .
futuro. int iswctype(wint_t eh, wctype_t attr_ob);
Poich la maggior parte delle funzioni per caratteri estesi hanno un equivalen-
te p~r cha~ e per il fatto che non sono molto utilizzate dai programmatori C++, ne La funzione wctype() restituisce un valore che pu essere passato al parame-
verra fornita solo una breve introduzione. tro attr_ob di iswctype(). La stringa puntata da attr specifica una propriet che
deve essere in possesso del carattere. Il valore di attr_ob viene utilizzato per de-
terminare se eh un carattere che ha tale propriet. In caso affermativo, iswctype()
restituisce un valore diverso da O, altrimenti restituisce O. Le stringhe di propriet
31.1 Le funzioni di classificazione della tabella seguente sono definite per tutti gli ambienti operativi.
per caratteri estesi
alnum alpha cntrl digit
L'header <cwctype> fornisce i prototipi per le funzioni per caratteri estesi che lower print punct
supportano la classificazion~ dei caratteri. Queste funzioni suddividono in cate- graph
space upper xdigit
gorie i caratteri estesi oppure convertono i caratteri da lettere minuscole a maiu-
scole e viceversa. La Tabella 31.1 elenca queste funzioni insieme alle corrispon- Il seguente programma illustra l'uso delle funzioni wctype() e iswctype().
denti funzioni per char descritte nel Capitolo 26.
lii nel ude <i ostream>
lii nel ude <cwctype>
Tabella 31.1 Le funzioni di classificazione per caratteri estesi.
using namespace std;
FUNZIONE EQUIVALENTE PER CHAR
int main()
int iswalnum(wint_t eh) isalnum()
{
int iswalpha(wint_t eh) isalpha() wctype_t x;

int iswcntrl (wint_t eh) iscntrl () x = wctype("spazio");


isdigit()
i f (i swctype(L' ', x)}
int iswgraph(wint_t eh) isgraph() cout << " uno spazio.\n";
int i swlower(wint_t eh) islower()
return O;
int iswprint(wint_t eh) i sprint()

int iswpunct(wint_t e) ispunct()


Questo programma visualizza la stringa " uno spazio.".
int iswspace(wint_t eh) i sspace() <cwctype> definisce anche le funzioni wctrans() e towctrans():
int -iswupper(wint_t eh) isupper()
wctrans_t wtrans(const char *mapping);
int iswxdigit(wint_t eh) isxdigit() wint_t towctrans(wint_t eh, wctrans_t mapping_ob);
wint_t tolower(wint_t eh) tol ower()
_ ba-funziorie-wctrans() restituisce un valore che pu essere passato al parame-
wint_t toupper(wint_t fl!)__ .. toupper() tro mapping_ob di towctrans(). Qui la -stringa-puntata da mappin~ s_p!cifica un
no CAPITOLO 31
LE FUNZIONI PER CARATTERI ESTESI m -----
mappaggio di un carattere in un altro. Questo valore pu essere utilizzato da
iswctrans() per mappare eh. Quindi viene restituito il valore mappato. Le seguenti stream non viene alterato. Se lo stream ha gi le caratteristiche desiderate, non
stringhe di mappaggio sono supportate da tutti gli ambienti di esecuzione. viene alterato. La funzione restituisce un valore positivo se lo stream usa caratteri
estesi, negativo se usa char e Oin caso di errore. Le caratteristiche di uno stream
tolc~wer toupper sono determinate dal suo primo uso.

Tabella 31.2 Le funzioni di I/O per caratteri estesi.


Ecco un breve esempio che illustra l'uso di wctrans() e towctrans().
FUNZIONE CHAR EQUIVALENTE
#include <iostream>
win_t fgetwc(FILE *stream) fgetc()
#i nel ude <cwctype>
using namespace std; wchar_t fgetws(wchar_t str, int num, FILE stream) fgets()

int main() wint_t fputwc(wchar_t eh, FILE stream) fputc()


{ int fputws(const wchar_t *str, FILE stream) fputs O
wctrans_t x;
int fwprintf(FILE *stream, const wchar_t fmt, .. .) fprintf()
x = wctrans("tolower"); int fwscanf(FILE *stream, const wchar_t fmt, .. ) fscanf()

wchar_t eh = towctrans(L'W', x); wint_t getwc(FILE stream) getc()


cout (char) eh;
wint_t getwchar( ) getchar()

return O; wint_t putwc(wchar_t eh, FILE stream) putc ()

wint_t putwchar(wchar_t eh) putchar()

Questo programma visualizza la minuscola "w". int swprintf(wchar_t str, size_t num, sprintf()
const wchar_t *fmt, .. ) Si noti raggiunta del parametro num, che
limita Il numero di caratteri scritti su str.

int swscanf(const wchar_t str,


const wchar_t *fmt, . ) sscanf()
31.2 le f4_11;ziqnj_ di I/O per caratteri estesi
wint_t ungetwc(wint_t eh, FILE *stream) ungete()
Molte-delle funzioni di 110 descritte nel Capitolo 25 hanno anche un'implemen- int vfwprintf(FILE stream,const wchar_t fmt,
tazione per caratteri estesi. Tali unioni sono elencate nella Tabella 31.2. Le fun- va_list arg) vfprintf()
zioni di I/O per caratteri estesi usano l'header <cwchar>. Si noti che swprintf() e
int vswprintf(wchar_t *str, size_t num,
wswprintf() richiedono un parametro aggiuntivo non richiesto dalle equivalenti const wchar_t *fmt, va_list arg) vsprintf()
funzioni per char. Si noti raggiunta del parametro num, che
limita ff numero di caratteri scritti su str.
Oltre alle funzioni elencate in questa tabella, stata aggiunta la seguente fun-
zione di 110 specifica per caratteri estesi: int vwprintf(const wchar_t *fmt, va_list arg) vprntf()

int wprintf(const wchar_t *fmt, .. ) printf()


iili fwide(FILE *stream, int how);
int wscanf(const wchar_t *fmt, .. ) scanf()
Se how positiva, fwide() lo trasforma in uno stream di caratteri estesi. Se /zo1r
negativa, fwide() lo trasforma in uno stream di char. Se how uguale a zero, Io--~-
LE FUNZIONI PER CARA f"F-E-R I EST ES I na
172 CAPITOLO 31

31.3 Funzioni per stringh~ ~i caratteri estesi 31.4 Funzioni di conversione per stringhe
di caratteri estesi
L'header <cwchar> definisce anche le versioni a caratteri estesi delle funzioni per
la manipolazione delle stringhe descritte nel Capitolo 27. Si noti che wcstok() Le funzioni elencate nella Tabella 31.4 costituiscono le versioni per caratteri este-
richiede un parametro in pi rispetto alla funzione char corrispondente. si delle nonnali funzioni per la conversione di valori numerici e temporali. Queste
funzioni usano l'header <cwchar>.
Tabella 31.3 Funzioni per stringhe di caratteri estesi.
Tabella 31.4 Le funzione di conversione per caratteri estesi.
FUNZIONE CHAR EQUIVALENTE
CHAR EQUIVALENTE
FUNZIONE
.:har_t *wcscat(wchar_t *strl, const wchar_t str2) strcat(J
size t wcsftime(wchar t str, size t mox, strftime()
wchar_t *wcschr(const wchar_t *str, wchar_t eh) strchr(J - cons"t wchar t *fmt.
const struct tm *ptr)
int wcscmp(const wchar_t strl, const wchar_t str2) strcmp(J
doubl e wcstod(const wchar_t start, wchar_t end); strtod(J
int wcscoll(const wchar_t strl, const wchar_t str2) strcoll ()
Tong wcstol (const wchar_t start, wchar_t end,
size_t wcscspn(const wchar_t *strl, const wchar_t str2) strcspn() int radix) strtol O

wchar_t *wcscpy(wchar_t strl, const wchar_t *str2) strcpy(J unsigned long wcstoul(const wchar_t start,
wchar_t end, int radix) strtoul ()
size_t wcslen(const wchar_t str) strlen(J

wchar_t wcsncpy(wchar_t *strl, const wchar_t str2,


size_t num) strncpy(J
31.5 Funzioni per array di caratteri estesi
wchar_t *wcsncat(wchar_t *strl, const wchar_t str2,
size_t num) strncat O
Le funzioni per la manipolazione di array come merncpy() hanno una funzione
int wcsncmp(const wchar t *strl ,const wchar t str2 equivalente per caratteri estesi. Tali funzioni sono elencate nella Tabella 31.5.
size_t numi- - ' strncmp(J
Questo gruppo di funzioni usa l'header <cwchar>.
wchar_t wcspbrk(const wchar_t *strl,const wchar_t *str2) strpbrk()

wchar_t wcsrchr(const wchar_t str, wchar_t eh) strrchr()


Tabella 31.5 Le funzioni per array di caratteri estesi.
-CHR EQUIVALENTE
size_t wcsspn(const wchar_t strJ,const wchar_t str2) strspn() FUNZIONE
wchar t wmemchr(const wchar t str, wchar_t eh, memchr(J
wchar_t *wcstok(wchar_t strl, const wchar_t str2, strtok(J
wchar_t endptr) Qui, endptr un puntatore che
- size_t num)
contiene le informazioni necessarie memcmp()
per continuare il processo di int wmemcmp(const wchar_t strl ,const wchar_t
estrazione dei token. *str2, size_t num)

wchar_t wmemcpy(wchar t strl, memcpy()


..::har_t wcsstr(const wchar_t strl, const wchar t str2) strstr(J
conSt wchar t str2,
s;ze_t wcsxfnn(wchar_t *strl, const wchar_t *str2, si ze_t num) strxfrm() size_t num) -

wchar t wmemmove(wchar t strl, const..~!'char_t str2, memmove()


- size t num)

wct\ar t wmemset(wchar_t *str, wchar_t eh, size_t num) memset()

- ---- ------" - --
n4. e A p I T o Lo 31
: Parte quarta
31.6 Funzioni per la conversione di caratteri
multibyte ed estesi LA LIBRERIA DI CLASSI
La libre?a delle funzioni standard C++ fornisce varie funzioni che supportano la . STANDARD DEL C++
conversione fra multibyte e caratteri estesi. Queste funzioni, elencate nella Tabel-
la 31.6 _usan~ l'~eader <cwchar>. Molte di esse sono le versioni riavviabili delle
normali funz10m per multibyte. La versione riavviabile utilizza le informazioni di
stato passate in un parametro di tipo mbstate_t. Se il parametro nullo, la funzio-
ne fornisce un proprio oggetto mbstate_t.

Tabella 31.6 Funzioni di conversione fra caratteri estesi e multibyte.


FUNZIONE DESCRIZIONE
win_t btowc(int eh) Converte eh nell'equivalente a caratteri estesi e restituisce il Lo standard per il C++ definisce un insieme esteso di
risultato. In caso di errore o se eh non un carattere mulffbyte di classi che offre il supporto per un gran numero di attivit molto comuni, come le
un solo byte, restituisce WEOF.
operazioni di I/O, la manipolazione delle stringhe e l'elaborazione numerica. La
size_t mbrlen(const char str, size t num, Versione riawiablle di mb 1en (), come descritto da state. libreria delle classi si aggiunge alla libreria delle funzioni descritta nella Parte
mbstate_t state) - Restituisce un valore positivo che indica la lunghezza del
successivo carattere multibyte. Se il carattere successivo nullo
terza. La libreria delle classi costituisce una parte fondamentale del linguaggio
restituisce O. In caso di errore restituisce un valore negativo. ' C++ e definisce in larga misura il suo carattere. Nonostante le sue dimensioni, la
libreria delle Classi facile da impiegare in quanto organizzata attorno a principi
size_t mbrtowc(wchar t out, Versione riawiabile di ~btowc ( J come descritto da stata.
const-char tn, size t num, Restituisce un valore positivo che indica la lunghezza del di programmazione a oggetti.
mbstate_t state) - successivo carattere mulfibyte. Restituisce Ose il carattere La libreria del C++ standard piuttosto estesa e una descrizione approfondita
succ~i~ nullo. In caso di errore restituisce un valore negativo.
Se s1 venfica un errore, a errno viene assegnata la macro di tutte le sue classi, funzionalit, attributi e dettagli implementativi non rientra
EILSEQ. negli scopi di questo volume (una descrizione completa della libreria di classi
int mbsinit(const mbstate_t state) Restituisce t rue se state rappresenta uno stato iniziale della
occuperebbe da sola un intero volume). Tuttavia, anche se la maggior parte delle
conversione. classi di utilizzo generale, alcune di esse sono dedicate principalmente agli
sviluppatori di compilatori o ai programmatori che implementano estensioni di
size_t mbsrtowcs(wchar t aut, Versione riawiabile di mbstowcs() come descritto da state.
const- char **in1 Inoltre mbs rtowcs () differisce da mbstowcs () per il fatto che in varia natura. Pertanto questa sezione descrive le sole parti della libreria che ven-
size t num, un puntatore indiretto all'array di origine. Se si verifica un errore, ----gono normalmente utilizzate in un'applicazione. Chi utilizza la libreria per ese-
mbstate_t state) a errno viene assegnata la macro EILSEQ.
guire operazioni molto specializzate potr acquisire una copia dello standard del
size_t wcrtomb(char out, wchar t eh, Versione riawiabile di wctomb () come descritto da state. Se si linguaggio C++ che contiene la descrizione tecnica dell'intera libreria di classi.
mbstate_t *state) verifica un errore, a errno viene assegnata la macro EILSEQ.

size_t wcsrtombs(char out, Versione riavviabile di wcstombs () come descritto da state.


const wchar t **in, Inoltre wcsrtombs () differisce da wcstombs() per il fatto che in
size t num,- un puntatore indiretto all'array di origine. Se si verifica un errore.
mbstate_t state) a errno viene assegnata la macro El LSEQ.

int wctob(wint_t eh) Converte eh nel suo equivalente multibyte a un solo byte. In caso
di errore restituisce EOF.

---- -
~

-------
-
--~'- - -- -

Capitolo 32

' Le classi di I/O


del C++ standard

32.1 Le classi di I/O


- 32.2 Gli header di I/O
" 32.3 I flag di formattazione e i manipolatori
di I/O .
32.4 I tipi del sistema di I/O
del C++ standard
32.5 Overloading degli operatori < e >
32.6 Le funzioni di I/O di utilizzo generale

'~

-~!uestocapitolo descrive la libreria di classi di I/O del


C++ standard. Come si detto nella Parte seconda, vengono comunemente utiliz-
zate due versioni della libreria di I/O del C++. La prima la libreria "vecchio
stile" che non definita dallo standard. La seconda rappresentata dal sistema di
I/O standard C++ a template. Poich la nuova libreria di I/O fondamentalmente
un sovrainsieme della precedente, in questo capitolo si parler unicamente delle
sue nuove funzionalit.
'.NOTA ".-.::. "- : Per una panoramica delle funzionalit di 110 del C++, con-
sultare i Capitoli 20 e 21.

32.1 Le classi di I/O

Il sistema di I/O del C++ standard si basa su una gerarchia piuttosto complessa di
classi template. Ecco le classi impieg!lte:

CLASSE SCOPO
basic_ios_ Fornisca operazioni di I/O di carattere generale

basi c_streambuf Supporto di basso livello per le operazioni di 110

basic_istream Supporto per le operazioni di input

(segue)
---- -----:.~-- -
----- ----
--:--...::..~
ns
LE CLASSI DI l/O_DEL C++ STANDARD

(continua)
template <class CharType, class Attr = char_traits<CharType>>
CLASSE class basic_ios: public ios_base
_SCOPO
basc_ostream
' Supporto per le operazioni di Input Qui CharType specifica il tipo dei caratteri (ad esempio char o wchar_t) e Attr
basi c_ iostream
Supporto per le operazioni di input specifica un tipo che descrive i suoi attributi. Il tipo generico char_traits una
basic_filebuf
classe di servizio che definisce gli attributi associati a un carattere.
Supporto di basso livello per le operazioni di I/O su file Come si detto nel Capitolo 20, la libreria di VO crea due specializzazioni
basic_ifstream delle gerarchie di classi template appena descritte: una per caratteri a 8 bit e una
Supporto per le operazioni di input da file
basi c_ofstream per caratteri estesi. Ecco l'elenco completo delle corrispondenze fra classi template,
Supporto per le operazioni di output su file
per caratteri e per caratteri estesi.
basic_fstream
Supporto per le operazioni di inpuVoutput su file
basi c_stringbuf
Supporto di basso livello per le operazioni di I/O su stringhe CLASSE TEMPLATE CLASSE PER CARATIERI CLASSE PER CARATTERI ESTESI
basi e_ i stringstream basic_ios ios wios
Supporto per le operazioni di input da stringhe
basi c_ostringstream basic_istream i stream wistream
Supporto per le operazioni di output su stringhe
basi c_stringstream basic_ostream ostream wostream
Supporto per le operazioni di inpuVoutput su stringhe

basic_iostream iostream wiostream


All'interno di questa gerarchia di classi di VO si situa anche la classe non-
basic_ifstream ifstream wi fstream
-template ios_base. Questa classe fornisce le definizioni di vari elementi del siste-
ma di VO. basic_ofstream ofstream wofstream
Il sistema di VO del linguaggio C++ si basa su due gerarchie di classi template basic_fstream fstream wfstream
correlate ma differenti. La prima deriva dalla classe di VO a basso livello chiama-
1:1 basic_strea~buf.. Questa classe fornisce le operazioni di input e output a basso basi e_ i stri ngstream i stri ngstream wi stri ngstream
livello e formsce Il supporto di base per l'intero sistema di I/O. Le classi basi c_ostringstream ostringstream wostringstream
basic_filebuf e basic_stringbuf derivano entrambe da basic_streambuf. Se non si
devono eseguire operazioni di VO molto avanzate, probabile che non si debba basi c_stri ngstream stringstream wstringstream
usare direttamente basic_streambuf o le sue sottoclassi. basi c_streambuf streambuf wstreambuf
La gerarchia di classi che viene normalmente utilizzata-deriva d~-b~sic ios.
Questa una- classe di I/O di alto livello che fornisce alle operazioni di VO su basic_filebuf fil ebuf wfilebuf

stream le funzionalit di formattazione, di controllo degli errori e le informazioni basi c_stringbuf stri ngbuf wstringbuf
sullo stato. La classe basic_ios viene utilizzata come base per varie classi deriva-
te, fra le quali basic_istream, basic_ostreal'Jl e basic_iostream. Queste classi sono Poich la maggior parte dei programmatori tilizza operazioni di VO a carat-
utilizzate rispet~ivamente per creare stream per operazioni di input, output e input/ teri, questi saranno i nomi pi utilizzati in questo capitolo. Pertanto quando si fa
output. In particolare, da basic_istream derivano le classi basic ifstream e riferimento alle classi di VO si indicher semplicemente il nome della classe per
basic_istringstream, da basic_ostream derivano le classi basic ~fstream e
caratteri piuttosto che il nome template interno. Ad esempio, in questo capitolo si
basic_ostringstream e da basic_iostream derivano le classi basi~ fstream e user il nome ios invece di basic_ios, istream invece di basic_istream e fstream
-ba~ic_stringstn:iar:i. Una classe base di basic_ios ios_base. Pertanto ~gni classe invece di basic fstream. Si ricordi sempre che esistono funzioni parallele per sTream
denvata da bas1c_1os ha accesso_anche ai membri di ios base.
di caratteri est;si che funzionano esattamente come quelle che verranno descritte.
Le cl~si_ di VO son~ parametrizzate per il tipo di car~teri su cui operano e per
______ le (arat~en_st1che associate a tali caratteri. Ad esempio ecco la specifica template
per bas1c_1os: -- -- - - - -
----------- ~-
- - - - - 780 C A P I T O_L_O _ 3 2
LE e LA s-s+-9-H-/-O -O E L e + + s T AN o A R D - 1s1-:-
32.2 Gli header di 1/0 posite funzioni chiamate manipolatori che possono ess~re incluse in un'espr~s
sione di UO. Questi manipolatori standard sono elencati nella seguente tabella.
Il sistema di UO del C++ standard utilizza vari header:

HEADER MANIPOLATORE SCOPO INPUT/OUTPUT


UTILIZZO
boolalpha Attiva il flag boolapha. Input/Output
<fstream> l/Osuflle
dee Attiva il flag dee. lnpuVOutput
<iomanip> Manipoliori di I/O .parametrizzati

<ios> endl Produce in output un carattere Output


Supporto di base per le operazioni di 110 di fine riga e svuota lo stream.
<iosfwd> Dichiarazioni forward usate dal sistema di 1/0 ends Produce in output un carattere nullo. Output
<iostream> I/O generale fixed Attiva il flag fixed. Output
<istream> Supporto di base per l'input f1 ush Svuota uno stream. Output
<ostream> Supporto di base per l'oUiput hex Attiva il ffag hex. Input/Output
<sstream> Stream basati su stringhe internal Attiva Il flag internal. Output
<streambuf> Supporto di I/O a basso livello left Attiva il flag left. Output

noboo 1a1 pha Disattiva il flag boolalpha. Input/Output


Molti di questi header sono utilizzati intername~t dal sistema di UO. In ge-
noshowbase Disattiva il flag showbase. Output
nere il programma include i soli header <iostream>, <fstream>, <sstream> o
<iomanip>. noshowpoint Output
Disattiva il flag showpoint.

noshowpos Disattiva il flag showpos. Output

noski pws Disattiva il flag skipws. Input


32.3 I flag di formattazione e i manipolatori di I/O
nounitbuf Disattiva nffag unltbuf. Output
A ogni stream associato un insieme di flag di formattazione che controllano il
modo-irrcui'Je informazioni vengono formattate. La classe ios_base dichiara ~no:u'.'.'.pp'.'._'.e'..'._rc'.:'.a'.:se:.__ _ _ _ __:D::isa::::tti::::"va::_:il:_::ff_:::ag:..:u:.:::ppe:.:.:.:.rcas--e._ _ _ _ _ _ _o_utp_u:-t--:-.:----:-:---. - - __ ---
un'enumerazione bitmask chiamata fmtflags in cui sono definiti i seguenti valori: oct Attiva il flagoct. lnpuVOutput

resetiosflags (fmtflags n Disattiva i flag specificati in f. lnpuVOutput


adjustfield basefield boolalpha dee
fixed right Attiva il flag righi. Output
floatfield hex internal
left oct right scientific Attiva il flag scientific. Output
scientific
show base showpoint showpos skipws setbase(int base) Imposta la base numerica. lnpuVOutput
unitbuf uppercase
setfill (int eh) Imposta il carattere di riempimento. Output

Questi valori sono utilizZ"ati per impostare o cancellare i flag di formattazione -set i osfl ags (fmtfl ags .f) Attiva i flag specificatt in f. Input/output
utilizzando funzioni come setf() e unsetf(). Per una descrizione dettagliata di que-
sti flag si consulti il Capitolo 20. setprecision (int p) Imposta le cifre di precisione. Output

Oltre a impostare o cancellare direttamente i flag di formjlttazi011e...Un.di_~ setw(int w) Imposta la larghezza del campo. Output
possibile modifica@J..Q"!fa!T!~tri_ di formattazione di uno stream impi~a~do ae_::
(se;.o
783
782 -G-A-P+-T-0 [ O 32

(conanua) I tipi pos_type e off_type


etti (normalmente interi) in grado di contene-
MANIPOLATORE SCOPO INPUT/OUTPUT i tipi pos_type e off_type cre~o o!fvamente la posizione e l'offset all'interno di
showbase Attiva il ffag showbase. Output re~~~~:e~:~~~e~~~t:~o~:niti da ics (e altre classi) e sono in ge~ere d~~lo
showpoi nt Attiva il flag showpoint. Output :sso tipo di streamoff o streampos (o dei loro equivalenti per caratten estesi .

showpos Attiva il flag showpos. Output

skipws Attiva il flag sklpws. Input 11 tipo openmode


unitbuf Attiva il flag unilbuf. Output Il tipo openmode definito da ios_base ~ desc~ve il modo in cui viene aperto un
uppercase Attiva il flag uppercase. Output file. Pu assumere uno o pi dei seguenti valon.

ws Salta gli spazi iniziali. Input Aggiunta alla fine del file. . .
app
Posizionamento alla fine del file per operazioni
ate
Per utilizzare un manipolatore dotato di parametri, si deve includere <iomanip>. di creazione. .
Apertura del file per operazioni binane.
binary
Apertura del file in input.
in
Apertura del file in output.
32.4 I tipi del sistema di 110 del C++ standard out
Cancellazione di un file.
trunc
Oltre al tipo fmtfla~s appena descritto, il sistema di I/O del C++ standard defini-
Questi valori possono essere combinati con l'operatore OR.
sce molti altri tipi.

I tipi streamsize e streamoff 11 tipo iostate


. , d. stream di IJO si tratta di
Un oggetto di tipo iostate descnvera lo stato i uno . b:
Un oggetto di tipo streamsize in grado di contenere il massimo numero di byte
un'enumerazione definita da ios_base che include i seguenti mem n.
che verranno trasferiti in un'operazione di I/O. In genere si tratta di una qualche
forma di intero. Un_o_g~tt~ ~j-~ipo streamoff in grado di contenere un valore che
indica-una posizione di offset all'interno di uno stream. In genere si tratta di una NOME SIGNIFICATO
qualcheforma di intero. Questi tipi sono definiti nell'header <ios> che viene in- Nessun errore.
goodbit
cluso automaticamente dal sistema di I/O.
Raggiunta la fine del file.
eofbit
Errore di VO non fatale.
I tipi streampos e wstreampos failbit

badbi t Errore dl VO fatale.


Un oggetto di tipo streampos in grado di contenere un valore che rappresenta una
posizione in uno stream di char. Il tipo wstreampos in grado di contenere un
valore che rappresenta una posizione in urro stream di wchar_t. Questi tipi sono
definiti nell'header <iosfwd> che viene incluso automaticamente dal sistema di I/O. . Il tipo seekdir
. . . do in cui verr esecruita un'operazione di accesso
~i:!~t~ :~~~~'.r~t~~~:::~~~~ deflnito in ios_bas~ e i valol'i consentiti s~n~ _ _ :-: ~
LE CLASSI [)..I l/G lJl:L '- T ~ ->' n .. - - .. -

784 CAPITOLO 32

beg Inizio del file. bad()


cur Posizione corrente. #include <iostrearn>
end Fine del file. bool bad() const;

La funzione bad() un membro di ios. . . .


La funzione bad() restituisce true quando nello stream associato s1 venficato
La classe failure un errore di I/O fatale, altrimenti restituisce false.
La funzione correlata rdstate().
La classe ios_base definisce il tipo per eccezioni failure che funge da classe base
per tutte le eccezioni che possono essere lanciate dal sistema di JJO. Tale classe
eredita exception (la classe standard per le eccezioni). La classe failure ha il se-
clear()
guente costruttore:
#include <iostream>
explicit failure(const string &str); void clear(iostate flags = goodbit);

Qui str un messaggio che descrive l'errore. Questo messaggio pu essere La funzione clear() un membro di ios.
ottenuto da un oggetto failure richiamando la sua funzione what(): La funzione clear() cancella i flag di stato associa~i .a uno s~eam. Se flags
goodbit (come nell'impostazione st~ndard). allora ?1ttl i flag d1 e:rore vengon~
virtual const char *what() const throw(); cancellati (riportati a O). Altrimenti ai flag d1 stato vien~ assegnato il valore spec1
ficato in flags.
La funzione correlata rdstate().

32.5 Overloading degli operatori < e >


eof()
Gli operatori < e/o > sono modificati tramite overloading nelle seguenti classi
rispetto a tutti i tipi di dati standard. #i nel ude <i ostream>
bool eof() const;
basi_istream
basic_ostream La funzione eof() un membro di ios. ..
La funzione eof() restituisce true quando si-raggiunge la fine del file d1 mput,
basic_iostream
altrimenti restituisce false.
Le funzioni correlate sono bad(), fail(), good(), rdstate() e clear().
Dunque ogni classe derivata da quelle elencate erediter questi operatori.

exceptions()
32.6 le funzioni di I/O di utilizzo generale
#include <iostream>
iostate exceptions() const;
La parte rimanente di questo capitolo descrive le funzioni di IJO di utilizzo aene-
rale fornite dal e++ standard. Come si detto, il sistema di JJO del C++ sta~dard
void exceptions(iostate flags) ;_""

si basa su una complessa gerarchia di classi template. Molti dei membri delle La funzione exceptions() un membro di ios. . . .
classi di basso livello non sono utilizzati nella programmazipne di applicazioni e La prima forma restituisce un oggetto iostate che.md1ca. quali flag hanno pro-
pertanto non verranno descritti. _____ .
vocato l'eccezione. La seconda forma imposta questi valon.
---- -
----=---:--::- - - -.
786 -- C A P I T O L O 3 2
LECITS sI DI I/ o DEL e.,. ;.-c;-i-r. ,, - n-;:;-c- -- !:9!__: __
- -- - -

La funzione correlata rdstate(). flush()


#include <iostream>
ostream &fl ush () ;
fa il O

#i nel ud-e <iostream> La funzione flush() un membro di ostream.


La funzione flush() provoca la scrittura fisica del buffer connesso allo stream
bool fail () const;
di output. La funzione restituisce l'indirizzo dello stream.
La funzione fail() un membro di ios. Le funzioni correlate sono put() e write{).
La_funzi~ne f~il9 restituisce true se sullo stream si verificato un errore di Il
O, altrtmenti restituisce false.
fstream(), ifstream() e ofstream()
Le funzioni correlate sono good(), eof(), bad(), clear() e rdstate().
lii nel ude <fstream>
fili() fstream();
explicit fstream(const char *filename,
ios::openmode mode= ios::in I ios::out);
#include <iostream>
char fill () const; ifstream();
explicit ifstream(const char *filename, ios::openmode mode=ios::in);
char fil 1{ char eh);

ofstream();
La funzione fili() un membro di ios. explicit ofstream(const char *filename,
Normalmente
. . quand0 si deve nemprre
un campo, viene utilizzato come carat- ios::openmode mode=ios::out I ios::trunc);
~~a!~e~~!Ille~to lo spazio. L~ funzion.e fill() consente di specificare un altro
cedente. di nemp1mento. La funzione restituisce il carattere di riempimento pre- Le funzioni fstream(), ifstream() e ofstream() sono i costruttori delle classi
fstream, ifstream e ofstream.
fill{).Per ottenere il carattere d'1nemp1mento
corrente basta usare la prima forma di Le versioni di fstream{), ifstream() e ofstream() che non richiedono parametri
creano uno stream che non associato ad alcun file. Questo stream pu successi-
Le funzioni correlate sono precision() e width(). vamente essere collegato a un file con open().
Le versioni di fstream(), ifstream() e ofstream() che accettano come primo
parametro il nome di un file sono quelle pi utilizzate nei programmi applicativi.
flagsQ _ . Anche se corretto aprire un file utilizzando la funzione open{), in genere si
preferisce usare i costruttori di fstream, ifstream e ofstream che aprono automati-
#include <iostream>
camente il file al momento in cui viene creato lo stream. Le funzioni costruttore
fmtfl ags fl ags () const;
fmtflags flags(fmtflags f); hanno gli stessi parametri e le stesse impostazioni standard della funzione open()
(per informazioni si consulti alla parte relativa a tale funzione). Ad esempio, ecco
t: fu~zione flags(~ un memb:o ~i ios (ereditato da ios_base).
att almp ma fo~a .d1 flags() restitmsce semplicemente i flao-0 di formattazione
il modo in cui viene normalmente aperto un file:

u ente ~soc1at1 allo stream. ifstream my~_ream("myfi 1e");


La seconda forma di flags(f imposta tutti i flag di formattazione associati a -~
Se per qualsiasi i;notivo il file non potesse essere _aperto, il valore della varia-
uno stream
modello d' b'secondo
. quanto . - I ic~to d a f . Quando si usa-questa versione, il
. - spec'fi
bile associata allo stream sar false. Pertanto sia che si apra il file con la funzione
stream:--A I ~t presente m[viene copiato nei flag di fonrtattazione associati allo
. n~ e..quest-a-vers1one-restituisce l'impostazione precedente costruttore sia che si usi una chiamata esplicit~a open(), sar n:_c_essario confer-
_ ~ f~nz1_9ni-correlate sono unsetf{) e setf{). -- - - - -
- - - - ----- ---- --- - - -.. - -
. __.:..:_ :..:. -; _-__:.
- - -~:--- - -----------:-:-:::~--=----------_..,,,,,,.,....
788 CAPITOLO 32 .---- --
. -.
LE CLASSI DI I/O DELC+-+ STAND-A-R-9- --789-

mare che il ~le. sia stato effettivamente aperto controllando il valore dello stream get(streambuf &bu~ legge i caratteri dallo stream di input all'oggetto streambuf.
Le funzioni correlate sono close() e open().
I caratteri vengono letti fino al codice di fine riga o fino alla fine del file. La
funzione restituisce l'indirizzo dello stream. Un eventuale carattere di fine riga
gcount() presente dello stream non verr estratto.
get(streambuf &buf, char delim) legge i caratteri dallo stream di input all'og-
#include <iostream> getto streambuf. I caratteri vengono letti fino al delimitatore o fino alla fine del
streamsize gcount() const; file. La funzione restituisce l'indirizzo dello stream. Un eventuale delimitatore
presente dello stream non verr estratto.
La funzione gcount() un membro di istream. Le funzioni correlate sono put(), read() e getline().
. ~ .~nzione gcount() restituisce il numero di caratteri letti dall'ulf
z1om di mput. ima opera-
le funzioni correlate sono get(), getline() e read(). getline()

#include <iostream>
getO istream &getline(char *buf, streamsize num);
istream &getline(char *buf, streamsize num, char delim);
#include <iostream>
1nt 9et0; La funzione getline() un membro di istream.
istream &get(char &eh): getliAe(char *buf, streamsize num) legge dei caratteri nell' array puntato da buf
~stream &get(char *buf, streamsize num); finch non :vengono letti num - I caratteri, finch non viene trovato un codice di
~stream &get(char *buf, streamsize num, char delim); fine riga o fino alla fine del file. L'array puntato da bufverr chiuso dal carattere
istream &get(streambuf &buf);
nullo. Se nella stringa viene rilevato il carattere di fine riga, questo verr estratto
istream &get{streambuf &buf, char delim);
ma non verr inserito in buf. La funzione restituisce l'indirizzo dello stream.
getline(char ..buf, streamsize num, char de/im) legge dei caratteri nell'array
La funzione get() un membro di istream. puntato da buffinch non vengono letti num - 1 caratteri, finch non viene incon-
In generale, get() legge i caratteri da uno stream di input La fio trato il delimitatore delim o fino alla fine del file. L' array puntato da buf verr
parametri d' t() I dal rma senza
i ge egge lo stream un singolo carattere e ne restituisce il val chiuso dal carattere nullo. Se nella stringa viene rilevato il delimitatore, questo
.gdie:(cha_r ~eh) l~gg~ ~Ilo stream un carattere e ne inserisce il valore in :~ verr estratto ma non verr inserito in buf. La funzione restituisce l'indirizzo dello
Qum rest1tu1sce l mdmzzo dello stream;- ----- stream.
fi ~:t(ch~r *buf, strea~size num) legge dei caratteri nell'array puntato da buf Le funzioni correlate sono get() e read().
fine : non ~engono letti num - I caratteri, finch non viene trovato un codice di
ne nga o mo alla fine del file. L'array puntato da buifverra' chi"uso dal
nullo. Se neIl a smnga viene
nlevato
. . carattere d' ti .
il carattere good()
P . 1 me nga, questo non verr
estratto. ertanto runarr nello stream fino ali' o erazione d" . .
funzione restituisce l'indirizzo dello stream. p i mput successiva. La #include <iostream>
get(char *bu~ streamsize num, char delim) legge dei caratteri nell'arra un- bool good() const;
tato da buffinche non venaono letti nu _ I ti , yp
il d r . d . "' m caratten, mche non viene mcontrato
e lillltatore elim o fino alla fine del file. L'array puntato da-buf verr chiuso La funzione good() un membro di ios.
dal caratte:e nul~o. Se nella stringa viene rilevato il delimitatore, questo non verr La funzione gdod() restitQis_~~ true se nello str~am non si verificato alcun
estratt~ e_nm~a ~e~lo stream fino all'operazione di input successiva. La funzio- errore di 1/0, altrimenti restituisce false.
ne restituisce I mdirizzo dello stream. . Le funzioni correlate sono bad()~ fail(), eof(), clear() e rdstate().

- ----- -.::....=:_:._- ___ _


790 CAPITOLO- 32 - - -----
LE e LA s s I D 1-ITO DEL e+ + s T ANDAR D 791
ignore()

#include <iostream> Il valore ios::binary fa.in modo che il file venga aperto per operazioni di UO
binarie. Normalmente il file viene aperto in modalit testo.
istream &ignore(streamsize num = 1, int delim = EOF);
Il valore ios::in specifica che il file utilizzabile per operazioni di input. Il
valore ios::out specifica che il file disponibile per operazioni di output. Tuttavia
La funzione ignore() un membro di istream.
la creazione di uno stream ifstream presuppone l'impiego di operazioni di input,
. ~funzione .ignor:O !eg~e ed e:imina i cai:atteri dallo stream di input. I carat- la creazione uno stream ofstream prevede operazioni di output e l'apertura di un
teri \en~ono letti ed ehmmat1 tinche non vengono letti num caratteri (normalmen- file con fstream prevede operazioni sia di input che di output.
te man e uguale a I) o finch non viene incontrato il carattere specificato da delim Il valore ios::trunc fa in modo che il contenuto di un file preesistente venga
(normalmente EOF). Se viene incontrato il delimitatore, questo verr rimosso distrutto; la lunghezza del file viene troncata a O.
dallo stream di input. La funzione restituisce l'indirizzo dello stream In ogni caso, se open() termina senza successo lo stream sar false. Pertant?,
Le funzioni correlate sono get() e getline().
prima di usare un file opportuno assicurarsi che l'operazione di apertura abbia
avuto successo.
open() Le funzioni correlate sono close(), fstream(), ifstream() e ofstream().

#i nel ude <fstream>


void fstream::open(const char *filename, peek()
ios::openmode mode= ios::in I ios:: out);
void ifstream::open(const char *filename, #include <iostream>
int peek(); .
ios::openmode mode= ios::in);'
voi d ofstream:: open (const char *fi lename,
ios::openmode mode= ios:: out I ios::trunc); La funzione peek() un membro di istream.
La funzione peek() restituisce il carattere successivo dello strearn oppure EOF
La funzione open() un membro di fstream, ifstream e ofstream. quando ci si trova alla fine del file. Questa funzione non estrae mai il carattere
La funzi~ne open() associa un file a uno stream. Quifilename il nome del dallo stream.
file che pu ~ncludere uno specificatore di percorso. La modalit di apertura del La funzione correlata get().
file detenrunata da mode che pu assumere uno o pi dei seguenti valori:

ios::app precision()
ios::ate
#include <iostream>
ios::binary
streamsize precision() const;
ios::in
streamsize precision(streamsize p);
ios::out
ios::trunc
La funzione precision() un membro di ios (ereditata da ios_base).
Normalmente quando si produce in output un valore in virgola mobile, ven-
Per combinare due o pi valori si usa l'operatore OR. gono utilizzate 6 cifre di precisione. Se si usa la seconda forma di precision() si
Con i.o~::app tutto I'outpu! verr agg~unto alla fine del file. Questo valore pu pu impiegare il valore di precisione specificato da p. Viene restituito il valore
essere ut~lizza~o. solo su file m grado d1 accettare operazioni di output. ios:-:ate precedente.
provoca 11 ?~s1z1ona~ento ~!a fine del file subito dopo l'apertura. Nonostante Le funzioni correlate sono width() e fili().
questo posm~namento iniziale alla fine del file, le operazioni di UO possono
comunque venficarsi in qualsiasi punto del file.

----- ------~--.
--------- - - LE e L A-S-sTlTrn-o -o E L e + + s TA ND ARD - 793~
792 CA P I T OL() - 3 2
Le funzioni correlate sono eof{), good(), bad(), clear(), setstate() e fail().
put()
#include <iostream> read()
ostream &put(char eh);
#include <iostream>
La funzione put() un membro di ostream istream &read(char *buf, streamsize num);
La funzione put{) scrive eh sullo stream di ;utput Restituisce 1 d" . d
stream. m mzzo ello
La funzione read() un membro di istream.
Le funzioni correlate sono write() e get(). La funzione read() legge num byte dallo stream di input e li inserisce nel
buffer puntato da buf Se prima di leggere num caratteri viene raggiunta la fine del
file, read() si ferma, imposta failbit e lascia nel buffer i caratteri disponibili (vedere
putback() gcount()). read() restituisce l'indirizzo dello stream.
Le funzioni correlate sono gcount(), readsome(), get(), getline() e write().
#include <iostream>
istream &putback(char eh);
readsome()
La funz~one putback() n
membro di istream.
La funz~one putback() restituisce eh allo stream di input #include <iostream>
La funzione correlata peek(). streamsize 'rea_dsome{char *buf, streamsize num);

La funzione readsome() un membro di istream.


rdstate() La funzione readsome() legge num byte dallo stream di input associato e li
inserisce nel buffer puntato da buf. Se lo stream contiene meno di num caratteri,
#i nel ude <i ostream> verranno letti i caratteri disponibili. readsome() restituisce il numero di caratteri
iostate rdstate() const; letti. La differenza fra read() e readsome() consiste nel fatto che qtiest'ultima non
imposta failbit quando legge meno di num caratteri.
La funz~one rdstate() un membro di ios. Le funzioni correlate sono gcount(), read() e write().
_C+~e~:s~~~: ~;~:~;:~~i:i:~:t~o stato d7llo stre~m. Il .sistema di I/O del
~treain attrvo.Tu stato corrente di uno p~ ogn~ operaz10ne. d1 I/O per ciascuno
iostate in cui sono definiti i seguenti fla~. eam contenuto m un oggetto di tipo seekg() e seekp()

#include <iostream>
istream &seekg(off_type offset, ios::seekdir ortgin)
NOME SIGNIFICATO
istream &seekg(pos_type posttion);
goodbit Nessun errore.
ostream &seekp(off_type offset, ios: :seekdir ortgin);
eofbit Raggiunta la fine del file.
ostream &seekp(pos_type posttion);
fa il bit Errore di UO non fatale.
-- ---- La funzione seekg() un membro di istream e la funzione seekP.0 un mem-
.badbi t Errore di 110 fat~le.
bro di ostream.
Nel sistema di I/O del C++, le funzioni seekQ(fe seekp() consentono di ese-
Questi fl.ag sono enumerati in ics (tramite.ios base) - - - _guire-operazioni. di accesso diretto ai file. In particolare il sistema di I/O C++
posta un bit di errore:-"""--- e~or~, r state() restituisce good5t, altrimenti im-:.
Se non s1 verificato alcun d - gestisce due puntatori per ogni file. Uno- il puntatore che specifica la posizione
- =-:::--::-- :-.- -
~-':.!'.
~~---
r:_ __ - .
LE CLASSI-DI 1/0 DEL C++ STAND~RD
795
794 CAPITOLO 32

in cui avverr la prossima operazione di input e laltro il puntatore che specifica minato stream. Non ha senso richiamar.e setf() se ~on in relazi.one a ~o strea:~:~
altre parole in C++ non esistono flag d1 formattaz1o?e globah. Ogm tream .,
dove avverr la prossima operazione di output. Ogni volta che viene eseguita
un'operazione di input. o di output, viene fatto avanzare il puntatore appropriato. sce in modo indipendente i propri flag di formattazione. . .. S2
La seconda versione di setf{) modifica i soli flag che sono imposran mfl~gsd.'
L-funzioni seekg() e seekp() consentono di accedere al file in modo non
sequenziale. I flag corrispondenti vengono prima azzerati e p011mpos
tati secondo
. quanto
. . .mfl 1-
cato da flags1. Anche se flags1 contiene altri flag, verranno modi:fitj,tl 1 soh ag
La versione a due parametri di seekg() sposta il puntatore di lettura a offset
byte dalla posizione specificata da origin. La versione a due parametri di seekp() specificati daflags2. . d . fl cr
Entrambe le versioni di setf() restituiscono l'impostazione precedente ei a.,
sposta il puntatore di scrittura a offset byte rispetto alla posizione specificata da
origin. Il parametro offset di tipo off_type, un tipo in grado di contenere il pi dello stream.
esteso \'alore valido che pu essere inserito in offset. Le funzioni correlate sono unsetf() e flags().
Il parametro origin di tipo seekdir, un'enumerazione contenente i seguenti
valori:
setstate()
ios::beg Posizionamento dall'inizio. ffeincl ude <iostream>
ios::cur Posizionamento dalla posizione corrente. void setstate(iostate flags) const;
ios::end Posizionamento dalla fine.
La funzione setstate() un membro di ics. . d' t0
La ,ersione a un solo parametro di seekg() e seekp() porta il puntatore del file La funzione setstate() imposta lo stato dello stream secondo quanto m ica
alla posizione specificata. Questo valore deve essere ottenuto precedentemente da flags. Per informazioni consultare rdstate().
con una chiamata, rispettivamente, a tellg() o tellp(). pos_type un tipo in grado di Le funzioni correlate sono clear() e rdstate().
contenere il pi esteso valore valido che pu essere inserito in position. Queste
funzioni restituiscono l'indirizzo dello stream.
Le funzioni correlate sono tellg() e tellp(). str()

setf() lii nel ude <sstream>


string str() const;
ffei nel ude <i ostream> void str(string &s)
fmtflags setf(fmtflags flags);
fmtflags setf(fmtflags flagsl, fmtflags flags2); La funzione str() un membro di stringstream, istringstream ~ ostring~tre::~
La prima forma della funzione str() restituisce un oggetto strmg con l att
La funzione setf() un membro di ios (ereditata da ios_base). contenuto dello stream di stringhe. d'1
La funzione setf() imposta i flag di formattazione di uno stream. Per informa- La seconda forma, elimina la stringa attualmente contenuta nello stream
zioni sui flag di formattazione consultare la prima parte di questo capitolo. stringhe e la sostituisce con la stringa puntata da s.
La prima versione di setf{) attiva i flag di formattazione specificati dajlags Le funzioni correlate sono get() e put().
(tutti gli altri flag non vengono modificati). Ad esempio, per attivare il flag showpos
per cout si pu usare la seguente istruzione:
stringstream(), istringstream() e ostringstream()
cout.setf(ios: :showpos);
--.11; nel ude <sstream>
expl icit stringstream(ios: :openmode mo de = las::in I ios::outh
Per impostare pi flag si pu utilizzare l'operatore OR.
explieit stringstream(const string &str, . .
importante-cmnp1~dere che una chiamata a setf()IarifeHmento a-un deter- _ _ _ ______ ios::openmode mode =-1os::in ios::out);
-----~~--=-=----=--:::-:::-:::-------
796 CAPITOLO 32

expl '. c'.t !stringstream(ios: :openmode mode=ios:: in);


sync_with_stdio()
expl~c1t ist~ingstream(const string str, ios::openmode mode=ios::in .
expl ~et ostri ngstre~( ios: :openmode mode= i os: :out); ).
#include <iostream>
expl1~t ostringstream(const string str, ios::openmode
mode=1 os:: out); bool sync_with_stdio(bool sync = true );

Una chiamata a sync_with_stdio() consente di utilizzare in sistema di I/O C


Le .funzioni s~ringstream(), istringstream() e ostringstream() sono rispetti insieme al sistema di I/O C++. Per disattivare la sincronizzazione di stdio si deve
mente I costrutton delle classi stringstream, istringstream e ostringstream Thva-
construct sr:e~~ th~t are tied to strings. . . ese passare a sync_with_stdio() il valore false. Viene restituita l'impostazione prece-
dente: true per la sincronizzazione e false per l'assenza di sincronizzazione. Nor-
no s~ ~~rs10m d1 stnngstream(), istringstream() e ostringstream() che specifica-
0
1 parametro openmode creano stream vu t Le
parametro t . . . . l" o I.

versioni che accettano un
malmente gli stream standard sono sincronizzati. Questa funzione affidabile
solo se viene richiamata prima di ogni altra operazione di 1/0.
s rzng m1zia 1zzano lo stream di strinahe .
Ecco un esempio che illustra l'uso di uno s;ea~ di stringhe.
tellg() e tellp()
Il Illustra l'uso di string streams.
#i nel ude <i ostream>
#include <iostream>
#i nel ude <sstream>
pos_type tellg();
using namespace std;
pos_type tellp(}:
int main()
{ La funzione tellg() un membro di istream e tellp() un membro di ostream.
stringstream s("Questa la str1nga in1z i a 1e. ") ;
Il sistema di I/OC++ gestisce due puntatori per ogni file. Uno il puntatore
che specifica la posizione in cui avverr la prossima operazione di input e l'altro
Il legge la stringa il puntatore che specifica dove avverr la prossima operazione di output. Ogni
string str = s.str(); volta che viene eseguita up.' operazione di input o di output, viene fatto avanzare il
cout str << endl; puntatore appropriato. Per determinare la posizione corrente del puntatore di let-
tura si usa tellg() mentre per determinare la posizione del puntatore di scrittura si
11 output sulla stringa usa tellp().
s << "Numeri: 11 << 10 << " " << 123 Z; pos_type un tipo in grado di contenere il massimo valore che pu essere
restituito da queste funzioni.
int i;
double d;
Il valore restituito da tellg{) e tellp() pu-e-ssereutilizzato come parametro per
s >> str >> ; >> d;
seekg() e seekp().
cout << str << 11 11 << i << 11 11 << d; Le funzioni correlate sono seekg() e seekp().

return O;
unsetf()

#include <iostream>
Ecco l'output.prodotto dal programma: voi d unsetf ( fmtfl ags flags);

Questa la stringa iniziale. La funzione unsetf() un m;mbro di ios (ereditata da ios_base).


Numeri : 10 123. 2
La funzioneunsetf() cancella uno o pi flag di formattazione. Vengono can-
cellati tutti i flag specificati daflags (tutti gli altri flag non vengono influenzati).
La funzione correlata str().__ . _!.!_funzion~correlate sono setf() e flags().
--- -- -
195- ~TOCQ32-=-:---~ -

widthO
Capitolo 33
#i nel ude <iostream>
streamsize width{) const; Le classi container STL
st reams i ze wi dth (s t reams i ze w) ;

La ~zione width() un membro di ios (ereditata da ios_base).


. La pruna forma di width() fornisce l'attuale larghezza del campo. La seconda
llll~osta la l:U-~ezza del campo. In questo caso w diviene la larghezza del campo
e viene restituita la precedente larghezza.
Le funzioni correlate sono precision() e fill().

writeO

#i ne 1ude <i os t ream> ~,~Juesto


""""' capitolo descrive le classi che implementano i
ostream &write(const char *buf, streamsize num); container definiti dalla libreria STL. I container sono quella parte della libreria
che fornisce un sistema di memorizzazione per altri oggetti. Oltre a fornire la
La funzione write() un membro di ostream. memoria necessaria per gli oggetti, i container definiscono il meccanismo di ac-
La funzion e wn1e () ~cnve
n~m .byte sullo stream di output associato al buffer cesso agli oggetti contenuti nel container. Pertanto i container sono disposith'i di
memorizzazione ad alto livello.
puntato da buf La funzione restituisce l'indirizzo deliostream
Le funzioni correlate sono read() e put{). . .NOJ.C::~:.;.~::::c.":-~ Per una panoramica della libreria STL, consultare il Capi-
tolo 24.

Nella descrizione dei container, verranno impiegate le seguenti convenzioni.


Quando si fa riferimento genericamente ai vari tipi di iteratori, nel volume vengo-
no impiegati i seguenti termini.

__ T~RMINE SIGNIFICATO
Bi Iter lteratore bidirezionale

Forlter lteratore in avanti

lniter Jteratore di input

Out Iter lteratore di output

Rand Iter lteratore di accesso diretto

Quando richiesta una funzione con predicato unario, si far riferimento a


UnPred. Quando necessario un predicato binario, verr utilizzato il tipo BinPred.
In un predicato binario gli argomenti sono sempre nello stesso ordine della fun-
zione che richiama il predicato. Per i predicati unari e binari, gli argomenti con-
tengo~o l!~alo:~~e~ tipo degli oggetti da memorizzare ~e.!_C_?~t_ain_::.
- - - - ---=..:.:.:...::_-:. ___ ---
- ------ ----------
-----aoo- CAPITOLO 33 . - - - - -

I,.e funzioni di confronto saranno indicate dal tipo Comp. const_iterator Un iteratore const.
Un'ultima annotazione: nella descrizione seguente, quando si dice che un reverse_iterator Un iteratore inverso.
iteratore punta alla fine di un container si intende che l'iteratore punta appena const_reverse_iterator Un iteratore const inverso.
value_type Il tipo di un valore contenuto in un cont.liner
dopo l'ultimo oggetto del container.
La libreria STL definisce le seguenti classi container. (equivale al tipo generico T).
allocator_type Il tipo dell'allocatore.
key_type II tipo di una chiave. . .
CONTAINER DESCRIZIONE HEADER RICHIESTO
key_compare n tipo di una funzione che confronta due chian.
bitset Un Insieme di bll <bitset> mapped_type Il tipo del valore contenuto in una mappa
(equivale al tipo generico T). .
deque Una coda a doppio concatenamento. <deque> Il tipo di una funzione che confronta. due v:tlon.
value_compare
li st Una lista lineare. <list> value_type Il tipo dei valori su cui si opera (eqm\":tle
al tipo generico T).
map Memorizza opple chiave/valore in cui a ogni chiave <map>
associato un solo valore. pointer Il tipo di un puntatore.
const_pointer Il tipo di un puntatore const.
multimap Memorizza coppie chiave/valore in cui a ogni chiave <map> Il tipo di un container.
possono essere associati due o pi valori. container_type
multi set Un Insieme in cui ogni elemento non <set>
necessariamente univoco.

priori ty_queue Una coda a priorit. <queue> La classe bitset


queue Una coda. <queue> . . . . . d. b't La sua specifo.:a templare :
La classe bitset supporta le operaz1om su ms1emi 1 1 .
set Un insieme in cui ogni elemento univoco. <set>

stack Uno stack. <stack> template <size_t <N>> class bitset;


vector Un array dinamico. <vector> Qui, N specifica la lunghezza del bitset in bit. La classe ha il seguente
costruttore:
Le prossime sezioni si occuperanno di ogni singolo container. Poich i container
sono impleI;Ilentati utilizzando classi template, _\lerranno utilizzati vari tipi fittizi. bitset( );
Nelle descrizioni seguenti, il tipo generico T rappresenta il tipo dei dati memoriz-
zati dal container. bitset(unsigned long bits);
Poich il nome dei tipi fittizi in una classe template arbitrario, le classi
container dichiarano delle versioni di questi tipi create con typedef. Questo rende explicit bitset(const string &s, size_t i =.O. size_t num =npos):
pi concreti i nomi dei tipi. Ecco i nomi typedef utilizzati dalle classi container.
. L da fonna costrUisce un
La prima forma costituisce un b1tset vuoto. a secon . L
size_type Un tipo intero equivalente pi o meno a size_t. b itset i cui bit veno-ono impostati secondo quanto specificato in bzrs.. a tde~ae
o d . La stnncra ev
reference L'indirizzo di un elemento. forma costruisce un bitset utilizzando la stnnga sa partire a .t. "'. O 1.
const_reference L'indirizzo const di un elemento.
contenere unicamente i numeri I.e O. Possono essere usate solo 1111111 s.size -
difference_type Pu rappresentare la differenza valori (il minore dei due). La costante npos un valore sufficientemente estes~----
fra due indirizzi. 'per descrivere la lunghezza massima di s.
iterator Un iteratore.
LE e LA.s si e o N T A i NE R s TL 803
802 CAPITOLO 33

Per bitset sono definiti gli operatori di I/O << e >>. (confinua)
La classe bitset contiene le seguenti funzioni membro. DESCRIZIONE
MEMBRO
Restituisce unumero di bit che Il bitset pu contenere.
size t size( ) const,
MEMBRO DESCRIZIONE
Restituisce lo stato del bit nella posizione i.
boOI test(size t ~ const,
bool any( ) const; Restituisce true se un bit nel bitset chiamante uguale a 1; altrimenti
restituisce false. Restituisce una stringa che contiene una rappresentazione del modello di bit
string to_string( ) const, del bitset chiamante.
size_type count( ) const; Restituisce il numero di bit a 1.
Converte il bitset chiamante in un unsigned long int
unsigned long to_ulong( ) const,
bitset<N> &fl ip( ) ; Inverte lo stato di tutti i bit contenuti nel bitsel chiamante e restituisce
*this.

bitset<N> &flip(size_t i); Inverte il bit nella posizione i nel bitset chiamante e restituisce ~th i s.
La classe deque
bool none( ) const; Restituisce true se nel bitset chiamante nessun bit uguale a 1. d atenamento La sua specifica
La classe deque supporta una coda a oppio eone .
bool operator !=(const bitset<N> Restituisce true se il bitset chiamante differisce da quello specificato
&op2) const; dall'operatore di destra, op2. template :
bool operator == (const bitset<N>
&op2) const;
Restituisce true se il bltset chiamante uguale a quello specificato
dall'operatore di destra, op2.
template _<class T, class Allocator =allocator<T>> class deque
. . . Il d La classe ha i seauenti
bitset<N> &operator &=(const Esegue i'ANO su ogni bit del bijset chiamante con i corrispondenti bit di op2 Qui T il tipo dei dati memonzzat1 ne a eque. "'
bi tset<N> &op2); e lascia il risultato nel bitset chiamante. Restituisce *thi s.
costruttori:
bi tset<N> &operator A= (const Esegue lo XOR su ogni bit del bitset chiamante con il bit corrispondente in
bi tset<N> &op2) ; op2 e lascia il risultato nel bitset chiamante. Restituisce *th i s.
explicit deque(const Allocator &a= Allocator() );
bitset<N> &operator l=(const bitset<N> &op2); Esegue !'OR di ogni bit del bitset chiamante con il bit corrispondente in op2 e
lascia Il risultato nel bitset chiamante. Restituisce *thi s. explicit deque(size_type num, const T ~val= T ( ),
bitsel<N> &operator -=( ) const; Inverte lo stato di tutti i bit del bltset chiamante e restituisce il risultato. const Allocator &a= Allocator( )),

bitset<N> &operator =(size_t 11Jl!!l); Esegue uno scorrimento a sinistra di ogni bit del bitset chiamante di num
posizioni e lascia il risultato nel bitset chiamante. Restituisce thi s.
deque(const deque<'f, Allocator> &ob);

bitset<N> &operator =(size_t num); Esegue uno scorrimento a destra di ogni bit del bitset chiamante di num template <class Inlter> deque(Inlter start, Inlter end,
posizioni e lascia Il risultato nel bilset chiamante. Restituisce *thi s.
const Allocator &a= Allocator( ));
reference operator [ ](size_type 1); Restituisce findirizzo del bit i del bttset chiamante.
L da forma costruisce una
bitset<N> &reset( ); Cancella tutti i bit del bltset chiamante e restituisce *thi s. La prima forma costruisce ~n~ deque vuota. e::=~~~ma costruisce una deque
deque contenen~e nu"'. element~ dd1. vablo~ val.;~\orma costruisce una deque che
bltset<N> &reset(size_t 1); Cancella Il bit nella posizione i del bitset chiamante e restituisce *thi s.
che contiene gh stessi elemenu 1 o a qu d
bitset<N> &set( ); Assegna 1 a tutti I bit del bltset chiamante e restituisce *thi s. contiene gli elementi dell'intervallo specificat? da start. e: e~nfronto
Pe! la classe deque sono definiti i seguenti oper~t~~ I e .
bitset<N> &set(size_t i, int val= 1); Assegna al bit nella posizione i il valore specificato da val nel bitset -
chiamante e restituisce "*thi s. Ogni valore diverso da zero contenuto in val
sar considerato uguale a 1. ==, <, <=, !=, >, >=
(segue)
La classe cleque contiene-le seguenti funzionLmem~ro: --=------.
-t-E--C-LA-SSI CONTAINER STL sos-

MEMBRO DESCRIZIONE (conttnua}

template <class In!ter> Assegna alla deque la sequenza definita da start ed end. DESCRIZIONE
MEMBRO
votd assign(lniter start, Initer end);
Aggiunge un elemento con il valore specificato da val alla fine
void push_back(const T &val);
void assign(size_type num, const T &val); Assegna a num elementi della deque il valore val. della deque.
Aggiunge un elemento con il valore specificato da val in
reference at(size type t); Restituisca l'indirizzo dell'elemento specificato da i. void push_front(const T &val);
const_reference at(size_type i) const; fronte alla deque.

Restituisca un iteratore inverso alla fine della deque.


reference back ( ) ; Restituisca l'indirizzo dell'ultimo elemento della deque. reverse i tera tor rbegi n( ) ;
const_reference back( ) const; const_reverse_iterator rbegin( ) const;
Restituisce un iteratore Inverso alrinizio della deque.
iterator begin( ); Restituisce un lteratore al primo elemento della deque. reverse iterator rend( ) ;
const_iterator begin( ) const; const_reverse_iterator rend( ) const;
cambia le dimensioni della deque secondo quanto
void clear( ); Rimuove tutti gli elementi dalla deque. void resize(size_type num, T val =T ( )) ; specificato da num. Se la deque deve essere anungata. allora
gli elementi con li valore specificato da val vengono aggiunti
bool empty( ) const; Restituisce true se la deque chiamante vuota altrimenti
restituisce false. alla fine.
Restituisca il numero di elementi attualmente presenti nella
const_iterator end( ) const; Restituisce un lteratore alla fine della deque. size_type size( ) const;
i terator end ( ) ; cleque.
Scambia gR elementi contenuti nella deque chiamante con
iterator erase(iterator i); Rimuove l'elemento puntato da i. Restituisce l'iteratore void swap(deque<T, Alloca tor> &ob);
quelli di ob.
dell'elemento eh~ segue quello rimosso.

i tera tor erase(iterator start, i tera tor end); Rimuove gli elementi dell'intervallo compreso fra start ed end.
Restituisce l'lteratore dell'elemento che segue l'ultimo
elemento rimosso. La classe list
reference front ( ) ; Restituisce l'indirizzo del primo elemento della deque.
const_reference front ( ) const; La classe tist supporta una lista. La sua specifica template :

allocator_type get_allocator( const; Restituisce l'allocatore della deque. template <class T, class Allocator = allocator<T>> class list
iterator insert(iterator i, Inserisce val immediatamente prima delrelemento specificato
const T &val); da i. Restituisce l'iteratore dell'elemento. Qui, T il tipo dei dati memorizzati nella lista. La classe ha il seguente
void insert(iterator i, size type num, Inserisce num copie di val immediatamente prima costruttore:
const_T _&va!); - dell'elemento specificato da i.

template <class Initer> Inserisce la sequenza definita da start ed end explicit list(const Allocator &a= Allocator() );
void nsert(iterotar i, immediatamente prima dell'elemento specificato da i.
In Iter start, In Iter end);
explicit list(size_type num, const T &val_= T ( ),
size_type max_size( ) const; Restituisce il numero massimo di elementi che la deque pu const Allocator &a= Allocator( ));
contenere.

reference operator[ ](size_type t); Restituisce l'indirizzo dell'elemento iesimo. list(const list<T, Allocator> &ob);
cons t reference
operator[ ] (size_type i) const;
template <class Inlter>list(Inlter start, lnlter end,
void pop_back( ); Rimuove l'ultimo elemento della deque. =
const Allocator &a Allocator( ));
void pop_front( ); Rimuove il primo elemento della deque.
La_nrima forma costruisce ~na lista vuta. La seconda forma costruisce ~na
- _ . , , , _ -- - truisce una hsta
(seg~e) -lista contenente num elementi d1 valor~ "!!_al. La _!ep;a iorma cos
-- ----==e--------..-------------------
-> - ___-----=-=------
--------- .

806 CAPITOLO 33

(continua}
che ~ontiene gli stessi elementi di ob. La uarta for . .
conuene gli elementi contenuti nell'intervalio s 'fi ma costruisce una lista che DESCRIZIONE
sono definiti i seguenti operatori di confronto: peci icato da start ed end. Per list MEMBRO
Unisce la lista ordinata contenuta in ob con la lista ordinata
void merge(list<T, Allocator> &ob); chiamante. Il risultato ordinato. Dopo l'unione, la lista
template <class Comp> contenuta in ob vuota. Nella seconda tonna, si pu
void merge(list<T, Allocator> &ob, specificare una funzione di confronto che determina la
=, <, <=, !=, >, >= Comp cmpfn); posizione relativa degli elementi.

La classe list contiene le seguenti funzioni membro. Rimuove l'ultimo elemento contenuto nella lista.
void pop_back( ) ;
Rimuove Il primo elemento contenuto nella lista.
void pop_front( );
MELSRO DESCRIZIONE Aggiunge un elemento con il valore specificato da val alla fine
void push_back(const T &val); della lista.
Tem;:::te <cl ass In Iter> Assegna alla lista la sequenza definita da start ed end.
v:id assign(lnlter start, In Iter end); Aggiunge un elemento con il valore specificato da val in
void push_front(const T &val); fronte alla lista.
Voic assign(size_type num, const T &val); Assegna alla lista num elementi di valore val.

I
Restituisce un iteratore inverso alla fine della lista.
refe-,,nce back( ) ; ~estituisce l'indirizzo dell'ultimo elemento contenuto nella ceverse i terator rbegi n ( ) ;
cons:_reference back( ) const; lista. const_reverse_iterator rbegin( ) const;
Rimuove dalla lista gli elementi con valore val.
iter::or begin( ) ; Restituisce un iteratore al primo elemento contenuto nella void remove(const T &val);

I. cons:_iterator begin( ) const; hsta. Rimuove gli elementi per i quali il predicato unario pr
template <class nPred> true.Restitulsce un lteratore lnvarso alla flne della lista.
voic :lear( ); Rimuove tutti gli elementi dalla lista. void remove_if(UnPred__pr);
1 boo1 "1'pty( ) const; Resti~isce true se la lista chiamante vuota altrimenti reverse iterator rend( ) ;
Restituisce un lteratore inverso all'inizio della lista.

restituisce false. const_reverse_i terator rend ( ) const;


Cambia le dimensioni della lista a quelle specificate da num.
iter;:or end( ) ; Restituisce un iteratore alla fine della lista. void resize(size_type num, T val = T ( )) ; Se la lista deve essere allungata, allora gli elementi con il
cons:_iterator end( ) const; valore specmcato da val vengono aggiunti alla fine.

i ter;_:or erase(iterator i); Ri~uove l'elemento puntato da i Restituisce l'iteratore


Inverte la lista chiamante.
del! elemento che segue quello rimosso. void reverse( ) ;
Restituisce il numero di elementi attualmente contenuti
iter;_:or erase(i tera tor start' itera tor end); Rim~".'3 gli ~lamenti dell'intervallo compreso fra start ed end size_type size( ) const; nella lista.
Rest1tu1sce I 1teratore dell'elemento che segue l'ultimo
elemento rimosso. - - Ordina la lista. La seconda forma ordina la lista utilizzando la
voi d sort ( ); funzione cf1 confronto cmpfn per determinare la posizione
refe-,,nce front( ) ; Restituisce l'indirizzo del primo elemento contenuto nella lista. template <class Comp> relativa degli elementi.
cons:_reference front( ) const; void sort(Comp cmpfn);
Il contenuto di ob viene inserito nella lista chiamante alla
a11 o:a tor_type get _a 11 ocator ( ) const; Restituisce l'allocatore della lista. void splice(iterator i, posizione puntata da i. Dopo l'operazione, ob vuota.
list<T, Allocator> &ob);
iter;_:or insert(iterator i, lns~rtsce .va! imm~iatamente prima dell'elemento specificato Celemento puntato da e/viene rimosso dalla lista ab e
const T &val = T( )) ; da 1. Rest1tu1sce r1teratore delrelemento. void splice(iterator i, memorizzato nella lista chiamante alla posizione puntata da i.
list<T, Allocator> &ob,
void ;nsert(iterator i, size_type num, Inserisce num copie di val immediatamente prima i tera tor el) ;
const T & val); dell'elemento specificato da ;_ Cintervallo definito da start ed end viene rimosso da ob e
void splice(iterator i, memorizzato nella lista chiamante a partir~ d_alla. posizione
temf ate <cl asi In Iter> ~nseris?e la sequenza definita da ;,;;1ed end list<T, Allocator> &.OB, puntata da i.
ioid insert(iterator i, ~".'.''.81ame.nte. prima dell'elemento specificato da ;_ !TERATOR start, i terator end);
lnlter start' In Iter end); Scambia gli elementi memorizzati nella lista chiamante con
void swap(list<T, Allocator> &ob); __guelli con_t~nuti in ob.
Restituisce il nu~ero massimo di elementi che ssono
essere contenuti nella lista. po __-(segue}

(segue)
808

(ctmtinua)

_MEMBRO DESCRIZIONE
MEMBRO
DESCRIZIONE
iterator begin( ) ; Restituisce un iteratore al primo elemento della mappa.
void unique( ) ; const_iterator begin( ) const;
ternplate <class inPred> Rimuove gli elementi duplicati dalla lista chiamante. La
void unique(BinPred pr); seconda forma determina runivocit uttllzzando pr.
void clear( ) ; Rimuove tutti gli elementi dalla mappa.

size_type count(const key_type &k) const; Restituisce il numero di volte che k presente nena mappa
(1 o O).
La classe map
bool empty( ) const; Restituisce true se la mappa chiamante vuota altrimenti
restituisce false.
La classe map supporta un container associativo in cui a ogni chiave univoca
corrisponde un valore. La sua specifica template la seguente: iterator end( ); Restituisce un iteratore alla fine della mappa
const_iterator end( ) const;

template <class Key, class T, class Comp = less<Key>, . pair<iterator, iterator>' RestituiscS una coppia di iteratori che puntano al PrirTI? e
equal range(const key_type &k); alrultimo elemento della mappa che contengono la chiave
class Allocator = allocator<T>> class map pair<const iterator, const_iterator> specificata.
equal Jange (const key_ type &k) const;
Qi, Key H tipo. delle chia".i, T il tipo dei valori memorizzati (mappati) e void erase(iterator t); Rimuove l'elemento puntato da i.
Comp una funzione che confronta due chiavi. La classe map ha i seguenti
costruttori: void erase(iterator start, iterator end); Rimuove gli elementi dell'intervallo compreso fra start ed end.

size_type erase(const. key_type &k); Rimuove dalla mappa gli elementi la cui chiave uguale al
explicit map(const Comp &cmpfn =Comp( ), valore k.
const Allocator &a =Allocator( ) ); iterator find(const key_type &k); Restituisce un tteratore alla chiave specificata. Se la chiave
const iterator find(const key_type &k) non viene trovata, allora restituisce un tteratore alla fine della
map(const map<Key, T, Comp, Allocator> &ob); cOnst; mappa.

allocator_type get_allocator( ) const; Restituisce l'allocatore della mappa


template <class Inlter:> map(Inlter start, Inlter end,
const Comp &cmpfn = Comp( ), iterator insert(iterator i, Inserisce val nella posizione o subito dopo l'elemento
const value_type &val); spacificato da /. Restituisce l'lleratore dell'elemento.
const Allocator &a =Allocator( ));
template <class Inlter> Inserisce un Intervallo di elementi.
void insert (Inlter start, In Iter end);
La prima forma costruisce una mappa vuota. La seconda forma costruisce una
mappa che contiene gli stessi elementi di ob. La terza forma costruisce una mappa pa i r<i tera tor, boo 1> Inserisce val nella mappa chiamante. Rastituisce r1teratore
insert(const value_type &val); dell'elemento. Celemento viene Inserito sol~ se ~on
che contiene gli .elementi nell'intervallo specificato da start ed end. L'eventuale presente nella mappa. Se l'elemento viene 1nsertto, ~ .
ordinamento della mappa determinato dalla funzione cmpfn. funzione restituisce pa i r<i tera tor' t rue>. Altnmenti
restituisce pair<iterator,< false>.
Per la classe map sono definiti i seguenti operatori di confronto:
key_compare key_comp( ) const; Restituisce 'oggetto funzione che confronta le chiavi.
==, <, <=, !=, >, >=
iterator lower_bound(const key_type &k); Restituisce un iteratore al primo elemento della mappa la cui
const iterator chiave maggiore o uguale a k.
lower_bound(const key_type &k) const;
Di seguito sono elencate le funzioni membro contenute in map. Nelle descri-
_ _ _ zioni, key_type il tipo della chiave e value_type rappresenta pair<Key, T>. size_type max_size( ) const: Restituisce il numero massimo di elementi che la mappa pu
contenere.

refernce operator[ ](const key_type &i); Restituisce !'Indirizzo delrelemento specificato daI. Se questo
---- -
elemento non esiste, viene inserito.

(segue)
------.
810 CA P I TOLO 3 3
~-~~----_-_-_---~~~~~~~---'~~L=E-_C~L_A_s_s_1_C~O_N_T_A_l_N--E_-_?._.~S_T_L~__..;..81...;.1-==--~-
(continua)
L'eventuale ordinamento della multimap determinato dalla funzione cmpfn.
MEMBRO Per la classe multimap sono definiti i seguenti operatori di confronto:
DESCRIZIONE
reverse_iterator rbegin( ) ;
const_reverse_iterator rbegin( ) const; Restituisce un iteratore inverso alla fine della mappa. =, <, <=, !=, >, >=
reverse_iterator rend( ) ;
const_reverse_iterator rend( ) const; Restituisce un iteratore inverso all'Inizio della mappa. Di seguito sono elencate le funzioni membro contenute in multimap. Nelle
size_type size( ) const;
descrizioni, key_type il tipo della chiave e value_type rappresenta pair<Key, T>.
Restituisce il numero di elementi attualmente presenti
nella mappa.
void swap(map<Key, T, Comp, MEMBRO DESCRIZIONE
Alloca tor> &ob); Scambia gli elementi contenuti nella mappa chiamante
quelli di ob. con iterator begin( ); Restituisce un tteratore al primo elemento della multimap.
iterator upper_bound(const key type &k); const_iterator begin( ) const;
const iterator - Ri:slilul~e un !teratore al primo elemento della mappa la cui
upper_bound(const key_type &k) const; chiave e maggiore o uguale a k. void clear( ) ; Rimuove tutti gli elementi dalla multimap.

value_compare value_comp( ) const; size_type count(const key_type &k) const; Restituisce il numero di volte che k si presenta nella
Resbluisce roggetto funzione che confronta 1valori. multimap.

bool empty( ) const; Restituisce true se la multlmap chiamanta vuota altrimenti


La classe multimap restituisce false.

iterator end( ); RestitUisce un tteratore alla fine della multimap.


La c~~s~ multimap supporta un container associativo.i cui valori vencrono mappati const_iterator end( ) const;
su c iav1 non necessariamente univoche. Ecco la sua specifica tem;late: pair<iterator, iterator> Restituisce una coppia di iteratori che puntano al primo e
equal range(const key type &k); all'ultimo elemento della multimap che contengono la chiave
pai r<const iterator, const i terator> specificala.
template <class Key, class T, class Comp less<Key> equal _range(const key_type &k) const;
class Allocator = allocator<T>> class mu,ltimap
voi d era se( i tera tor i); Rimuove l'elemento puntato da i.

Qu!, Key il ~ipo delle chiavi, T il tipo dei valori memorizzati (ma ati e voi d erase(iterator start, iterator end); Rimuove gli elementi dell'intervallo compreso fra start ed end.
~~:t~o~~a funzmne che confronta due chiavi. La classe multimap ha i s:ue~ti size_type erase(const key_type &k); Rimuove dalla multimap gi elementi la cui chiave uguale al
valore k.

=
explicit multimap(const Comp &cmpfn Comp( ),
iterator find(const key_type &k);
const iterator find(const key type &k)
RestifuT5celffiltera!ore alla chiave specfflc:ata. Se la chiave
non viene trovata, restituisce un iteratore alla fine della
const Allocator &a = Allocator( ) ); cOnst; - multimap.

allocator_type get_allocator( ) const; Restituisce l'allocatore della multimap.


multimap(const multimap<Key, T, Comp, Allocator> &ob);
iterator insert(i tera tor i, Inserisce val nella posizione o subito dopo l'elemento
const value_type &val); specificato da I. Restituisce l'ileratora delfelemento.
template <class Inlter> multimap(Inlter start, Inlter end,
const Comp &cmpfn Comp( ), = template <class lnlter> Inserisce un intervallo di elementi.
=
const Allocator &a Allocator( ));
void insert(lnlter start, In Iter end);

iterator insert(const value_type &val); lnserls_ce val nella multimap chiar..ante.


La prima forma costruisce una lf
una multimap che contiene cri' ?1u ima~ v~ota. La seconda forma costruisce Restituisce l'oggetto funzione che confronta le cnia'li.
multimap che co ti r~: stess1.element~_d1 ob. La terza forma costruisce una -
n ene g 1 e em~n~ ~~ll'intervallo specifica10da-sra11 ed e11zt;--
___
-.:.::.:..:_- :.______ _
- - --. - - LE e LA s s I e o N T A l'N ER-s T l. 813

La prima forma costruisce un multiset vuoto. La seconda forma costruisce un


_DESCRIZIONE multiset che contiene gli stessi elementi di ob. La terza forma costruisce un multiset
i~,,,. lower bound(const key_type &k); che contiene gli elementi dell'intervallo specificato da start ed end. L'eventuale
cor~!_.::2rator- Reslit~isce un ileratore al primo elemento della multimap
cui chiave maggiore o uguale a k.
!;- ordinamento del multiset determinato dalla funzione cmpfn.
: _,._bound (const key_type &k) const;
Per la classe multiset sono definiti i seguenti operatori di confronto:
SiZ!-_:_.-:e max_size( ) const;
Restituisce il numero massimo di elementi che osso
essere contenuti nella multimap. P no ==. <, <=, !=, >, >=
~::;:-A_fterat?r rbegin( );
. --~erse_1terator rbegin( ) const; Restituisce un iteratore inverso alla fine della multimap.
Di seguito sono elencate le funzioni membro contenute in multiset. Nelle de-
re::-~_fterator rend( ); scrizioni, sia key_type che value_type sono typedef per Key.
cor,_,_.-:...,erse_iterator rend( ) const; Restituisce un iteratore Inverso alrinizio della multirnap.

siz~_tr..e size( ) const


muitr-;a:c ' Restituisce il numero di elementi attualmente presenti netta MEMBRO DESCRIZIONE
iterator begin{ ); Restituisce un lteratore al primo elemento del ITIJltiset.
voi~ !.:!!o{mul.timap<Key, T, Comp,
const_iterator begin( ) const;
j'locator> &ob); Sca~~a gti eternanti contenuti nella multirnap chiamante con
quelh d1 ob.
void clear( ); Rimuove tutti gli elementi dal multiset.
it~r~t~r upper bound(const key type &k);
con~t_ ::era tor - R~stiruisce un iteratore al primo elemento della multima la
cui chiave maggiore o uguale a k. P size_type count(const key_type &k) const; Restituisce il numero di volte che k presente nel multiset.
"::>"hr_bound(const key_type &k) const;
boo 1 empty { ) cons t ; Restituisce true se il multiset chiamante vuoto altrimenti
val,~_:::ir.are value_comp( ) consti
Restituisce l'oggetto funzione che confronta i valori. restituisce false.

itera tor end( ) ; Restituisce un lteratore alla fine del multisel


const_iterator end( ) const;
La classe multiset
pai r<i tera tor, itera tor> Restituisce una coppia di iteratori che puntano al primo e
La classe multiset support equal_range(const key_type &k) const; all'ultimo elemento del multiset che contengono la chiave
sua specifica template: a un ms1eme contenente chiavi non univoche. Ecco la speclfteata.

void erase(iterator t); Rimuove l'elemento puntato da i.

template <class Ky, class Comp less<Key>, void erase(iterator start, iterator end); Rimuove gli elementi dell'Intervallo compreso fra start ed end.
- class Allocator = allocator<Key>> class multiset size_type erase(const key_type &k); Rimuove dal multiset gli elementi la cui chiave uguale al
valore k. -- - - -- --
Qui Key il tipo delle chiavi e e , f
La classe h . omp e una unzione che confronta due chiavi iterator find(const key_type &k) const; Restituisce un iteratore alla chiave specificata. Se la chiave
a 1 seguenti costrnttori: non viene trovata, restituisce un tteratore alla fine del multiset.

explicit multiset(const Comp &cmpfn =Comp( ), allocator_type get_allocator( ) const; Restituisce 1'.allocatore del multisel

const Allocator &a =Allocator( ) ); itera tor i nsert (i tera tor i, Inserisce val nella posizione o subito dopo relemento
const value_type &val); spaclfteato da I. Restlruisce l'lteratore delretemento.
multiset(const multiset<Key, Comp, Allocator> &ob); template <class Inlter> Inserisce un Intervallo di elementi.
_void tnsert (In Iter start, In Iter end);
template <class Initer> multiset(Initer start, Initer end iterator insert(const value_type &val); Inserisce val nel m~ifiset chiamante. Restttulsce l'lteratore
const Comp &cmpfa = Comp( ), ' dell'elemento.
--=- __ const Allocator &a= Allocateir( )); e)!_c.ompare key_comp( ) const; Restituisce l'oggetto \unzione che confronta le chiavi.

- - - - - - --=--==-: ~-
--------
814 :.!PITOLO 33 __
LE CLASSI CONTAINER STL 815
t::::tm:Ja)

Il primo costruttore priority_queue() crea una coda a priorit vuota. Il secondo


MBIBRO crea una coda a priorit che contiene gli elementi specificati dall'intervallo defi-
DESCRIZIONE
iterator ~::i.er_bound(const key_type &k) nito da start ed end. Nonnahnente priority_queue impiega come container un vector.
const; Restituisce un tteratore al primo elemento del multiset la .
chiave maggiore o uguale a k. cui In alternativa si pu usare come container una deque. Il container contenuto in
size_ty;l:! iw:_size( ) const; un oggetto protetto chiamato e di tipo Container. La classe priority_queue contie-
Restituisce il numero massimo di elementi che il multiset p . ne le seguenti funzioni membro.
contenere. uo
reverse_i~e-ator
rbegin( );
=st_re~..e_iterator rbegin( ) const; Restituisce un fteratore inverso alla fine del multisel
MEMBRO DESCRIZIONE
l".!Yel"Se_i~tor rend( );
bool empty( ) const; Restituisce true se la coda a priorit vuota altrimenti restituisce lalse.
=st_re~_iterator rend( } const; Restituisce un ileratore inverso alrinizio del multisel

s:::e_tYPf' sfze ( } cons t; void pop( ) ; Rimuove Qprimo elemento della coda a priorit.
Restitui~ Il numero di elementi attualmente presenti
nel mult1sel void push(const T &val); Aggiunge un elemento alla coda a priorit.
''~id 5""1'.,::U!ti set<Key, Comp,
A, :r.ator> &ob); Sca~bia gli elementi contenuti nel multiset chiamante con size_type size( ) const; Restituisce nnumero di elementi attualmente presenti nella coda a priorit.
quelh di ob.
i:.?rator im:er_bouna(const ke t &k) const value_type &top( ) const; Restituisce rindirizzo dell'elemento con la priorit pi elevata. ~elemento non
conS"-.; Y_ ype
R~stituisce un !teratore al primo elemento del multiset la cui viene rimosso.
chiave maggiore o uguale a k.
" 1 ""'-'~ value_comp( ) const;
Restituisce l'oggetto tu~zione che confronta i valori.
La classe queue
la classe priority_queue La classe queue supporta una coda semplice. Ecco la sua specifica template:
La classe priority queue sup ort . ;:.
templare la se;ente: p a una coda semplice a priorit. La sua specifica template <class T, class Container deque<T>> class queue

Qui, T il tipo dei dati memorizzati e Container il tipo del container utilizza-
telllliate <class T, class Container vector<T> to per la coda. La classe ha il seguente costruttore:
cIass C~mp = less<Container::val~e_type>>
c1ass pnonty_queue explicit quemon~Lontainer &cnt = Container( ));
Qui_. T il tipo dei dati da me . , . .
utilizzato per contenere la coda ~nonzzare._ Container e il tipo del container Il costruttore queue() crea una coda vuota. Nonnalmente queue utilizza come
~ermina quando un b de omp specifica la funzione di confronto che container una deque ma una queue pu essere consultata solo in modo FIFO (First
mem ro elh coda ha
;ii.'tro. La classe priority queue h . ' . una pnont !Il!enore
" rispetto a un In First Out). Come container di una coda si pu usare anche una lista. Il container
- a 1 seguenti costruttori: contenuto in un oggetto protetto chiamato e di tipo Container.
Per queue sono definiti i seguenti operatori di confronto:
expli.'it priority q (
- u~ue const Comp &cmpfn = Comp( ), .
Contamer &cnt = (\)ntainer( )); ==, <, <=, !=, >, >=
ternp!are <class I It -
ne er> pnonty _queue(Inlter start, Inlter end La classe queue contiene le seguenti funzioni membro.
const . omp &cmpjri = Comp{ ),
Contamer &.f!lJ. = ~,....._,tai.oer(_));___ _

------ --=-----=-:_. -

---- - ----
816
LE C1..A sSiC O NTAI NE R S TL--817

MEMBRO DESCRIZIONE
va 1ue type &back ( ) ;
MEMBRO DESCRIZIONE
Restituisce l'indirizzo dell'ultimo elemento della coda.
const- nlue_type &back( ) const; iterator begin( ) ; Restituisce un lteratore al primo elemento dell'insieme.
const_iterator begin( ) const;
bool empty( ) const;
Restituisce true se la coda chiamante vuota allrlmenti restituisce false.
void clear( ); Rimuova tutti gli elementi dall'Insieme.
va 1ue type &front ( ) ;
Restituisce l'indirizzo del primo elemento della coda.
const-value_type &front( ) const; size_type count(const key_type &k) const; Restituisce nnumero di volte che k si presenta nell'Insieme.
void pop(); Rimuove Il primo elemento della coda. bool empty( ) const; Restituisce true se l'insieme chiamante vuoto altrimenti
restituisce false.
void push(const T &val);
Aggiunge alla fine della coda un elemento con il valore specificato da val.
const iterator end( ) const; Restituisce un lteratore alla fine dell'insieme.
size_type size( ) const; i terator end( ) ;
Restituisce nnumero di elementi attualmente presenti nella coda.
pair<iterator,. iterator> Restituisce una coppia di iteratori che puntano al pri~o e
equal_range(const key_type &k) const; all'ultimo elemento dell'insieme che contengono la chiave
La classe set specificala.

void erase(iterator i); Rimuova l'elemento puntato da L


La classe set supporta un insieme di chiavi univoche. Ecco la sua specifica template:
void erase(iterator start' i terator end); Rimuove gli elementi dell'Intervallo compreso fra start ed end.
template <class Key, class Comp less<Key>, size_type erase_(const key_type &k); Rimuove dall'insieme gli elementi la cui ch~ve ugual? al
class Allocator =allocator<Key>> ~l.ass set valore k. La funzione restituisce il numero d1 elementi nmossl.

iterator find(const key_type &k) const; Restituisce un iteratore alla chiave specificata. Se la.chiave
Qui Key il tipo delle chiavi e Comp una funzione che confronta due chiavi. non viene trovata, restituisce un tteratore alla fine dell insieme.
La classe set ha i seguenti costruttori:
allocator_type get_allocator( ) const; Restituisce l'allocatore delrinsieme.

explicit set(const Comp &cmpfn =Comp( ), itera tor insert(iterator i, Inserisce val nella posizione o subito dopo l'eleme~to ..
const value_type &val); specificato da i. Gli elementi duplicati non vengono inseriti.
const Allocator &a =Allocator( ) ); Restituisce l'ileratore dell'elemento.

set(const set<Key, Comp, Allocator> &ob); template <class Inlter> Inserisce un intervallo di elementi. Gli elementi duplicati non
void insert(Inlter start, In Iter end); vengono inseriti.

template <class Inlter> set(Initer stari; -Initer end, pair<iterator, bool> Inserisce val nell'insieme chiamante. Restituisce l'iteratore
insert(const value_type &val); dell'elemento. J:elemento viene inserito solo. se non ~si.sie
const Comp &cmpfn =Comp( ), ancora Se l'elemento viene inserito, la funzione restituisce
const Allocator &a =Allocator( )); pai r<i tera tor, true>. Altrimenti restituisce
pair<iterator,< false>.

La prima forma costruisce un insieme vuoto. La seconda forma costruisce un iterator lower_bound(const key_type &k) Restituisce un iteratore al primo elemento delrinsieme la cui
const; chiave maggiore o uguale a k.
insieme che contiene gli stessi elementi di ob. La terza forma costruisce un insie-
me che contiene gli elementi dell'intervallo specificato da start ed end. L'even- key compare key_comp( ) const; Resiitulsce l'oggetto funzione che confronta le chiavi.
tuale ordinamento dell'insieme determinato dalla presenza della funzione cmpfn. size_type max_size( ) const; Restituisce il numero massimo di elementi che possono
Per la classe set sono definiti i seguenti operatori di confronto: essere contenuti nell'insieme.

=, <, <=. !=, >, >=___ _ reverse iterator rbegin( ) ; Restituisce un lteratore. inverso alla fine dell'insieme.
const_reverse_iterator rbegin( ) const;

La classe set contiene le seguenti funzioni membro: (segue)


LE e LA s s I e o N T ArN-E-R- S-+--b--- 81~-- -----~-- ~ -
818 CAPITOLO 33

DESCRIZIONE
(ccntinua)
Restituisce true se lo stack chiamante vuoto altrimenti restituisce false.
bool e111PtY( ) const-;
MEMBRO DESCRIZIONE RimUOVB la cima deHo stack, ovvero rultimo elemento del container.
void pop( ) ;
reverse_iterator rend( ) ; Restituisce un ileralore Inverso alrinizio dell'insieme.
const_reverse_iterator rend( ) const; Inserisce (push) un elemento alla fine dello stack. Cultimo
void push(const T &val); elemento del container rappresenta la cima dello stack.
size_type size( ) const; Res~u!sce il numero di elementi attualmente presenti
nell'1ns1eme. Restituisce n numero di elementi attualmente presenti nello stack.
size_type size( ) const;
Restituisce J'lnd'llizzo della cima dello stack, ovvero dell'ultimo elemento del
void swap(set<Key, Comp,Allocator> &ob); Scambia gli elementi contenuti nell'insieme chiama te
quelli di ob. n con value_type &top( ) ; container. Celemento non viene rimosso.
cont value_type &top( ) const;
iterator upper_bound(const key type &k) Restituisce un ileratore al primo elemento dell'insieme I8 .
const; - chiave maggiore o uguale a k. CUI

value_compare value co111~( ) const; Restituisce l'oggetto funzione che confronta i valn.
La classe vector
La classe vector supporta un array dinamico. Ecco la sua specifica template.

La lasse stack template <class T, class Allocator = allocator<T>> class vector


La classe stack supporta uno stack. La sua specifica template la seguente: Qui T il tipo dei dati memorizzati e Allocator specifica l' allocatore. La classe
template <class T, class Container deque<T>> ciass stack ha i seguenti costruttori.

Qui T il tipo dei dati memorizzati e Cont . 1 . explicit vector(const Allocator &a= Allocator() );
to per contenere lo stack La classe ha 11 seguente
amercostruttore:
I tipo del container utilizza-
explicit vector(size_type num, const T &val= T ( ),
const Allocator &a = Allocator( ));
explicit stack(const Container &cnt =Container());
vector(const vector<T, llocator> &ob);
t . k orr;:a men~e come _container vie-
Il costruttore stack() crea uno stack vuoto N 1
ne utilizzata una deque ma l'accesso a u
(Last In First Out) c . no s ac pu avvemre solo m modo LIFO template <class Inlter> vector(Inlter start, Inlter end,
. . ome contamer per lo stack si pu anch tT - - - --- ------
o una lista:- Il container contenuto in un b e u ~ izzare un vettore const Allocator &a;: Allocator( ));
Container. mem ro protetto chiamato e e di tipo
La prima forma costruisce un vettore vuoto. La seconda costruisce un vettore
Per stack sono definiti i seguenti operatori di confronto: che contiene num elementi di valore val. La terza forma costruisce un vettore che
contiene gli stessi elementi di ob. La quarta forma costruisce un vettore che con-
==, <, <=, !=, >, >= tiene gli elementi dell'intervallo specificato da start ed end.
Per la classe vector sono definiti i seguenti operatori di confronto.
La classe stack contiene le seguenti funzioni membro.
= <, <;:, !;:, >, >;:
La classe vector contiene le seguenti funzioni membro.

- ---- - - - -
LE CLASSI C-0-f<trll:t N"E R-S TL 821
820 CAPITOL0__ 33

(conUnua)
MEMBRO DESCRIZIONE
Assegna al vettore la sequenza definita da start ed end.
DESCRIZIONE
template <class Inlter> MEMBRO
.voi d assign(Inlfer start In Iter end); Rimuove fultimo elemenlo del vettore.
void pop_back( ) ;
void assign(size_type num, const T &val); Assegna al vettore num elementi di valore val. Aggiunge un elemento con Il valore specificatO da .a alla fine
void push_back(const T &val); del vettore.
reference at(size_type i); Restituisce l'indirizzo dell'elemento specificato da i.
const_reference at(size_type i) const; Restituisce un tteratore Inverso alla fine del vettore.
reverse iterator rbegin( ) ;
reference back( ) ; Restituisce l'indirizzo dell'ultimo elemento del vettore. const_reverse_iterator rbegin( )" const;
const_reference back( ) const; Restituisce un tteratore inverso all'inizio del vettore.
reverse _i tera tor rend ( );
itera tor begin( ) ; Restituisce un iteratore al primo elemento del vettore. const_reverse_iterator rend( ) const;
const_iterator begin( ) const; Imposta la capacil del vettore in modo cha sia ~
void reserve(siZe_type num); uguale a num.
size_type capacity( ) const; Reslituisc~ la capacil attuale del vettore. Questo Hnumero
di elementi che pu conlenere prima di dover allocare Cambia le dimensioni del vettore a quelle specilicale da num.
ulteriore memoria. void resize(size_type num, T val =T ( )); Se Hvettore deve essere allungato. allora gn elemeoti
contenenti il velare specificato da l'ai vengono ag;i<Jnti
void clear( ) ; Rimuove tutti gli elementi dal vettore. alla fine.
bool empty( ) const; Res.tit~isci-1rue se nvettore chiamante vuolo altrimenti Restituisce Hnumero di elementi attualmente presenti nel
resttu1SCe false. size_type size( ) const; vettore.
itera tor end( ) ; Restituisce un iteratore alla fine del vettore. Scambia gli elementi contenuti nel vettore chiamam2 con
const_iterator end( ) const; void swap(vector<T, !locato,;. &ob); quelfldob.
iterator erase(iterator i). Rimuove l'elemento puntato da i. Restituisce l'iteratore
del'elemento che segue quello rimo~.

iterator erase(iterator start, iterator end); Rim~o~e gli ~lamenti dell'intervallo compreso fra start ed end La libreria STL contiene anche una specializzazione di vector per valori
Restitmsce.11teratore dell'elemento che segue l'ultimo
elemento nmosso. booleani che include tutte le funzionalit di vector ma vi aggiunge due nuovi
reference front( ) ; Restituisce l'indirizzo del primo elemento del vettore. membri.
const_reference front( ) const;
Inverte tutti I bit del vettore.
allocator_type get_allocator( ) const; Restituisce rallocatore del vettore. void flip( );
Scambia I bit specificati da I e j.
iterator inse~t(iterato.r ! , const T &val); :elnRsce .val im~ialamente prima dell'elemento specificato static void swap(reference i, reference j);
est1luisce I 1teratore dell'elemento.

void insert(iterator i, size_type num, lns~risce num copie di val immedalamente prima
const T & val); dell elemento specificalo da ;,

template <class lnlter> ~nserisce la sequenza definila da start ed end


void insert{iterator i, Inlter start, immedialamente prima delrelemento specificato da ;.
In Iter end) ;

size_type max_size( l const; Restituisce Il numero massimo di elementi che Il vettore pu


contenere.

reference operator[ ] (size type i) const Restituisce l'Indirizzo dell'elemento specificato da i.


const_reference operator[ ] (size type i)
const; -

(segue)
: Capitolo 34

Gli algoritmi STL

Ouesto capitolo descrive gli algoritmi definiti dalla li-


breria S1L Questi algoritmi operano sui container tramite gli iteratoci. Tutti gli
algoritmi sono funzioni template. Ecco i nomi dei tipi generici utilizzati dagli
algoritmi.

NOME GENERICO RAPRESENTA


Bilter Un lteratore bidireiionale

Forlter Un lteratore in avanti

Inlter Un tteratore di input

Outlter Un tteratore di output

Rand Iter Un iteratore di accesso diretto

Un tipo di dati
------
Size Un tipo intero

Fune Un tipo di funzione

Genera tor Una funzione che genera oggetti

BinPred Un precficato binario

UnPred Un predicato unario

Comp Una funzione di confronto

--.:.=...._:_-.:._ __ _
GTIAl.-G-0 R1 T MI S T L __825
824 CAPITOLO 34

adjacent_find() L'algoritmo copy_backward() uguale a copy() tranne per il fatto che trasferi-
sce gli elementi partendo dalla fine della sequenza.
template <class Forlter>
Foriter adjacent find(Foriter start, Forlter end);
temp 1ate <cl ass Fo;Iter, c.1 ass BinPred>
Foriter adjacent_find(Forlter start, Foriter end, BinPred pfn);
template <class Initer, class T>
d const T &val);
size_t count{Inlter start, In It er en
L'algoritmo adjacent_find{) ricerca un elemento adiacente corrispondente nella
sequenza specificata da start ed end e restituisce un iteratore che punta al primo
L'algoritmo count() restituisce il numero di elementi contenuti nella. sequenza
elemento. Se non trova alcuna coppia adiacente, restituisce end. La prima versio-
ne ricerca elementi equivalenti. La seconda versione consente di specificare un . compresa fra start ed end che corrispondono al valore val.
metodo per determinare gli elementi corrispondenti.
count_if{)
biryary_search()
template <class Initer, class nPred>
template <class Foriter, class T> size_t count{Initer start, Inlter end, UnPred pfn);
bool binary_search(Forlter start, Foriter end, const T &val);
template <class Forlter, class T, class Comp> L'algoritmo count_if() restituisce il numero d~ elementi_d~lla sequenza com-
bool binary_search(Forlter start, Forlter end, c?.nst T &val, presa fra start ed end per i quali il predicato unano pfn rest1tu1sce true.
Comp cmpfn);

L'algoritmo binary_search{) esegue una ricerca binaria su una sequenza ordi- equal()
nata che inizia da start e termina in end per trovare il valore specificato da val.
tem late <class Initerl, class Initer2>
L'algoritmo restituisce true se val viene trovato e false altrimenti. La prima ver- b~ol equal(Inlterl start!, Inlterl endl, Init:r2 st:rt2);
sione confronta gli elementi contenuti nella sequenza specificata. La seconda ver- templ ate <cl ass Inlterl cl ass Inlter2 cl ass BrnPred
sione consente di specificare una funzione di confronto. bool equal (Initerl startl Initerl endl' Initer2 start2,
BinPred pfn);

copy() . --- --;;- .--i:Intervallo


L'algoritmo equal() determi_na quando due m::i~!~ s~~~z~";:~ta da start2.
templ ate <cl ass Inlter, cl ass Outiter~ determinato da starti ed endl vien~ confronti~tat~cce true alqtn"menti restituisce false.
.. - al' r algontmo res ms .
Outiter copy(Initer start, In Iter end, Outiter result); Se gh mterval1I sono ugu 1, d' "fi n predicato binario che determina
La seconda forma consente l spec1 icare u .
L'algoritmo copy{) copia la sequenza che inizia da start e termina in end inse- l'uguaglianza di due elementi.
rendo il risultato nella sequenza puntata da result. L'algoritmo restituisce un
iteratore che punta alla fine della sequenza risultante. L'intervallo da copiare non
deve sovrapporsi a result. equal_range{)

- template <class Forlter, class T> Foriter end,


<Foriter' Forlter> equal _range{Foriter start
copy_backward() Pai r ----- const T &val);
template <class Foriter, class T, class Comp> d
templ ate <cl ass Bi Iterl, cl ass Bi Iter2>- Foriter>~al range(Forlter start Forlter en
--=c BiLter2 copy_backward (Bi Iterl start, Bi Iteri.end .. Bi Iter2 re sul t); pair<Foriter, - - - - _ .Qn2_t-:T-&_vq~:'7'~-omp cmpfn);
-------- - - -

826. CAPITOLO 34
GLI ALGORITMI STL 827

- L'algori~o e~ua!_range() restituisce un intervallo tale per cui un elemento restituisce un iteratore che punta all'ultimo elemento della sequenza, altrimenti
possa essere msento m una sequenza senza alterarne l'ordine. La regione restituisce l'iteratore endl.
deve essere ese "t "fi ,. 1n cui
. gu1 a ~ spec1 cata d~I mte~all? compreso fra start ed end. Il La seconda forma consente di specificare un predicato binario che determina
valo~ V1e~e passato m val. Per specificare 1 cnteri di ricerca basta indicar I la corrispondenza degli elementi. -
funzione d1 confronto cmpfn. . e a
La classe template pair una classe di servizio che nei suoi membri fi t
second pu contenere una coppia di oggetti. rs e find_first_of()

template <class Foriterl, class Foriter2>


fili() e fill_n() Fwdlterl find first of(Foriterl startl, Foriterl endl,
- - Foriter2 start2, Foriter2 end2);
template <class Foriter, class T> templ ate <cl ass Foriterl, cl ass Foriter2, cl ass Bi nPred>
void fill (Foriter start, Foriter end, const T &val); Fwditerl fi nd fi rst of (Forlterl startl, Foriterl endl,
temp~ate <class Foriter, class Size, class T> - - Forlter2 start2, Foriter2 end2,
vo1d fill_n(Foriter start, Size num, const T &val); Bi nPred pfn);

1
~li algori.~ fill() e fill_n() riempiono un intervallo con il valore specificato da L'algoritmo find_first_of() trova il primo elemento nella sequenza definita da
va er fill9 I mtervallo specificato da start ed end. Per fill_n() l'intervallo inizia start] ed endl che corrisponde a un elemento dell'intervallo compreso fra start2
da start e s1 estende per num elementi. ed end2. Se non viene trovato un elemento corrispondente, l'algoritmo restituisce
l' iteratore endl.
findQ La seconda forma consente di specificare un predicato binario che determina
quando due elementi corrispondono.
template <class Initer, class T>
In Iter find(Initer start, In Iter end, const T &val);
find_if()

~~algoritmo find() ricerca nell'intervallo compreso fra start ed end il valore template <class Initer, class UnPred>
speci dc~? ~a val. L'algoritmo restituisce un iteratore che punta alla prima occor- Inlter find_if(Initer start, Inlter end, UnPred pfn);
renza e e emento oppure end se il valore non contenuto nella sequenza.
T afg5i1frho lr'id_if() ricerca nell'intervallo compreso fra start ed end un ele-
find_end() mento per il quale il predicato unario pfn restituisce true. L'algoritmo restituisce
un iteratore che punta alla prima occorrenza dell'elemento oppure a end se il
template <class Foriterl, class Foriter2> valore non contenuto nella sequenza.
Fwditerl find_end(Foriterl startl Foriterl endl
Foriter2 start2, Foriter2 end2).
template <cl~ss Foriterl, class Foriter2, class Bin~red>
for_each()
Fwditerl f1nd_end(Foriterl startl foriterl endl
templ ate<cl ass In Iter, cl ass Fune>
Forit!!r_?__ ~tart2, Foriter2 .end2, Bi nPred pfn); Fune for_each(Initer start_~!niter end, Fune fn);
L'algoritmo trovar ltim0
. ,. u iteratore della sequenza definita da start2 ed ena.2-- L'algoritmo for_each() applica la funzione fa a tutti gli elementi dell'interval-
ne11 intervallo definito d 1 d
_ _ _ __ !.!_far~ e end]. Se la sequenza viene trovata, l' al ero ritmo lo ompreso fra start ed end e restituisce fa.
---~-- . - b

- ---- - ------
828
------
CAPITOLO 34

generate() e generate_n()
iter_swap()
template <class Foriter, class Generator>
void generate{Foriter start, Foriter end, Generator fngen); templ ate <cl ass Foriterl, cl ass Foriter2>
template <class Foriter, class Size, class Generator> void i-ter_swap(Foriterl i, Foriter2 j)
void generate_n(Outiter start, Size num, Generator fngen);
L'algoritmo iter_swap() scambia il valore puntato dai due iteratoci fomiti come
G:i algoritmi ~e~erate() e generate_n() assegnano agli elementi di un inter- argomenti.
vallo 11 valore rest1tu1to da una funzione. Per generate() l'intervallo in cui eseg -
r~ assegnamento e, speciiicato da start ed end. Per generate_n() l'intervallo ini-
I' u1
z~a da start e p.rocede per num elementi. La funzione generatore (senza parametri) lexicographical_compare()
viene passata mfngen.
template <class Initerl, class Initer2>
bool lexicographical_compare{lniterl startl, Inlterl endl,
includes() Initer2 start2, 1nlter2 end2);
template <class Initerl, class 1niter2, class Comp>
template <class Initerl, class Initer2> bool lexicographical compare(Inlterl startl, lniterl endl,
bool includes(Initerl startl, Initerl endl, - Inlter2 start2, 1nlter2 end2,
Initer2 start2, Initer2 end2}; Comp cmpfn);
template <class Initerl, class Initer2, class Comp>
bool 1ncludes(Initerl startl, Initerl endl, ... L'algoritmo lexicographical_compare() confronta alfabeticamente la sequen-
In Iter2 s tart2, In Iter2 end2, Comp cmpfn); za definita da start] ed endl con la sequenza definita da start2 ed end2. Se la
rima se uenza minore in senso lessicale rispetto alla seconda ~ov_vero se la
L'algoritmo includes() determina se la sequenza definita da start] ed endl ~rima se~uenza precede nell'ordine "a dizionario") l'al.gori~o
restituisce true. _
include tutti gli elementi della sequenza definita da start2 ed end2. Se crli elementi La seconda forma consente di specificare una funzione di confronto che de
vengono tutti trovati restituisce true, altrimenti restituisce false. "' termina quando un elemento minore di un altro.
La seconda forma consente di specificare una funzione di confronto che de-
termina quando un elemento minore di un altro.
lower_bound{)

inplace-=-n:ierge() template <class Foriter, class T>


Foriter lower bound{Foriter start, Foriter end, const T &val);
templ ate <cl ass Bi Iter> template <class-Forlter, class T, class Comp>
void inplace_merge(Biiter start, Bi Iter mid, Bi Iter end); Forlter lower bound{Foriter start, Foriter end, const T &val,
template <class Biiter, class Comp> - Comp cmpfn);
void inplace_merge(Biiter start, Biiter mid, Bi Iter end, Comp cmpfn);
L'algoritmo lower_bound() trova il primo punto della sequenza definita da
In una singola sequenza, l'algoritmo inplace_merge() unisce l'intervallo defi- start ed end che non minore di val.
nito da start e mid con quello definito da mid ed end. Entrambi gli intervalli devo- Quindi restituisce un iteratore a tale punto. - . . .
no essere ordinati in senso ascendente. Dopo l'esecuzione, la sequenza prodoha La seconda forma consente di specificare una funzione di confronto che de-
sar ordinata in senso ascendente. termina quando un elemento minore di un al~r~. _
La seconda formaconsente di specificare una funzione di confronto che de-
-- termina quando un elemento minore di un altro.
------
-----------
- -- ---=- -~-
830 CAPITOLO 34

make_heap()
template <class Inlterl, class Inlter2, class Outiter, class Comp~
template <class Randlter>
Outiter merge(Inlterl startl, Initerl endl,
void make heap(Rand!ter start, Rand!ter end); Inlter2 start2, Inlter2 end2,
template <class Rand!ter, class Comp> Outlter-result, Comp cmpfn);
void make_heap(Rand!ter start, Rand Iter end, Comp cmpfn);
ordinate inserendo il risultato in
L'algoritmo merge() umsce dudae se~uen~~o definite da start} ed endl e da
.
L'algoritmo make_heap() costruisce uno heap partendo dalla sequenza defi- una terza sequenza. Le sequenze umre s
nita da start ed end
La seconda forma consente dj specificare una funzione di confronto che de- start2 ed end2. . tata da result. L'algoritmo restitu-
Il risultato viene insento nella sequenza pun . lt te
termina quando un elemento minore di un altro. ta alla fine della sequenza nsu an d
isce un iteratore che pun . 'fi una funzione di confronto che e-
La seconda forma consente ~1 spec1. care
maxo termina quando un elemento minore di un altro.

template <class T>


const T &max(const T &i, const T &J); min()
template <class T, class Comp>
templ ate <cl ass T> .
const T &max(const T &f, const T &;/, Comp cmpfn);
const T &min(const T &t, const T &J);
templ ate <cl ass T, cl ass Comp>
L'algoritmo max() restituisce il maggiore di due ~alori. const T &min(const T &i, const T &j, Comp cmpfn);
La seconda forma consente di specificare una funzione di confronto che de-
termina quando un elemento minore di un altro. 1 minore di due valori.
L'algoritmo min() rest1tu1sce i_ . fu zione di confronto che de-
La seconda forma consente di specificare una n
max_element() termina quando un elemento minore di un altro.

template <class Foriter>


Foriter max_element(Foriter start, Foriter Zast);
min_element()
template <cla~s Foriter, class Comp>
Foriter max_element(Foriter start, Foriter Zas~~ Comp cmpfn); template <class Forlter>
Forlter min element(Forlter start,
Forlter Zost);
- 1 e
template <class For!ter, e ass o
mp>
Z st Comp cmpfn);
L'algoritmo max_element() restituisce un iteratore ali' elemento maggiore Forlter min_element(Forlter start, Foriter a
dell'intervallo definito da start e fast.
La seconda forma consente di specificare una funzione di confronto che de- .
L'algoritmo max_element() restituisce un it~ratore aIl' e lemento minore del-
termina quando un elemento minore di un altro.
l'intervallo definito da start e las~. 'fi funzione di confronto che de-
La seconda forma consente d1 spec1. care una
merge{) termina quando un elemento minore d1 un altro.

template <class In!terl, class In!ter2, class Outiter~


Out Iter merge(Initerl. starti, InI~erT"endT,-
mismatch{)
Initer2 start2, Initer2 end2,
s_~artl
1 In!terl . class Inlter2> -
Outiter result.Ji ----- __ template <e ass Inlter2: mismatch{Inlterl Inlterl endl
pai r<Initerl Inlter2 start2);
--.-~---.---..- -: G L \ A L G U ffrr "" ' '> '
832---G-A-P. I T O L 0 3 4

templ ate <cl ass Inlterl, cl ass Initer2, cl ass Bi nPred> partial_sort()
pair<Inlterl, Initer2> mismatch(Initerl startl, Inlterl endl,
Initer2 start2, BinPred pfn); template <class Randiter> dit d)
void partia1_sort(Randiter start, Randiter mid, Ran er en
temp1ate <class Randiter, class Comp> .
L'algoritmo mismatch() trova la prima differenza fra gli elementi di due se- void partia1 sort(Randlter start, Randlter mid,
quenze. L'algoritmo restituisce gli iteratoti ai due elementi. Se non viene trovata - Randiter end, Comp cmpfn);
alcuna differenza, vengono restituiti gli iteratoti che puntano all'ultimo elemento
di entrambe le sequenze: L'algoritmo partial_sort() ordina l'interva~lo compr~~~{[.~:::~~0e;:;~~~
La seconda forma consente di specificare un predicato unario che determina l'esecuzione, saranno in ordine i soli elementi compresi
quando un elemento uguale a un altro.
La classe template pair contiene due dati membro chiamatifirst e second che fra s~~:c;~~~ forma consente di specificare una funzione di confronto che de-
contengono le coppie di valori. .
termmaqu ando un elemento minore di un altro.

next_permutation() partial_sort_copy()
templ ate <cl ass Bi Iter> tempi ate <cl ass In Iter, cl ass Randiter>
bool next_pe:mutation(Bilter start, Bilter end); Randiter partial_sort_copy(Initer start, Itnitter R:~~iter res end);
template <class Bilter, class Comp> Randiter res_s ar -
bool next_permutation(Bilter start, Biiter end, c.?mp cmfn); temp1ate <class Initer, class Randiter, c1ass Comp>
Randlter partia1_sort_copy(I;arn~e;t:rta:e~~s~:~~:r R:~~iter res_end,
L'algoritmo next_permutation() costruisce la permutazione successiva in una
sequenza. Le permutazioni vengono generate partendo da una sequenza ordinata: Comp cmpfn);
la prima permutazione rappresentata da una sequenza dal pi basso al pi alto.
Se la permutazione successiva non esiste, next_permutation() ordina la sequenza
come la sua prima permutazione e restituisce false. Altrimenti restituisce true.
n::e
d' l'. t rvallo definito da stan ed end e
L'algoritmo partial-;-sort_co~y(~?~ m~ nella sequenza di destinazione
poi c?pia un numero d1 elemedn~'talg1 or~t:~ restituisce un iteratore che punta al-
La seconda forma consente di specificare una funzione di confronto che de- defimta da res_stan e res_en . . nte
termina quando un elemento minore di un altro. l'ultimo elemento copiato della sd~quen~fia nsul~~a funzione di confronto che de-.
La seconda forma consente 1 spec1 lcare
termina quando un elemento minore di un altro.
nth_elernent()

template <class Randiter>


partiti on()
void nth_element(Randlter start, Rand Iter element, Randlter end);
templ ate <cl ass Rand Iter, cl ass Comp>
temp late <class Biiter, c1ass UnPre_d> d UnPred pfn);
void nth_el ement(Randlter start, Rand Iter element, Biiter partition(Biiter start, B1 It er en
Rand Iter end, Comp cmpfn);
. definita da start ed end in modo
L'algoritmo parti~ion() _dispo?: la se~~:~:~ ecificato da pfn restituisce true
che tutti g~~ ~l~ment1 per _1_ qua~.11 pre t't . se~ false Poi restituisce un iteratore
L'algoritmo nth_element() dispone la sequenza specificata da start ed end in
modC>che tutti g-li elementi minori di -eTement precedano tale elemento e tutti gli precedano quelli per i quali il pre .1cato _res 1 ~: redic~to false.
elementi maggiori. chi>\ punta al primo degli elementi per 1 quah 11 p -- - - -
La seconda forma consente di specificare una funzione di confronto che de-
termina quando un elemento m~nore di un altro.
834 CAPITOLO 34 - - .--=--- - --=::._:. - -

pop_he~p()

template.<class Randiter> random_shuffleO


void pop~heap(Randiter start, Randiter end}; template <class Randlter>
template <class R.and!ter, class Comp> void random shuffle(Randlter start, Randiter end);
void pop_heap(Randiter start, Rand!ter end, Comp cmpfn); 1 la;s Randiter class Generator> }
te:~i~t~a:~om shuffle(Ra~diter start, Randiter end, Generator rand_gen ;
L'algoritmo pop_heap() scambia gli eI.ementifirst e last- I e poi ricostruisce
loheap.
L'algoritmo random_shuffle() rende casuale la sequenza definita da start ed
La seconda forma consente di specificare una funzione di confronto che de-
termina quando un elemento minore di un altro. endLa seconda forma specifica un generatore personalizzato di numeri casuali.
Questa funzione deve avere la seguente forma generale:
prev_permutation()
rand_gen(num);
template <class Biiter>
bool prev_permutation(Biiter start, Biiter end); Tal funzione deve restiti.iire un numero casuale compreso fra O e num.
template <class Bi!ter, class Comp>
bool prev_permutation(Bi!ter start, Bi!ter end, Comp cmpfn);
remove(), remove_if(), remove_copy() e remove_copy_if()
L'algoritmo prev_permutation() costruisce la permutazione precedente in una template <cJ'ass Foriter, cl ass T> )
sequenza. Le permutazioni vengono generate partendo da una sequenza ordinata: Far Iter remove(Foriter start' Forlter end, const T &val ;
la prima pennutazione rappresentata da una sequenza dal pi basso al pi alto. templ ate <cl ass Foriter, cl ass UnPred> )
Se la permutazione precedente non esiste, prev_permutation() ordina la sequenza Foriter remove i"f(Foriter start Foriter end, UnPred pfn ;
come la sua ultima permutazione e restituisce false. Altrimenti restituisce true. template <class Foriter, class Out!ter, class T>
La seconda forma consente di specificare una funzione di confronto che de- Outiter remove copy(Initer start, Inlter end,
termina quando un elemento minore di un altro. - Outiter result, const T &val);
templ ate <cl ass Foriter' class Outiter' cl ass UnPred:::,
Outiter remove copy i f(Initer start, In Iter end'
push_heap{) - - Outrter result, UnPred pfn);
templ ate -<cl ass Rand Iter>
.
L'algoritmo remove() nmuove d aIL'..i.nterv_____
allo ,specificato
. gli
. .elementi. uguali
void push heap(Rand!ter start, Rand!ter end);
a val. Restituisce un iter~tore. c e p d ll'i tervallo gli elementi per i quali il pre-
template <c1ass Randiter, class Comp> . . . h unta alla fine degh elementi nmanentt.
void push_heap(Randiter start, Randiter end, Comp cmpfn); L'algoritmo remove_rf() nm~ove a. ~ ta alla fine degli elementi rima-
dicato pfn true. Restituisce un iteratore c e pun

L'algoritmo push_heap() inserisce un elemento alla fine dello heap. L'inter- nenti. () .a dall'intervallo specificato gli elementi uguali
vallo specificato da start ed end deve rappresentare uno heap valido. L'algoritmo remove_copy copi l R ftu"sce uniteratore
a val e inserisce il risultato nella sequenza puntata da resu t. es l i
La seconda forma consente di specificare una funzione di confronto che de-
termina quando un elemento minore di un altro. che punta alla flne del risultato ..f() . dall'intervallo specificato gli elementi
L'algoritmo remove_copy_1 copia tatada
r i uali il predicato pfn ~.true e inserisce il risultato.nell~_:equenza pun
!:sul( 13-estituisce un iteratore che punta alla fine del nsultato.
--------
-- --- -
--~--- -- ----
------ -------

--- - --~-- -

----- -
---- --.
836 CAPITOLO 34
-GllALGORITMI sn-- 837

replaceQ, replace_if(), replace_copy()-e replace_copy_if() rotate{) e rotate_copy()


tenplate <class Foriter, classi>
template <class Foriter>
void replace(Foriter start, Foriter end,
void rotate(Foriter start, Forlter mid, Forlter end};
const T &old, const T &new);
terr;late <class Foriter, class UnPred, class T> template <class Forlter, class Outlter~
v:id replace_if(Foriter start, Foriter end, Outiter rotate_copy(Foriter start, Foriter mid, Forlter end,
Outlter result);
UnPred pfn, cons t T &new) ;
terrplate <class Foriter, class Outiter, class T>
v.rtiter replace copy(Initer start, Initer end, Outiter result, .
L' algoritmo rotate() esegue una rotazmne tra degli .elementi
a sm1s . dell'inter-
.
- const T &old, const T &new); vallo specificato da start ed end in modo che l'elemento mzd divenga tl nuovo
terq:;l ate <cl ass Foriter, cl ass Out Iter, cl ass UnPred, cl ass T> primo elemento. . d d d me-
0-.otiter replace copy if(Initer start, In!ter end, Outlter result, L'al oritmo rotate_copy() copia l'intervallo specificato . a start e en '
- - UnPred pfn, const T &new); morizz!do il risultato in result. Nell'eseguire questa operaz1~ne: esegu\un~ ro-
tazione a sinistra degli elementi in modo che l' eleme.nto mzd divenga t pnmo
_L'algoritmo replace() sostituisce dall'intervallo specificato gli elementi ugua- elemento. Restituisce un iteratore che punta alla fine d1 result. .
li a old con gli elementi uguali a new.
L'algoritmo replace_if() sostituisce dall'intervallo specificato gli elementi per
i quali il predicato pfn true con gli elementi il cui valore new. search(}
L'algoritmo replace_copy() copia dall'intervallo specificato gli elementi uguali template <class Foriterl, class Foriter2>
a old, li sostituisce con quelli il cui valore new e inserisce il risultato nella Foriterl search(Foriterl startl, Foriterl endl,
sequenza puntata da result. L'intervallo originale non viene modificato. Restitui- Foriter2 start2, Forlter2 end2);
sce un iteratore che punta alla fine del risultato. template <class Foriterl, class Foriter2, class iUnPred>
L'algoritmo replace_copy_if() copia dall'intervallo specificato gli elementi per Foriterl search (Foriterl start I Foriterl endl . }.
i quali il predicato pfn true, li sostituisce con quelli il cui valore new e inserisce Foriter2 start2' Foriter2 end2 81 nPred pfn
il risultato nella sequenza puntata da result. L'intervallo originale non viene mo-
dificato. Restituisce un iteratore che punta alla fine del risultato. L'algoritmo search() ricerca una sottosequenza in una sequenza. La .sequenz~
in cui ese uire la ricerca definita da starti ed endl. La sottoseq~enza :icercata .e
reverse() e reverse_copy() s ecificat! da start2 ed end2. Se viene trovata la sottoseqi:~~~l-~~-~ntmo rest1-
P.
tmsce tore a11 1n1z10 di tale sequenza' altrimenti
un itera . rest1tu1sce
endl
h d t rmina
template <class Biiter> La seconda forma consente di specificare un predicato bmano c e e e
void reverse(Bi Iter start, Bi Iter end); quando un elemento uguale a un altro.
template <class Bi Iter, class Outiter~
Outlter reverse_copy(Bilter first; Bi!ter last, Outiter result);
search_n{)

end.
L'algoritmo reverse() inverte l'ordine dell'intervallo specificato da start ed template <class Forlter, class Size, class T>
Foriter search_n(Forlter start Foriter end,
- L'algoritmo reverse_copy\) copia in ordine inverso l'intervallo specificato da Size num, const T &val); -~
stan ed end e memorizza il risultato in result. Restituisce un iteratore che punta temp 1ate <cl ass Foriter' cl ass Si ze, cl ass T cl ass Bi nPred>
alla fine di result.
F Iter search n (Forl-ter-stort, Forlter end,
or - Siie num, const T &val, BinPred pfn);
GLI ALGORITMI STL 839
838 e A p I T o to. 34

_ L'~~ri~o search_n() ricerca in una sequenza una sottosequenza di num set_simmetric_difference{)


elementt il cm valore val. La sequenza in cui eseguire la ricerca definita d
':1 e~J.
sta,'"!1. iterato~
Se viene trovata la sottosequenza, l'algoritmo restituisce un
templ ate <cl ass Initerl, cl ass Inlter2, cl ass out Iter>
Outiter set synmetric difference(Initerl startl, Initerl endi,
all IDlZIO dt tale sequenza, altrimenti restituisce end]. . I~Iter2 start2, In!ter2 last2, Outiter result);
La seconda forma consente di specificare un predicato binario che determina template <class Inlterl, class Initer2, class Outiter, class Comp>
quando un elemento uguale a un altro. . Outiter set sy1T111etric difference(Initerl starti, Initerl endl,
I~Iter2 sta;tz, Initer2 last2, Outiter result,
Comp cmpfn) ;
set_difference()
L'algoritmo set_simmetric_difference() produce una sequenza che contiene la
template <class.Inlterl, class Inlter2, class Outiter> differenza simmetrica fra i due insiemi ordinati definiti da starti ed endl e start2
Outlter set_difference(Inlterl startl, Initerl endl: ed end2. In pratica l'insieme risultante contiene gli elementi che non sono comuni
Inlter2 start2, Initer2 last2, Outiter result);
ai due insiemi. Il risultato viene ordinato e inserito in result. L'algoritmo restitui-
template <class Initerl, class Initer2, class Outiter, class omp>
Outiter set_difference(Initerl startl, Initerl endl, sce un iteratore che punta alla fine del risultato.
Initer2 start2, Inlter2 last2, La seconda forma consente di specificare un predicato binario che determina
Outiter result, Comp cmpfn); quando un elemento minore di un altro.

_L'algoritmo set_difference() produce una sequenza che contiene la differenza


fra I dU:. in~iemi ordi~ati definiti da starti ed endl ~- start2 ed end2. In pratica set_union()
sot~ae l ms17me definito da start2 ed end2 dall'insieme definito da starti ed endl.
template <class Initerl, class Inlter2, class Outiter>
Il nsultato viene ordinato e inserito in result. L'algoritmo restituisce un iteratore Outiter set union(Initerl startl, Inlterl endl,
che punta alla fme del risultato. Inlter2 start2, Inlter2 last2, Outiter result);
La seconda forma consente di specificare un predicato binario che determina template <class .Initerl, class Inlter2, class Outiter, class Comp>
quando un elemento minore di un altro. Outiter set union(Inlterl startl, Initerl endi,
Initer2 start2, Inlter2 last2, Outiter result,
Comp cmpfn);
set_intersection()
. L'algor.itmo.set_union() produce una sequenza che contiene l'unione di due
templ ate <cl ass Inlterl, cl ass Inlter2, cl ass Outiter:: insiemi ordinati definiti da starti ed endl e start2 ed end2. In pratica l'insieme
Outlter Si!t_iinitersection(Initerl starti, Inlterl endi, risultante contiene tutti gli elementi dei due insiemi. Il risultato viene ordinato e
Initer2 start2, Initer2 last2, Outiter result);
inserito in result. L'algoritmo restituisce un iteratore che punta alla fine del
templ ate <class Initerl, crass Initer2, class Outiter, class Comp>
Outiter set_iinitersection(Initerl startl, Initerl endi, risultato.
Inlter2 start2, Inlter2 last2, La seconda forma consente di specificare un predicato binario che determina
Outlter resu l t, Comp cmpfn) ; quando un elemento minore di un altro.

,. L' alg.oritmo .set_. intersection () produce una sequenza che contiene


I mte~z10.ne fra 1 due ms~emi ordinati definiti da starti ed endl e start2 ed end2. sort()
ln.prau.ca ~t tratta degli lmenti comuni ai due insiemi. Il risultato viene ordinato
template <class Randiter>
e. msento m result. L'algoritmo restituisce un iteratore che punta alla fine del void sort(Randlter start, Randiterend);
nsultato. templ ate <cl ass Rand Iter, cl assComp>
~seconda forma-consente di-specificare un predicato binario che determina void sort(Randlter: s~art, Randlter end, Comp cmpfn);
-- -- quando un elemento ~~ore.di-un altro. - - - - - -
_____
- - - - ,----- ...
840 CAPITOLO 34

swap()
L'algoritmo sort() ordin,a l'intervallo specificato da start ed end.
La seconda forma consente di specificare un predicato binario che determi templ ate <cl ass T>
quando un elemento minore di un altro. na void ~wap(T &i, T &j);

L'algoritmo swap{) scambia i valori indicati da i ej.


sort_heap()

template <class Randiter>


void sort_heap{Randiter start, Randiter end);
swap_ranges()
template <class Randiter, class Comp>
template <class Forlterl, class Forlter2>
voi d. sort_heap{Randiter start, Randiter end, Comp cmpfn);
Foriter2 swap_ranges(Foriterl startl, Foriterl endl,
Foriter2 start2);
L'algoritmo sort_heap() ordina lo heap nell'intervallo specificato da start ed
end. L'algoritmo swap_ranges() scambia gli elementi dell'intervallo specificato
La seconda forma consente di specificare un predicato binario che determina da starti ed endl con gli elementi della sequenza che parte da start2. Restituisce
quando un elemento minore di un altro. un iteratore che punta alla fine della sequenza specificata da start2.

stable_partition()
transform{)
templ ate <cl ass Bi Iter, cl ass UnPred> template <class Initer, class Outiter, class Fune>
Bilter stable_partition{B1Iter start, Biiter end, UnPred pfn); Outiter transform(Inlter start, Inlter end,
Outiter result, Fune unaryfunc);
L'algoritmo stable_partition() dispone la sequenza definita da start ed end in templ ate <cl ass Inlterl, cl ass Initer2, cl ass Outiter, cl ass Fune>
modo che tutti gli elementi per i quali il predicato specificato da pfn restituisce Outiter transform(Initerl startl, Initerl endl,
true precedano q~elli per i quali il predicato restituisce false. Il partizionamento Initer2 start2, Outiter re sul t,
stabile,_ ovvero viene mantenuto l'ordinamento relativo della sequenza. Restitui- Fune binaryfunc);
sce un iteratore che punta all'inizio degli elementi per i quali il predicato false.
L'algoritmo transform() applica una funzione a un intervallo di elementi e
memorizza il risultato in result. Nella prima forma l'intervallo specificato da
stable_sort() start ed end. unaryftmc.specifica la funzione unaria da applicare. Questa funzione
riceve il valore di un elemento nel suo parametro e deve restituire la sua trasfor-
template <class Randiter>
void stable_sort{Randlter start, Randlter end);
mazione.
Nella seconda forma la trasformazione viene applicata impiegando una fun-
template <class Randiter, class Comp>
zione binaria che riceve nel primo parametro l'elemento della sequenza che deve
void stable_sort(Randlter start, Randlter end, Comp cmpfn);
essere trasformato e nel secondo parametro un elemento della seconda sequenza.
Entrambe le versioni restituiscono un iteratore che punta alla fine della se-
L'algoritmo stable_sort() ordina l'intervallo specificato da start ed end. L'ordi-
namento stabile, ovvero viene mantenuto l'ordinamento relativo dell'intervallo. quenza risultante.
La seconda forma consente di specificare un predicato binario che determina
quando un elementQ_minore di un altro.
- - - --=-----. _ _.. ---:- - -
842 CAPITOLO 34
---------
-------- ----..
unique() e unique_copy()
: Capitolo 35
template <class Foriter>
Foriter unique(Foriter start, Foriter end); lteratori, allocatori
templ ate <cl ass Foriter, cl ass BinPred>
Foriter unique(Foriter start, Foriter end, BinPred pfn) e oggetti funzione STL
template <class Foriter, class Outiter> '
Out Iter unique_copy(Foriter start, Foriter end, Out Iter result).
35.1 Gli iteratori
template <class Foriter, class Out!ter, class BinPred> '
_outiter unique_copy(Foriter start, Foriter end, outiter result 35.2 Gli oggetti funzione
BinPred pfn);
35.3 Gli allocatori

. L'algoritmo unique() elimin~ dall'intervallo specificato gli elementi duplica-


~ La se~onda forn;a consen.te d1 specificare un predicato binario che determina
1 uguaglianza degh. elementi. L'algoritmo unique {) restituisce un iteratore eh
punta alla fine dell'intervallo. e Ouesto capitolo descrive le classi e le funzioni che
. L'algori.tmo uniq~e_co~yO. copia l'intervallo specificato da start ed end eli- supportano gli iteratori, gli allocatori e gli oggetti funzfone. Questi componenti
minando gh ele~ent1 ~uphcat1. Il risultato viene inserito in result. La seconda fanno parte della libreria SlL e possono essere utilizzati anche per altri scopi..
forma consente d1 specificare un predicato binario che dete ....... :n l' .
d r I . L' . UU..l a uguag11anza
eg 1 e e1?ent1. algontmo unique_copy() restituisce un iteratore che punta alla
fime dell'intervallo.
35.1 Gli iteratori
upper_bound() Mentre i container e gli algoritmi costituiscono le basi della libreria STL, gli
iteratoci sono come la "colla" che li tiene uniti. Un iterato re una generalizzazione
template <cl ass Foriter, cl ass T> (o pi precisamente un'astrazione) di un puntatore. Gli iteratori vengono gestiti
Forlter upper_bound(Foriter start, Foriter end, const T &val); nel programma come se fossero puntatori e implementano gli operatori standard
template <class Foriter, class T, class Comp> per i puntatori. Essi offrono la possibilit di scorrere il contenuto di un container
Forrter upper_bound(For!ter start, Foriter end, const T &val, cos come si usa un puntatore per scorrere il contenuto di un array.
Comp cmpfn);
Il linguaggio C++ standard definisce un insieme di classi e funzioni che
supportano gli iteratori. Tuttavia per la maggior parte delle operazioni di pro-
L'algoritmo upper_bound() trova l'ultimo punto della sequenza definita da grammazione che impiegano la libreria STL tali classi non vengono utilizzate
start ed end che non maggiore di val. Quindi restituisce un iteratore a tle punto direttamente. Al contrario si usano gli iteratoci fomiti dai vari container STL ma-
~ seconda forma consente di specificare una funzione di confronto che de~ nipolandoli come qualsiasi altro puntatore. Nonostante tutto quello che si detto
ternuna quando un elemento minore di un altro. finora, fondamentale conoscere il funzionamento delle classi per iteratori e il
loro contenuto. Ad esempio, questo consente di creare degli iteratori specifici per
rispondere a particolari situazioni. Inoltre gli sviluppatori di librerie troveranno
molto utili le classi per iteratori.
Gli iteratori usano l'header <iterator>.

I tipi base per gli iteratori

Esistono cinque tipi base per [li iteratori.


- =-::.-- - -.
---------

---- -- - .. __ .
----- --
- -----w-..::
-- A-L-LOc-A-TO--R-lrO G G-E T TI ..F.J.ULUQ_lfE'_s~T L 845
I T E_R A T O R I
844 -C-llPll-0 L6 3 5

struct iterator
ITERATORE ACCESSO CONSENTITO typedef T value_type;
Accesso diretto Memorizza e legge il puntatore. ~accesso agli elementi pu awenire in modo casuale.
typedef Di st di fference_ type;
typedef Pi nter pointer;
Bidirezionale : Memorizza legge il puntatore. Spostamento in avanti e indietro. typedef Ref reference;
typedef Cat i tera tor_category;
In avanti Memorizza e legge il puntatore. Solo spostamento in avanti.
};
Input Legge ma non memorizza il puntatore. Solo spostamento In avanti.
, . he u contenere la differenza fra due indirizzi,
Output Memorizza ma non legge il puntatore. Solo spostamento in avanti. Qui difference_type e un tipo c . ~ a po'inter il tipo di un puntatore a un
, i ti 0 del valore su cui s1 oper , .
value_type e i P, 1 u di un indirizzo per il valore e iterator_category descnve
valore, reference e 1 po
In generale, un iteratore dotato di maggiori capacit di accesso pu essere
il tipo dell'iteratore. . . i Il loro nome pu essere me-
utilizzato al posto di un iteratore dotato di minori capacit. Ad esempio, al posto Per le categorie sono definite le seguenti c1ass .
di un iteratore di input si pu utilizzare un iteratore in avanti. morizzato in iterator_category.
La libreria STL supporta anche l'impiego di iteratori inversi. Si tratta di iteratori
bidirezionali o ad accesso diretto che scorrono il contenuto di una sequenza in
struct input_iterator_tag {};
senso inverso. Pertanto se un iteratore inverso punta alla fine di una sequenza, struct output i tera tor_tag {}; . {}.
incrementando tale iteratore lo si fa puntare al penultimo elemento. -d 1'terator tag public input iterator_tag '
struc t f orwar _ - . - d t t tag {};
Esistono anche iteratori basati su stream. Infine vengono fornite delle classi struct bidirectional_iterator_tag: publ~c forwar _, era or_
per iteratori di inserimento che semplificano le operazioni di inserimento degli struct random_acce~s_iterator_tag: publ1c
elementi in un container. bidirectional_iterator_tag {};
Tutti gli iteratori devono supportare le operazioni per puntatori consentite dal
loro tipo. Ad esempio, un iteratore di input deve supportare gli operatori->, ++, , ==
e!=. Inoltre per assegnare un valore non si pu usare l'operatore. Al contrario, un La classe iterator_traits . . a molto comodo per espor-
~:ic~:~~i~~r:!~:;ri~a:: ~~~t~;~~r:~{:~~a~~:~ ~~~n7ta nel seguente modo:
1
iteratore ad accesso diretto deve supportare gli operatori->,+,++,-,--,,<,>,<=,
>=, -=, +=, ==,!=e [].Inoltre l'operatore deve consentire l'assegnamento.

templ ate<cl ass Itera tor> struct itera tor_trai ts {


Le classi per iteratori a basso livello tor .. difference type difference_type;
type def It er a -
. - . -------~
typedef Iterator: :value_type value_type;
L'header <iterator> definisce numerose classi che forniscono il supporto per typedef Itera tor:: pointer pointer;
l'implementazione degli iteratori. Come si detto nel Capitolo 24, ognuno dei typedef Iterator: :reference reference;
container STL definisce il proprio tipo di iteratore tramite typedef. Pertanto, quando typedef Itera tor:: itera tor_category itera tor_category;
si usano i container standard STL, in genere non si interagisce direttamente con le
classi per iteratori a basso livello. Tuttavia si possono utilizzare queste classi per
derivare i propri iteratori.
Molte delle classi per iteratori utilizzano il tipo ptrdiff_t. Questo tipo in gra- Gli iteratori predefiniti
do di rappresentare la differenza esistente fra due puntatori. fi h ssono essere uti-
L' header <iterator> contiene numerosi iteratori p~~~~t:~~ ~a ~~~e per creare altri
La classe lterator lizzati direttamenre_dal programma Q .poslslon~ bella 35 l s1 noti che quattro di
elencatJ. ne a 1a
la classe iterator la classe base per gli iteratori e ha il seguente aspetto: iteratori. Questi iteraton son 0 L ncipale decrli iterato_ripc;:r_filream
questi iteratori operano su stream. 0 scop~ pn . da n:;_e decrli alcroritmi. Inol-
template <class Cat, class T, class Dist ptrdiff_t, quello di consentire la manipolaz~od~~ deg~1 str~~mQua~do si us~no ;uesti iteratori
class Potnter T , class Ref ,._I.&>_____ _ tre si noti la presenza deg1lit~ltorl' imsenmen . - - --- - - - - --- . -
------~-

- - - ----
-- ----
. -1-T-E ElA T O R I , A L L O C A T O R I .E O G G E T I I ~ U N '- 1 u " c.
846 CAPITOLO 35

1nt main()
nelle istruzioni di assegnamento, essi inseriscno gli elementi in una sequenza
{
senza cancellare gli elementi esistenti. vector<i nt> v;
In questa sezi~ne verranno esaminati tutti. gli iteratori predefiniti. vector<int>::iter,iltor itr;
int i;
L'iteratore in~ert_iterator
La classe inserUterator supporta gli iteratori di output che inseriscono oggetti in for(i=O; i<S; i++)
un container. Ecco la definizione template della classe: v.push_back(i);

template <class Cont> class insert_iterator: cout << "Array originale: ";
public iterator<output_iterator_tag, void, void, void, void> itr = v.begin();
while{itr != v.end())
cout *i tr++ << " ;
Qui Cont il tipo del container su cui opera l'iteratore. insert_iterator ha il
cout endl ;
seguente costruttore:
itr = v.begin();
insert_iterator(Cont &cnt, typename Cont::iterator itr); itr += 2; Il punta all'elemento 2

Qui cnt il container su cui si opera e itr un iteratore per il container che Il crea insert_iterator all'elemento 2
verr utilizzato per inizializzare insert_iterator. insert_iterator<vector<int> > i_itr(v, itr);
insert_iterator definisce i seguenti operatori:=,*,++. i:n una variabile protected
chiamata container contenuto un puntatore al container. L' iteratore del container 11 inserisce senza sovra seri vere
contenuto in una variabile protected chiamata iter. *i itr++ = 100;
Inoltre definisce la funzione inserter() che crea un iteratore insert_iterator. *(itr++ = 200;
Ecco l'aspetto di tale funzione: cout "L'array dopo l'inserimento: ";
itr = v.begin();
template <class Cont, class lterator> insert_iterator<Cont> while(itr != v.end())
inserter(Cont &cnt, lterator itr); cout << *i tr++ << " ";

Gli iteratori di inserimento eseguono un inserimento senza cancellare il con- return O;


tenuto di un container. Per comprendere appieno il funzionamento di un iteratore
di inserimento, si consideri il seguente programma. Innanzitutto il programma
crea un piccolo vettore di interi e poi utilizza un iteratore insert_iterator per inse- Ecco l'output prodotto dal programma.
rirvi nuovi elementi senza cancellare gli elementi esistenti.
Array originale: O 1 2 3 4
Il Illustra l'uso di insert_iterator. L'array dopo 1 'inserimento: O 1 100 200 2 3 4
#i nel ude <i ostream>
#i nel ude <itera tor> Nel programma, se gli assegnamenti dei valori 100 e 200 fossero s~ati esegui~i
#i ne 1ude <vector> con un iteratore standard, due degli elementi dell'array sarebbe~o stati :ancellati.
usi ng namespace std; Gli stessi principi si applicano anche agli iteratori back_11'!_~rt_1terator e
fronUns~rt_i~erator.

--~ - ~
---- - -
t TE R A T O R I , A L L O CA T O R I E O G G-i;.i:..T-LE.U NZ I O N E S TL --a49
848 CAPITOLO 35

Ecco la definizione template della classe:


Tabella 35.1 Le classi predefinite degli iteratori predefiniti. -
CLASSE DESCRIZIONE template <cfass Cont> class front_insert_iterator:
public iterator<output_iterator_tag, void, void, void, void>
i csert_itera tor Un iteratore di output in qualsiasi punto del contalner.

b::k_i nsert_i tera tor Un lteratore di output per l'Inserimento alla fine del container. Qui Cont il tipo del container su cui opera l' iteratore. front_insert_iterator ha
f.-::nt_i nsert_ i tera tor Un Heratore di output per l'inserimento all'Inizio del container. il seguente costruttore:
re>erse iterator Un iteratore inverso, bidirezionale 0 ad accesso diretto.
explicit front_insert_iterator(Cont &cnt);
istream_iterator Un iteratore di Input da stream.
Qui cnt il container su cui si opera. Gli inserimenti verranno eseguiti ali' ini-
i s:reambuf_i tera tor Un iteratore di Input da streambuf.
zio del container.
os:ream_i tera tor Un iteratore di output su stream. front_insert_iterator definisce i seguenti operatori: =, *, ++. In una variabile
os:reambuf_i tera tor Un lteratore di output su iterator. protected chiamata container contenuto un puntatore al container.
Inoltre definisce la funzione front_inserter() che crea un iteratore
front_insert_iterator. Ecco l'aspetto di tale funzione:
L'iteratore back_insert_iterator
template <class Cont> front_insert_iterator<Cont> inserter(Cont &cnt);
La classe back insert iterator sup rt r . . .
getti alla fine diun container utilizz~~d~ g 1 ~eraton di output che inseriscono og-
della classe: pus _back(). Ecco la definizione template L'iteratore reverse_iterator
La classe reverse_iterator supporta le operazioni di iterazione inversa. Un iteratore
inverso si comporta al contrario di un normale iteratore. Ad esempio l'operatore
templa~e-:=class Cont> class back_insert_iterator: ++ fa in modo che l'iteratore inverso proceda a ritroso. Ecco la sua definizione
pubhc iterator<output- iterator- tag vo1d , vod
I , VOI'd , VOI.d>
template:
Qui Cont
il seguente il tipo del contamer
costruttore: su cui opera l' 1teratore.
.
back_insert_iterator ha template <class Iter> class reverse_iterator:
public iterator<iterator_traits<lter>::iterator_category,
iterator_traits<lter>::value_type,
explicit back_insert_iterator(Cont &cnt); iterator_traits<lter>::difference_cype-,- ----
iterator_traits<Iter>: :pointer,
fine Qui cnt il container su cui s1 opera.
del container. Gr1 msenmenti
. verranno eseguiti alla iterator_traits<lter>: :reference>

back_insert_iterator definisce i seguenti o eratori - * . . Qui Iter un iteratore ad accesso diretto o un iteratore bidirezionale.
protected chiamata container , t P -, ++. In una vanab1le
Inoltre definisce I f e ~on enuto un puntatore al container. reverse_iterator ha i seguenti costruttori:
a unzione back inserter() h .
back_insert iterator Ecco l'as tt d' 1 f- . c e crea un 1teratore
- pe o 1 ta e unz10ne:
reverse_iterator( );
explicit reverse_iterator(Iter itr);
-- templare
&cnt); <class Cont> back_msert_1terator<Cont>
. back_inserter(Cont--.
Qui itr un iteratore-che specifica la posizione di partenza.
Se Iter un iteratore ad accesso diretto, allora sono disponibili i seguenti opera-
L'iterato re front~insert_iterator . .
tori:->,+,++,-,--,*,<,>,<=,>=,-=,+=,==, !=e[]. Se Iter un iteratore bidirezionale,
=---=-e-.1:8_classe front_insert_iterator
oetti ll'inizio d' supporta
.. g r.
1 iteraton. di.-output che .msenscono
:---- 0"- - allora saranno dispombllslamen~~i~~tQri.:> ++, --, *,==e I=.
1 un container uttltzza!!d~ush,..:.trol}!_{. ___ -==-::-:----. :__:- .
____ _
e
850 CAPlfoT6- 3~
---- -~ --~-

- I T E R A T o R I ' A-L L o e A T o A I E OGGETT I F u N z I o N E s TL -ss1


La cl~sse reverse_iterator definisce un membro protected chiamato cu
che un 1teratore alla posizione corrente. rrent cout << *in_it++;
} while (*in_it != '.');
reverse_iterator definisce anche la funzione base() il cui prototipo :
return O;
Iter base( ) const;

La funzione restituisce un iteratore alla posizione corrente. t:iteratore istreambuf iterator


La classe istreambuf_iterator supporta le operazioni di un iteratore di input di
L'iteratore lstream_lterator caratteri su uno stream. Ecco la sua definizione template:
La classe istream_iterator supporta le operazioni di un iteratore di input su uno
stream. Ecco la sua definizione template: template <class Char1)'pe, class Attr = char_traits<CharType> >
class istreambuf_iterator:
template <class T, ~lass Cha:1YI>e, class Attr = char_traits<Char'lype>, public iterator<input_iterator_tag, CharType, typename Attr::off_type,
. . class D1st = ptrdiff_t> class Distream_iterator: Char1)'pe *, Char1)'pe &>
pubhc 1terator<input_iterator_tag, T, Dist, const T *, const T &>
Qui CharType il tipo dei caratteri (char o wchar_t) su cui opera lo stream.
Qui T il tipo ~ei_dati : C~arType il tipo dei caratteri (char o wchar_t) su cui istreambuf_iterator ha i seguenti costruttori:
?~r~ l~ s~eam. D'.st e un tip~ In grado di contenere la differenza esistente fra due
mdmzzi. 1stream_1terator ha 1 seguenti costruttori: istreambuf_iterator( ) throw( );
istreambuf_iterator(istream_type &stream) throw( );
istream_iterator( );
istreambuf_iterator(streambuf_type *streambuf) throw( );
~stream_~terator(istream_type &stream);
1stream_1terator(const istream_iterator<T, CharType, Attr, Dist> &ob); II primo costruttore crea un iteratore per uno stream vuoto. Il secondo crea un
iteratore allo stream specificato. Il tipo istream_type un typedef che specifica il
. II Primo costruttore crea un iteratore per uno stream vuoto. II secondo crea un tipo dello stream di input. La terza forma crea un iteratore utilizzando il buffer per
1
~eratore allo stream specificato. II tipo istream_type un typedef che specifica il stream specificato da streambuf
tipo dello stream di in t L f
. .
1stream_iterator. pu . a terza orma crea una copia di un oggetto La clas~e istreambuf_iterator definisce i seguenti operatori: e++. Per oggetti
di tipo istreambuf_iterator sono definiti anche gli operatori == e !=.
d" .La ~lasse ist:eam_iterator definisce i seguenti operatori: ->, e++. Per oggetti _istreamb.uUt~rator definisce la funzione membro equa!() che ha il seguente
I t1~~stre~m_1terator sono definiti anche gli operatori == e !=. aspetto:
un breve pr~gr~ma. che illustra il funzionamento di istream_iterator. II
0

programma legge e v1sual1zza 1 caratteri da cin finch non riceve il carattere punto. bool equal(istreambuf_iterator<CharType, Attr> &ob);
Il Uso di istream iterator
#include <iostrea;;;-,. Il suo funzionamento un po' fuorviante. Restituisce true se I'iteratore chia-
#i nel ude <i tera tor> mante e ob puntano entrambi alla fine dello stream. Inoltre restituisce true se
usi ng namespace std; entrambi gli iteratori non puntano alla fine dello stream. Non richiesto che l'og-
getto puntato sia lo stesso. Negli altri casi restituisce false. Gli operatori == e !=
int main() funzionano allo stesso modo~
{
istream_iterator<char> in_it(cin); t:iteratore ostream_lterator
La classe ostream_iterator supporta le operazioni di un iteratore di output su uno
do {
st_ream. Ecco la sua definizione template: ____
- ----- -~---- -~

- - - - -- .--~
852 CJrP11-ot o - a s I TE RATO R I, ALLOCATO R 1-E-o Gi~rf-fl I FU1Hr0N E~-=-::-- .&53

template <class T, class Char'JYpe, class Attr = char_traits<Char'JYpe> > return O;


class ostream_iterator:
public iterator<output_iterator_tag, void, void, void, void>
Ecco l'outpufdel programma
Qui T il ~po dei dati da trasferire e CharType il tipo dei caratteri (char 0
wchar_t) su cm opera lo stream. ostream_iterator ha i seguenti costruttori: XY Gli i tera tori C++ sono molto potenti.
187 .23-102. 7
ostream_iterator(ostream_type &stream); t.:iteratore ostreambuf_iterator
ostream_~terator(ostream_type &stream, const CharType *delim); La classe ostreambuf_iterator supporta le operazioni di un iteratore di output di
ostream_1terator(const os~:am-::iterator<T, Charfype, Attr> &ob); caratteri su uno stream. Ecco la sua definizione template:
Il primo c~struttore crea un iteratore per lo stream puntato da stream. Il tipo template <class Char'JYpe, class Attr =char_traits<Char'JYpe> >
ostream_type ~ un typedef che specifica il tipo dello stream di output. La seconda class ostreambuf_iterator:
f~rma .crea un. uerator~ ~Ilo sr:eam specificato da stream e usa i delimitatori spe- public iterator<output_iterator_tag, void, void, void, void>
c?Jcat1 da delim. I delmutaton vengono scritti sullo stream dopo ogni operazione
d1 output. La terza forma crea una copia di un oggetto ostream_iterator. Qui CharType il tipo dei caratteri (char o wchar_t) su cui opera lo stream.
La classe ostream_iterator definisce i seguenti operatori: =, * e ++.
ostreambuf_iterator ha i seguenti costruttori:
Ecco un breve programma che illustra il funzionamento di ostream_iterator.

Il Uso di ostream iterator


... ostreambuf_jterator(ostream_type &stream) throw( );
#include <iostrea~>
ostreambuf_iterator(streambuf_type *streambuf) throw( );
#i nel ude <i tera tor>
using namespace std; Il primo costruttore crea un iteratore per lo stream puntato da sm.am. Il tipo
ostream_type un typedef che specifica il tipo dello stream di output. La seconda
int main() forma crea un iteratore allo stream specificato. II tipo streambuf_type un typedef
{ che specifica il tipo del buffer dello stream.
ostream_iterator<char> out_it(cout); La classe ostreambuf_iterator definisce i seguenti operatori: =, * e ++. La fun-
zione membro failed() ha il seguente aspetto:
*out_it = 'X';
out_it++;
*out_it =-'Y'; bool failed( ) const throw( );
out_it++;
*out_it = ' '; Se non si verificato alcun problema restituisce false, altrimenti restituisce
true.
char str[] = "Gli iteratori C++ sono molto potenti. \n";
char *p = str;
Due funzioni per iteratori
while(*p) *out_it++ = *p++;
Per gli iteratoti sono definite due funzioni particolari: advance() e distance(). Ecco
ostream_iterator<doubl e> out_doubl e_ i t(ct); il loro aspetto:
*out_double_it = 187_.23; __ _
out double it++ template <class Inlter, class Dist> void advance(lnlter &itr, Dist cl);
*out_doubl;_ft: -102.7; template <class-Inlter>- distance(Inlter start, Inlter_en~ _
855
854 CAPITOLO 35 ITEAATOAI, ALLOCATOAI E OGGETTI FUNZIONE STL

La funzione advance() incrementa itr della quantit specificata da d. La fun- Ecco la classe base di tutti gli oggetti funzione binari, ovvero binary_function:
zione distance() restituisce il numero di elementi compresi fra start ed end.
Queste due funzioni esistono perch solo gli iteratori ad accesso diretto consen- template <classAgumentl, class Agument2, class Result>
tono di sommare o sottrarre un valore da un iteratore. Le funzioni advance() e struct binary_function {
distance() consentono di superare questa restrizione. Occorre per sapere che alcu- typedef Argumentl first_argument_type;
ni iteratori non sono in grado di implementare queste funzioni in modo efficiente. typedef Argument2 second_argument_type;
typedef Result result_type;
};

35.2 Gli oggetti funzione La classe base per tutte le funzioni unarie unary_function:

Gli oggetti funzione sono classi che definiscono operator(). La libreria STL de- template <class Argument, class Result> struct unary_function
finisce vari oggetti funzione liberamente utilizzabili dai programmi. anche pos- typedef Argument argument_type;
sibile definire oggetti funzione specifici. Il supporto degli oggetti funzione si tro- typedef Result result_type;
va nell'header <functional>. Inoltre <functional> definisce varie entit di supporto };
per gli oggetti funzione, ovvero i binder, i negatori e gli adattatori.
. t' tipi aenerici utilizzati
Queste classi template forniscono nomi concre i per i "' od , . lt,
ffbrA.--"'7?':-c:: Per una panoramica sugli oggettifa.nzione, consultare il Ca- dagli oggetti funzione. Anche se t~cnicamente so~o sol.o una com ita, m rea a
pitolo 24. vengono utilizzati ogni volta che si creano og?etti f~nzion~. . . T cos
Le specifiche template di tutti gli oggett~ fun~ione bm~ sono simi i,
come le specifiche template di tutti gli oggetti funzione unan.
Cosa sono gli oggetti funzione

Vi sono due tipi di oggetti funzione: oggetti binari e unari. Di seguito sono elenca- template <class T> struct plus : binary_function<T, T, T>
ti gli oggetti funzione binari: {
T operator() (const T &argl, const T&arg2) const;
};
plus minus multiplies divides modulus
equal_to not_equal_to greater greater_equal less template <class T> struct negate : unary_function<T, T>
less_equal Iogical_and logical_or
{
T operator() (const T &arg) const;
Ed ecco gli oggetti funzione unari. };

logical_not negate Ogni funzione operator() restituisce il risu~tato specificato.

La chiamata a un oggetto funzione ha il seguente aspetto:


I binder

I binder collegan_o un valore a un a:gom7nto di un ob~g~tto. ~n~~~~(~~n:~ ~~~~ 1


fa.nc_ob<type>( )
ducendo un oggetto funzione unano. Esistono due m er. rn
Ad esempio: Ecco come sono definiti:
less<int>() template <class BinFunc, class-+>-- . -- ---------
binderlst<BinFunc>:.bindl..t(const BmFunc &op~ ~-~LT_ -
- - - richiama less() su operandi di tipo~ - - ---- _....;.::__-__: --

856 CAPITOLO 35 -- I TE A A T O A I , A L LO CA T O A I E O G G E T T I F U N Z I O N E S TL 857 - - - -


~~~:...=..:.:..:...:..~..!......'.::...::..::..:::..:::.:.:..:...:::..:.:...:._.:::_.:::...:::..::..::.,;...:..:_;_;~:..:...:..:.:...=.....:...;..;;_--=:..

&value); I negatori
template <class BinFunc, class T>
I negatori restitui~cono dei predicati che forniscono l'opposto del predicato che
binder2nd<BinFunc> bind2nd(const BinFunc &op, const T
modificano. I negatori sono not1 () e not2() e sono definiti nel seguente modo:
&val1,te);
template <class UnPred> unary_negate<UnPred> notl(const UnPred
Qui op un oggetto funzione binario, come ad esempio fess() o greater(), che
romisce loperazione desiderata e value il valore da collegare. bind1 st() restitu- &pred);
template <class BinPred> binary_negate<BinPred> not2(const BinPred
lSCe un oggetto funzione unario in cui !'operando sinistro collegato a value.
bind2nd() restituisce un oggetto funzione unario in cui I' operando di destra col- &pred);
legato a value. Il pi utilizzato decisamente bind2nd(). In entrambi i casi il
risultato di iln binder un oggetto funzione unario a cui viene collegato il val~re Ecco le loro classi:
specificato.
Ecco laspetto delle classi binder2nd e binder1 st. template <class UnPred> class unary_negate:
publ ic unary_function<typename UnPred: :argument_type, bool>
templ ate <cl ass Bi nFunc> cl ass bi nderlst:
publ ic unary_function(typename Bi nFunc: :second_argument_type, public:
expl icit unary_negate(const UnPred &pred);
typename Binfunc: :result_type>
bool operator() (const argument_type &v) const;
protected: );
BinFunc op;
template <class BinPred> class binary_negate:
typename BinFunc: :first_argument_type value;
public binary function<typename BinPred::first_argument_type,
public:
- typename BinPred: :second_argument_type,
binderlst(const BinFunc &op,
bool>
const typename BinFunc: :first_argument_type &v);
result_type operator() (const argument type &v) const
}; - . publ ic:
expl icit binary_negate(const BinPred &pred);
template <class BinFunc> class binder2nd: bool operator() (const first_argument_type &vl,
const second_argument_type &v2) const;
p_u_bl ic unary_f!!!l~J:illn!.tY.pename Bi nFunc:: fi rst_argument_type,
typename BinFunc: :result_type> );

protected: In entrambi i casi, operator() restituisce la negazione del predicato specificato


BinFunc op; dapred.
typename Bi nFunc:: second_argument_type val ue;
pub li e: .
bfnder2nd(const BinFunc &op, Gli adattatori
const typename BinFunc: :second argument type &v)
result_type operator() (const argument type &v) co;st. , L'header <functionaf> definisce varie classi chiamate adattatori che consentono di
); - ' adattare u!_l puntatore a funzio!!e alla fonna utilizzabile dalla libreria STL. Ad
esempio si pu usare un adattatore per utilizzare una funzione come strcmp()
<fiircdiBi?Func il tipo di un oggetto funzione binario. Si noti che_entrambe le come un predicato. Esistono adattatori anche per i puntatorLmeml;>:d.
Cl assi e
r.
tano unary functi on. Q.u.esto 11. motivo
bind2ndQ bi d O -
. per cu1: l'oggetto
prodotto da
e ~ .1st pu essere_ut~hzzato ogni volta che richiesto l'impiegona
_ =-:U.D7.tonc unnna. ----- --- . - ..
1-_. Gli adattatori da-puntatore a funzione
Ecoo_~ ad~tttori~:!''''llatore fuoziono
858 CAPITOLO S5 ITERATORI, All:O-G-A-T-E)RI E OGGETT) __F_UNZIONE STL 859

template <class Argument, class Result> template<class Result, class T, class Argument>
pointer_to_unary_function<Argument, ResuJt> mem_funl_t<Result, T, Argument>
ptr_fun_(Result (*fanc)(Argument)); mem_,fun 1(Result (T:: *ftmc)(Argument));
template <class Argumentl, class Argument2, class Result>
pointer_to_binary_function<Agument l, Argument2, Result> Qui mem_fun() restituisce un oggetto di tipo mem_fun_t e mem_fun1 restitu-
ptr_fun(Res~lt (*fenc)(Argumentl, Argument2)); isce un oggetto di tipo mem_fun1_t. Ecco le loro classi:

Qui ptr_fun() restituisce un oggetto di tipo pointer_to_unary_function oppure ternplate <class Result, class T> class mem_fun_t:
pointer_to_binary_function. Ecco le loro classi: public unary_function<T * Result> {
publie:
ternplate <class Argument, class Result> explieit mem_fun_t(Result (T::*fune)());
class pointer to unary function: Result operator() (T *fune) eonst;
publ ic unary_f~nctio~<Argument, Result> };

public: ternplate <class Result, elass T,


explicit pointer to unary function(Result (*func)(Argument)); class Argument> elass mem_funl_t:
Result operator(J(A;:-gument arg) const; publ i e binary_function<T *, Argument, Resul t>
}; publie:
expl ieit mem_funl_ t(Resul t (T: :*/unc)(Argument));
templ ate <cl ass Argumentl, cl ass Argument2, cl ass Res.ult> Result operator() (T *fune, Argument arg) eonst;
cl ass pointer_to_bi nary_functi on: };
public binary_function<Argumentl, Argument2, Result>
Qui il costruttore mem_fun_t richiama la funzione membro specificata ?al
public: suo parametro. Il costruttore mem_fun1_t richiama la funzione membro specifi:
expl i cit pointer_ to_bi nary_ functi on( cata nel suo primo parametro passandole come secondo parametro un valore di
Resul t (*fune) (Argumentl, Argument2)); tipo Argument. . .
Result operator() (Argumentl argl, Argument2 arg2) const; Esistono classi e funzioni parallele per l'utilizzo di indirizzi e membn. Ecco
}; '

la fonna generale delle funzioni:


Per le funzioni unarie, operator() restituisce: template<class Result, class T> - - . -- -
mem.Jun_t<Result, T> mem_fun_ref(Result (T::*fanc)( ));
fanc(arg).
template<class Result, class T, class Argument>
Per le funzioni binarie, operator() restituisce: mem_funl_t<Result, T, Argument>
mem_fun l_ref(Result (T:: *fanc)(Argument));
fanc(argl, arg2);
Ecco l'aspetto delle classi mem_fun_ref e mem_fun1_ref.
Il tipo del.risultato dell'operazione specificata dal tipo generico Result.
template <elass Result, elass-~T> elass mem_fun_ref_t:
Adattatore da puntatore a funzione membro pub]i__unJ!.rY_funetion<T, Result>
Gli adattatori da puntatore a funzione membro sono:
publie:
template<class Result, cla~s J:.> _ __ explteit. mem_fun_ref_t(Result (T::*func)());
mem_fun_t<Result, T> mem::.fun(Res\!ltff~;_'l]'.iz)(J);
--- - ---
860 CAPITOLO 35 861

Resul t operator() {T &fune) const;


}; Devono essere definite anche le operazioni == e I=.
L'allocatore standard allocator, definito nell'header <memory>. Ecco la sua
specifica tempate:
templ ate <cl ass Resul t, cl ass T, cl ass Argument>
class mem_funl_ref_t:
public binary_function<T, Result, Argument> template <class T> class allocator

publ ic: Qui T il tipo degli oggetti che saranno allocati da allocator. allocator defini-
explicit mem funl ref t{Result (T::*func){Argument)); sce i seguenti costruttori:
Resul t operator() (r &fune, Argument arg) const;
};
allocator( ) throw( );
allocator(const allocator<T> &ob) throw( );

35.3 Gli allocatori Il primo crea un nuovo allocatore e il secondo crea una copia di ob.
Per allocator sono definiti gli operatori == e I=. La Tabella 35.2 mostra le
Un allocatore gestisce le operazioni di allocazione della memoria per un container. funzioni membro definite da allocator. . .
Poich la libreria STL definisce un allocatore standard che viene utilizzato auto- definita anche una specializzazione di allocator per puntaton vo1d .
maticamente dai container, la maggior parte dei programmatori non ha bisogno di
conoscere i dettagli del funzionamento degli allopatori o n crearne di nuovi. Tut- Tabella 35.2 Le funzioni membro di allocator.
ta\ia questi dettagli possono essere utili per creare nuove classi di libreria. FUNZIONE DESCRIZIONE
Tutti gli allocatori devono soddisfare vari requisiti. Innanzitutto devono defi-
nire i seguenti tipi: pointer address(reference ob) const;
const_pointer address(const_reference ob) const; Restituisce l'Indirizzo di ob.

const_pointer Un puntatore const pointer a un oggetto di tipo


pointer allocate(size_type nu~, typename
allocator<void>::const_po1nter h = O), Restituisce un puntatore alla memoria anocata le cui . .
value_type. dimensioni sono sufficienti per contenere num oggetti di
const_reference L'indirizzo const di un oggetto di tipo value_type. ttpo T. Il valore di h (che pu essare ignorato) aiuta a
difference_type soddisfare la richiesta.
Pu rappresentare la differenza fra due indirizzi.
pointer Un puntatore a un oggetto di tipo value_type. void construct(pointer ptr, const_reference val); Costruisce In ptr un oggetto di tipo T.
reference L'indirizzo di un oggetto di tipo value_type. void deallocate(pointer ptr, size_type num); Oealloca num oggetti di tiPO-T a partire da ptr. Il valore di
size_type ptr deve essere stato ottenuto tramite a11 oca te O.
Pu contenere le dimensioni del!' oggetto pi esteso
che pu essere allocato. void dest.roy(pointer ptr);
value_type Distrugge l'oggetto In ptr. Viene automaticamente
Il tipo dell'oggetto da allocare. richiamato Il suo distruttore.

size_type max_size( ) const throw( ); Restituisce il numero massimo di oggetti di tipo T che
In secondo luogo devono fornire le seguenti funzioni. possono essere allocati.

address Restituisce un puntatore, dato un indirizzo.


allocate Alloca la memoria.
deallocate Libera la memoria.
max_size
Restituisce il numero massimo di oggetti che possono
essere allocati.
construct Costruisce un oggetto.--
destroy Distrugge un oggetto.
-~--- ----
---~-----'~w-;

- .
: Capitolo 36

La classe stri ng

36.1 La classe basic_strlng


36.2 La classe char_traits

Ouesto capitolo descrive la classe per stringhe del C++


standard. Il linguaggio C++ supporta due tipi di stringhe di caratteri. Il primo
costituito dagli array di caratteri chiusi da un carattere nullo. Questo viene anche
chiamato con il nome di "stringa C". Il secondo modo consiste nell'impiegare una
classe di tipo basic_string. Esistono due specializzazioni di basic_string: string
che supporta stringhe di char e wstring che supporta stringhe di wchar_t (caratteri
estesi). In genere si utilizzano oggetti di tipo string.
La classe basic_string in pratica un container. Questo significa che gli iteratori
e gli algoritmi STL possono operare anche sulle stringhe. Tuttavia le stringhe
hanno anche alcune funzionalit aggiuntive.
char_traits una classe utilizzata da basic_string per definire vari attributi dei
caratteri che costituiscono la stringa. importante comprendere che anche se la
maggior parte delle stringhe composta da caratteri char o wchar_t, le stringhe
basic_string possono operare su qualsiasi oggetto che possa essere utilizzato per
rappresentare un carattere. In questa sezione verranno descritte sia la classe
basic..::.strin-g che la classe char_traits.
~ Per una panoramica sull'uso della classe string, consultare il
Capitolo 24.

36.1 La classe basic_string


La specifica template della classe basic_string la seguente:

template <class CharType, class Attr =char_traits<CharType>,


class Allocator =allocator<T> > class basic_string

- -- ------
LA CLASSE STRING. 865

Qui CharType il tipo di carattere utilizzato, Attr la classe che descrive le vale -1. Questa costante rappresenta la lunghezza massima che pu essere rag- .
caratteristiche dei caratteri e Allocator specifica l' allocatore. La classe basic_string giunta da una stringa. - . . .
ha i seguenti costruttori: Nelle descriiioni, il tipo generico CharType rappresenta 11 tipo di carattere
contenuto in una stringa. Poich i nomi dei tipi fittizi di una classe template so~o
explicit basic_string(const AllOcator &a= Allocator( )); arbitrari, basic_string dichiara versioni typedef di questi tipi. Questo ~ende .pi
basic_string(size_type Zen, Charfype eh , concreti i nomi impiegati per i tipi. Ecco di seguito i tipi definiti da bas1c_stnng:
const Allocator &a= Allocator( ));
basic_string(const Char'fype *str, const Allocator &a= Allocator( )); size_type Un tipo intero che in genere equivale a size_t.
basic_string(const Char'JYpe *str, size_type len, reference Un indirizzo per un carattere all'interno di una
const Allocator &a= Allocator( )); stringa.
basic_string(const basic_string &str, size_type indx =O, const_reference Un indirizzo consta un carattere all'interno di una
size_type Zen=npos, const Allocator &a = Allocator( )); stringa.
template <class nter> basie_string(lnlter start, Inlter end, iterator Un iteratore
const Allocator &a= Allocator( )); const_iterator Un iteratore const
reverse_iterator Un iteratore inverso
La prima forma costruisce una stringa vuota. La seconda forma costruisce const_reverse_iterator Un iteratore inverso const
una stringa contenente Zen caratteri di valore eh. La terza forma costruisce una value_type Il tipo del carattere contenuto nella stringa.
stringa che contiene gli stessi elementi di str. La quarta forma costruisce una allocator_type Il tipo di allocatore.
stringa che contiene una sottostringa di str che inizia da O e si estende per Zen pointer Un puntatore a un carattere all'interno di una
caratteri. La quinta forma costruisce una stringa a partire da un'altra basic_string stringa.
utilizzando la sottostringa che inizia da indx e si estende per Zen caratteri. La sesta const_pointer Un puntatore consta un caratter all'interno di una
forma costruisce una stringa che contiene gli elementi dell'intervallo specificato stringa.
da start ed end. traits_type Un typedef per char_traits<CharType>.
Per basic_string sono definiti i seguenti operatori di confronto: difference_type Un tipo che pu contenere la differenza esistente
fra due indirizzi.
==,<,<=,!=,>,>=
La Tabella 36. l elenca le funzioni membro definite da basic_string. ~oich la
Inoltre viene definito l'operatore+ che fornisce il risultato del concatenamento maggior parte dei programmatori impiega stringhe ~i ~har e per semphfica;--e la
di una stringa con un'altra e gli operatori di 110 <<.e uiHiZ"zabili per eseguire discussione le tabelle usano il tipo string ma le funz10m possono essere applicate
operazioni di input e output di stringhe. anche a og~etti di tipo wstring (o a qualsiasi altro tipo di basic_string).
L'operatore + pu essere utilizzato per concatenare un oggetto stringa con un
altro oggetto stringa oppure un oggetto stringa con una stringa C. Pertanto posso- Tabella 36.1 Le funzioni membro di string.
no essere utilizzate le seguenti varianti: DESCRIZIONE
MEMBRO
Aggiunge strana fine della stringa chiamante. Restituisce
string &append(const string &str};
stringa + stringa *thi s.
stringa + stringa e
Aggiunge una sottostringa di stralla fine ~~a strin~a .
stringa e + stringa string &append(const string &str,
chiamante. La sottostringa da aggiungere inizia da mdx e s
size type indx,
estende per len. caratteri. Restituisce thi s.
size=type Zen};
L'operatore +_pu. essere utilizzato anche per concatenare un carattere alla Aggiunge sir alla fine della stringa chiamante. Restituisce
string &append(const CharType *str);
fine della stringa. La classe basic_string definisce la costante npos e normalmente *this.

(segue)
-.- - --=------- ----------------------L-"__:"=--""~,::;-":_~-~--=----~-=-- - __. __ _
860--' -c API T O L-0--3-6

Tabella 36.1 le funzioni membro di string. (continua)


Tabella 36.1 le funzioni membro di stn"ng. (continua}
_DESCRIZIONE
MEMBRO DESCRIZIONE MEMBRO Confronta una sottostringa di str con una sottoslringa della
int compare(slze type. indx, size_type Zen, stringa chiamante. La sottostringa In the inVOking string inizia
string &append(const CharType *str ~~~?i prfml_11l!171 caratteri di stralla fine della ......,.. da indx e si estende per ~n caratteri. La sottostringa In sir
size_type num); "'""''""ile. RestibJisce *thi s. .,,.. const stri ng &str,-
s i ze_type tndxZ, inizia de illdx2 e si estende per lenZ caiatteri. Restituisce uno
size_type Zen2) const; dei seguenti valori:
string &append(size_type Zen, CharType eh); Aggiunge len caratteri specificati da
chiamante. Restituisce *thi s. eh alla fine deDa stnnga Minore di zero se *this <sir
Zero se *thi s = str
Maggiore di zero se th i s > sir
template<class lnlter> Aggiunge la sequenza specificata da start
string &append(Inlter start, della stringa chiamante. Restituisce *thi s~ end alla fine Confronta sir con la stringa chiamante. Restituisce uno dei
Inlter end); int compare(const CharType *str) const; seguenti valori:
Minore di zero se thi s < str
string &assign(const string &str); Assegna sir aua stringa chiamante RestitulSC8 *thi s.
Zero se *thi s =sir
Maggiore di zero se *thl s >sir
string &~ssign(const string &str, Assegna unadasottostringa di. .str. alla Stringa
sottostrin chmmante
. La
s1ze_type indx, ga assegnare 1n121a da dx Confronta una sottostJ:lnga di sir con una sottostringa della
size_type Zen); caratteri. Restituisce *thi s. m e SI estende per ten
int compare(size type indx, size type Zen, stringa chiamante. La sottostringa deRa stringa chiamante
const CharType *str, - inizia da indx e si estende per ~n caiatteri. La sottostringa di
string &assign(const CharType *str); Assegna Sir alla stringa chiamante. Restituisce *thi s. size_type ZenZ = npos) const; sir inizia da zero e si estende per Jen2 caratteri. Restituisce
uno dei seguenti valori:
. string &~ssign(const CharType str Restitu~ i primi ~caratteri di sir alla stringa chiamante
Asseg Minore di zero se *this <sir
s1ze_type Zen); lSC8 *thl s. . Zero se *thi s =sir
Maggiore di zero se *th i s > str
string &assign(size_type Zen, CharType eh); Assegna~ncaratterispecificatida
chiamante. Restituisce *thi s. eh alla fine deUa siringa
Apartire da indx, copia /en caratteri della stringa chiamante
size type copy(Cha~Type str, nell'array di caratteri puntato da str. Restituisce il numero di
template<cl ass Inlter> Assegna la sequenza specificata da - size type Zen, caratteri copiati.
chiamante. Restituisce *this. start ed endalia stringa
string &assign(Inlter start' In Iter end); size)ype indx = O) const;
Restituisce un puntatore al primo carattere della stringa
reference at(size type inci )- Restituisce l'indirizzo del carattere specificala da indx. const CharType *data( ) const; chiamante.
const_reference at(size_typxe indx) const;
Restituisce true se la stringa chiamante vuota e false
iterat~r begin( ) ; Restitu'isce un iteratore al primo elemento della strin_ga. boo l empty ( ) const; altrimenti.
const_1terator begin( ) const;
Restituisce un iteratore che punta alla fine un lteratore.
const CharType c_str( ) const; Restituisce un puntatore alla versi . iterator end();
carattere nullo) della stringa chia.=i: Stringa C (chiusa dal const_iterator end( ) const;
Rimuove Il carattere puntato da i. Restituisce un iteratore al
slze_type capacity( ) const; Restituisce l apacit corrente dliii Stringa oue51 il lterator erase(iterator i); carattere che segue quello rimosso.
nun:ieedero di cara~eri che pu contenere prima: di dover
rich1 re ultenore memoria. Rimuove i caratteri nell'intervallo compreso fra start ed end.
itera tor erase(i terator start, iterator end); Restituisce un lteratore al carattere che segue l'ultimo
int compare(const string &str) const; Confronta sir con la stringa chiamante Restitu" . carattere rimosso.
seguenti valori: isce uno dei
Minore di zero se *thi s <Sir Apartire da lndx, rimuove len caratteri della stringa
Zero se *this =sir string &erase(slze type indx = O, chiamante. Restituisce thi s.
Maggiore di zero se *thi s > str size_type Zen= npos);
Restituisce l'indice della prima occorrenza di Sir nella stringa
int compare(size_type indx, size type
const string &str) const;- Zen, ~;=~=~n~~ :~ :ttos~nga della stringa chiamante. La size_type find(const string &str, chiamante. La ricerca Inizia dall'indice indx. Se non viene
Restituisce uno dei m e :" est~nde per len caratteri. size_type indx = O) const; trovata alcuna corrispondenza viene restituito npos.
. . seguenti valon:
Minore d1 zero se *thl s <sir Restituisce l'indice della prima &correnza di Sir nella stringa -
Zero se *thi s =sir size_type find(const CharType str, chiamante. La ricerca Inizia dall'ind"ice indx. Se non viene
Maggiore di zero se *th i 5 > sir .size_type indx =O) const; trovata alcuna corrispondenza viene restituito npos.

(segue) ---(segue)
868- CAPITOLO 36
ouo

Tabella 36.1 Le funzioni membro di string.


MEMBRO
..
(continua)

DESCRIZIONE
Tabella.36.1 Le funzioni membro di string.
MEMBRO
(conttnua)

DESCRIZIONE
size type find(const CharType *str;
- si ze type indx, Restituisce l'indice della prima occorrenza dei primi /en size type find_last_of(const CharType str, Restituisce l'indice dell'ultimo carattere della stringa .
siz(type Zen) const; caratteri di sir nella stringa chiamante. la ricerca inizia -size_type indx npos) const; chiamante che corrisponde con un carattere di sir. La ~rea
dall'indice indx. Se non viene trovata alcuna corrispondenza inizia dall'indice inclx. Se non viene trovata alcuna comspon
viene restituito npos.
danza viene restituito npos.
size type find(CharType eh,
- size_type tndx = O) const; Restituisce l'indice della prima occorrenza di eh nella stringa size type find_last_of(const CharType str, Restituisce l'indice dell'ultimo carattere della stringa
chiamante. Se non viene trovata alcuna corrispondenza viene - size type tndx, chiamante che conisponde con un carattere dei primi le;1
restituito npos.
size::_type Zen) const; caratteri di sir. la ricerca inizia dall'Indice incbc. Se non Viene
size type find first of(const string &str, trovata alcuna corrispondenza viene restituito npos.
- size_tl'P"e indx = O) const; Restituisce l'indice del primo carattere della stringa chiamante
che corrisponde con un carattere di str. la ricarca inizia size type find_last_of(CharType eh, Restituisce l'indice dell'ultima occorrenza di eh della ~a
dall'indice indx. Se non viene trovata alcuna corrispondenza - size_type indx = npos) const; chiamante. la ricerca Inizia dall'Indice incbc. Se non Viene
. viene restituito npos.
trovata alcuna corrispondenza viene restituito npos.
size_type find_first_of(const CharType str,
size_type indx = O) const; Restituisce llndice del primo carattere della siringa Chiamante size type find_last_not_of( Restituisce l'indice dell'ultimo carattere della stringa .
che conisponde con un carattere di str. la ricerca inizia - const string &st-r, chiamante che non corrisponde ad alcun carattere di Sir. La
dall'indice indx. Se non viene trovata alcuna corrispondenza size_type indx = npos) const; ncarca Inizia dall'indice indx. Se non viene trovala alcuna
viene restituito npos.
corrispondenza viene restituito npos.
size type find first of(const CharType *str,
- size t)pe indx, Restituisce l'indice del primo carattere della stringa chiamante size type find_last_not_of( Restituisce l'Indice dell'ultimo carattere della stringa
size:type Zen) const; che corrisponde a un carattere dei primi len caratteri of sir. La - const CharType *str, chiamante che non corrisponde ad alcun carattere di sir. La
ricerca inizia dall'indice indx. Se non viene trovata alcuna size_type indx = npos) const; ricerca inizia dall'indice indx. Se non viene trovata alcuna
corrispondenza viene restituito npos. corrispondenza viene restituito npos.
size_type find_first_of(CharType eh,
size_typt? indx = O) const; Restituisce l'indice della prima occorrenza di eh nella stringa size type find_last~not_of( Restituisce l'indice dell'ultimo carattere della stringa . . .
chiamante. la ricerca inizia dall'indice indx. Se non viene - const CharType str, chiamante che non conisponde ad alcun carattere dei pnrrn
trovata alcuna corrispondenza viene restituito npos. size type tndx, 1en caratteri di str. la ricerca inizia dall'indice inclx. Se non
size type find first not of( size:type Zen) const; viene trovata alcuna corrispondenza viene restituito npos.
-const strii.g &str, - Restituisce l'indice del primo carattere della stringa chiamante
size_type indx = O) const; che non corrisponde ad alcun carattere di str. La ricerca inizia size type find_last_not_of(CharType eh, Restttulsce l'indice dell'ultimo carattere della stringa
dalrindice indx. Se non viene trovata alcuna corrispondenza - size_type indx = npos) const; chiamante che non corrisponde a eh. la ricerca inizia
viene restituito npos.
dall'indice indx. Se non viene trovata alcuna corrispondenza
size type find first not of( viene restituito npos.
- const CharType *str~ Restituisce l'indice del primo carattere della stringa chiamante
size_type indx O) const; che non corrisponde ad alcun carattere di str. la ricerca inizia allocator_type get_allocator( ) const; Restituisce l'allocatore della stringa
dall'indice indx. Se non viene trovata alcuna corrispondenza
viene restituito npos.
iterator i nsert(iterator i, Inserisce eh appena prima del carattere specificato da indx.
size type ..find first not of( const CharType &eh ) ; Viene restituito un iteratore al carattere
-const CharType *str~ Restituisce l'indice del primo carattere della stringa chiamante
size_type indx, che non corrisponde ad alcun carattere dei primi /en caratteri string &insert(size_type indx, Inserisce sir nella stringa chiamante all'indice specificato da
size_type len) const; of str. La ricerca inizia dall'indice indx. Se non viene trovata
const string &str); lndK. Restituisce *thi s.
alcuna corrispondenza viene restituito npos.
size type find first not of( string &insert(sze_type tndxl, Inserisce una sottostringa di str nella ~a ~h!a~nt~
-CharType eh, - - Restituisce l'indice del primo carattere della stringa chiamante
const string &str, alfindice specificato da indx1. la sottostringa 1mZJa da imix2 e
size_type indx = O) const; che non corrisponde a eh. la ricerca inizia dall'indice indx. Se
si ze type indx2, si estende per len caratteri. Restituisce *thi s.
non viene trovata alcuna corrispondenza viene restituito npos.
size:type Zen);
size_type find_last_of(const string &str,
size_type indx = npos) const; ResUtuisce l'indice dell'ultimo carattere-della stringa
string &insert(size_type indx, Inserisce str nella stringa chiamante all'indice specificato da
chiamante che conisponde con un carattere di str. La ricerca
const CharType *s~r); indx. Restituisce *thi s.
inizia dall'indice indx. Se non viene trovata alcuna corrispon
denza viene restituito npos.
string &insfrt(size_type indx, Inserisce i primi len caratteri di str nella stringa chiamante
const CharType str, all'indice specificato da indx. Restituisce *thi s.
(segue) size_type Zen);

(segue)


~--
870 CAPITOLO 36 LA CLASSE STRING 971-

Tabella 36.1 Le funzioni membro di string. (conffnua) Tabella 35;1 Le funzioni membro di string. (conttnua)

MEMBRO DESCRIZIONE MEMBRO DESCRIZIONE


str!ng &insert(size type indx, Inserisce /en caratteri di valora eh nella stringa chiamante string &replace(size_typ indx, Sostituisce fino a lenl caratteri della stringa chiamante a
sfze type Zen~ all'Indice specificato da indx. Restituisce *thi s. si ze type Zenl, partire da lndx con I /en2 caratteri specificati da cli.
CharType eh); size-type len2, Restituisce *thi s.
CharType eh);
vofd fnsert(iterator i, size type Zen, Inserisce /en copie di eh appena prima dell'elemento
const CharType &eh) - specificato da i. string &replace(iterator start, Sostituisce l'Intervallo specificato da start ed end con str.
iterator start, Restituisce *thi s.
template <class Inlter> )nserisce la sequenza definita da start ed end appena prima const string &str);
vofd insert(iterator i, Inlter start, dell'elemento specificato da i.
Initer end); string &replace(iterator start, Sostituisce l'intervallo specificato da start ed end con str.
iterator start, Restituisce *this.
slze_type length( ) const;. Restituisce Hnumero di caratteri contenuti nella stringa. const CharType *str);

sfze_type max_size( ) const; string &replace(iterator start, Sostituisce l'intervallo specificato da start ed end con i primi
Restituisce il numero massimo di caratteri che la stringa pu
contenere. i tera tor end, /en caratteri di str. Restituisce *thi s.
const CharType *str,
reference operator[ ] (size type tndx) const; Restituisce l'indirizzo del carattere specificato da indx. size_type Zen);
const_reference operator[ ] (size_type indx)
const; string &replace(iterator start, Sostituisce l'intervallo specificato da start ed end con I /en
itera tor end, si ze type Zen, caratteri specificati da eh. Restituisce *th i s.
stri ng &operator(const string &str); Assegna alla stringa chiamante la stringa o il carattere CharType eh); -
strfng &operator-(const CharType str); specificato. Restituisce *thi s.
strfng &operator-(CharType eh); template <class In!ter> Sostituisce l'Intervallo speciflCalo da starti ed end1 con i
string &replace(itrator starti, caratteri specificati da start2 ed end2.
stri ng &operator+={const stri ng &str); Aggiunge la stringa o il carattere specificato alla fine della i tera tor endI, Restituisce *thi s.
stri ng &operator+=(const CharType *str); stringa chiamante. Restituisce *thi s. Inlter start2,
string &operator+={CharType eh); In Iter end2);

reverse_iterator rbegi n( ) ; Restituisce un iteratore inverso alla fine della stringa. void reserve(size_type num = O); Imposta la capacit della stringa a un valore uguale ad
const_reverse_iterator rbegin( ) const; almeno num.

reverse_ iterator rend{ ) ; Restituisce un iteratore inverso all'inizio della stringa. void resize(size type num) Cambia le dimensioni della stringa a quelle specificate da
const_reverse_iterator rend( ) const; void resize(size=type num, CharType eh); num. Se la stringa deve essere allungata, alla fine vengono
aggiunti elementi con il valore specificato da eh.
strfng &replace(size type indx, Sostituisce fino a len caratteri della stringa chiamante, a
size-type Zen, partire da lndx con la stringa contenuta in str. Restituisce size_type rfind(const string &str, Restituiscel'lncf1C9dell'ultimaoccorrenzadistrnellastringa
const string &str); *this. size_type indx = npos) const; -chiamante: La ricerca Inizia dall'indice lndx. Se non viene
trovata alcuna corrispondenza restituisce npos.
string &replace(size type indxl, Sostituisce fino a fan 1 caratteri della stringa chiamante a
size-type lenl, partire da indx1 con i /en2 caratteri della stringa contenuta in size_type rfind(const CharType *str, Restituisce l'indice dell'ultima occorrenza di srr nella stringa
const stri ng &str, sir che inizia in lndx2. Restituisce *thi s. size_type tndx = npos) const; chiamante. La ricerca inizia dall'indice indx. Se non viene
si ze type indx2, trovata alcuna corrispondenza rastituisce npos.
size=type Zen2);
size_type rfind(const CharType *str, Restituisce l'indice dell'ultima occorrenza dei primi /en
string &replace(size type tndx, Sostituisce fino a len caratteri della stringa chiamante, a size type tndx, caratteri di str nella stringa chiamante. La ricerca Inizia
size type Zen, partire da indx con la stringa contenuta in sir. Restituisce size)ype Zen) const; dall'indice indx. Se non viene trovata alcuna corrispondenza
const CharType *str); *this. restituisce npos.

strfng &replace(size type indxl, Sostituisce fino a lent caratteri della stringa chiamante a size_type rfind(CharType eh, Restituisce l'indice dell'ultima occorrenza di eh nella stringa
size type Zenl, partire da lndx1 con i /en2 caratteri della stringa contenuta in size_type indx = npos) const; chiamante. La ricerca Inizia dall'indice lndx. Se non viene
const- CharType *str, strche Inizia da indx2. Restituisce *thi s. trovala alcuna corrispondenza restituisce npos.
size_type len2);
size_type size( ) const; Restituisce il numero di caratteri attualmente contenuti nella
stringa.
--~----- {seefe}
(segue}
-----~-:-.~ __.,.._-:
----L-A C-LAS SE S Tf! 1-N G 873
872 CAPITOLO 36

Tabella 36.1 Le funzioni membro di string. (continua)


Tabella 36.2 Le funzioni membro della classe char traits. (continua)

MEMBRO DESCRIZIONE
MEMBRO DESCRIZIONE
static int compare(const char_;type strl, Confronta num caratteri di str1 con quelli di st12. Se le
string substr(size type indx = O, Restituisce una sottostringa di len caratteri a partire da indx const char type str2, stringhe sono uguafi restituisce zero. Altrimenti, restituisce un
size)ype Zeri npos) const; nella stringa chiamante. valore minore di zero se stri minore di sl!2 o maggiore di
size_t num);
zero se str1 maggiore di str2.
void swap(string &str) Scambia i caratteri contenuti nella stringa chiamante con
quelli di ob. Copia num caratteri di trom in to. Restituisce to.
static char_type copy(char_type *to,
const char type *from,
size_t num);

static int.:.type eof( ); Restituisce il codice di fine file.


36.2 la classe char_traits Confronta ch1 con ch2 e restituisce true se I caratteri sono
static bool eq(const char_type &ehl,
const char_type &ch2); uguali e fa 1se altrimenti.
La classe char_traits descrive vari attributi associati a un carattere. 'Ecco la sua
specifica template: stati e bool eq_int_type{const int_type &chl, Restituisce true se ch1 uguale a ch2e false altrimenti.
const int_type &eh2);

template<class Char'fype> struct char~traits stati e const char type *find(const char_type str, Restituisce un puntatore alla prima occorrenza di eh in
size t num, str. Vengono esaminati solo i primi numcaratteri. In caso
const char_type *eh); di fallimento restituisce un puntatore nullo.
Qui CharType specifica il tipo del carattere.
static size_t len~th(const char_type *str); Restituisce la lunghezza di srr.
La libreria C++ fornisce due specializzazioni di phar_traits: una per caratteri char
e una per caratteri wchar_t. La classe char_traits dflnisce i cinque tipi seguenti. static bool lt(const char_type &ehl, Restituisce true se ch1 minore di ch2e false altrimenti.
const char_type &eh2);

char_type Il tipo del carattere. Si tratta di un typedef per stati e char_type move{char_type *to, Copia num caratteri di from in to. Restituisce to.
CharType. const char_type *from,
size_t num);
int_type Un tipo intero che pu contenere un carattere di tipo
char_type o il carattere EOF. Se eh non il carattere EOF, restituisce eh. Altrimnti,
stati e int_type not_eof(const int_type &eh);
restituisce il carattere EOF.
off_type Un tipo intero che pu rappresentare un offset in una
stringa. stati e state_type get_state(pos_type pos); Restituisce lo stato della conversione.
pos_type Un tipo intero che pu rappresentare una posizione in
stati e char_type to_char_type(const int_type &eh): Converte eh in un char::tYPe e-restituisce li risultato.
uno stream.
state_type Un oggetto che contiene lo stato di una conversione static int_type to_int_type(const char_type &eh); Convertechlnunint_typeerestituisceilrisultato.
(si applica a caratteri multibyte).

La Tabella 36.2 elenca le funzioni membro della classe char_traits.

Tabella 36.2 Le funzioni membro della classe char_traits.


MEMBRO DESCRIZIONE
stati e void assign{char type &chl, Assegna ch2 a chi.
const char_type &ch2);

static char_type' assign{char type str, Assegna ch2 ai primi num caratteri di sir. Restituisce str.
size -t num,
char)ype ehz);

(seguer-
: Capitolo 37

Le classi per numeri

37.1 La classe complex


37.2 La classe valarray
: 37.3 Gli algoritmi numerici

Una delle funzionalit aggiunte durante la standardiz-


zazione del linguaggio C++ la libreria di classi numeriche. Queste classi aiutano
nello sviluppo di programmi numerici. Molte delle funzioni membro di queste
classi corrispondono alle funzioni ereditate dalla libreria C. La differenza consi-
ste nel fatto che molte delle funzioni numeriche descritte in questo capitolo ope-
rano su oggetti di tipo valarray che in pratica sono array di valori oppure su oggetti
di tipo complex 'che rappresentano un numero complesso. Grazie all'introduzione
delle classi numeriche, il C++ standard ha esteso le possibilit di programmazio-
ne in campo numerico.

37.1 La classe complex

La classe complex, che rappresenta numeri complessi, definita dall'header


<complex>--Tale file definisce anche una serie di funzioni e operatori applicabili a
oggetti di tipo complex.
Ecco la specifica template di complex:

template <class T> class complex

Qui T specifica il tipo utilizzato per memorizzare i componenti di un numero


complesso. Vi sono tre specializzazioni predefinite di complex:

- class complex<float>-
class complex<double>
class complex<long double>

--- -- ---------
- ~---- - --------
876------c-A Pn ol o s 1

La classe complex ha i seguenti costruttori: #include <complex>


using namespace std; -
complex(const T &real= T( ), const T &imaginary T( ));=
complex(const complex &ob); int-main()
template <class Tl> complex(const complex<Tl> &ob); {
complex<double> cmpxl(l, O);
Il primo costruisce un oggetto complex con un componente reale e un compo- comp 1ex<doub1 e> cmpx2 ( 1, 1);
nente immaginario. Se non vengono specificati questi valori assumono il valore
cout cmpx 1 " " << cmpx2 << endl ;
standard O(zero). il secondo crea una copia di ob. Il terzo crea un oggetto complex
a partire da ob.
omp 1ex<doub1 e> cmpx3 = cmpx 1 + cmpx2;
Per gli oggetti complex sono definite le seguenti operazioni: cout cmpx3 endl ;

+ * cmpx3 += 10;
-= += I= *= cout cmpx3 endl ;
= -- !=
return O;
Gli operatori che non eseguono assegnamenti sono modificati tramite
overloading in tre modi. Uno per operazioni riguardanti un oggetto complex a
sinistra un oggetto scalare a destra, un altro per operazioni che riguardano un Ecco l'output del programma:
oggetto scalare a sinistra e un oggetto complex a destrii e infine uno per operazioni
che riguardano due oggetti complex. Ad esempio, sono consentite le seguenti opera- (1,0) (1,1}
zioni: (2, 1)
(12, 1)

complex_ob + scalar
scalar + complex_ob
Tabella 37.1 Le funzioni definite dalla classe compi ex.
complex_ob + complex_ob
FUNZIONE DESCRIZIONE
Le operazioni che riguardano quantit sc;al~- ~~~~~-o solo il componente templ ate <cl ass T> Restituisce' il valore assoluto di ob.
reale. La classe complex definisce due funzioni membro: real() e imag() rappre- T abs(const complex<T> &ob);
sentate di s-eguito:
template <cl ass T> Restituisce rangolo di fase di ob.
T arg(const complex<T> &ob);
T real( ) const;
template <class T> complex<T> RestillJisceilconlugatodiob.
T imag( ) const; conj(const complex<T> &ob);

template <class T> Restituisce il coseno di ob.


La funzione real() restituisce il componente reale dell'oggetto chiamante e complex<T> cos(const complex<T> &ob);
ovviamente la funzione imag() restituisce il componente immaginario. Per gli
oggetti complex sono definite le funzioni elencate nella Tabella 37 .1. template <cl a~s T> Restituisce il coseno iperbolico di ob.
complex<T>
Ecco un semplice programma che-Illustra l'uso della classe complex: cosh(const complex<T> &ob);

template <class T> Restituisce e"'.


Il Illustra l'uso della classe complex. compi ex<T>
#include <iostream> exp(const complex<T> _&!'b);

- ---- -----
--------- ~ -- - - - ------ ____." --:" ,

LE CLASSL PER NUMERI 879


878 CAPITOLO 37

Tabella 37.1 Le funzioni definite dalla classe complex. (continua) Tabella 37.1 Le funzioni definite dalla classe complex. (conunuaJ
DESCRIZIONE
FUNZIONE DESCRIZIONE FUNZIONE
Restituisce la tangente iperbolica di ob.
temp 1ate <cl ass T" Restituisce Hcomponente immaginario di of ob. template <class T>
T imag(const complex<T> &ob); complex<T>
tanh(const complex<T> &ob);
template <class T> RestibJisce il logaritmo nabJrale di ob.
complex<T>
1og (cons t comp 1ex<T> &ob) ;

template <cl ass T> RestibJisce il logaritmo In base 10 di ob. 37.2 La classe valarray
complex<T>
loglO(const complex<T> &ob);
L'header <valarraY> definisce una serie di classi per il supporto degli array di
template <class T> RestibJisce la magnitudine di ob elevato al quadrato. numeri. La classe principale valarray che definisce un array monodimensionale
T norm(const complex<T> &ob); di valori. In tale classe definita un'ampia variet di operatori e funzioni membro
template <class T> RestibJisce un numero complesso con la magnibJdlne e anche un gran numero di funzioni non membro. Anche se la descrizione di
complex<T> specificata da ve rangolo di fase theta. valarray che verr fornita in questa sezione dovrebbe essere sufficiente per la
polar(const T &v, const T &thetaO);
maggior parte delle situazioni, coloro che fossero particolarmente interessati al-
template <class T> RestibJisce b'. i' elaborazione numerica dovranno studiare valarray in maggior dettaglio. Un'ul-
complex<T>
pow(const complex<T> &b, int e); tima annotazione: anche se valarray molto estesa, la maggior parte delle sue
operazioni piuttosto intuitiva.
template <class T> Reslifiiisce b'. La classe valarray ha la seguente specifica template:
complex<T>
pow(const complex<T> &b,
const T &e);
template <class T> class valarray
template <class T> Restituisce b'.
complex<T> La classe definisce i seguenti costruttori:
pow(const complex<T> &b,
const complex<T> &e);

template <class T> Restituisce b'. valarray( );


complex<T> explicit valarray (size_t num);
pow(const T &b, valarray(const T &v, size_t num);
const complex<T> &e);
valarray(const T *ptr, size_t num);
template <class T> AestibJisce il componente reale di ob. valarray(const valarray<T> &ob);
T real (const complex<T> &ob);
valarray(const slice_array<T> &ob);
template <class T> AestibJisce il seno di ob. valarray(const gslice_array<T> &ob);
complex<T> sin(const complex<T> &ob); valarray(const mask_array<T> &ob);
template <class T> RestibJlsce il seno iperbolico di ob. valarray(const indirect_array<T> &ob);
complex<T>
sinh(const complex<T> &ob);
Il primo costruttore crea un oggetto '\'Uoto. Il secondo crea un valarray di lun-
tJ?mPlate <class T> AestibJlsce _~ radice quadrata di ob. ghezza man. Il terzo crea un valarray di lunghezza nume inizializzato a v. Il quarto
complex<T> crea un valarray di lunghezza num e lo inizializza con gli elementi puntati da ptr. -
sqrt(const complex<T> &ob);
Il quinto crea una copia di ob. I quattro costruttori successivi creano un valarray a
template <class T> AestibJisce la tangente di ob. partire da una delle classi helper di valarray. Questi costruttori non :vengono utiliz~
- - - - - - complex<T>
tan (cons t comp 1ex<T>:__!ob) ; zati direttamente dal programma-ma-vengono richiamati_11_ytorpaticamente quan-
do si svolgono le operazioni.definite dayalarray. - - - -- -
(ssgue} - ------ - - - -
880--6--A P l O l O 3 7 -- .~ - __ ___., ~"':

.LE CLASSI -PE11 NUMt::,,, 001

Per valarray sono definiti i seguenti operatori:


Tabella 37;2 Le funzioni membro di valarray. (continua)
+ * I FUNZIONE DESCRIZIONE
-= += I= *= val array<T> operator+( ) const; Pi unario applicato a ciascun elemento dell'array

Il=
-- l= << chiamante. Restituisce l'array risultante.
% %= va 1array<T> operator-( ) const; Meno unario applicato a ciascun elemento dell'array
I I= & chiamante. Restituisce l'array risultante.
&= []
va 1array<T> operator-( ) const; NOT unario bita-blt applicato a ciascun elemento
dell'array chiamante. Restituisce l'array risultante.
Questi ~peratori hanno varie forme modificate tramite overload h
ranno descntte nelle relative tabelle. mg c e ver- valarray<T> operator! ( ) const; NOT unario logico applicato a ciascun elemento
dell'array chiamante. Restituisce l'array risultante.
La Tabella 32.3 elenca le funzioni e gli operatori membro defi1 e1 . I
i:~~~e~l; ~7( elenca~ ~nz~oni operatore ~on membro definite ~~ ::i:~a~~i~
1
val array<T> &operator+= (const T &v) const; Somma va ciascun elemento dell'array chiamante.
Resbluisce l'Indirizzo dell'array chiamante.
. e enca e nz1oru trascendentali definite per valarray.
valarray<T> &operator-=(const &v) const; Sottrae v da ciascun elemento dell'array chiamante.
Restituisce l'indirizzo dell'array chiamante.
Tabella 37.2 Le funzioni membro di valarray.
FUNZIONE valarray<T> &operator/= (const T &v) const; Divide ciascun elemento dell'array chiamante per v.
DESCRIZIONE Restituisce l'Indirizzo dell'array chiamante.
valarray<T> apply(T func(T)) const;
val array<T> apply(T func(const T &ob)) const; Applica lune(} alrarray chiamante e restituisce un array valarray<T> &operator*=(const T &v) const; Moltiplica ciascun elemento dell'array chiamante per v.
contenente. il risultato. Restituisce l'indirizzo dell'array chiamante.
valarray<T> cshift(int num) const;
Esegue una rotazione a sinistra di num posizioni va 1array<T> &operator%=(const T &v) const; Assegna a ciascun elemento dell'array chiamante il
d.ell'array chiamante (owero esegue uno scorrimento resto della divisione per v. Restituisce rindirizzo
~1rcolare a sinistra). Restituisce un array contenente il dell'array chiamante.
risultato.
T max ( ) const; valarray<T> &operator"=(const T &v) const; Esegue uno XOR di v con ciascun elemento dell'array
R~stituisce il valore pi elevato contenuto nell'array chiamante. Restituisce l'indirizzo dell'array chiamante.
chiamante.
T min( ) const val array<T> &operator&=(const T &v) const; Esegue un ANO di vcon ciascun elemento dell'array
R~slituisce il valore pi basso contenuto nell'array chiamante. Restituisce l'indirizzo dell'array chiamante.
chiamante.
val array<T> valarray<T> &operatorl=(const T &v) const; Esegue un OR di v con ciascun elemento dell'array
&operator=(const valarray<T> &ob); Assegna gll elementi di ob agli elementi correspondenli chiamante. Restituisce l'Indirizzo dell'array chiamante.
nell'array chiamante. Restituisce l'indirizzo dell'array
chiamante. valarray<T> &operator(const T &<id const; Esegue uno scorrimento a sinistra di v posizioni di
valarray<T> &operator=(const T &v); ciascun elemento dell'array chiamante. Restituisce
Assegna a ciascun elemento dell'array chiamante il l'indirizzo dell'array chiamante.
valore v. Restituisce l'indirizzo dell'array chiamante.
va 1array<T> valarray<T> &perator=(const T &v) const; Esegue uno scorrimento a destra di vposizioni di
&operator=(const slice_array<T> &ob); Assegna un sottoinsieme. Restituisce l'indirizzo ciascun elemento dell'array chiamante. Restituisce
dell'array chiamante. l'indirizz dell'array chiamante.
valarray<T>
&operator=(const gslice_array<T> &ob); Assegna un sottoinsieme. Restituisce l'indirizzo valarray<T> Somma gli elementi corrispondenti delfarray chiamante
dell'array chiamanle. &operator+=(const valarray<T> &ob) const; e di ob. ResUtuisce l'indirizzo dell'array chiamante.
va array<T>
&operator=(const mask_array<T> &ob); Assegna un sottoinsieme. Restituisce l'indirizzo val array<T> Sottrae gli elementi di ob dagli elementi corrispondenti
dell'array chiamante. &operator-=(const valarray<T> &ob) const; dell'array chiamante. Restituisce l'indirizzo dell"array
val array<T> chiamante.
&operator=(const indirect_array<T> &ob); Assegna un sottoinsieme. Restituisce.l'indirizzo
dell'array chiamante.
(segue)

(segue/

----
882 CAPITOLO 37
L E C L-A.S S I P E R N U M E R I 883
Tabella 37.2 Le funzioni membro di valarray. (continua)
Tabella 37.2 Le funzioni membro di valarray. (continua)
FUNZIONE DESCRIZIONE
FUNZIONE DESCRIZIONE
valarray<T>. __
Divide gli elementi de!l'array chiamante per gli elementi
- indirect array<T>
&operator/=(const val array<T> &ob) const; Restituisce il sottoinsieme specificato.
corrispondenti di ob. Restituisce l'indirizzo delfarray operator[ ] (const valarray<size_t> &ob);
chiamante.
valarray<T> ' . va 1array<T> Restituisce il sottoinsieme specificato.
&operator'=(consl valarray<T> &ob) const; Moltiplica gli elementi corrispondenti dell'array
operator[ ] (const valarray<size_t> &ob)
chiamante e di ob. Restituisce l'indirizzo dell'array const;
chiamante.
va 1array<T> void resize(size_t num, T v = T( ) ) ; Ridimensiona l'arrey chiamante. Se devono essere
Divide gli elementi dell'array chiamante per gfi elementi
&operator%=(const valarray<T> &ob) const; aggiunti degli elementi, gH viene assegnato il valore
corrispondenti d ob e memorizza il resto. Restituisce
l'indirizzo dell'array chiamente. specificato da v.
va 1a rray<T> size_t size( ) const; Restituisce le dimensioni (in termini di numero di
Applica l'operatore XOR agli elementi corrispondenti di
&operator"=(const valarray<T> &ob) const; ob e dell'arrey chiamante. Restituisce l'indirizzo delrarray
elementi) delrarray chiamante.
chiamante.
valarray<T> shift(int num) const; Fa scorrere l'array chiamante verso sinistra di num
valarray<T> posizioni. Restituisce un array contenente il risultato.
Applica l'operatore ANO agli elementi corrispondenti di
&operator&=(const valarray<T> &ob) const; ob e dell'array chiamante. Restituisce l'indirizzo delfarray
chiamante. T sum( ) const; Restituisce la somma dei valo.ri contenuti nell'array
chiamante.
va 1array<T>
Applica l'operatore OR agli elementi corrispondenti di ob
&operatorl=(const valarray<T> &ob) const; e delrarrey chiamante.
Restituisce l'indirizzo dell'array chiamante.
va 1array<T> Tabella 37.3 Le funzioni non membro definite per valarray.
Gli elementi dell'array chiamante vengono falli scorrere
&operator=(const valarray<T> &ob) const; a sinistra del numero di posizioni speciftcate dagli FUNZIONE DESCRIZIONE
elementi corrispondenti di ob. Restttuisce l'indirizzo
dell'array chiamante. template <class T> valarray<T> Somma va ciascun elemento di ob. Restituisce un array
va 1array<T> operator+ (cons t va 1array<T> ob, contenente il risultato.
Gli elementi dell'array chiamante vengono falli scorrere const T &v);
&operator=(const valarray<T> &ob) const; a destra del numero di posizioni specificate dagU
elementi corrispondenti di ob. Restituisce l'indirizio template <class T> valarray<T> Somma va ciascun elemento di ob. Restituisce un array
dell'array chiamante. operator+(const T &v, contenente il risultato.
const va 1array<T> ob);
T &operator[] (size_t indx) ; Restituiscel'indirizzodell'elementochesitrovaalfindice
specificato. template <class T> valarray<T> . Somma ciascun elemento di obi con l'elemento .
operator+(const valarray<T> obl, - - - ciimspondente di ob2. Restituisce un array contenente 11
T operator[] (size_t indx) const; Restituiscefivalorechesitrovaall'indicespeciftcato. const val array<T> &ob2); risultato.
sli ce_array<T> operator[ ](s 1i ce ob); Restituisce il sottoinsieme specificalo.
template <class T> valarray<T> Sottrae vda ciascun elemento di ob. Resliluisce un
operator-(const valarray<T> ab, array contenente il rtsultato.
valarray<T> operator[ ](slice ob) const; Restituisceilsottoinsiemespecificato. const T &v);
gsl ice_array<T> operator[ ] (const gslice &ob); Restituisce il sottoinsieme specificato. template <class T> valarray<T> Sottrae ciascun elemento di ob da v. Resliluisce un
operator-(const T &v, array contenente il risultalo.
va 1array<T> operator[ ]( const gs 1i ce &ob) const; Restituisce il sottoinsieme specificato. const va 1array<T> ob);
mask array<T> Restituisce il sottoinsieme specificato. template <class T> valarray<T> Sottrae ciascun elemento di ob2 dall'elemento .
-operator[ ] (valarray<bool> &ob);
operator-(const valarray<T> _obl, corrispondente di obt. Restituisce un array contenente li
const val array<T> &ob2); risultato. -
valarray<T> Restituisce il sottoinsieme specificato.
operator[ ] (valarray<bool> &ob) const;
template-<class T> valarray<T> Moltiplica ciascun elemento di ob per v. Restituisce un
operator*(const valarray<T> ob, array contenente il risultato.
const T &v);
- - - - -r~ueJ
(segue)
884 -(;A..p.-i-+-& L O 3 7 885

Tabella 37.3 Le funzioni non membro definite per valarray. (confinua) Tabella 37.3 Le funzioni non membro definite per valarray. (continua)
FUNZIONE DESCRIZIONE FUNZIONE DESCRIZIONE

template <class T> valarray<T> Moltiplica ciascun elemento di ob per v. Restituisce un template <class r... valarray<T> Applica l'operatore ANO fra ciascun elemento di obt e
operator*(const T &v, array contenente il risultato. operator&(const valarray<T> obl, l'elemento corrispondente di ob2. Restituisce un array
const valarray<T> ob)_; const va1array<T> &ob2); contenente il risultato.

templ ate <cl ass T> va 1array<T> Moltiplica gli elementi corrispondenti di obi e ob2. template <class T> valarray<T> Applica l'operatore OR fra ciascun elemento di ob e v.
operator*(const valarray<T> obl, Restituisce un array contenente il risultalo. operatori (const valarray<T> ob, Restituisce un array contenente Il risultato.
const val array<T> &ob2); const T &v);

template <class T> valarray<T> Divide ciascun elemento di ob per v. Restituisce un array template <class T> valarray<T> Applica l'operatore OR fra ciascun elemento di ob e v.
operator/ (const va 1array<T> ob, contenente il risultato. operatori (const T &v, Restituisce un array contenente il risultato.
const T &v); const val array<T> ob);

template <class T> valarray<T> DMde v per ciascun elemento di ob. Restituisce un array template <class T> valarray<T> Applica l'operatore OR fra ciascun elemento di ob1 e
operator/(const T &v, contenente il risultato. operatori (const valarray<T> obl, l'elemento corrispondente di ob2. Restituisce un array
const valarray<T> ob); const val array<T> &ob2); contenente il risultato.

template <class T> valarray<T> Divide ciascun elemento di obi per l'elemento template <class T> valarray<T> Fa scorrere a sinistra ciascun elemento di ob per il
operator/(const valarray<T> obl, corrispondente di ob2. Restituisce un array contenente il operator(const val array<T> ob, numero di posizioni specificate da v. Restituisce un array
const valarray<T> &ob2); risultato. const T &v); contenente il risultato.

template <class T> valarray<T> Ottiene il resto della divisione di ciascun elemento di ob template <class T> valarray<T> Fa scorrere a sinistra v per il numero di posizioni
operator%(const valarray<T> ob, per v. Restituisce un array contenente il risultato. operator<<(const T &<v, specificate dagli elementi di ob. Restituisce un array
const T &v); const va 1array<T> ob); contenente il risultato.

template <class T> valarray<T> Ottiene if resto della divisione di v per ciascun elemento template <class T> valarray<T> Fa scorrere a sinistra ciascun elemento di obi per il
operator%(const T &v, di ob. Restituisce un array contenente il risultato. operator(const val array<T> obl, numero di posizioni specificate dal corrispondenti
const valarray<T> ob); const valarray<T> &ob2); elementi di ob2. Restituisce un array contenente il
risultato.
template <class T> valarray<T> Ottiene il resto della divisione di ciascun elemento di obi
operator%(const valarray<T> obl, per l'elemento corrispondente di ob2. Restituisce un template <class T> valarray<T> Fa scorrere a destra ciascun elemento di ob per il
const valarray<T> &ob2); array contenente il risultato. operator(const val array<T> ob, numero di posizioni specificate da v. Restituisce un array
const T &v); contenente Il risultato.
template <class T> valarray<T> Applica l'operatore XOR fra ciascun elemento di ob e v.
operator"(const valarray<T> ob, Restituisce un array contenente il risultato. template <class T> valarray<T> Fa scorrere a destra v per il numero di posizioni
const T &v); operator(const T &v, specificate dagli elementi di ob. Restituisce un array
const va 1array<T> ob); contenente Il risultato.
template <class T> valarray<T> Applica l'operatore XOR fra ciascun elemento di ob e v.
operator"(const T &v, Restituisce un array contenente il risultato. template <class T> valarray<T> Fa scorrere a destra ciascun elemento di obi per il
const valarray<T> ob); operator(const valarray<T> obl, numero di posizioni specificate dai corrispondenti
const valarray<T> &ob2); elementi di ob2. Restituisce un array contenente il
template <class T> valarray<T> Applica l'operatore XOR fra ciascun elemento di obt e il risultato.
operator"(const valarray<T> obl, corrispondente elemento di ob2. Restituisce un array
const valarray<T> &ob2); contenente il risultato. template <class T> valarray<bool> Per ogni I, esegue ob(i] = v. Restituisce un array
operator==(const valarray<T> ob, bOleano contenente Il risultato.
template <class T> valarray<T> Applica l'operatore ANO fra ciascun elemento di ob e v. const T &v);
operator&(const valarray<T> ob, Restituisce un array contenente il risultato.
const T &v); template <class T> valarray<bool> Per ogni I, esegue v = ob(i]. Restituisce un array
operator==(const T &v, booleano contenente il risultato.
template <class T> valarray<T> Applica l'operatore ANO fra ciascun elemento di ob e v. const valarray<T> ob);
operator& (cons t T &v, Restituisce un array contenente il risulta~o: =__
const valarray<T> ob); template <class T> valarray<bool> Per ogni i, esegue obt[l] = ob~]. Restituisce un array
operator==(const valarray<T> obl, booleano contenente Il risultato.
const' Yalarray<T> &ob2);
{segue I
(segue)
886 e A P-1 +o L ...__3i_

Tabella 37.3 Le funzioni non membro definite per valarray. (continua) Tabella 37.3 Le funiomnon membro definite per valarray. (conunuaJ
FUNZIONE ,DESCRIZIONE FUNZIONE DESCRIZIONE
template <class T> valarray<bool> Per ogni ~ esegue ob[iJ != v. Restituisce un array template <class T> valarray<bool> Per ogni i, esegue v>= ob[i]. Restituisce un array -
operator!=(const valarray<T> ob, booleano contenente Il risultato. operator>=(const T &v, booleano contenente il risultato.
const T &v); const \lal array<T> ab):
template <class T> valarray<bool> Per ogni I, esegue v1= ob[i]. Restituisce un array template <class T> valarray<bool> Per ogni I, esegue ob1[i] >= obl![]. Restituisce un array
operator!=(const T &v, booleano contenente il risultato. operator>=(const valarray<T> obl, booleano contenente il risultato.
const valarray<T> ob); const valarray<T> &ob2);
template <class T> valarray<bool> Per ogni i, esegue obt[i] != ob$ Restituisce un array template <class T> valarray<bool> Per ogni I, esegue ob[i] && v. Restituisce un array
operator!=(const valarray<T> obl, booleano contenente il risultato. operator&&(const val array<T> ob, booleano contenente il risultato.
const va1array<T> &ob2) ; const T &v);
template <class T> valarray<bool> Per ogni ~ ~segue ob[iJ < v. Restituisce un array template <class T> valarray<bool> Per ogni i, esegue v && ob[i]. Restituisce un array
operator<(const valarray<T> ab, booleano contenente nrisultato. operator&&(const T &v, boolsano contenente il risultato.
const T &v); const valarray<T> ob);

template <class T> valarray<bool> Per ogni i, esegue v < ob[i]. Restituisce un array template <class T> valarray<bool> Per ogni i, esegue obt[i] && ob2[i]. Restituisce un array
operator<( const T &<v, booleano contenente il risultato. operator&&(const val array<T> obl, booleano contenente il risultato.
const val array<T> ob); const va larray<T> &ob2);
template <class T> valarray<bool> Per ogni i, esegue oblp] < obl![ij. Restituisce un array template <class T> valarray<bool> Per ogni i, esegue ob[i] Il v. Restituisce un array
operator<(const valarray<T> obl, booleano contenente Il risultato. operatori I (const valarray<T> ob, booleano contenente Il risultato.
const va 1array<T> &ob2); const T &v);
ternplate <class T> valarray<bool> Per ogni i, esegue ob[i] <= v. Restituisce un array template <class T> valarray<bool> Per ogni I, esegue v Il ob{i]. Restituisce un array
operator<(const valarray<T> ob, booleano contenente Il risultato. operatori I (const T &v, booleano contenente il risultato.
const T &v); const val array<T> ob);
template <class T> valarray<bool> Per ogni i, esegue v <= obP]. Restituisce un array template <class T> valarray<bool> Per ogni I, esegue obt[i] Il ob2[ij. Restituisce un array
operator<(const T &<v, booleano contenente Il risultato. operatori I (const valarray<T> obl, booleano contenente il risultato.
const valarray<T> ob); const valarray<T> &ob2);
template <class T> valarray<bool> Per ogni i, esegue obl[ij <= ob~]. Restituisce un array
operator<(const valarray<T> obl, booleano contenente Il risultato.
const valarray<T> &ob2);
Tabella 37.4 Le funzioni trascendentali definite per valarray.
template <class T> valarray<bool> Per ogni i, esegue ob[i] > v. Restituisce un array
operator>(const valarray<T> ob, booleano contenente Il risultato. FUNZIONE DESCRIZIONE
const T &v);
template<class T> valarray<T> Ottiene il valore assoluto di ciascun elemento di ob e
template <class T> valarray<bool> Per ogni i, esegue v > ob[i]. Restituisce un array abs(const valarray<T> &ob); restituisce un array contenente il risultato.
operator>(const T &v, booleano contenente il risultato.
const valarray<T> ab); template<class T> valarray<T> Ottiene l'arcocoseno di ciascun elemento di ob e
acos(const valarray<T> &ob); restituisce un array contenente il risultato.
template <class T> valarray<bool> Per ogni I, esegue obt[i] > ob2[i]. Restituisce un array
operator>(const valarray<T> abl, booleano contenente il risultato. ternplate<class T> valarray<T> Ottien l'arcoseno di ciascun elemento di ob e
const valarray<T> &ob2); asin(const valarray<T> &ob); restituisce un array contenente il risultato.
template <class T> valarray<bool> Per ogni i, esegue ob[i] >= v. Restituisce un array template<class T> valarray<T> Ottiene l'arcotantente di ciascun elemento di ob e
operator>=(const valarray<T> ob, booleano contenente il risultato. atan(const valarray<T> &ob); restituisce un array contenente il risultato.
const r &v);
tempiate<class T> valarray<T>- Per ogni i, ottiene l'arcotancente di obf{i) I ob2[] e
atan2(const valarray<T> &obl, restituisce un array contenente il risultato.
- ---(segue) const valarray<T> &ob2);

(segue)

- ~---~ ~-
--- -- -~ ~
888---e-A-Prr-o i:-o 3 1

Tabella 37.4 Le funzioni trascendentali definite per valarray. (ccntlnuaJ #include <valarray>
#include <cmath>
FUNZIONE DESCRIZIONE
using namespace std;
template<elass T> valarray<T> Per ogni i, ottiene l'an:otangente di vI ob1[i] e
atan2(const T &v, cons.t valarray<T> &ob); restituisce un array contenente Hrisultato. int main()
template<elass T> valarray<T> Per ogni i, ottiene l'arcotangente di obl[ij I ve restituisce {
atan2(const valarray<T> &ob, const T &v); un array contenente il risultato. valarray<int> v(lO);
int i;
template<class T> valarray<T> Ottiene il coseno di ciascun elemento di ob e restituisce
cos(const valarray<T> &ob); un array contenente il risultato.
for(i=O; i<lO; i++) v[i] i;
template<class T> valarray<T> Ottiene 11 coseno iperbolico di ciascun elemento di ob e
cosh(const valarray<T> &ob); restituisce un array contenente il risultato. cout "Contenuto originale: ";
template<class T> valarray<T> Calcola la funzione esponenziale per ciascun elemento for(i=O; i<lO; i++)
exp(const valarray<T> &ob); di ob e restituisce un array contenente il risultato. cout v[i] << Il I l ;
cout endl ;
template<class T> valarray<T> Ottiene il logaritmo naturale di ciascun elemento di ob e
log(const valarray<T> &ob); restituisce un array contenente nrisuhato.
v = v.cshift(3);
template<class T> valarray<T> Ottiene il logaritmo comune di ciascun elemento di ob e
loglO(const valarray<T> &ob); restituisce un array contenente il risultato. cout << "Contenuto dopo lo scorrimento: ";
template<class T> valarray<T> Per ogni i, calcola ob1[i]""'I e restituisce un array for(i=O; i<lO; i++)
pow(const valarray<T> &obl, contenente il risultato. cout v[i]_ << " ";
const val array<T> &ob2); cout endl ;
template<class T> valarray<T> Per ogni i, calcola ~ e restituisce un array contenente
pow(const T &v, const valarray<T> &ob); il risultato. valarray<bool> vb = v < 5;
cout "Elementi minori di 5: ";
template<class T> valarray<T> Per ogni i, alcola ob l[i]' e restituisce un array
pow(const valarray<T> &ob, const T &v); contenente il risultato. for(i=O; i<lO; i++)
cout vb[i] << " ";
template<class T> valarray<T> Ottiene il seno di ciascun elemento di ob e restituisce un cout endl endl;
sin(const valarray<T> &ob); array contenente il risultalo.

template<class T> valarray<T> Ottiene lhe il seno iperbolico di ciascun elemento di ob e valarray<double> fv(S);
sinh(const valarray<T> &ob); restitui~.e un arra~ :~ri_t~_nente il risultato. for(i=O; i<S; i++) fv[i] (double) i;

template<class T> valarray<T> Ottiene la radice quadrala di ciascun elemento di ob e


sqrt(conSt valarray<T> &ob); restituisce un array contenente il risultato. cout "Contenuto originale: ";
for(i=O; i<S; i++)
templ ate<cl ass T> val array<T> Ottiene la tangente di ciascun elemento di ob e cout fv[i] " ";
tan(const valarray<T> &ob); reslttuisce un array contenente il risultato.
cout endl ;
template<class T> valarray<T> ottiene la tangente iperbolica di ciascun elemento di ob
tanh(const valarray<T> &ob); e restituisce un array contenente il risultalo. fv = sqrt(fv);
cout "Radi ci quadrate: ";
for(i=O; i<S; i++)
Il seguente programma illustra alcune delle molte funzionalit offerte da cout << fv [i] " " ;
valarray. cout endl ;

Il Illustra 1 'uso della classe valarray fv =_fv + fv._; _ _ _ .


#i ne 1 ude .si os_t r.eam: _ _ cout "D_?~~io_ delle radici quadrate: ";
L.E_C.L_$f)_E R. NUMERI 891
-89D-B-A P 1T O L O 3 7

for(i=O; i<5; i++) elementi (stride). Questi sono i valori restituiti dalle funzioni membro.
cout fv[i] " ": Ecco un programma che illustra l'uso della classe slice.
cout endl :
Il Illustra 1 'uso della classe slice
fv = fv - 10.0; #i nel ude <i ostream>
cout "Dopo aver sottratto 10 da ciascun elemento:"; #include <valarray>
for(i=O; i<5; i++) using namespace std;
cout << fv[i] << " ":
cout endl ; int main()
{
retum O; val array<int> v(lO), resul t;
int i:

Ecco l'output prodotto dal programma: for(i=O; i<lO; i++) v[i] i;

Contenuto originale: O 1 2 3 4 5 6 7 8 9 cout "Contenuto di v: ";


Contenuto dopo lo scorrimento: 3 4 5 6 7 8 9 O 1 2 for(i=O; i<lO; i++)
Elementi minori dj 5.: 1 1 O O O O O 1 1 1 cout << v[i] " ";
cout endl ;
Contenuto originale: O 1 2 3 4
Radici quadrate: O 1. 1.41421 1.73205 2 ..- result = v[slice(0,5,2)]:
Doppio delle radici quadrate: O 2 2.82843 3.4641 4
Dopo aver sottratto 10 da. ciascun elemento: cout "Contenuto di result: ";
-10 -8 -7.17157 -6.5359 -6 for(i=O; i<result.size(): i++)
cout result[i] " ";

Le classi slice e gslice return O;

L'header <valarray> definisce due classi di servizio chiamate slice e gslice che
incapsulano una porzione di un array. Queste classi sono utilizzate con le fonne Ecco l'output del programma:
per sottoinsi.eifuoell"operatore []di valarray.
Ecco !:aspetto della classe slice. Contenuto di v: O 1 2 3 4 5 6 7 8 9
Contenuto di result: O 2 4 6 8
class slice
publ ic: Come si pu vedere, l'array prodotto costituito da cinque elementi div che
sl i ce(); iniziano da O e procedono di due elementi alla volta.
slice(size_t start, size t len, size_t interval); Questa l'aspetto della classe gslice:
size_t start() const;
size_t size() const;
cl ass gsl i ce
size t stride();
}:-r- - publ i c; : .-...
gslice();
gslice()(size_t start, const valarray<size_t> &lens.,_ - -
Il primo costruttore crea una slice vuota. Il secondo costruttore crea una slice const valarray<size_t> &intervals);
che specifica l'elemento di partenza, il numero di elementi e l'intervallo fra-gli----- si ze~t s:tart(-)-const;--
'892 C A P--J+-0-b-0-3 7 - --~-=---- -- -
-i:-i:-c L S SI PER N_~-_l1:ffR I 893

valarray<size_t> Size() const;


Contenuto di v: O 1 2 3 4 5 6 7 8 9 10 11
valarray<size_t> stride() const;
}; -Contenuto di resul t: O 3 6 2 5 8 4 7 10

Il primo costruttore crea una gslice vuota. Il secondo costruttore c Le classi helper
gslice h 'fi l' . . . rea una
. e e spec1 tea e1emento mlZlale, un array che specifica il numero d' l
menti e un array che ~~ecific~ l'intervallo fra gli elementi (stride). Le qu~n~i~~ Le classi numeriche utilizzano alcune classi di supporto che non vengono mai
~~ll: lungh~zze e de~h mteralh devono coincidere. Le funzioni membro restitui- istanziate direttamente dal programma: slice_array, gslice_array, indirect_array e
o o. questi parametri. Quest~ classe utilizzata per creare array multidimensionali mask_array.
a partrre da un valarray (che e sempre monodimensionale).
Il seguente programma illustra l'uso di gslice.

Il Illustra il funzionamento di gslice() 37.3 Gli algoritmi numerici


#include <iostream>
#include <valarray> L'header <numeric> definisce quattro algoritmi numerici utilizzabili nell'elabo-
using namespace std; razione del contenuto dei container.

i nt mai n ()
{ L'algoritmo accumulate()
valarray<int> v(l2}, result;
valarray<-size_t> len(2}, interval (2); L'algoritmo accumulate() calcola la somma di tutti gli elementi che si trovano al-
int i; l'interno dell'intervallo specificato e restituisce il risultato. Ecco i suoi prototipi:

for(i=O; 1<12; i++} v[i] i; template <class Inlter, class T> T accumulate(lnlter start, Inlter end, T v);
template <class Inlter, class T, class BinFunc>
len[O] = 3; len[l] = 3;
T accumulate(Inlter start, Inlter end, T v, BinFuncftmc);
interval [O] = 2; interval [1] = 3;

cout "Contenuto di v: 11 ; Qui, T il tipo dei valori su cui si opera. La prima versione calcola la somma
for(i=O; j<l2; i++} __________ _ di tutti gli elementi contenuti nell'intervallo compreso fra start ed end. La secon-
cout << v(i] << 11 n; da versione applica fune al totale (in pratica ftmc specifica il modo in cui deve
cout -.:< endl ; essere eseguita la somma). Il valore di v fornisce un valore iniziale a cui deve
essere sommato il totale.
result = v[gslice(O, len,interval}]; Ecco un esempio d'uso dell'algoritmo accumulate().

cout << "Contenuto di- result: "; Il Illustra 1 'uso di accumulate()


for(i=O; i<result.size(); i++} #include <iostream>
cout result[i] << " "; #include <vector>
#include <numeric>
return O;
using namespace std;

int main()
Ecco l'output del programma: {
vector<int> v(5);
int i, total; - - _
L E C CA S S I P E R N U-M-E R I. 895
894 CAPITOLO 37

f~r(i=O; i<5; i++) v[i] i; vector<int> v(lO), r(lO);


int i;
total = accumulate(v.begin(), v.end(), O);
for(i=O; i<lO i++) v[i] i*2;
cout << "Sonma di v: " << total; cout "Sequenza originale: ";
for(i=O; i<lO; i++)
return O; cout << v[i] << " ";
cout endl ;

adjacent_difference(v.begin(), v.end(), r.begin());


Il programma produce il seguente output:
cout "Sequenza risultante: ";
Sonma di v: 10 for(i=O; i<lO; i++)
cout << r[i] << " ";
L'algoritmo adjacent_difference()
return O;
_L'algoritmo adjacent_difference() produce una nuova sequenza in cui ciascun ele-
mento la differenza fra elementi adiacenti della sequenza originale (il primo
Il programma produce il seguente output:
elemento del risultato uguale al primo elemento originale). Ecco i prototipi per
adjacent_difference():
Sequenza origina 1e: O 2 4 6 8 10 12 14 16 18
. Sequenza risultante: O 2 2 2 2 2 2 2 2 2
template <class Initer, class Outlter>
outlter adjacent_difference(lniter start, Inlter end, Outlter result); Come si pu vedere, la sequenza risultante contiene la differenza fra il valore
template <class Initer, class Outlter, class BinFunc>
degli elementi adiacenti.
outlter adjacent_difference(lnlter start, Inlter end, Outlter result,
BinFuncfanc);
L:algoritmo inner_product()
Qui start ed end sono iteratori che puntano all'inizio e alla fine della sequenza
originale. La sequenza risultante viene memorizzata nella sequenza puntata da L'algoritmo inner_product() produce la somma del prodotto degli ele~~nti corri-
result. Nella prima forma gli elementi adiacenti vengono sottratti, ovvero l'ele- spondenti in due sequenze e restituisce il risultato;-Eece-i -suoi prototipi:
mento nella posizione n viene sottratto dall'elemento che si trova alla posizione n
+ I. Nella seconda agli elementi adiacenti si applica la funzione binaria fanc. template <class Initer 1, class Inlter2, class T>
Viene restituito un iteratore che punta alla fine di result. T inner_product(Inlterl start I, Inlterl endl, Inlter2 start2, T v);
Ecco un esempio che utilizza adjacent_difference(). template <class Inlterl, class Inlter2, class T, class BinFuncl, class
BinFunc2>
Il Illustra l'uso di adjacent_difference() T inner_product(lnlterl start!, Inlterl endl, lnlter2 start2, T v,
#i nel ude <i ostream> BinFunclfanc2, BinFunc2fanc2);
#i nel ude <vector>
#include <numeric> Qui start] ed endl sono iteratori che puntano all'inizio e alla fine della-prima
usi ng namespace std;
sequenza. L'iteratore start2 punta all'inizio della seconda sequenza. Il valore v
fornisce il valore iniiiale cui deve essere sommato il totale. Nella seconda forma
int main()
{
fancl specifica una funzione binaria che determina il modo in cui viene calcolato
896 CAP-lTOLO 37 LE CLASSI PER NUMERI 897

il totale e func2 specifica una funzione binaria che determina il modo in cui ven- Qui startl ed end! sono iteratori che puntano all'inizio e alla fine della se-
gono moltiplicate due sequenze. quenza originaria. L' iteratore result punta ali' inizio della sequenza risultante. Nella
Ecco un programma che illustra l'uso di inner_product(). seconda forma,fanc specifica una funzione binaria che determi~a il modo in cui
viene calcolato il totale. Viene restituito un iteratoreche punta alla fine di result.
Il Illustra l'uso di inner_product() Ecco un esempio di uso di partial_sum().
#i nel ude <iostream>
#i nel ude <vector> Il Illustra l'uso di partial_sum()
#include <numeric> #include <iostream>
usi ng n.amespace std; #include <vector>
#include <numeric>
int main() usi ng namespace std;
{
vector<i nt> vl (5), v2 (5); int main()
int i, total; {
vector<int> v(S), r(S);
for{i=O; i<5; i++) vl[i] i; int i;
for(i=O; i<5; i++) v2[i] i+2;
for(i=O; i<lO; i++) v[i] i;
total = inner product{vl.begin(), vl.end(), cout "Sequenza originale: ";
- v2.begin(), O); for(i=O; i<S:. i++)

cout "Il prodotto interno : " total;

cout << v[i] << 11 11


;
return O;
cout endl ;

parti al sum(v.begin(), v.end(), r.begin());


Ecco l'output prodotto:
cout "Sequenza risultante: ";
Il prodotto interno : 50 for(i=O; i<5; i++)
11
- ----COU.L_<< r[i] << ";

L'algoritmo partial_sum() return O;

L'algoritmo partial_sum() somma una sequenza di valori inserendo il totale cor-


rente nei successivi elementi di una nuova sequenza (ovvero crea una sequenza Ecco dall'output prodotto:
costituita dai totali della sequenza originaria). Il primo elemento del risultato sar
uguale al primo elemento della sequenza originaria. Ecco i prototipi di Sequenza originale: O 1 2 3 4
partial_sum(): Sequenza risultante: O 1 3 6 10

template <classclnlter, class Outlter>


Outlter partial_sum(Inlter start, Inlter end, Outlter result);
template <class Inlter, class Outlter, class BinFunc> ------
Outlter..partial_sum.QnJ.ter smrJ, InJter end, Outlter result,
__-:-- BinFunc.f!nc); - - - --- -
--:_--:.....:t....--

: Capitolo 38

Le classi per la gestione


delle eccezioni

38.1 Le eccezioni
38.2 La classe auto_ptr
38.3 La classe pair
38.4 La localizzazione
38.5 Altre classi interessanti

Uuesto capitolo descrive le classi per la gestionedelle


eccezioni. Inoltre descrive le classi auto..;,ptr e pair e fornisce una rapida introdu-
zione alla libreria di localizzazione.

38.1 Le eccezioni

La libreria standard C++ definisce due header strettamente correlati con le ecce-
zioni: <exception> e <Stdexcept>. Le eccezioni sono utilizzate per rilevare una
condizione di errore. Questa sezione esamina entrambi questi header.

L'header <exception>

L'header <exception> definisce le classi, i tipi e le funzioni che sono in relazione


con la gestione delle eccezioni. Ecco le classi definite da <exception>.

class exception {
publ ic:
exception{) throw{);
exception(const bad_exception &ob) throw{);
virtual -exception() throw{);

exception &operator=(const exceptfil &ob) throw{);


virtual const char *what({) const throw();
};

class bad_exception: public exception (


--~---
- - - - - - - -_;:.;::=:.:...-=-.::...____ .
priet dell'oggetto. Il vantaggio principale di questo approccio il fatto che gli X(} { cout "costruzione\n"; }
oggetti allocati dinamicamente possono essere distrutt\ quando viene gestita -X(} { cout "distruzione\n"; }
un'eccezione. void f() { cout "All'interno di f()\n";
Ecco la specifica template della classe auto_ptr: };

int main()
template <class T> class auto_ptr {
auto_ptr<X> pl(new X), p2;
Qui T specifica il tipo del puntatore contenuto in auto_ptr.
Ecco l'aspetto dei costruttori di auto_ptr: p2 = pl; 11 trasferisce 1a propri et
p2->f();
explicit auto_ptr(T *ptr = O) throw( );
11 assegnamento a un normale puntatore
auto_ptr(const auto_ptr &ob) throw( ); X *ptr = p2.get();
~-
ptr->f();
template <class T2> auto_ptr(const auto_ptr<T2> &ob) throw( );
return O;

Il primo costruttore crea un auto_ptr che punta all'oggetto specificato da ptr.


Il secondo costruttore crea una copia del puntatore auto_ptr specificato da ob e Ecco l'output prodotto dal programma:
trasferisce la propriet al nuovo oggetto. Il terzo conv,erte (se possibile) ob nel
tipo T e trasferisce la propriet.
costruzione
La classe auto_ptr definisce gli operatori =, * e -> e le seguenti funzioni All'interno di f()
membro: All'interno di f()
distruzione
T *get( ) const throw( );
Si noti che la funzione f() membro di X pu essere richiamata tramite auto_ptr
T *release( ) const throw( ); o tramite il normale puntatore restituito da get().

La funzione get() restituisce un puntatore all'oggetto memori~_fil_OcL.afun


zione release() rimuove la propriet dell'oggetto memorizzato dall' auto_ptr chia-
mante e restituisce un puntatore all'oggetto. Dopo una chiamata a release(), l'og- 38.3 La classe pair
getto puntato non verr pi distrutto automaticamente quando l'oggetto auto_ptr
La classe pair viene utilizzata per memorizzare coppie di oggetti, come se si trat-
uscir dal campo di visibilit.
tasse di un container associativo. La classe ha l~ seguente specifica template:
Ecco un breve programma che illustra l'uso di auto_ptr.
template <class Ktype, class Vtype> struct pair
Il Illustra l'uso di auto_ptr.
li nel ude <i ostream> typedef Ktype fi rst_type;
#include <memory> typedef Vtype second_type;
usiiig namespace std; Ktype fi rs t;
Vtype second;
class X {
___ _p~b.1 i_c :_ I/ costruttol-i
pair();

----- - - - -
CAPITOLO 38 LE CLASSI PER LA GEs-ae-NE--D-EL-LE ECCEZIONI 905 --

1111 I r(const Ktype &k, const Vtype &v); 38.5 Altre classi interessanti
t1:1111111ate<class A, class B> pair(const<A, B> &ob);
Ecco alcune. classe molto interessanti definite dalla libreria standard C++.

111 genere il valore di first contiene una chiave e il valore di second contiene il CLASSE DESCRIZIONE
v11l1 ll'e associato a tale chiave. Per pair sono definiti i seguenti operatori: ==, !=,
"="',~"'e>. type_info Utilizzata insieme all'operatore typei d e descritta in modo approfondilo nel
Capitolo 22. Usa l'header <typeinfo>.
, SI pu costruire una coppia pair utilizzando uno dei suoi costruttori oppure la
1111\1.lone make_pair() che costruisce un oggetto pair sulla base di tipi utilizzati come numeric_limts Incapsula vari limiti numerici. Usa l'header <1 imits>.
p111m11etri. make_pair() una funzione generica che ha il seguente prototipo: raw_storage_i tera tor Incapsula l'allocazione di memoria non inizializzata. Usa l'header <memory>.

lemplate <elass <.Ktype, class Vtype>


pair<.Ktype~ Vtype>> make_pair(const Ktype &k, const Vtype &v);

Come si pu vedere restituisce un oggetto pair costituito dai valori dei tipi
11
1''-'dfkati da Ktype e Vtype. mak::_pair() ha il vantaggio che i tipi degli oggetti
llW111orizzati vengono detenninati automaticamente dal compilatore e non devo-
"'' ~NHcre specificati esplicitamente. ,
Lu classe pair e la funzione make_pair() richiedm.10 l'uso dell 'header <Utility>.

aH.4 La localizzazione

11 \ 't+ standard fornisce un'estesa libreria di classi di localizzazione. Queste classi


l.\'nsentono a un'applicazione di impostare oppure ottenere informazioni relative
ll\1';1111biente geopolitico in cui verr eseguito il programma. Pertanto le classi
lldinJKcQno ad t<~~w.pio il fonnato della valuta, della data e dell'ora e l'ordine
\~\\1.zuto per questi valori. Inoltre forniscono la classificazione dei caratteri. La
hh1x-l'ia dnocalizzazione utilizza l'header <locale> e opera tramite una serie di
1.'hlssi che definiscono varie informazioni associate a locale. Tutti questi elementi
ll,'rvuno dalla classe facet che contenuta nella classe locale.
C>ncstamente la libreria di localizzazione straordinariamente estesa e com-
pl\'ssu e la descrizione delle sue funzionalit non rientra negli scopi di questo
'''lllmc.

~-- I!' ___;:::::-:::::: -


____ _.

---,

: Parte quinta
APPLICAZIONI C++

.i;:

La Parte quinta di questo volume presenta due appli-


cazioni C++ d'esempio. Lo scopo di questa sezione duplice. Innanzitutto gli
esempi aiutano a illustrare i vantaggi della programmazione a oggetti. In secondo
luogo mostrano come il linguaggio C++ possa essere applicato alla soluzione di
due problemi di programmazione molto differenti.

l: . --- -------~

i
---:...--~ -----

-- - .-=::----

: Capitolo 39

Integrazione delle nuove


classi: una classe
. personalizzata
per le stringhe

39.1 La classe StrType


39.2 Le funzioni costruttore e distruttore
39.3 Operazioni di I/O di stringhe
39.4 Le funzioni di assegnamento
39.5 Il concatenamento
39.6 Sottrazione di sottostringhe
39.7 Gli operatori relazionali
- 39.8 Funzioni varie
39.9 !.:intera classe StrType
39.1 O Uso della classe StrType
39.11 Creazione e integrazione di nuovi tipi
39.12 Un esercizio

. uesto capitolo descrive e implementa una piccola


classe per stringhe. Come si sa, il linguaggio C++ fornisce gi una classe per
stringhe molto potente chiamata basic_string. Lo scopo di questo capitolo non
quello di sviluppare un'alternativa a tale classe ma_piJJttQS!Q quello di dare al
lettore un esempio del modo in cui si pu aggiungere e integrare nell'ambiente
C++ un nuovo tipo di dati. La creazione di una classe per stringhe un esempio
classico in questo senso. Nel passato, molti programmatori hanno affinato le pro-
prie capacit sviluppando una classe per stringhe realizzata con tecniche a ogget-
ti. In questo capitolo si prover a eseguire la stessa operazione.
Anche se la classe per stringhe sviluppata in questo capitolo molto pi sem-
plice di quella fornita dal C++ standard, presenta un grande vantaggio: offre un
-controllo completo sul modo in cui le stringhe vengono implementate e manipo-
late. Si trover che questa classe, oltre a _essere un esercizio divertente, pu risul-
tare utile in varie situazioni. "' ---

-----
- - - - --=:::.. - - - -
. .- ------
---w - ---
-----~~--'-"-'c:__o_"_.:'=~='"'"""-'='"""'--~_:_
' -----~-----_---- ----
_______

39.1 La classe StrType StrType operator+(StrType &o); 11 concatena un oggetto StrType


StrType operator+(char *s); 11 concatena una stringa quotata
La classe StrType prende a modello quella fornita dalla libreria standard. Natural- friend StrType operator+(char *s, StrType &o); I* concatena
mente non sar cos estesa o sofisticata. La classe per stringhe definita in questo una stringa quotata e un oggetto StrType *I
capitolo avr i seguenti requisiti.
L'assegnamento di stringhe avverr tramite l'operatore di assegnamento. StrType operator-(StrType &o); 11 sottrae una sottostri nga
StrType operator-(char *s); Il sottrae una sottostringa quotata
A un oggetto stringa sar possibile assegnare altri oggetti stringa o stringhe
quotate. 11 operazioni relazionali fra oggetti StrType
11 Il concatenamento di due oggetti stringa sar ottenuto con l'operatore+. int operator==(StrType &o) { return !strcmp(p, o.p); }
11 La cancellazione di sottostringhe verr eseguita tramite l'operatore-. int operator!=(StrType &o) { return strcmp(p, o.p); }
int operator<(trType &o) { return strcmp(p, o.p) < O; }
11 Il confronto fra stringhe verr eseguito tramite operatori relazionali. int operator>(StrType &o) { return strcmp(p, o.p) > O; }
Gli oggetti stringa saranno inizializzati con una stringa quota.ta o un oggetto int operator<=(trType &o) { return strcmp(p, o.p) < O; }
stringa. int operator>=(StrType &o) { return strcmp(p, o.p) ->= O;
11 Le stringhe potranno avere lunghezza arbitraria e variabile. Questo significa
che la memoria delle stringhe sar allocata in modo dinamico.
11 operazioni fra oggetti StrType e stringhe quotate
int operator==(char *s) { return !strcmp(p, s); }
Verr fornito un metodo per convertire gli oggetti stringa in stringhe chiuse int operator!=(char *s) { return strcmp(p, s); }
dal carattere nullo. int operator<(char *s) { return strcmp(p, s) < O;
Anche se la classe per stringhe che verr sviluppata sar meno potente della int operator>(char *s) { return strcmp(p, s) > O; }
classe standard, includer una funzionalit che non definita da basic_string: la int operator<'?(char *s) { return strcmp(p, s) < O; )
cancellazione di una sottostringa con l'operatore-. int operator>=(char *s) { return strcmp(p, s) >= O; }
La classe per la gestione di stringhe si chiamer StrType. Ecco la sua di-
int strsize() { return strlen{p); } Il restituisce le dimensioni della
chiarazione:
stringa
void makestr(char *s) { strcpy{s, p); } Il crea una stringa quotata
cl ass StrType
char *p; operator char *() { return p; } Il conversione in char *
int size; };
public:
StrType();
StrType(c_har *str);
--- --La parte privata di StrType contiene due soli elementi: p e size. Quando viene
StrType(const StrType &o); 11 costruttore di copie creato un oggetto StrType, la memoria che conterr la stringa viene riallocata
dinamicamente con new e in p viene inserito un puntatore a tale area di memoria.
-StrType() { del ete [] p; } La stringa puntata da p sar un normale array di caratteri chiuso dal carattere
nullo. Anche se tecnicamente questo non necessario, le dimensioni della stringa
friend ostream &operator(ostream &stream, trType &o); sono contenute in size. Poich la stringa puntata 'da p una normale stringa chiusa
friend istream &operator(istream &stream, StrType &o); dal carattere nullo, sarebbe possibile calcolarne le dimensioni senza problemi.
Ma come si vedr, questo valore viene utilizzato cos frequentemente dalle fun-
StrType operator=(StrType &o); 11 assegna un oggetto StrType zioni membro di StrType da non giustificare un continuo ricorso alla funzione
StrType operator=(char *s); Il assegna una stringa quotata strlen().
Le prossime sezioni descrivono il funzionamento della classe StrType.

- - - - - -_..:_:_-__ :.-.-= -- ~ -
-912--CAP IT oLo 39 1NrEG"R"Az-1 ONt- o Ei:t:E~iuo'virris s 1. . . -9.13- ____ _

39.2 Le funzioni costruttore e distruttore il fatto di sapere che tutti gli oggetti StrType contengono una stringa chiusa dal
carattere nullo, semplifica le altre funzioni membro.
Un oggetto StrType pu essere dichiarato in tre diversi modi: senza inizializzazione, Quando un. oggetto StrType viene inizializzato con una stringa quotata,
tramite una stringa quotata oppure con un oggetto StrType. Ecco i costruttori che - innanzitutto si determinano le dimensioni della stringa. Questo valore viene me-
supportano queste tre operazioni: morizzato in size. Poi tramite new viene allocata un'area di memoria sufficiente e
la stringa di inizializzazione viene copiata nella memoria puntata da p.
Il Nessuna inizializzazione esplicita. Quando l'inizializzazione dell'oggetto StrType avviene tramite un altro og-
StrType: :StrType() {
getto StrType, le operazioni sono simili a quelle svolte nel caso della stringa quo-
size = l; Il crea spazio per il tenninatore nullo
try {
tata. L'unica differenza il fatto che le dimensioni della stringa sono note e dun-
p = new char[size];
que non devono essere calcolate. Questa versione del costruttore StrType anche
catch ( bad a11 oc xa) il costruttore di copie della classe. Questo costruttore viene richiamato ogni volta
cout "E-;:rore di allocazione\n"; che si inizializza un oggetto StrType tramite un altro oggetto StrType. Questo
exit(l); significa che verr richiamato quando dovranno essere creati oggetti temporanei
e quando alle funzioni verranno passati oggetti di tipo StrType. Per una descrizio-
strcpy(p, '"'); ne dei costruttori di copie, consultare il Capitolo 14. .
I costruttori appena realizzati consentono dunque di utilizzare le seguenti di-
chiarazioni:
Il Inizializza con una stringa quotata.
StrType: :StrType(char *str) {
StrType x("stringa"): 11 usa una stringa quotata
size = strlen(str) + l; Il crea spazio per il ter.minatore nullo StrType y(x); 11 usa un altro oggetto
try {
StrType z: Il nessuna inizializzazione esplicita
p = new char[size];
catch (bad_alloc xa)
cout "Errore di allocazione\n"; Il distruttore di StrType non fa altro che liberare la memoria puntata da p.
exit (1);

strcpy(p, str);
39.3 Operazioni di 110 di stringhe

Il Inizializza con un oggetto StrType. Poich le operazioni di input e output di stringhe sono molto comuni, la classe
StrType: :StrType(const StrType &o) { StrType non pu fare a meno di eseguire l'overloading degli operatori e:
size = o.size; .
try { 11 Produce in output una stringa.
p = new char[si ze]: ostream &operator<<(ostream &stream, trType &o)
catch (bad a11 oc xa) {
cout "E-;:rore di allocazione\n"; stream o.p;
exit(l); return stream;
}
strcpy(p, o.p);
Il Legge una stringa.
istream &operator(istream &stream, StrType &o)
Quando un oggetto StrType viene creato senza inizializzatori, gli viene asse- {
gnata una stringa nulla. Anche se la stringa sarebbe potuta rimanere non definita. char t[255]; Il dimensioni arbitrarie - modificabile a piacere
tnt 1en-,- - -
914 - CAPI T0-HJ-39-- - I N IT<rR A Z I O N E D E-b-L-E-N il.O.&E. C ~-i=-:- --,--:i-K>

stream.getline(t, 255); if(o.size > size) {


len = strlen(t) + 1; dele.te [] p; Il libera la vecchia memoria
try { -
if(len > o.size) p = new char[o.size];
delete [] o.p; catch (bad_alloc xa) {
try { cout "Errore di allocazione\n";
o.p = new char[len]; exit(l);
catch (bad_alloc xa)
cout "Errore di allocazione\n"; size = o.size;
exit(l);

o.size = len; strcpy{p, o.p);


strcpy(temp.p, o.p);
strcpy(o.p, t);
return stream; return temp;

Come si pu vedere, l'output molto semplice. Si deve solo notare che il Il Assegna una stringa quotata a un oggetto StrType.
parametro o viene passato per indirizzo. Poich gli oggetti StrType possono esse- StrType StrType: :operator=(char *s)
{
re piuttosto estesi, il passaggio per indirizzo pu essere molto pi efficiente ri-
int len = strlen(s) + 1;
spetto al passaggio per valore. Per questo motivo, tl;!lti i parametri StrType vengo- if(size < Hm) {
no passati per indirizzo (cos come avviene per ogni funzione che accetti parame- delete [] p;
tri StrType). try {
L'input di una stringa un po' pi difficoltoso. Innanzitutto si deve leggere la p = new char[len];
stringa utilizzando la funzione getline(). La lunghezza massima della stringa catch (bad alloc xa)
limitata a 254 caratteri pi il terminatore nullo. Come indicato dai commenti, cout "E-;:rore di allocazione\n";
questo valore pu essere modificato a piacere. I caratteri vengono letti .fino al exit(l);
codice di fine riga. Dopo aver letto la stringa, se le sue dimensioni superano il
valore attualmente contenuto in o, tale area di memoria viene cancellata e viene size = len;
allocata un'area di maggiori dimensioni. Quindi_ vi viene_ copiata la QY_a stringa. }
strcpy(p, s);
return *thi s;

39.4 Le funzioni di assegnamento


Queste due funzioni controllano innanzitutto che la memoria attualmente pun-
L'assegnamento di un oggetto StrType pu avvenire in due modi: si pu assegnare tata da p (dell'oggetto StrType di destinazione) sia sufficiente per contenere ci
un altro oggetto di tipo StrType oppure si pu assegnare una stringa quotata. Ecco che vi verr copiato. In caso negativo, la vecchia memoria viene rilasciata e viene
l'overloading delle funzioni operator=() che eseguono queste operazioni: allocata nuova memoria. Poi la stringa viene copiata nell'oggetto e viene restitu-
ito il risultato. Queste funzioni consentono i seguenti assegnamenti:
- 11Assegna un oggetto StrType a un oggetto StrType.
StrType StrType: :operator=(StrType &o) StrType x("test"), y;
{ ------
StrType temp(o.p); y = x; 11 Da oggetto StrType a oggetto StrType
916- - CA P I T O LO 3 9 --

x = "nuova stringa per x"; 11 da stringa quotata a oggetto StrType


Il Concatena un oggetto StrType e una stringa quotata.
StrType StrType: :operator+(char *s)
{
Ogni funzione di assegnamento deve restituire il valore assegnato (ovvero il int len;
valore destro) in modo da poter eseguire assegnamenti multiplt_ StrType temp;-

Str:Type x, y, z; delete D temp.p;

x = y = z = "test"; len = strlen(s) + strlen(p) + 1;


temp.size = len;
try {
temp.p = new char[len];
39.5 Il concatenamento catch (bad alloc xa) {
cout "E-;:rore di allocazione\n";
Il concatenamento di due.stringhe viene eseguito-impiegando l'operatore+. La exit(l);
classe StrType deve rispondere a tre diverse situazioni di concatenamento.
strcpy(temp.p, p);
Concatenamento di un oggetto StrType e di un altro oggetto StrType.
Concatenamento di un oggetto StrType e di una stringa quotata. strcat(temp.p, s);
Concatenamento di una stringa quotata e di un oggetto StrType.
Quando utilizzato in queste situazioni, l'operatore + produce come risultato return temp;
un oggetto StrType formato dal concatenamento dt:;i suoi due operandi. I due
operandi non vengono modificati in alcun modo. 11 Concatena una stringa quotata e un oggetto StrType.
Ecco l'aspetto dell'overloading delle funzioni operator+(): StrType operator+(char *s, StrType &o)
{
11 Concatena due oggetti StrType. int len;
StrType StrType: :operator+(StrType &o) StrType temp;
{
int len; delete [] temp.p;
StrType temp;
len = strlen(s) + strlen(o.p) + 1;
de f ete -l]temp.-p;-- temp.size = len;
1en = str-len(o.p) + strlen(p) + 1; try {
temp.size = len; temp.p = new char[len];
try { catch (bad a11 oc xa) {
temp.p = new char[len]; cout "E-;:rore di allocazione\n";
catch (bad_alloc xa) { exit(l);
cout "Errore di allocazione\n"; l
exit(l); strcpy(temp.p, s);

strcpy(temp.p, p); strcat(temp.p, o.p);

strcat(temp.p, o.p); return temp;

return temp;
918 CAPITOLO 3 9 --+N-HGR"AZIONE D~LLE-NUOVE CLASSI ... 919

Queste tre funzioni si comportano fondamentalmente allo stesso modo. sl++;


Innanzitutto creano un oggetto temporaneo di tipo StrType chiamato temp. Que-
sto oggetto conterr il risultato del concatenamento e sar l'oggetto restituito dal-
le funziqni. Quindi la memoria puntata da temp.p viene liberata. Il motivo di ci }
che qua.do si crea temp, viene allocato un solo byte di memoria in quanto non vi temp.p[i] '\O';
" un'inizializzazione esplicita. Successivamente viene allocata un'ara di memo- return temp;
ria sufficiente per contenere il concatenamento delle due stringhe. Infine le due
stringhe vengono copiate nella memoria puntata da temp.p e viene restituito il 11 Sottrae 1a stringa quotata da un oggetto StrType.
valore di temp. StrType StrType: :operator-(char *substr)
{
StrType temp(p);
char *sl;
39.6 Sottrazione di sc:>ttostringhe int i, j;

Una funzione molto utiie ma assente in basic_string la sottrazione di sottostringhe. sl = p;


Cos come viene implementata dalla classe StrType, la sottrazione di sottostringhe for(i=O; *sl; i++) {
rimuove da una stringa tutte le occorrenze della sottostringa specificata. La sottra- [ if(*sl!=*substr) { Il se non la prima lettera della sottostringa
zione di sottostringhe ottenuta tramite l'operatore-. temp.p[i] = *sl; Il la copia in temp
La classe StrType supporta due casi di sottrazione. Una consente la sottrazio- sl++;
ne di un oggetto StrType da un altro oggetto StrType e.l'altra consente di sottrarre
da un oggetto StrType una stringa quotata. Ecco i'aspetto delle due funzioni else {
operator-(): for(j=O; substr[j] ==sl [j] && substr(j]; j++) ;
if(!substr[j]) { Il una sottostringa, la cancella
sl += j;
Il Sottrae una sottostringa da una stringa utilizzando oggetti StrType.
i--;
StrType StrType: :operator-(StrType &substr)
{
else { 11 non una sottostringa, continua a copi are
StrType temp(p);
char *sl; temp.p[i] = *sl;
i nt i, j;
sl++;

sl = p; _
}
for(i=O; *sl; i++) {
if(*sl!=*substr.p) { Il se non la prima lettera della sottostringa temp.p[i] = '\O';
temp.p[i] = *sl; Il la copia in temp return temp;
sl++;

else { Queste due funzioni copiano il contenuto dell'operando sinistro in temp ri-
for(j=O; substr.p[j)==sl[j] && substr.p[j]; j++) ; muovendo tutte le occorrenze della sottostringa specificata dall'operando di de-
if(!substr.p[j]) { Il una sottostringa, la cancella stra. Quindi restituiscono l'oggetto StrType risultante. Nessuno degli operandi
sl += j; viene modificato .
i--; ._ . !-:!! s;lasse StrType consente dunque lesecuzione di sottrazioni come le seguenti:
else { Il non una sottostringa, continua a copiare StrType x("Mi piace il C++"), y("piace");
temp. p-C:iJ = *s 1; -- -----
- - -StrType z;
.. -.
------ ' .,
920 --cAPTT o L o 39
---~-=------ -- INTEGRAZIONE- D Et LE NUOVE G_L.A S SI. !lir=---
~~~~~__;~;;._:;_;_~====_:;_;.;:..::.....:.;_;,_:__:_::_.=....:...:..;__;__;~

z = x - Y; 11 z conterr "Mi il C++" if(x < y) cout "x minore di y";

z = x - "C++"; 11 z conterr "Mi il " if(z=="tre") cout << "z uguale a tre":

Il rimozione dei duplicati y = "un;


z = "ABCDABCD"; z = "no":
x = z -"A"; Il x conterr "BCDBCD" if(x==(y+z)) cout "x uguale a y+z":

39.7 Gli operatori relazionali 39.8 Funzioni varie

~a.classe ~trType ~upporta tutti gli operatori relazionali. Tali operatori sono defi- La classe StrType definisce tre funzioni che consentono di integrare meglio gli
mtJ. n~lla d1ch1araz10ne della classe ma verranno ripetuti qui per facilitare la con- oggetti StrType nell'ambiente di programmazione C++. Si tratta delle funzioni
sultaz10ne: strsize() e makestr() e della funzione di conversione operator char o:
Queste
funzioni sono definite nella dichiarazione di StrType e hanno il seguente aspetto:
Il operazioni relazionali fra oggetti StrType
int operator==(StrType &o) { return !strcmp(p, o.p); } int strsize() { return strlen(p); } Il restituisce le dimensioni della strin-
int operator!=(StrType &o) { return strcmp(p, o.p); } ga
int operator<(trType &o) { return strcmp(p, o.p) < O; } void makestr(char *s) { strcpy(s, p); } Il crea una stringa quotata
int operator>(StrType &o) { return strcmp(p, o.p) ..> O; } operator ch~r *{) ( return p: ) 11 conversione in char *
int operator<=(trType &o) { return strcmp(p, o.pf <= O; }
int operator>=(StrType &o) { return strcmp(p, o.p) >= O; } Le prime due funzioni sono molto semplici. Come si pu vedere, la funzione
strsize() restituisce la lunghezza della stringa puntata da p. Poich l'effettiva lun-
11 operazioni fra oggetti StrType e stringhe quotate ghezza della stringa pu essere differente rispetto al valore contenuto nella varia-
int operator==(char *s) { return !strcmp(p, s); }
bile size (ad sempio a causa dell'assegnamento di una stringa pi breve) la lun-
int operator!=(char *s) { return strcmp(p, s); }
i nt operator<(char *s) { return strcmp(p, s) < O;
ghezza viene calcolata richiamando strlen(). La funzione makestr() copia in un
i nt operator>(char *s) { return strcmp(p, s) > O:
array di caratteri la stringa puntata da p. Questa funzione utile quando si vuole
int operator<=(char *s) { return strcmp(p, s) <=O; ottenere da un oggetto StrType una stringa chiusa dal carattere NULL.
int operator>=(char *s) { return strcmp(p, s) >= O; La funzione di conversione operator char *() restituisce p che ovviamente un
puntatore alla stringa contenuta nell 'oggetti:>. Qiiestafuiiiibne consente di utiliz-
Gli operatori relazionali sono molto semplici; non dovrebbe essere difficile zare gli oggetti StrType in tutte le situazione in cui necessario impiegare una
capire come sono stati implementati. Tuttavia si deve tenere in considerazione stringa chiusa dal carattere nullo. Ad esempio l'oggetto StrType potr essere uti-
che 1~ classe StrType implementa il confronto fra due oggetti StrType oppure con- lizzato nel seguente modo:
fronti che usano un oggetto StrType come operando sinistro e una stringa quotata
c?~e oper~ndo destro. Per poter eseguire confronti ponendo la stringa quotata a StrType x("Salve");
char s [20]:
sm1stra e I oggetto StrType a destra sar dunque necessario aggiunaere un nuovo
gruppo di funzioni relazionali. "'
Il copia un oggetto stringa con la funzione strcpy()
. D~te le funzioni per operatori relazionali definite da StrType, pqssibile ese- strcpy(s, x); 11 conversione automattca in char *
guire 1 seguenti confronti: -
Si ricordi die-la funzione di conversione viene eseguita automaticamente quan-
StrType x("uro"); y("due"), z("tre");
do un oggetto StrType viene impiegato in un'espressione per la quale definita la-

-- --- -- - - ----
- -
ll't1EG.RAZIONE oTET!:NUOVE CLASSI ... ;23
CA P I T O Lu- 3 ~ ._,___

StrType operator= (StrType &o); 11 assegna un oggetto StrType


conversione. In questo caso, poich il prototipo della funzione strcpy() dice al
StrType operator=(char *s); 11 assegna una stringa quotata
comQ.ilatore che il suo secondo argomento deve essere di tipo char *,viene auto-
maticamente eseguita una conversione da StrType a char * e questo provoca la StrType oirerator+(StrType &o); 11 concatena un oggetto Str~pe
restituzione di un puntatore alla stringa contenuta in x. Questo puntatore verr StrType operator+(char *s); 11 concatena una stringa quotata
quindi utilizzato da strcpy() per copiare la stringa in s. Grazie alla funzione di friend StrType operator+(char *s, StrType &o); I* concatena
conversione, si potr utilizzare un oggetto StrType al posto della stringa chiusa dal una stringa quotata e un oggetto StrType *I
carattere nullo come argomento per qualsiasi funzione che prenda un argomento
di tipo char . StrType operator-(StrType &o); 11 sottrae una sottostri nga
StrType operator-(char *s); 11 sottrae una sottostri nga quotata
~Qt,\_~~ La conversione a char *inganna l'incapsulazione poich una
volta che unafunzione ottie,ne un puntatore alla stringa contenuta nell'oggetto, 11 operazioni relazionali fra oggetti StrType
tale funzione potr modificare direttamente la stringa, scavalcando le funzioni . ' int operator==(StrType &o) { return !strcmp(p, o.p); }
membro di StrType e all'insaputa dell'oggetto puntato. Per questo motivo la con- int operator!=(StrType &o) { return strcmp(p, o.p); l
versione in char *deve. essere utilizzata con cautela. La perdita di incapsulazione int operator<(StrType &o) { return strcmp{p, o.p) < O;
in questo caso controbilanciata dalla maggiore utilit e integrazione con le int operator>(StrType &o) { return strcmp(p, o.p} > O;
altre funzioni della libreria. Tuttavia non sempre consigliabile seguire questo int operator<=(StrType &o} { return strcmp(p, o.p) <= O;
approccio. int operator>=(StrType &o} { return strcmp(p, o.p} >= O;

Il operazioni fra oggetti StrType e stringhe quotate


int operator==(char *s) ( return !strcmp(p, s}; l
39.9 L'intera classe StrType int operator!=(char *s} ( return strcmp(p, s); l
int operator<{char *s} { return strcmp(p, s} < O;
Ecco il listato completo della classe StrType e una breve funzione main() che int operator>(char *s) I return strcmp(p, s) > O;
illustra le sue funzionalit: int operator<=(char *s) I. return strcmp(p, s) <= O;
int operator>=(char *s} { return strcmp(p, s} >= O;
#include <iostream> int strsize() I return strlen(p); ) Il restituisce le dimensioni
#i nel ude <new> della stringa
#i nel ude <estri ng>
void makestr(char *s} { strcpy(s, p); ) 11 stringa chiusa
#include <cstdlib> dal carattere nullo
usi ng namespace std;
operatorcnar.,cr! return p; } /I conversione in char *
cl ass Str.!ype };
char *p;
int size;
Il Nessuna inizializzazione esplicita.
StrType: :StrType{} I
publ ic:
size = l; Il crea spazio per il terminatore nullo
StrType();
StrType(char *str); try {
p = new char[si ze];
StrType(const StrType &o); 11 costruttore di copie
catch (bad all oc xa) {
cout "E-;:.rore di allocazione\n";
-StrType() { delete [] p; }
exit(l};
friend ostream &operator(ostream &stream, StrType &o);
strcpy(p, "");
friend istream &operator>>(istream .&stream, StrType &o);
924 -~~EG RAZI o NE orriJ"uoVfCC.\ssi'.-:-:- .::-925.

Il Inizializza con una stringa quotata. exit(l);


StrType: :StrType(char *str) {
size = strlen(str) + l; Il crea spazio per il terminatore nullo o.size = len;
try { }
p = new char[si ze]; strcpy(o.p, t);
catch (bad_alloc xa) return stream;
cout "Errore di allocazione\n";
exit(l);
11 Assegna un oggetto StrType a un oggetto StrType.
strcpy(p, str); StrType StrType: :operator=(StrType &o)
{
StrType temp(o.p);
11 Inizializza con un oggetto StrType.
StrType: :StrType(const StrType &o) { if(o.siz > size) {
size = o.size; delete [] p; Il libera la vecchia memoria
try { try {
p = new char[size]; p = new char[o.size];
catch (bad_alloc xa) catch (bad al loc xa) {
cout "Errore di allocazione\n"; cout "E-;:rore di allocazione\n";
exit(l); exi t (1);

strcpy(p, o.p); size = o.size;

11 Produce in output una stringa. strcpy(p, o.p);


ostream &operator(ostream &stream, StrType &o) strcpy(temp.p, o.p);
{
stream o.p; return temp;
return stream;

11 Assegna una stringa quotata a un oggetto StrType.


11 Legge una stringa. StrType StrType: :operator=(char *s)
istream &operator(istream &stream, StrType &o) {
{ int len = strlen(s) + l;
char t[255]; Il dimensioni arbitrarie - modificabile a piacere if{size < len) {
int len; delete [] p;
try {
stream.getl ine(t, 255); p = new char[len];
len = strlen(t) + l; catch (bad alloc xa)
cout "E-;:rore di allocazione\n";
if(len > o.size) exit(l);
delete [] o.p;
try { size = len;
o.p = newchar[len]; I
catch (bad_alloc xa) { strcpy{p, -s); - - - - -
cout << "Errore-di-allocazione~--- __
----- ---------
926 f;A P I T O LO 3 9 -- --- --- --ttTEG RAZ 1-Q.liLE___D J.. _E_N~U O V E CLASSI. 927

return *this;
return temp;

Il Concatena due oggetti StrType.


11 Concatena unc1 stringa quotata e un oggetto StrType.
StrType StrType: :operator+{StrType &o}
StrType operator+{char *s, StrType &o)
{
{
int len;
int len;
StrType temp;
StrType temp;
~-
delete O temp.p;
delete O temp.p;
len = strlen{o.p} + strlen{p} + l;
temp.size = len;
len = strlen{s} + strlen{o.p) + l;
try {
temp. si ze = 1en;
temp.p = new char[len];
try {
catch {bad_a 11 oc xa} {
temp.p = new char[len];
cout "Errore .di allocazione\n"; catch {bad alloc xa} {
exit(l);
cout "E~rore di allocazione\n";
exit{l}:
strcpy(temp.p, p};
strcpy{temp.p, s);
strcat{temp.p, o.p};
strcat{temp.p; o.p};
return temp;
re tu rn temp;

Il Concatena un oggetto StrType e una stringa quotata.


StrType StrType: :operator+{char *s}
{
11 Sottrae una sottostri nga da una stringa utilizzando oggetti StrType.
StrType StrType: :operator-{StrType &substr}
int len;
{
StrType temp;
StrType temp{p};
char *sl;
del ete -0-t-emp-cp;---
int i, j:
len = strlen(s} + strlen{p} + l;
sl = p;
temp.size = len;
for{i=O; *sl; i++} {
try {
if{*sl!=*substr.p) { Il se non la prima lettera della sottostringa
temp.p = new char[len];
temp.p[i] = *sl; Il la copia in temp
catch (bad_alloc xa} {
sl++;
cout "Errore di allocazione\n"; }
exit{l};
else {
for(j=O; substr.p[j]==sl[j] && substr.p[j]; j++} ;
strcpy(temp.p, p};
if{!substr.p[j])- Il una sottostringa, la cancella
sl += j;
strcat(temp.p, s};
i--;
- --~ --=..... - -

928 CAP t T-ot-6-3 9 . 929

else { 11 non una sottostri nga, continua a copi are cout << sl s2;
temp.p[i] = *sl;
sl++; s3 = sl;
cout sl;.

} s3.makestr(s);
temp.p[i] = '\O'; cout "Converte in stringa: " << s;
return temp;
s2 = "Questa una nuova stringa,";
cout s2 endl ;
11 Sottrae la stringa quotata da un oggetto StrType.
StrType StrType: :operator-(char *substr) StrType s4(" Anche questa.");
{ sl = s2+s4;
StrType temp(p); cout s 1 endl ;
char *sl;
int i, j; if(s2==s3) cout "Stringhe uguali.\n";
if(s2!=s3) cout "Stringhe differenti.\n";
sl = p; if(sl<s4) cout "sl minore di s4\n";
for(i=O; *sl; i++) { if(sl>s4) cout "sl maggiore di s4\n";
if(*sl!=*substr) { 11 se non la prima lettera della sottostringa if(sl<=s4) cout "sl minore o uguale a s4\n";
temp.p[i] = *sl; Il la copia in temp if(sl>=s4) ~out "sl maggiore o uguale a s4\n";
sl++;
if(s2 > "ABC") cout "s2 maggiore di ABC\n\n";
else {
for(j=O; substr[j]==sl[j] && substr[j]; j++) ; sl = "uno due tre uno due tre\n";
if(!substr[j]) { Il una sottostringa, la cancella s2 = "due";
sl += j; cout "Stringa iniziale: " sl;
i--; cout "Stringa dopo aver sottratto due: ";
} s3 = sl - s2;
else { 11 non una sottostri nga, continua a copi are cout s3;
temp.p[i] ,;_ *sl;.. __ .----
sl++; cout endl ;
s4 = "Salve!";
s3 = s4 + " Le stringhe C++ sono divertenti\n";
} cout s3;
temp.p[i] = '\O'; s3 = s3 - "Salve!";
return temp; s3 = "Non siete d'accordo che" + s3;
cout s3;

int main{} sl = s3 - "Non ";


{ cout sl;
StrType sl("Una sessione d'esempio sull'uso di oggetti stringa. \n"); s3 = sl;
StrType s2(sl);
StrType s3; cout <<"Introdurre una.stringa: ";
char s [BO]; cin sl; _ _
cout << sl << endl;
-------=-::;;...:.....:;- _ _,
------
---930.-- &A P I T O LO 3 9 ' N r E G RAZTO N E oE L L E
N u OVTc LA s s 1.-:-:--931- -

cout "sl lunga " << sl.strsize() << " caratteri. \n"; 39.1 O Uso della classe StrType
puts (sl); 11 converte in to char * Per concludere questo capitolo verranno presentati due brevi esempi che illustra-
no l'uso della c1asse StrType. Come si vedr, grazie agli operatori definiti per la
sl = s2 = s3; classe e grazie alla funzione di conversione in char *, la classe StrType ben
cout sl << s2 << s3; integrata nell'ambiente di programmazione C++, ovvero pu essere utilizzata come
qualsiasi altro tipo definito dal C++ standard.
sl = s2 = s3 = "Arrivederci ";
Il primo esempio usa oggetti StrType per creare un semplice dizionario dei
cout sl s2 s3;
sinonimi. Innanzitutto crea un array bidimensionale di oggetti StrType. All'inter-
return O; no di ogni coppia di stringhe, la prima contiene la parola chiave che pu essere
ricercata e la seconda contiene una serie di parole alternative o correlate. Il pro-
gramma chiede una parola e, se la parola contenuta nel suo elenco, visualizza le
Il programma produce il seguente output: alternative. Questo programma molto semplice ma si noti la pulizia e la chiarez-
za con la quale vengono gestite le stringhe grazie alla classe StrType e ai suoi
Una sessione d'esempio sull'uso di oggetti stringa. operatori. Si ricordi che il file header deve contenere la classe StrType.
Una sessione d'esempio sull'uso di oggetti stringa.
Una sessione d'esempio sull'uso di oggetti stringa. #include "str.h"
Converte in stringa: Una sessione d'esempio sull'uso di oggetti stringa. #include <iostream>
Questa una nuova stringa. using namespace std;
Questa una nuova stringa. Anche questa.
Stringhe differenti. StrType thesaurus[] [2] = {
sl maggiore di s4 "libro", "volume, tomo",
sl maggiore o uguale a s4 "negozio", "mercante, bottega, spaccio",
s2 maggi ore di ABC "pistola", "rivoltella, arma, mano armata",
"corsa", "gara, trotto, competizione",
Stringa iniziale: uno due tre uno due tre "pensare", "ispirarsi, contemplare, riflettere",
Stringa dopo aver sottratto due: uno tre uno tre "calcolare", "analizzare, determinare, risolvere"
1111

);
'
Salve! Le stringhe C++ sono divertenti
Non siete d'accordo che Le stringhe .c++--SooO-dtve!".tenti
siete d'accordo che Le stringhe C++ sono divertenti int main()
Introdurre una stringa: Ma che bello il C++ {
sl 1unga 19 caratteri. StrType x;
Ma che bello il C++
siete d'accordo che Le stringhe C++ sono divertenti cout <<"Introdurre una parola: ";
siete d'accordo che Le stringhe C++ sono divertenti cin x;
siete d'accordo che Le stringhe C++ sono divertenti
Arri vederci Arri vederci Arri vederci j int i;
for(i=O; thesaurus[i] [O]!=""; i++)
Qui si suppone che l'utente-immetta l stringa "Ma che belloil C++". if(thesaurus[i][O]==x) cout <". ~h~saurus[i][l];
Per accedere con facilit alla classe StrType si deve rimuovere la funzione
return O;
main() ~ mserueil listato nel file STR.H. Poi baster includere questo file header
in qualsiasi programma nel quale si voglia utilizzare StrType.
932- - C A P I T O LO 3 9
- -HH-E-G RAZ I O N E O E L L E N U O V"E C ("A S S I. .. 933--

Il prossimo esempio usa un oggetto StrType per controllare se esiste~ .


ne eseguibile di un programma. Per utilizzare il programma -si deve specifi
nella riga di comando il nome del file senza estensione. Il programma cerca return O;
file eseguibile avente tale nome aggiungendo via via una nuova estension:-.
tenta di aprire tale file e mostra il risultato. Se il file non esiste, ovviament-nlt':"~"'
pu essere aperto. Dopo aver tentato un'estensione, questa viene sottratta dal oom;& ~f -- Ad esempio, se il programma si chiama ISEXEC e supponendo che esista
del file, al quale viene aggiunta una nuova estensione. Anche in questo caso, rU.SO TEST.EXE, l'esecuzione del comando ISEXEC TEST produrr il seguente output:
della classe StrType e dei suoi operatori semplifica notevolmente la manipolazi~
ne delle stringhe. Ricerca di TEST.EXE - Esiste
Ricerca di TEST.COM - Non esiste
#include "str.h" Ricerca di TEST.BAT - Non esiste
#include <iostream>
#include <fstream> Un'annotazione riguardante questo programma: il costruttore ifstream utiliz-
using namespace std;
za un oggetto StrType. Questa tecnica funziona grazie alla funzione di conversio-
ne char *() che viene richiamata automaticamente. Come illustrato da questa si-
Il estensioni dei file eseguibili
char ext[3] [4] = { tuazione, un'attenta applicazione delle caratteristiche del linguaggio C++ con-
"EXE", sente di raggiungere una notevole integrazione tra i tipi standard del C++ e i tipi
"COM", creati dal programmatore.
"BAT"
};

int main(int argc, char *argv[]) 39.11 Creazione e integrazione di nuovi tipi
{
StrType fname; Come dimostra l'esempio della classe StrType, piuttosto facile creare e integra-
int i; re un nuovo tipo nell'ambiente C++.In generale si deve seguire questa procedura.
1. Eseguire l'overloading degli operatori appropriati, inclusi gli operatori di I/O.
if(argc!=2)
cout "esempio: nomefile\n";
2. Definire tutte le funzioni di conversione appropriate.
return l; 3. Fornire i costruttori che consentono di creare gli oggetti in varie situazioni.
L' estendibilit un elemento fondamentale della potenza del-linguaggio C++.
Dunque importante saperla sfruttare in modo appropriato.
fname = argv[l];

fname = fname + "."; Il aggiunge il punto


for(i=O; i<3; i++) {
39.12 Un esercizio
fname = fname + ext[i]; I/ aggiunge 1 'estensione
cout << "Ricerca di " << fname << " ";
Ecco un esercizio interessante. Si pu provare a implementare la classe Stffype
ifstream f(fname);
if(f) .{_
. utilizzando la libreria STL. Per contenere i caratteri che formano la stringa si
cout << "- Esiste\n"; potr usare un container. Per operare sulla stringa si potranno usare gli iteratori e
f.close(); per eseguire la manipolazione della stringa si potranno usare gll algoritmi.

else cout "- Non esiste\n";


_Jname.=_fname - ext[i]; j; sottrae l'estension.:_ ___ _
- -- . __ _____
. -,
-----
- ---

Capitolo 40

Un analizzatore
di espressioni realizzato
con tecniche a oggetti

40.1 Le espressioni
40.2 L'elaborazione delle espressioni:
il problema
40.3 Analisi di un'espressione
40.4 La classe parser
40.5 Sezionamento di un'espressione
40.6 Un semplice parser di espressioni
40.7 Aggiunta delle variabili
40.8 Controllo della sintassi in un parser
a discesa ricorsiva
40.9 Realizzazione di un parser generico
40.10 Alcune estensioni da provare

.Anche se il C++ standard piuttosto esteso, esistono


anche elementi che il linguaggio non fornisce. In questo capitolo si parler di uno
di essi: il parser di espressioni. Un parser di espressioni pu essere utilizzato per
valutare un'espressione algebrica, come (10- 8) * 3. I parser sono piuttosto utili e
possono e~s~~I!l.P.~c;:,gati in un'ampia variet di applicazioni. Sono anche una
delle entit pi elusive della programmazione. Per vari motivi, le procedure uti-
lizzate per creare un parser non sono state molto sviluppate e diffuse. Inoltre mol-
ti programmatori abili in altri campi vengono intimiditi dal processo d( analisi
delle espressioni.
L'analisi delle espressioni in realt piuttosto semplice, talvolta molto pi
semplice di altri campi della programmazione. Ci dovuto al fatto che l'analisi
di espressione un argomento ben definito che deve seguire le rigide regole del-
.I' algebra. Questo capitolo svilupper un parser a discesa ricorsiva e tutte le routine
di supporto necessarie per valutare espressioni numeriche complesse. Verranno
create tre versioni del parser. Le pni:ne due sono versioni non~generiche e I 'ultima
generica e pu essere applicata a qualsiasi tipo numerico. Ma prima di poter
sviluppare un parser, necessaria una breve introduzione alle espressioni e alla
loro analisi.
- ----- --------
-- -
--~ .:~ --
936 CAPITOLO 40
----u N AN A LI z z A rcrn E DI Es p RE s s I o NT" .. 937

40.1 Le espressioni 40.2 L'elaborazione delle espressioni: il problema


Poich un parser valuta espressioni algebriche, importante comprendere quali Chi non .abbi~ mai riflettuto sul problema dell'analisi delle espressioni, potrebbe
sono gli eementi che costituiscono le espressioni. Anche se esistono vari tipi di pensare che si tratti di n'operazione piuttosto semplice. Per comprendere meglio _
espressioni, questo capitolo si concentra sulle sole espressioni numeriche. Per il problema, si provi a valutare questa semplice espressione:
quanto riguarda questo capitolo, le espressioni numeriche sono costituite dai se-
guenti elementi:
10-2*3
11 i numeri
11 gli operatori+,-,*,/,\% e= Come si sa, il risultato di questa espressione 4. Sebbene sia possibile gene-
11 le parentesi rare con facilit un programma che calcoli questa specifica espressione, il proble-
11 le variabili ma creare un programma che dia la risposta corretta per qualsiasi espressione.
Inizialmente si potrebbe pensare a una routine come la seguente:
In questo parser, 1' operatore A indica un elevamento a potenza (e non l'opera-
tore XOR come in C++) e= l'operatore di assegnamento. Questi elementi pos-
sono essere combinati per creare espressioni secondo le regole classiche dell'al- a = leggi il primo operando
gebra. Ecco alcuni esempi: while(ci sono altri operandi) {
op =leggi l'operatore
b = leggi il secondo operando
10-8
a=aopb
(100 - 5) * 14/6
a+b-c .-
IQAS
a= 10-b Questa routine legge il primo operando, l'operatore e il secondo operando e
quindi esegue la prima operazione, poi legge l'operatore e I' operando successivi,
esegue l'operazione seguente e cos via. Tuttavia, se si impiega questo semplice
Per gli operatori verranno considerate le seguenti regole di precedenza:
approccio, l'espressione 10 - 2 * 3 fornir il valore 24 (ovvero 8 * 3) invece di 4,
in quanto questa procedura non tiene conto della precedenza degli operatori. Non
Alta + - (unari)
A
si possono considerare gli operandi e gli operatori semplicemente da sinistra a
destra poich le regole dell'algebra stabiliscono che la moltiplicazione debba es-
*/% sere eseguita prima della sottrazione. Qualcuno potrebbe pensare che questo pro-
+- blema possa essere rislto confacilite talvolta, in casi molto limitati, questo
Bassa- = vero. Ma il problema peggiora se si aggiungono le parentesi, l'elevamento a po-
tenza, le variabili, gli operatori unari e cos via.
Gli operatori aventi lo stesso livello di precedenza si valutano da sinistra a Anche se vi sono vari modi per scrivere una routine di valutazione delle espres-
destra. sioni, quella sviluppata in questo capitolo ha il pregio di essere scritta con facilit
Negli esempi contenuti in questo capitolo, tutte le variabili sono costituite da e infatti molto comune. Il metodo utilizzato quello del parser a discesa ricorsiva
un'unica lettera (ovvero possibile impiegare fino a 26 variabili, da A a Z). Per le e nel corso del capitolo s scopriranno i motivi di questo nome. Altri metodi utiliz-
variabili non viene fatta alcuna distinzione fra lettere maiuscole e minuscole (a e zati per realizzare parser impiegano tabelle complesse che devono essere generate
A_vengono trattate come la stessa variabile). Per la prima versione del parser, tutti da un programma distinto. In questi casi si parla di parser a tabelle.
i valori numerici vengono elevati a double anche-se possibile scrivere con faci-
lit delle routine in grado di gestire altri tipi di valori. Infine, per mantenere la
chiarezza e la semplicit della logica del parser, verr incluso un livello m.olto
limitato di-verifica-degli-errori. -------

--- --------
938 CAPITOLO 40 UN ANALIZZATORE DI ESPRES-SlONI ... 939

40.3 Analisi di un'espressione 1. Leggere il primo termine, 9 I 3.


2. Leggere ciascun fattore e dividere gli interi. Il valore risultante 3.
Vi sono vari modi per analizzare e valutare un'espressione. Se si 'usa un parser a
3. Leggere-il secondo termine, (100 + 56). A questo punto iniziare ad analizzare
discesa ricorsiva, le espressioni possono essere considerate come strutture dati
ricorsive, ovvero come espressioni definite in termini di altre espressioni. Se, per ricorsivamente la seconda subespressione.
il momento, si immagina che le espressioni possano utilizzare solo gli operatori 4. Leggere i termini e sommarli. Il risultato 156.
+, -, *,I e le parentesi, tutte le espressioni possono essere definite tramite le se- 5. Uscire dalla chiamata ricorsiva e sottrarre 156 da 3. La risposta -153.
guenti regole: A questo punto le cose potrebbero risultare un po' confuse. In effetti il concet-
to abbastanza complesso e richiede un po' di adattamento. Occorre ricordare un
espressione-> termine[+ termine] [-termine] paio di cose sull'analisi ricorsiva delle espressioni. Innanzitutto la precedenza
termine-> fattore[* fattore] [/fattore] degli operatori implicita nel modo in cui sono definite le regole di produzione.
fattore-> variabile, numero o (espressione) fo secondo luogo, questo metodo di analisi e valutazione delle espressioni molto
simile al modo in cui un essere umano valuta le espressioni matematiche.
Le parentesi quadre indicano un elemento opzionale mentre la freccia -> si La parte rimanente di questo capitolo sviluppa tre parser. Il primo analizza e
pronuncia "produce". Per questo motivo tali regole sono normalmente chiamate valuta espressioni in virgola mobile di tipo double costituite unicamente.da valori
regole di produzione delle espressioni. Pertanto, la definizione di termine si pu costanti. Successivamente questo parser verr esteso per supportare l'uso di va-
leggere nel seguente modo: "termine produce fattore per fattore oppure fattore riabili. Infine la terza versione del parser implementata sotto forma di classe
diviso fattore". Si noti che la precedenza degli operatori implicita nel modo in template e pu essere utilizzata per analizzare espressioni di qualsiasi tipo.
cui sono definite le espressioni.
L'espressione:

10+5*B 40.4 La classe parser


Il parser di espressioni si basa sulla classe parser. Ecco 1' aspetto della prima versio-
costituita da due termini: 10 e 5 * B. Il secondo termine contiene due fattori: 5 e
ne di questa classe. Le versioni successive estenderanno questa prima versione.
B. Questi fattori sono un numero e una variabile.
Al contrario l'espressione:
cl ass parser {
char *exp_ptr; // punta all'espressione
14*(7-C)
char token[80]; //contiene il token corrente
char tok_type; //contiene il tipo del token
contiene due fattori: 14 e (7 - C).I fattori sono un numero e un'espressione rac-
chiusa fra parentesi. L'espressione fra parentesi contiene due termini: un numero void eval_exp2(double &result);
e una variabile. void eval exp3(double &result);
Questo processo costituisce la base per un parser a discesa ricorsiva che un void eva(exp4(double &result);
insieme di funzioni ricorsive operanti in modo concatenato che implementano le void eval_expS(double &result);
regole di produzione. Ad ogni passo, il parser esegue le operazioni specificate in void eval_exp6(double &result);
una sequenza corretta dal punto di vista algebrico. Per vedere il funzionamento di void atom(double &result);
void get_token();
queste regole di produzione, si prover a esaminare la seguente esp~:ssione:
voi d serrar (i nt errar);
int isdelim(char c);
913 - (100 + 56) public:
parser();
Ecco-la.sequenza che verr eseguita. double eval_exp(char *exp);
- --} ;
941
940 C A P+f-et-6--4 O .

La classe pars.er contiene tre variabili membro private. L'espressione da valu- NUMBER e DELIMITER (DELIMITER utilizzato sia per gli operatori che per.le
tare contenuta in una stringa chiusa dal carattere nullo puntata da exp_ptr. Per- parentesi).
Di seguito si pu vedere l'aspetto della funzione get_token(). Tale funzione
tanto il parser valuta espressioni contenute in una normale stringa C. Ad esempio,
le seguenti stringhe contengono espressioni valutabili dal parser: - ottiene dal!' espressione puntata da exp_ptr il token successivo e lo inserisce nella
variabile membro token. Poi inserisce il tipo del token nella variabile membro
"10-5" tok_type.
"2 * 3.3 / (3.1416 * 3.3)"
Il Ottiene il token successivo.
void parser: :get_token()
Quando il parser inizia l'esecuzione, exp_ptr deve puntare al primo carattere
{
della stringa contenente l'espressione. Durante l'esecuzione il parser avanza nella register char *temp;
stringa fino a trovare il codice nullo di chiusura della stringa.
Il significato delle altre due variabili membro, token e tok_type, verr descrit- tok_type = O;
to nella prossima sezione. temp = token;
Il punto di ingresso nel parser rapp~ese.ntato da. eval_exp() che deve essere *temp = '\O';
richiamata con un puntatore all'espressione da analizzare. Le funzioni da
eval_exp2() a eval_exp6(), insieme ad atom() costituiscono il parser a discesa if(!*exp_ptr) return; /I alla fine dell'espressione
ricorsiva. Tali funzioni implementano una versione estesa delle regole di produ-
zione di cui si parlato in precedenza. Nelle prossime versioni del parser, verr whil e(i sspace(*exp_ptr)) ++exp_ptr; /I salta gli spazi vuoti
aggiuf!ta anche una funzione evat_exp1 ().
if(strchr("+-*1%"= () 11 , *exp_ptr) ){
La funzione serror() gestisce gli errori di sintassi dell'espressione. Le funzio-
tok_type = DELIMITER;
ni get_token() e isdelim() suddividono l'espressione nei suoi componenti come
Il avanza fino al carattere successivo
descritto nella prossima sezione. *temp++ = *exp_ptr++;

else if(isalpha(*exp ptr))


while(!isdelim(*exp_ptr)) *temp++ = *exp_ptr++;
40.5 Sezionamento di un'espressione tok_type = VARIABLE;

Per poter valutare le espressioni, occorre essere in grado di spezzare un'espres- else if(isdigit(*exp_ptr))
sione nei suoi componenti...Poich questa un'operazione fondamentale per I' ana- while(!isdelim(*exp_ptr)) *temp++ = *exp_ptr++;
lisi, se ne e_ar}er prima di descrivere il parser vero e proprio. tok_type = NUMBER;
Ogni componente di un'espressione un token. Ad esempio, l'espressione:

A* B-(W + 10) *temp = '\O':

contiene i token A, *, B, -, (, W, +, 10 e ). Ogni token rappresenta un'unit


indivisibile dell'espressione. In generale occorre una funzione che restituisca di
11 Restituisce true se e un del imitatore.
i nt parser:: i sdel im(char c)
volta in volta i singoli token di un'espressione. La funzione deve anche essere in
{
grado di saltar~ gli spazi e i caratteri di tabulazione e rilevare la fine dell' espres- if(strchr(" +-1""%"=()", e) Il c==9-ll c=='\r' Il c==O)
sione. La funzione che verr utilizzata per eseguire questo compito si chiama return 1;
get_token(), una funzione membro della classe parser. return O;
Oltre a individuare il token, occorre determinare il tipo del token irid!viduato.
Per il parser ~i:~:pato in questo capitolo_,~~~~n~-~ol~ t~e tipi di token: VARIABLE,
- - - - ----- ----
----=--- -----;;--
942 CAPITOLO 40

(continua)
Osservando attentamente le funzioni precedenti, si nota che dopo le prime
inizializzazioni, get_token() controlla se stafo trovato il codice nullo che chiude
TOKEN TIPO DEL TOKEN
l'espressione. A tale scopQ_controlla il carattere puntato da exp_ptr. Poich exp_ptr
punta all'espressione da analizzare, se punta a null significa che stata raggiunta e VARIABLE
la fine dell'espressione. Se l'espressione contiene altri token, innanzitutto DELIMITER
get_token() salta gli spazi iniziali. Dopo aver saltato tali spazi, get_token() punte-
r a un numero, una variabile, un operatore o, se l'espressione termina con una DELIMITER
sequenza di spazi, al carattere null. Se il carattere successivo un operatore, verr NUMBER
restituito in token sotto forma di stringa ed in tok_type verr inserito DELIMITER.
Se invece il carattere successivo una lettera, si presume che si tratti di una varia- null null

bile. Anch'essa verr restituita come stringa in token e a tok_type verr assegnato
il valore VARIABLE. Se il carattere successivo una cifra, verr letto l'intero nu- Si ricordi che token contiene sempre una stringa chiusa dal carattere nullo,
mero che verr inserito nella stringa token con il tipo NUMBER. Infine, se il carat- anche se essa costituita da un solo carattere.
tere successivo non nessuno di quelli elencati, si presume che sia stata raggiunta
la fine dell'espressione. In questo caso, token sar null per segnalare il
-- raggiungimento dlla fine dell'espressione.
Come si detto in precedenza, per non complicare il codice di questa funzio- 40.6 Un semplice parser di espressioni
ne, stata omessa una buona parte degli elementi di verifica degli errori e sono
state fatte alcune supposizioni. Ad esempio, l'espressione sar chiusa da qualsiasi Ecco finalmente la prima versione del parser. Il parser pu valutare espressioni
carattere non riconosciuto. Inoltre in questa versione le variabili possono avere un costituite unicamente da costanti, operatori e parentesi. Pertanto non accetter
nome di qualsiasi lunghezza ma solo la prima lettera sar significativa. Natural- espressioni contenenti variabili.
mente sar possibile aggiungere tutte le verifiche di errore e i dettagli richiesti
dall'applicazione che si deve sviluppare. /* Questo modulo contiene il parser a discesa ricorsiva
Per comprendere meglio il processo di tokenizzazione, si studi ci che viene che non contiene vari ab il i.
restituito per ciascun token analizzando la seguente espressione: */
#i ne 1ude <i os t ream>
A+lOO:-(B*C)/2 #i nel ude <cstdlib>
#i nel ude <eetype>
lii nel ude <estri ng>
using namespaee std;
TOKEN TIPO DEL TOKEN
A VARIABLE enum types { DELIMITER = 1, VARIABLE, NUMBER};

DELIMITER
elass parser {
100 NUMBER char *exp_ptr; //punta all'espressione
ehar token[SO]; //contiene il token corrente
DELIMITER ehar tok_type; // contiene il tipo del token
DELIMITER
void eval exp2(double&result); r

B VARIABLE void eval-exp3(double&result);


DELIMITER
void evaCexp4(double &result);
void eval-expS(double&result);
-~segue)
voiil_e~al-exp~(~ouble &result);
----- -------
944-- CAPITOLO 4 - 0 - - - - - - - U N ANALIZZATORE DI E-SERESSIONI... -945

void atom(double &result); case '+':


voi d get token (); result = result + temp;
void ser;or(int error); break;
i nt i sdel im(char e);
public:
parsero;
double eval_exp(char *exp);
}; Il Moltiplica o divide due fattori.
void parser::eval_exp3(double &result)
11 costruttore di parser {
pa.rser: :parsero register char op;
{ double temp;
exp_ptr = NULL;
eval exp4(resul t);
whil e((op = *token) '*' 11 op == 'I' 11 op '%') {
Il Punto di ingresso nel parser. get token();
double parser::eval exp(char *exp)
{ - eval_exp4(temp);
switch(op) {
double result; case '*':
result = result * temp;
exp_ptr = exp; bi:eak;
case 'I':
get_token(); resul t = result I temp;
if( ! *token) { break;
serror(2); Il non contiene alcuna espressione case '%':
return O.O; result = (int) result % (int) temp;
break;
eval exp2(result);
if(*token) serror(O); 11 l'ultimo token deve essere null
return result;

Il Elabora un elevamento a potenza


11 Sonrna o- sottrae due tenni ni. void parser: :eval_exp4(doubl e &resul t)
void parser: :eval_exp2(double &result) {
{ double temp, ex;
register char op; register int t;
doub le temp;
eval expS(result);
eval exp3(result); if(*token== A) {
while((op = *token) '+' 11 op ,_,) {
get token();
get tokenO; eval_exp4(temp);
eval_Xi)3(temp); ex = result;
switch(op) { if(temp==O.O) {
case. 1 - 1 : resul t = 1.0;
result = result - temp; . return;

---- ----~---~-
946 CAPITOLO 40 ------ - -\J N ANALIZZA T--erftE-tJ-1 ESPRESSI ON I. . . -947

for(t=(int)temp-1; t>O; --t) result = result * (double)ex;


static har *e[]=
"Errore di sintassi",
"Parentesi non bi 1anci ate",
11 Va 1uta un + o un - unari o. "Non contiene al cuna espressione"
void parser::eval_expS(double &result) );
{ cout e [errar] << endl ;
register char op;

op = O; 11 Ottiene il token successivo.


if((tok_type == DELIMITER) && *token== '+' 11 *token == ' - ' ) { void parser: :get_token()
op = *token; {
get_token(); register char *temp;
}
eval exp6(resul t); tok_type = O;
if(op=='-') result = -result; temp = token;
*temp = '\O';

11 Elabora un'espressione fra parentesi. if(!*exp_ptr) return; Il alla fine dell'espressione


void parser: :eval_exp6(double &result)
{ whi1e(isspace(*exp_ptr)) ++exp_ptr; Il salta gli spazi vuoti
if((*token == '(')) {
get_token O; if(strchr("+-*1%"=()", *exp_ptr)) {
eval exp2(resul t); tok_type = DELIMITER;
if(*token != ') ') 11 avanza fino al carattere successivo
serr:or(l); *temp++ = *exp_ptr++;
get_token();
else if(isalpha(*exp_ptr)) {
else atom(resul t); while(!isdelim(*exp_ptr)) *temp++ = *exp_ptr++;
tok_type = VARIABLE;

Il Legge il valore di un numero. e1 se i f(tsdtgit(*exp ptr)) {


void parser: :atom(double &result) while(!isdelim(*exp_ptr)) *temp++ = *exp_ptr++;
{ tok_type = NUMBER;
swi tch ( tok _type) {
case NUMBER:
result atof(token); *temp = '\0';
get_ token () ;
return;
default: Il Restituisce true se c un delimitatore.
serror(O); int parser::isdelim(char c)
-~ {
if(strchr(" +-/*%"=()", c) Il c==9 Il c=='\r' 11 c==O)
return .1;
1/-V.isuaJjzza un errore di sintassi. return O;
void_ parse.i:_: :ser:o:(int errar)
948 ---u N A N A L I Z Z A-T 0 A E -0-LE..S P R-f-6-6 i'iJN r.--: :---=:J1+.1:1

La prima versione del parser pu gestire i seguenti operatori: +, -, *,/e%. Il funzionamento del parser
. Inoltre pu gestire l'elevamento a potenza intera e il meno unario. Infine il parser
gestisce correttamente le parentesi. La valutazione delle espressioni si svolge nel- Per comprendere esattamente il modo in cui il parser valuta un'espressione, si
le funzioni ricorsive e mutuamente esclusive che vanno da eval_exp2() a eval_exp6() provi a seguire cosa accade nell'analisi della seguente espressione (si supponga
e nella funzione atom{) che restituisce il valore di un numero. I commenti posti che"xp_ptr punti all'inizio dell'espressione).
all'inizio di ciascuna funzione descrivono il ruolo che tali funzioni giocano nel-
1' analisi dell'espressione. 10-3*2
La seguente funzione main() illustra l'uso di questo parser.
Quando viene richiamata eval_exp(), il punto di ingresso nel parser, questa
int main() ottiene il primo token. Se il primo token nullo, la funzione visualizza un mes-
{
saggio e quindi termina. Ma in questo caso il token contiene il numero 10. Poich
char expstr[BO];
il primo token non nullo, viene richiamata eval_exp2(). Come risultato,
evat_exp2() richiama eval_exp3() ed eval_exp3() richiama eval_exp4() che a sua
cout "Per uscire digitare il punto. \n";
volta richiama eval_exp5(). La funzione eval_exp5() controlla se il token un
parser ob; // istanzia un parser operatore + o - unario; in questo caso non cosl e dunque viene richiamata
eval_exp6(). A questo punto, eval_exp6() pu richiamare ricorsivamente eval_exp2()
for(;;) { (nel caso di un'espressione fra parentesi) oppure atom() per conoscere il valore
cout << "Introdurre un'espressione: "; del numero. Poich il token non una parentesi aperta, viene eseguito atom() e a
cin.getline(expstr, 79); result viene assegnato il valore 10. Quindi viene letto un altro token e la funzione
if(*expstr=='. ') break; inizia a uscire dalla catena di chiamate a funzione. Poich il token ora un opera-
cout "Il risultato : " ob.eval_exp(expstr) << "\n\n"; tore, la funzione esce fino a eval_exp2().
};
Ci che accade ora molto importante. Poich il token -, questo viene salva"
to in op. Quindi il parser legge il token successivo, il numero 3, e ricomincia la
return O;
discesa nella catena delle funzioni. Come prima si entra in atom(). In valore 3
viene restituito in result e viene letto il token *.Questo provoca una risalita nella
catena delle funzioni fino a eval_exp3() dove viene letto il token finale 2. A questo
Ecco un esempio d'uso. punto inizia la prima operazione aritmetica, la moltiplicazione fra 2 e 3. Il risulta-
to viene restituito a eval_exp2() che esegue la sottrazione. La sottrazione fornisce
Per uscire digitare il punto. la risposta 4. Anche se a prima vis~a questa procedura pu sembrare complessa, si
Introdurre un'espressione: 10-2*3
possono trovare altri esempi per verificare che questo metodo funziona corretta-
Il risultate : 4
mente per ogni espressione.
Introdurre un'espressione: (10-2) *3 Questo parser pu essere adatto per realizzare una semplice calcolatrice. Ma
Il risultato : 24 prima di poter essere utilizzata in un linguaggio per computer, in un database o in
una calcolatrice pi sofisticata, occorre dargli la possibilit di gestire le variabili.
Introdurre un'espressione: 10/3 Questo l'argomento della prossima sezione.
. Il risultato : 3.33333

-Introdurre un'espressione:
40.7 Aggiunta delle variabili

Le variabili sono utilizzate da tutti i linguaggi di programmazione, da molte cal-


colatrici e da_!_ fogli elet~ronic~per contenere valori che possono essere utili in

---- --- --------- -


---: f'-- -- '
--- -----~ --: J_
950 CAPITOLO 40 " UN ANALIZZATORE DI ES P R E.S.S I ON I... 951

seguito. Se il parser deve essere utilizzato per tali applicazioni, occorre espander- Inoltre si deve modificare la funzione atom() in modo da gestire sia numeri
lo per includere le variabili. Questa aggiunta richiede varie modifiche al parser. che variabili. Ecco la nuova versione:
Innanzitutto occorre aggiungere le variabili. Come si detto in precedenza, per le
variabili verranno utilizzate le lettere a A a Z. Le variabili sono contenute in un Il Legge il valore di un llumero o di una variabile.
array all'interno della classe parser. Ogni variabile utilizza una posizione di un void parser: :atom(double &result)
array di 26 elementi double. Pertanto alla classe parser si dovr aggiungere tale {
array: switch ( tok_ type) {
case VARIABLE:
double vars[NUMVARS]; Il contiene il valore delle variabili result = find_var(token);
get_token();
return;
Inoltre si dovr modificare il costruttore della classe parser.
case NUMBER:
result = atof(token) ;
11 costruttore di parser get_token();
parser: :parser()
return;
{
default:
int i; serror(O);

exp_ptr = NULL;

for(i=O; i<NUMVARS; i++) vars[i] O.O;


Tecnicamente queste aggiunte sono tutto ci che necessario per consentire
al parser di utilizzare le variabili. Tuttavia non stato ancora previsto alcuno
strumento per assegnare un valore a queste variabili. Spesso loperazione viene
Come si pu vedere per comodit le variabili vengono tutte inizializzate a O.
eseguita all'esterno del parser ma si pu trattare il segno di uguaglianza come un
Ora necessario pensare a una funzione in grado di accedere al valore di ogni
operatore di assegnamento (cos come avviene in C++) in modo che tale operato-
singola variabile. Poich le variabili hanno un nome che va da A sua Z, il loro
nome pu essere utilizzato con facilit per gestire l'indice dell' array sottraendo al re entri a far parte del parser. Vi sono vari modi per eseguire questa operazione.
Un metodo consiste nell'aggiungere alla classe parser una nuova funzione, chia-
codice ASCII della lettera della variabile il codice ASCII della lettera A. L'opera-
mata eval_exp1 (). Questa funzione inizia la catena delle discese ricorsive. Questo
zione viene eseguita dalla funzione membro find_var():
significa che eval_exp() deve richiamare eval_exp1 ()e non eval_exp2() per inizia-
re l'analisi dell'espressione. Ecco l'aspetto della funzione eval_exp1 ():
Il Restituisce il valore di una variabile.
doubl e parser: :find var(char *s)
{ - 11 Elabora un assegnamento.
if(!isalpha(*s)){ void parser: :eval expl (doubl e &result)
serror(l);
{ -
return O.O; int slot;
char ttok_type;
return vars [toupper(*token)- 'A'); char temp_token[BO];

i f (tok_type==VARIABLE)
Il salva il vecchio token
Questa funzione accetta variabili dal nome molto lungo ma solo l~ prima letter strcpy(temp_token, token);
sar significativa. Naturalmente possibile modificare questa funzione per adat- ttok_type . = tok_type;
tarla alle proprie esigenze.

---- ___ __
-------
,
952 C A P I .:f-Q-.b-0-4-0-

Il calcola l'indice della variabile I* Questo modulo contiene il parser a discesa ricorsiva
slot = toupper(*token) - 'A' ; che riconosce le vari abi 1 i.
*I
get tokenO;
if(;token != '=') { #include <ios'tream>
putbackO; 11 restituisce il token corrente #include <cstdlib>
/I ripristina il vecchio token - non un assegnamento lii ne 1ude <cctype>
strcpy(token, temp_token): #include <cstring>
tok_type = ttok_type; using namespace std;

else enum types { DELIMITER = 1, VARIABLE, NUMBER};


get_token(); Il legge la parte successiva dell'espressione
eval_exp2(resul t); const int NUMVARS = 26;
vars[slot] = result;
return; class parser {
char *exp_ptr; Il punta all'espressione
char token[80]; Il contiene il token corrente
char tok_type; 11 contiene il tipo del token
eval_exp2(result); double vars[NUMVARS]; 11 contiene il valore delle variabili

void eval_expl(double &result);


Come si pu vedere, la funzione deve avanzare per vdere se si sta eseguendo void eval_exp2{double &result);
un assegnamento. Questo dovuto al fatto che il nome di una variabile precede void eval_exp3(double &result);
sempre un assegnamento ma il solo nome della variabile non garantisce che void eval_exp4(double &result);
void eval_expS(double &result);
l'espressione successiva sia un assegnamento. Questo significa che il parser ac-
void eval_exp6(double &result);
cetter l'espressioneA = 100, ovvero un assegnamento ma sar in grado di scopri- void atom(doubl e &resul t);
re che A/ 10 non un assegnamento. Per fare questo, eval_exp1 () legge il token voi d get_ token ();
successivo dallo stream di input. Se non si tratta del segno di uguaglianza, il tok~n void putback();
viene restituito allo stream con putback() per essere riutilizzato in seguito. Pertan- void serror(int error);
to occorre i11ciudere _n_el!~. class.:_J?~Ser anche la funzione putback(): double find_var(char *s);
int isdelim(char c);
Il Restituisce un token allo stream di input. publ ic:
void parser: :putback() parsero;
{ double eval_exp(char *exp);
char *t; };

t = token; Il costruttore di parser


for(; *t; t++) exp_ptr--; parser: :parserO
{
int i:
Dopo aver apportato tutte le modifiche necessarie, il parser avr il seguente
----aspetto. exp_ptr = NULL;

for(i=O; i<NUMVARS; i++) vars[;J O.O;

- - - - -------
954 CAPITOLO 40 U bLA.N.ALLZ ZA TOR E DI ESPRESSIONI ... 955

Il Punto di ingresso riel parser. eval_exp2{result);


double parser: :eval_exp{char *exp) , ...
{
double result; 11 Somma sottrae due termini.
void parser: :eval_exp2{double &result)
exp_ptr = exp; {
register char op;
get token () ; double temp;
i f(i*token) {
serror{2); Il non contiene alcuna espressione eval exp3{result);
return o.o; whil;{{op = *token) '+' 11 op == '_,) {
get token();
eval_expl (resul t); eval exp3{temp);
i f(*token) serror(O); 11 1 1 ultimo token deve essere null swit~h{op) {
return result; case '-':
result = resul t - temp;
break;
Il Elabora un assegnamento. case 1 + 1 :
void parser: :eval expl(double &result) resul t = resul t + temp;
{ - break;
int slot;
char ttok_type;
char temp_token[BO];

i f ( tok_ type==VARIABLE) Il Moltiplica o divide due fattori.


Il salva il vecchio token void parser: :eval_exp3(double &result)
strcpy(temp_token, token); {
ttok_type = tok_type; register char op;
doub 1e temp;
I/ calcola 1 indi ce della vari ab il e
slot = toupper(*token) - 'A; eval exp4(result);
whil ;{{op = *token) '*'Il op== 'I' Il op=='%')
get token(); get_token();
if(;token != '=') { eval_exp4(temp);
putback(); 11 restituisce i 1 token corrente switch(op) {
11 ri pristina il vecchi o token - non un assegnamento case 1 *':
strcpy(token, temp_token); result = result * temp;
tok_type = ttok_type; break;
case 'I':
else { result = resul t I temp;
get_token{); /I legge la parte successiva dell 'spression~ break;
eval_exp2(result); case 1 %1 :
vars[slot] = result; --r.esulL= (int) result % (int) temp;
return; break;
}
} -}- ---- -
- - - - --
956- .. CAPITOLO 4 O uN ANALIZZATO R1:-1JTTS PR-ESSI O NI. ;-,-- 957

11 Elabora un e 1evamento a potenza Il Legge i~ valore di un numero o di una variabile.


void parser: :eval_exp4(double &result) void parser: :atom(double &result)
{ {
double temp, ex; switch(tok_type) {
register int t; case VARIABLE:
result = find var(token);
eval exp5{result); get_token(); -
if(*token== '"') { return;
get_token () ; case NUMBER:
eval_exp4(temp); result = atof(token);
ex = result; get_token();
if(temp==O.O) { return;
result = 1.0; default:
return; serror(O);

for(t=(int)temp-1; t>O; --t) result = result * (double)ex;

Il Restituisce un token allo stream di input.


void parser: :putback()
Il Valuta un + o un - unario. {
void parser: :eval_expS(double &result) char *t;
{
register char op; t = token;
for(; *t; t++) exp_ptr--;
op = O;
if ( ( tok_ type == DELIMITER) && *token== '+' 11 *token ,_,) {
op = *token; Il Visualizza un errore di sintassi.
get_token (); void parser::serror(int error)
{
eval exp6(result); static char *eO= {
if(op=='-') result -result; "Errore di sintassi",
"Parentesi non bilanciate",
"Non contiene alcuna espressione"
Il Elabora un'espressione fra parentesi. );
void parser: :eval_exp6(double &result) cout e[error] endl;
{
if((*token == '(')) {
get token(); Il Ottiene il token successivo.
eval_exp2(result); - void parser: :get_token()
if(*token != ')') {
serror(l); register char *temp;
get_token();

__ -~----
-tok_type = O;
eiS"atOm{re5u1 tf; - ---- . __:_::::. - :._.__:- :...__ temp = token;

----
l
958 CAPITOLO 40 --u N ANALIZZA ToR-ED I ES P A ESSI ON I... . 959..

*temp = '\O'; A= 10/4


A-B
if(!*exp_ptr) return; Il alla fine dell'espressione
C=A * (F-21)
while(iss.pace(*exp_ptr)) ++exp_ptr; Il salta gli spazi vuoti

if(strchr("+-*/%"=()", *exp_ptr)) {
tok_ type = DELIMITER;
40.8 Controllo della sintassi in un parser
11 avanza fino al carattere successivo
a discesa ricorsiva
*temp++ = *exp_ptr++;
Prima di parlare della versione template del parser, opportuno esaminare breve-
else if(isalpha{*exp ptr)) { mente la verifica della sintassi. Nell'analisi delle espressioni, un errore di sintassi
while(!isdelim(*exp_ptr)) *temp++ = *exp_ptr++; semplicemente una situazione in cui l'espressione non segue le rigide regole
tok.:_type = VARIABLE; richieste dal parser. La maggior parte delle volte si tratter di un .eqore umano, in
genere di un errore di digitazione. Ad esempio le seguenti espressioni non verran-
else if(isdigit{*exp ptr)) { no accettate dai parser creati in questo capitolo:
while(!isdelim(*exp_ptr)) *temp++ = *exp_ptr++;
tok_ type = NUMBER; .
10 ** 8
(10-5)*9)
/8
*temp = '\D';

La prima espressione contiene due operatori in sequenza, la seconda contiene


Il Restituisce tru se c un delimitatore. un numero errato di parentesi e l'ultima contiene il segno di divisione all'inizio
int parser: :isdelim{char c) dell'espressione. Nessuna di queste condizioni pu essere consentita dai parser.
{ Poich gli errori di sintassi possono fare in modo che il parser fornisca risultanti
if(strchr{" +-1*%--=()", c) 11 c==9 11 c=='\r' 11 c==O) errati, opportuno trovare il modo per risolvere queste situazioni.
return l; Dell'analisi del codice dei parser, si sar probabilmente notata la presenza
return O; della funzione serror() che viene richiamata in alcune situazioni. A differenza di
molti altri tipi di parser, i parser a discesa ricorsiva semplificano la verifica della
sinta_ssi P_<?ich e2_~~si verifica principalmente in atom(), find_ var() ed eval_exp6(),
Il Restituisce il valore di una variabile.
dove vengono controllate le parentesi. L'unico problema del controllo della sin-
double parser: :find var(char *s)
{ - tassi che il parser non viene chiuso in caso di errore. Questo porta a una succes-
if(!isalpha{*s)) sione di messaggi di errore. Il modo migliore per implementare la funzione serror()
serror(l); consiste nell'eseguire una qualche forma di reset. Ad esempio, i compilatori C++
return O.O; forniscono una coppia di funzioni di salto chiamate setjmp() e longjmp(). Queste
due funzioni consentono a un programma di saltare in un'altra funzione. Pertanto
return vars [toupper(*token)- 'A']; serror() potrebbe eseguire un salto con longjmp() in un punto sicuro del program-
. ma, ali' esterno del parser.
A seconda dell'uso che si fa del parser, si potrebbe anche decidere che il
. Per provare questa nuova versione del parser, si pu impiegare la stessa fun- ~meccanismo di gestione delle ccezioni fornito dal linguaggio C++ (implementa-
zione main() utilizzata per il primo parser. La seconda versione del parser accetta to tramite try, catch e throw) pu essere comodo per la gestione degli errori.
anche le seguenti espressioni: Se si lascia il codice cos come , il parser pu produrre pi messaggi di
errore. Questo pu essere un problema in alcune situazioni e vantaggioso in altre.__ _
-------- -
--------~-
960

dove importante rilevare tutti gli errori. Ma generalmente sar opportuno esten- voi d get token () , putback O:
dere la sezione di controllo della sintassi prima di utilizzare un parser di questo void ser;or(int error); _
tipo in un programma commerciale.. PType find var(char *s):
int isdeli;(char c);
publ ic:
parser();
40.9 Realizzazic:~ne di un parser generico PType eval_exp(char *exp):
}:
I due parser precedenti operavano su espressioni numeriche in cui tutti i valori
// costruttore di parser
dovevano essere di tipo double. Anche se questo pu andare bene per applicazioni
template <class PType> parser<PType>: :parser()
che utilizzano valori double, certamente eccessivo per le applicazioni che utiliz- {
zano solo valori interi. Il fatto di definire nel codice il tipo dei valori utilizzati int i;
riduce senza motivo le possibilit di applicazione del parser. Fortunatamente, gra-
zie all'impiego di un template per classi, non difficile creare una version gene- exp_ptr = NULL;
rica del parser che funzioni con ogni tipo di dati per i quali siano definite le espres-
sioni algebriche. In questo modo il parser potr essere utilizzato sia con i tipi for(i=O; i<NUMVARS; i++) vars[i] (PType) O;
standard che con i tipi numerici creati dal programmatore. Ecco la versione gene-
rica del parser di espressioni.
/ / Punto di ingresso nel parser.
template <c_lass PType> PType parser<PType>: :eva1_exp{char *exp)
// Un parser generico.
{
#include <iostream> PType re su 1t:
#i nel ude <cstdl i b>
#i nel ude <cctype> exp_ptr = exp;
#include <cstring>
usi ng namespace std; get_token () :
if{!*token) {
enum types { DELIMITER = 1, VARIABLE, NUMBER}; serror{2): //.non contiene alcuna espressione
return (PType) O;
const int NUMVARS = 26;
eval expl(result):
template <class PType> class parser { if{*token) serror(O): //l'ultimo token deve essere null
char *exp_ptr: //punta all'espressione return result;
char token[BO]; //contiene il token corrente
char tok_type; // contiene il tipo del token
PType vars [NUMVARS]: // contiene i 1 val ore del 1e vari ab il i / / Elabora un assegnamento.
template <class PType> void parser<PType>::eval_expl(PType &result)
void eval_expl(PType &result); {
void eval_exp2{PType &result); int slot:
void eval_exp3{PType &result); char ttok type;
void eval_exp4(PType &result); char temp=token[BO]:
void eval_expS{PType &result); -
void eval_exp6(PType &result): if(tok type==VARIABLE)
void atom(PType &resul t); // s~lv.a. il vec.chio_.token
.strcpy(temp_t~~"L toke~J:
962 CAPITOLO 40 -- UN ANALIZZATORE DI ESPRESSIONI... 963

ttok_type = tok_type;
register char op;
Il calcola l'indice della variabile PType temp;
slot = toupper(*token) - 'A';
eval_exp4(result);
get token(); while((op = *token) '*' 11 op == 'I' 11 op == '%') {
if(;token != '=') { get_token () ;
putback(); Il restituisce il token corrente eval exp4(temp);
11 ri pristina il vecchi o token - non un assegnamento switch(op) {
strcpy ( token. temp_token); cas~ '*':
tok_type = ttok_type; result = resul t * temp;
break;
else { case 'I':
get_token(); Il legge la parte successiva dell'espressione result = result- I temp;
eval exp2(result); break;
vars(slot] = result; case '%':
return; result = (int) result % (int) temp;
break;

eval_exp2 (resul t);

11 Elabora un elevamento a potenza


11 Somma o sottrae due termini. template <class PType> void parser<PType>: :eval_exp4(PType &result)
templ ate <class PType> void parser<PType>: :eval_exp2(PType &result) {
{ PType temp, ex;
register char op; register int t;
PType temp;
eval_expS(result);
eval_exp3(result); if(*token== '"') {
while((op = *token) , +, 11 op == , _ , ) { get_token ();
get_token (); eval_exp4(temp);
eval_exp3(temp); ex = result;
switch(op) { if(temp==O.O) {
case 1 - 1 : result = (PType) 1;
result = result - temp; return;
break;
case '+': for(t=(int)temp-1; t>O; --t) result = result * ex;
resul t = result + temp;
break;

Il Valuta un+ o un - unario.


template <class PType> void parser<PType>: :eval_expS(PType &result)
{
/1-MoH-iplica o divide due fattori. register char op;
_-_templ ate <cl ass -PType> void parser<PType>:--:eva1_exp3(EI,YP~r_e~!!_li)
- --- - -----
-- - -- ~ -
964. CAPITOLO 40 UN ANALIZZATORE DI ESPRESSIONI ... -- 965

op = O;
if{{tok_type == DELIMITER) && *token=='+' 11 *token ,_,) {
op = *token; Il Visualizza un errore di sintassi.
get_token{): template <class PType> void parser<Plyp>::serror{int error)
{
eval_exp6{result): static char *eO= {
if{op=='-') result -result; "Errore di sintassi",
"Parentesi non bilanci ate",
"Non contiene al cuna espressione"
11 Elabora un'espressione fra paren~esi. );
template <class PType> void .parser<PType>: :eval exp6(PType &result) cout e[error] endl;
{ . -
i f {{ *token == ' ( ' ) ) {
get_token{): Il Ottiene il token successivo.
eval_exp2(result): template <class PType> void parser<PType>::get_token{)
if(*token != ') ') {
serror(l); registe!:_ char *temp;
get_token O;
tok_type = O;
e'lse atom(result); temp = token;
*temp ='\O';

Il Legge il valore di un numero o di una variabile. if(!*exp_ptr) return; Il alla fine dell'espressione
templ ate <cl ass PType> voi d parser<PType>: :atom{PType &result)
{ while(isspace{*exp_ptr)) ++exp_ptr; Il salta gli spazi vuoti
swi tch {tok_type) {
case VARIABLE: if(strchr( 11 +-*l%"=() 11 , *exp_ptr)) {
result = find_var(token); tok type = DELIMITER;
get_ token O: Il ;vanza fino al carattere successivo
return; *temp++ = *exp_ptr++;
case NUMBER: )
result = {PType) atof(token); else if(i salpha(*exp_ptr))
get_ to'ken () ; while(!isdelim(*exp_ptr)) *temp++ *exp_ptr++;
return; tok_type = VARIABLE;
default: )
serror(O): else if(isdigit(*exp ptr))
while(!isdelim(*exp_ptr)) *temp++ = *exp_ptr++;
tok_type = NUMBER;

Il Restituisce un token allo stream di input.


template <c1ass PType> void pcrrser<PType>: :putback() *temp-.: '\O':
{ - .-
char *t;
Il Restituisce true se e uri delimitatore.
t = token;. template <class PType> int parser<PType>::-tsdelinr{1:'ha'l"-tj"--
for(: *t; t+:L:_exp:_ptr--; 4=-------- - - __ .. --
---~------
--=--- - :-.--- -

966 CAPITOLO 40

if(strchr(" +-/*%"=()", c) Il c==9 Il c=='\r' Il c==O)


return l;
return O; return O;

// Restituisce il valore di una variabile. Ecco un esempio d'uso.


template <class PType> PType parser<PType>: :find_var(char *s)
{ Parser per numeri in virgola. mobile. Per uscire digitare il punto
i f ( ! i sa1pha (*s) ){ Introdurre un'espressione: a=lO.l
serror(l); Il risultato : 10.1
return (PType) O;
Introdurre un'espressione: b=3.2
return vars [toupper(*token)- 'A']; Il risultato : 3.2

Introdurre un'espressione: a/b


Come si pu vedere, il tipo di dati su cui opera il parser specificato dal tipo Il risultato : 3.15625
generico PType. La seguente funzione illustra l'uso del parser generico:
Introdurre un'espressione:
int main()
{ Parser per numeri interi. Per uscire digitare il punto
cha r exps t r [80] ; ,' Introdurre un'espressione: a=lO
Il risultato : 10
// Illustra l'uso del parser per numeri in virgola mobile.
parser<doubl e> ob; Introdurre un 'espressione: b=3
Il risultato : 3
cout "Parser per numeri in virgola mobile. ";
cout "Per uscire digitare il punto\n"; Introdurre un'espressione: a/b
for(;;) { Il risultato : 3
cout << "Introdurre un'espressione: ";
ci n.getl i ne(expstr, 79); Introdurre un'espressione:
if(*expstr=='. ') break;
cout -"Il risultato : " ob.eval_exp(expstr) "\n\n"; Come si pu vedere, il parser in virgola mobile usa valori in virgola mobile e
il parser per interi usa valori interi.
cout endl ;

/I Illustra l'uso del parser per numeri interi.


parser<int> lob; 40.1 O Alcune estensioni da provare
cout "Parser per numeri interi "; Come si detto in precedenza in questo capitolo, questi parser svolgono solo un
cout "Per uscire digit~r:e il punto\n"; livello minimo di verifica degli errori. Dunque si pu pensare di aggiungere un
for(;;) {
controllo degli errori pi dettagliato. Ad esempio si potrebbero evidenziare i punti
-cout <<-!!.Introdurre un'espressione: ";
dell'espressione in cui stato rilevato l'errore. Questo consentir di trovare e
Cin.getline(expstr, 79);
correggere l'errore di sintassi. . - - -
if(*expstr=='. ') break;
- - -cout-- "Il risultato~:_ __:_ ob.eval_exp(expstrL.~:L'.'.Jn.\n.";.
---eos come il parser-pu valutare_soJ<L.espressioni numeriche. Tuttavia, con
- - -poche aggiunte, possibile consentire al parser di valutare altri tipi dLespressioni,
-968 CAPITOLO 40

ad esempio stringhe, coordinate spaziali o numeri complessi. Ad esempio, per Indice analitico
consentire al parser di valutare oggetti stringa si possono apportare le seguenti
modifiche.
1. Definire un nuovo tipo di tokeri chiamato STRING.
2. Estendere get_to~en() in modo da consentire il riconoscimento delle stringhe.
3. Aggiungere ad atom() un nuovo caso per la gestione di token di tipo STRING.
Dopo aver implementato questi passi, il parser potr gestire espressioni come
le seguenti:

a= ''uno"
b= "due"
c=a+b
A array, 95, 337 B
Il risultato in e sar il concatenamento di a e b per produrre la frase "unodue". allocazione, 363
bidimensionali, 102
Ecco un_suggerimento applicativo per il parser. Creare un una piccola calcola- abort(), 753 compattamento, 480 bad(). 785
trice che accetti un'espressione introdotta dall'utente e ne visualizzi il risultato. abs(), 754 creazione, 340 BASIC, 6, 8
accumulate(), 893 binary_search(), 824
Questa potrebbe essere un'interessante aggiunta a un qualsiasi programma com- acos(), 733
dinamici
binder, 855
uso, 626
merciale. Se il programma viene realizzato per Windows, l'operazione sar parti- adattatori, 857 generare un puntatore, 97 uso dei, 681
colarmente facile. da puntatore a funzione, 857 inizializzati, 340 binding
da puntatore a funzione iniziali:zZazione, 1!1 anticipato, 467
membro, 858 di input, 623 ritardato, 467
adjacent_difference(), 894 limiti, 97 blocchi, 61, 93
adjacent_find(), 824 monodimensionale, passaggo bool, 16
advance{), 853 alla funzione, 98 break, 71
algoritmi, 632, 664 monodimensionali, 95, 97 bsean::h(), 756
accumulate(), 893 multidimensionali, 108, 112 Bubblesort, 478
adjacent_difference{), 894 non dimensionati, bufferizzazione della riga, 198
inner_product(), 895 inizializzazione, 113 bug, 6
numerici, 893 non inizializzati, 340 byte, 555
partial_sum(), 896 di oggetti, 337
algoritmi STL, 823 operazioni di 1/0, 621
allineamento di puntatori, 128- G------
dell'output, 209 relazione con i puntatori,
allocatori, 860 127
allocazione di stringhe, 106
e
degli array, 363 abbreviazioni, 60
nelle strutture, 181
della memoria, 362 aspetto di un programma, 9
asctime(), 742
di oggetti, 364 blocco di codice, 7
asin(), 734
allocazione dinamica, 136 compilazione separata, 12
assegnamenti multipli, 40
ambiguit, 390 funzione, 7
asserto. 754
ANSI (American National linguaggio per
assign()
Standards lnstitute), 3 programmatori, 7
prototipo, 688
applicazioni linguaggio strutturato, 5
atan(), 734
C++, 907 origini del linguaggio, 3
atan2(), 734
argc, 150 parole chiave, 9
atexit(), 754
argomenti peculiarit del, 5
atof(), 755
standard, 388, 390 CANSI
-atoi(), 755

-1---- -
-- .
argv, 150 atol(), 755
auto, 28
elenco parole chiave, IO
tipi di dati, 17

-----t- ---
_______ 1N_D_IC~E_A~~--_A_L_tT_l_C_O_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __
97_0_ _ INDI eE ANALI T-1 e o- 971

C++ container, 635 uso di dynamic_cast, 593 dichiarazioni fclose(), 698 flag
descrizione del, I container STI., 799 valarray, forward, 310 prototipo. 225 di formattazione, 780
elementi di base, 268 definizione di funzioni 879, 880, 881, 882; 883, stile delle, 359 llStl ~ . 227 flag di formattazione
forma generale, 297 inline, 316 884, 885, 886, 887, 888 differenze feof( ,:-o\ll! attivazione, 529
introduzione alle classi, 279 deque, 803, 804, 805 yector, 819, 820, 821 fra C e C++, riepilogo, 628 prototipo, 229 cancellazione, 525
linguaggio, 261 derivate, 429 clear(), 785 difftime(), 743 uso di, 228 impostazione, 524
manipolatori, 532 eccezioni, 503 prototipo, 573 direttive per il preprocessore, 245 ferror(), 699 valore dei, 528
nuovi header, 277 elenco oggetti, 300 clearerr(), 698 distance(), 853 prototipo, 232 flags(), 786
origini del, 263 ereditate, 429 clock(), 742 distruttori, 293, 437, 912 fflush(), 699 float, 15, 35
parole riservate, 297 exception, 515, 900 COBOL, 8 esecuzione, 327 fgetc(), 699 floor(), 736
programma di esempio, 268 failure, 784 codici speciali, 37 div(), 757 fgetpos(), 700 flush(), 787
stream, 520 per i file, 549 commenti, 259 do-while, 61 fgets(), 230, 700 prototipo, 566
tipo di dati bool, 275 friend, 312 compilatori, 8 double, 15, 35 prototipo, 230 frnod(), 736
trasformazione automatica front_insert_iterator, 848 concatenamento, 916 file, 220 fopen(), 701
in int, 274 generiche, 469, 482 const, 25 accesso diretto, 566 prototipo, 223
vecchio stile e moderno, 275 argomenti non-tipi, 488 apertura, 223, 550 usodi, 227
calloc(), 749 specializzazioni esplicite,
container. 632
generici
E cancellazione, 234 Valori consentiti, 701
campi bit, 182 492 vector, 637 chiusura, 225, 550 for, 61
caratteri, 15, 555, 721 gerarchia di, 778 conteggio, 664 eccezioni, 899 indicatore di posizione, 221 for_each(), 827
di controllo, 721 gslice, 890, 891 conversione di tipo raccolta, 507 modalit di apertura, 550 forme prefisse e postfisse
stampabili, 721 helper, 893 automatica, 39 restrizioni, 510 personalizzazione delle degli operatori, 401
ceil(), 735 di I/O, 777 conversioni rilancio, 512 operazioni di I/O, 573 FORTRAN, 265
char, 15 insert_iterator, 846 cast, 58 editor, 8 posizione corrente, 570 fprint()
chiamata istream_iterator, 850 conversioni di tipo, 40 BOOM, 733 puntatore, 223 prototipo, 239
a funzione e array, 148 istreambuf_iterator, 851 copy(), 824 elementi rilevamento della fine, 563 fprintf(), 702
per indirizzo, 145 iterator, 844 copy_backward(), 824 rimozione e sostituzione, 669 scrittura di un carattere, 226 fputc(), 703
per indirizzo, creazione, 146 iterator_traits, 845 cos(), 735 enumerazione, 169 system fputs(), 230, 703
per valore, 145 per iteratori a basso livello, cosh(), 735 enumerazioni; 188 funzionamento, 222 prototipo, 230
cicli 844 costanti, 35, 56 eof(), 785 funzioni di tipo, ANSI, 222 fread()
do-while, 84 list, 805, 806, 807, 808 carattere speciali, 37 equa!(), 825 di testo prototipo, 235
for, 74 locali, 330 esadecimali, 36 equal_range(), 825 lettura e scrittura, 553 uso di, 236
for senza COfPO, 81 di localizzazione, 904 intere, 35 ERANGE, 733 file header free(), 750
for, varianti, 76 map, 808, 809, 810 ottali, 36 ereditariet, 267, 288, 429, 437 <cermo>, 733 prototipo, 138
infiniti, 80 membri static, 320 stringa, 36 di una classe base come <climits>, 16 freopen(), 704
while, 81 multimap, 810, 811, 812 in virgola mobile, 35 protected, 435 <cstdio>, 196 prototipo, 243
classe multiset, 812, 813, 814 costrutti da pi classi base, 436 <cstdlib>. 753 frexp(), 737
per stringhe, 863 nidificazione, 330 if-else-if, 65 dei membri protected, 432 <cstring>, 721 fscanf(), 704
classe di memorizzazione ostream_iterator, 851 costruttori, 293, 437, 912 espressioni, 15, 56, 61, 92, 936 <cwctype>, 768 prototipo, 239
specificatori, 27 ostreambuf_iterator, 853 di copie, 377 analisi delle, 938 <errno.h>, 733 fseek(), 705
class~. -303; 305 pair, 903 esecuzione, 327 condizionali, 70 <exception>, 899 macro, 705
accesso alle, 445 parser, 939 espliciti, 599, 617 conversioni di tipo, 57 <fstream>, 549 prototipo, 237
astratte, 464 priority_queue, 814 parametrizzati, 317 elaborazione, 937 <functional>, 634, 857 fsetpos(), 705
auto_ptr, 90 I queue, 815 passaggio di parametri, 441 sezionamento, 940 <iterator.>, 843 fstream(), 787
back_insert_iterator, 848 reverse_iterator, 849 con un solo parametro, 3 I9 spaziatura e parentesi, 59 <stdexcept>, 901 ftell(), 706
bad_exception, 515 set, 816, 817, 818 count(), 825 estrattori <stdlib.h>, 753 funzione
basate su array, 621 slice, 890, 891 count_if(), 825 creazione, 542 <string.h>, 100 uso degli oggetti, 674
base, 429 specificatoli di accesso, 300 ctime(), 742 etichette, 61 <Utility>, 634 funzioni, 143
controllo dell'accesso, 429 stack, 818, 819 exceptions(), 785 <Wchar.h>, 767 di allocazione dinamica della
base virtuali, 448 per stream C++, 520 exit(), 757 <Wctype.h>, 767 memoria, 749
basic_string, 863, 865, string, 683 exp(), 735 file header. argomenti, 145
866, 867, 868, Str1)'pe, 910, 922
D extem, 28 <typeinfo>, 577 argomenti standard, 383
869, 870, 871, 872 uso di, 931 fili(), 786, 826 per array di caratteri
bitset, 801, 802, 803 template, 779 dati fill_n(), 826 estesi; 773
per caratteri, 779 applicazione di typeid, 585 membri, 302
membri static, 320
F find(), 826 di assegnamento, 914
per caratteri estesi, 779 argomenti standard, 490 prototipo, 691---- - di classificazione per caratteri
char_iraits, 872, 873 creazione per lassi dati principali, 15 find_end(), 826 - este, 768
complex, 875, 877, definizione circolare. 160 fabs(), 736 find_first_of(), 827 di conversione, 599
generiche per array, 486
878, 879 tipi di dati generici, 485 dichiarazione di variabili, 8S -- - fail(T,-786 - - find_if(), 827 creazione, 611
- - --- --
972 INDICE ANALITICO i.

di conversione per stringhe di di servizio, 753 getenv(), 757 indirizzi, 337, 351 di un indirizzo, 215 mappe, 658
. carati, 773 per stringhe, 721 getline(), 789 indipendenti, 357 di interi senza segno, 213 costruttori, 659
costruttore, esecuzione;- 438 per stringhe di caratteri gets(), 707 come parametri, 35 I di numeri, 212 memorizzazione
costruttore, overloading, 373 estesi, 772 gmtime(), 743 restituzione di, 356 di singoli caratteri con di oggetti, 662
distruttore, esecuzione, 438 template, 471 good(), 789 restrizioni, 358 scanf(), 213 max(), 830
efficienza, 167 parametri standard, 476 gruppo di scansione, 215 di tipo derivato, 358 di stringhe, 214 max_element(), 830
elementi implementativi, 166 terminate(), 513 inner_product(), 895 lexicographical_compare(), 829 mblen(), 759
end(), 651 di tipo void, 159 inplace_merge(), 828 libreria, 10 mbstowcs(), 759
exitQ, 89 uncaught_exceptionQ, 515 input STI. (Standard Template mbtowc(), 760
fili(), 530 unexpected(), 513
H soppressione, 218 Library), 11 memchr(), 724
flush(), 566 uscita dalle, 154 inseritori libreria di classi, 775 memcmp(), 725
forma generale, 143 di utilizzo generale, 166 header creazione, 536 libreria standard memcpy(), 725
formato rilocabile, 11 variabili locali, 144 di I/O, 780 int. 15 prototipi di funzione, 164 memmove(), 725
frieod, 307, 407 varie, 921 HUGE_VAL, 733 interpreti, 8 libreria STI. memoria
generate, 471 virtuali, 453, 459 intervalli di valori, 16 algoritmi, 632, 664, 665, allocata, inizializzazione, 362
generiche, 469 attributo virtual, 458 isaloum(), 721 666, 667, 823 memsetQ, 726
. per il compattamento di un chiamata tramite isalpha(), 722 classi container, 635, 800 merge(), 830
array, 480 indirizzo, 456 iscntrl(), 722 componenti standard, 634 min(), 831
ordinamento, 478 pure, 462 isdigit(), 722 container, 632, 799 min_element(), 831
restrizioni, 477 uso, 464 I/O isgraph(), 723 elementi della, 634 mismatch(), 831
uso delle, 478 width(), 530 binario islower(), 723 file header, 634 mktime(), 745
getQ, 556 write(), 558 uso con stream basati su isprint(), 723 funzionamento generale, 636 modelli, 469
getline(), 561 funzioni di allocazione dinamica array, 628 ispunct(), 723 introduzione alla, 632 funzioni generiche, 469
diUO, 697 del C, 136 da console isspace(), 724 iteratori, 633, 843 overloading esplicito, 473
di UO di utilizzo generale, funzioni di conversione fra lettura e scrittura di istringstream(), 795 oggetti funzione, 675 tipo di dati generico, 472
784 caratteri estesi e mul stringhe, 199 istruzioni linguaggi modf(), 738
di UO per caratteri estesi, 770 di conversione fra caratteri operazioni, 202 di blocco, 93 ad alto livello, 4 modificatori
ignore(), 565 estesi e mul, 774 formattato, operazioni, 522 break, 88 di medio livello, 4 *e#, 210
inline, 313 fwrite(), 706 funzionamento degli catch, 505 strutturati e non strutturati, 6 di accesso, 25
rnain(), 159 prototipo, 235 operatori, 271 di ciclo, 61 linker, 10 di formato, 207, 217
di manipolazione uso di, 236 manipolatori per la composte, 61, 93 linking, 11
creazione, 544 formattazione, 532 continue, 91 liste, 647
matematiche, 733 operazioni ad accesso goto, 87 memorizzazione di oggetti,
membro, 302, 599, 614 diretto, 237 if, 62 656
N
const, 614
G operazioni binarie, 555 if nidificati, 64 ordinamento, 653
definite da vector, 639 operazioni Ce C++; 219 di iterazione, 61, 74 unione, 654 namespace, 278, 599
non-const, 614 gcount(), 788 operazioni C++ vecchie e retum, 87 localeconv(), 743 negatori, 857
utilizzate di list, 648 geline() nuove, 520 di salto, 61, 86 localtime(), 745 next_permutation(), 832
volatile, 617 prototipo, 561 operazioni da di selezione, 61, 62 log(), 737 nomi esterni, I8
membro static, 325 generate(), 828 console, 195, 242 switch, 70 loglO(), 737 nth_element(), 832
numero variabile generate_n(), 828 operazioni non swith nidificate, 13- - - --foni. 11 numeri
di parametri, 165 gestione formattate, 555 using, 603 longjmp(), 758 interi, 15
overloading, 284, 371 dei dati, 210 operazioni su array, 621 isupper(), 724 lower_bound(), 829 in virgola mobile, 15
parametri formali, 145 gestione delle eccezioni, personalizzazione delle isxdigit(), 724 in virgola mobile doppi, 15
passaggio di oggetti, 331 497, 899 operazioni, 573 iter_swap(), 829
peek(), 566 applicazioni, 516 sistemi di, 519 iteratori, 633, 843
M
per date, ore e localizzazione,
741
per classi derivate, 506
opzioni, 507
stato delle operazioni, 571
identificatori, 18
accesso ai vettori, 641
predefiniti, 845
o
precision(), 530 principi di, 497 identificazione run-time dei tipi tipi di base, 843 macro, 753
prototipi, 162 get(), 788 (RTII), 577 che operano come funzioni, ofstream(), 787
push_back(), 652 altre forme di, 561 una semplice 248 oggetti
push_front(), 652 prototipo, 556 applicazione, 583 predefinite, 258 allocazione, 364
put(), 556 getc(), 707 if, 61 L main(), 150 assegnamento, 335
putback(), 566 uso di, 227 ifstream(), 787 make_heap(), 830 creazione, 375
read(), 558 getchar(), 707 ignore(). 790 labs(), 758 malloc(), 750 inizializzati, 375
regole di visibilit, 144 alternative a, 198 prototipo, 565 ldexp(), 737 prototipo, 137 non inizializzati, 375
problemi, 198 incapsulamento, 266 !div(), 758 manipolatori passaggio di indirizzi, 355

I
restituzione.deLvalori~155
restituzione di puntatori, 157 prototipo, -197-. --includes{}, 828 lettura di uo, 780 restituzione di, 334
974 INDICE ANALITICU-- ____ -- ------ --
INDIC_E ANALITICO 975

oggetti funzione, 675, 854 creazione di operator funzionamento, 949


membro, 396 generico, realizzazione, 960 numero magico - R modificata tramite
binari, 676
C"'8Zione, 679 dell'operatore (), 422 partial_sort(), 833 Versione 2, 63 overloading, 526
definizione, 854 dell'operatore ->, 424 partial_sort_copy(), 833 numero magico - setjmpQ, 761
Versione 3, 65 raise(), 761 setlocale(), 745
unari, 676 dell'operatore <, 784 partial_sum(), 896
numero magico - rand(), 761 setstate(), 795
uso di quelli forniti, 676 dell'operatore <<, 535 partition(), 833
Versione 4, 66 random_shuffle(), 835 setvbufO, 716
openO. 790 dell'operatore>, 784 pcount{)
un semplice dizionario, 20 I rdstate(), 792 short, 17
operatori, 38, 56 dell'operatore , 535 prototipo, 623
tris (tic-tac-toe), 114 read(), 793 signal(), 762
(}e[], 55 dell'operatore O. 418 peek{), 791
dell'operatore virgola, 425 prototipo, 566 programmi di servizio per la prototipo, 558 signed, 16
?, 67
esplicito di una funzione, 473 perror(), 708 gestione di file, 8 readsome(), 793 sin(), 738
di allocazione dinamica,
polimorfismo, 266, 453 programmi per la gestione di realloc(), 750 sinh(), 739
337, 360 flessibilit, 373
pop__!ieap(), 834 eventi in tempo reale, 8 register, 28, 33 sistemi operativi, 8
aritmetici, 38, 41 delle forme abbreviate degli
posizionamento, 221 puntatori, 95, 119, 337 remove(), 712, 835 sizeof
bit-a-bit, 38, 46 operatori, 402
aritmetica, 123 prototipo, 234 uso di per la trasportabilit
const_cast, 595 funzione modificata tramite, pow(), 738
381 - array d~ 128 remove_copy(), 835 del codice, 191
di conversione cast, 587 precision(), 791
assegnamento, 123 remove_copy_if(), 835 sort(), 839
di decremento, 40 I delle funzioni, 284 predicati, 634
C++ remove_if(), 835 sort...heap(), 840
di incremento, 40 I di funzioni, 371, 390 binari, 634
verifiche di tipo, 343 rename(), 712 sottostringhe
dynamic_cast, 587 delle funzioni costruttore, unari, 634
confronti, 125 replace(), 836 sottrazione di, 918
di incremento e 373 preprocessore, 245
definzione, 119 replace_copy(), 836 spazi
decremento, 42 di new e delete, 409 direttive, 245
#define, 246 espressioni, 122 prototipo, 669 caratteri diversi da, 217
logici, 38 uso con array, 414
a funzioni, 133 . replace_copy_if(), 836 eliminazione, 216
ordine di precedenza. 56 degli operatori, 287, 395 #elif, 251
indicizzazione, 109 replace_if(), 836 spazi dei nomi, 599
overloading, 287 di operatori particolari, 418 #else, 251
indirizzamento return, 154 opzioni, 607
overloading delle forme di operatori tramite funzioni #endif, 251
multilivello, 129 reverse(), 836 senza nome, 605
abbreviate, 402 friend, 403 #errar, 249
#if, 251 . inizializzazione, 131 prototipo, 671 sili, 609
per i puntatori, 51 restrizioni degli
punto, 173 operatori, 402 #ifdef, 253 ai membi di una classe, 348 reverse_copy(), 836 spazi di nomi
non inizializzati, 139 rewind(), 231, 712 basi, 600
punto e freccia, 55 di un template di #ifndef, 253
a oggetti, 341 prototipo, 231 specificatoli, 28
reinterpret_cast, 597 funzioni, 475 #include, 250
operatori per i, 121 rfind() 'itn, 206, 215
relazionali, 38, 920 uso di friend per gli operatoi #line, 256
problemi, 138 prototipo, 691 di formato, 212
relazionali e logici, 43 ++e--, 405 #pragma, 256
a puntatori, 129 ricorsione, 160 di minima ampiezza di
restrizioni all'overloading della versione nothrow di per compilazini
degli, 402 relazione con gli array, 127 rotate(), 837 campo, 207
new e delete, 417 condizionali, 250
a struttura rotate_copy(), 837 di precisione, 208
di risoluzione del campo uso di defined, 255
d'azione, 281, 329 operatori dichiarazione, 178 specifiche di linking, 620
di scorrimento, 50 p #e##, 257 uso, 178 sprintf(), 717
sizeof, 53 prev_permutation(), 834 a strutture, 177
this, 343
s sqrt(), 739
srand(), 762
static_cast, 596 printf(), 708, 710
string, 686 parametri prototipo, 203 - - - - -- ---- a tipi derivati, 345 sscanf(), 717
dichiarazione di, 165 scanf(), 713, 714, 715
ternario?, 51 programmazione a oggetti variabili, 120 stable_partition(), 840
lettura di caratteri, 213
virgola, 54 di utilizzo generale, 166 identificazione run-time dei push_heap(), 834 stable_sort(), 840
parametri, 217
operatore parametri indirizzo, 143 tipi, 577 put(), 792 static, 28, 30
prototipo, 211
di assegnamento, 38 parole riservate operatori cast, 577 prototipo, 556 str(), 795
specificatori di formato, 713
operazioni asm, 619 programmazione a oggetti (OOP), putback(), 792 strcat(), 726
search(), 837
di I/O di stringhe, 913 export, 493 264, 265 prototipo, 566 strchr(), 726
search_n(), 837
di I/O binarie e non namespace, 600 ereditariet, 267 putc(), 711 strcmpQ, 727
seekg(), 793
formattate, 555 overload, 383 incapsulamento, 266 prototipo, 226 strcoll(), 727
prototipo, 566, 570
ordinamento protected, 432 polimorfismo, 266 uso di, 227 strcpy(), 728
seekp(), 793
generico, 478 typedef, 193 programmi putchar(), 711 strcspn(), 728
prototipo, 566, 570
ordine di valutazione, 57 typename, 493 Countdown, 152 prototipo, 197 stream. 220, 520
sequenza istruzioni, 71
ostringstream(), _795 using, 603 DTOS: Legge il contenuto di puts(), 712 classi per C++, 520
set di caratteri ASCII, 16
ottimizzatori prestazionali, parser un fiie, 228 prototipo, 200 di input/output su array, 625
set_difference(), 838
Overloading aggiunta delle variabili, 949 KTOD: Un programma di di output su array, 621
set_intersection(), 838
. ambiguit, . 390 controllo della sintassi,-959 scrittura su file, 227 predefiniti per C++, 522
set_simmetric_difference(), 839
overloading, 143, 388 di espressioni, 935, 943 numero magico - standard, 240
set_union(), 839 svuotamento, 235
- di un costruttore, 373 estensioni, 967 Versione I, 63 _setbuf(),_71-6__ __

-~-1-
di testo, 220 -------- __
qsort(), 760 setf(), 794 strerror(), 728 -
976 INDICE ANALITICO

strftime(), 746 di funzioni


overloading, 475
V
stringhe, 99, 683
di caraneri, 99 funzioni generiche, 469
chiuse dal carattere overloading_esplicito, 473 va_arg(), 765
nullo, 95, 693 potenza dei, 494 va_end(), 765
confronto, 692 terminate() va_start(), 765
container, 694 prototipo, 513 variabili, 19, 56
di controllo, 217 time(), 747 automatiche, 19
lettura e scrittura, 199 tipi dichiarazione, 19
manipolazione di base, 688 creazione e integrazione, globali, 23
operazioni sulle, 688 933 globali, static, 31
ricerca di, 691 iostate, 783 inizializzazione, 34
stringstream(), 795 off_type, 783 locali, 19, 144
strlen(), 728 openmode, 783 dichiarazione, 272
prototipo, 27 pos_type, 783 locali, memoriziazione, 22
stmcat(), 729 seekdir, 783 _ __ locali, static, 30
stmcmp(), 729 del sistema di I/O, 782 membro, 302
stmcpy(), 729 streamoff, 782 parametri formali, 23
strpbrk(), 730 streampos, 782 puntatore, 51
strrchr(), 730 streamsize, 782 vettori, 637
strspn(), 730 wstreampos, 782 accesso tramite iteratoti, 641
StrStr(), 731 tmpfile(), 718 elementi
strtod(), 763 tmpnam(), 718 inserimento e
strtok(), 731 token, 942 cancellazione, 643
strtol(), 763 tolower(), 732 . memorizzazione di
strtoul(), 764 toupper(), 732 oggetti, 645
strutture, 169, 170, 303 towctrans(), . 769 ,.- vfprintf(), 719
accesso ai membri, 173 transform(), 841 visualizzazione
all'interno di strutture, 181 prototipo, 672 dei caratteri, 203
array, 174 typeid, 578, 583 indirizzo di memoria, 205
assegnamenti, 173 applicazioni a classi dei numeri, 203
dichiarazione, 170 tempiale, 585 void, 15, 16
membri, 170 sostituzione con volatile, 27
passaggio alle dynamic_cast, 591 vprintf(), 719
funzioni, 175. 176 vsprintf(), 719
puntatori a, 177
strxfrrn(), 731 Lf
swap(), 8.!l "..!' f
i.,r~

swap_ranges(), 841
switch, 61 uncaught_exception()
prototipo, 515 wchar_t, 16
sync_with_stdio(), 797
unexpected() wcstombs(), 765
system(), 764
prototipo, 513 wctomb(), 766
ungete(), 718 wctrans(), 769
unioni, 169, 185, 305 while, 61
anonime, 306 width(), 798
unique(), 842 write(), 798
tan(), 739 unique_copy(), 842 prototipo, 558
tanh(), 74-0 unsetf(), 797
tellg(), 797 unsigned, 16
tellp(), 797 upper_bound(), 842
template. 469 utilizzo di n vecchio
funzione con due tipi compilatore, 279
generici, 472

Potrebbero piacerti anche