Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
+
Indice
Prefazione xv
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
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
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
- - - - -----
~
-
: Prefazione
---~-----------
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
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
-------
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.
Le espressioni
----- ------- ..
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.
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
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. } {
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:
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.
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
\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
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;
int (32 bit) long int Nulla Sottrazione, anche meno unario
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));
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 --- --~~-~-
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 :
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(->)
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
&
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>
(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
~ 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);
::.::::------ - -- -
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,
- ------ --- - - -- -
---_ ~ _.::;..... --
--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 */
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) {
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 */
#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);
.~ fl(inc.'. n).
.. -
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) ;
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: --
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;
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;
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++) ;
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;
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.
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():
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.
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
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
.. ,
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.
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.
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:
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:
p = sample;
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");
111-
..
-- -~-:: - --......-~- .
102 CAPITOLO 4
GLI ARRYE LE STRINGrtE-- W3-
4.5 Gli array bidimensionali Si pu visualizzare l'array num nel modo seguente:
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] ;
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];
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-.----
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] ;
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
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");
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:
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]
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];
--~---
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);
~- ,--=-=-:.-.-::-----
---1:~
~~~;-----
--==-~''!~
114 CAPITOLO 4 GLl ARRAY E LE STRINGHE 115
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];
Il I puntatori
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
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.
.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 -;::;:::-
! ~-.-
-- ~-=-- ----- - .~
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");
#define SIZE 50
ch+S 3005
void push(int i);
Memoria in.t pop(void);
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;
str[4]
pop(void)
{
if(pl==tos) oppure
printf("Superato il 1imi te inferiore del 1o stack");
exit(l); *(pl+4)
1
l
128 CAPiTOLO I PJH'HALO.R I - 129
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]
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:
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);
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
- -- --- -- .
----.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;
- --- ------------
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:
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:
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;
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 */
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>
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;
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>
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():
Tuttavia, come regola generale, una funzione non pu essere la destinazione int main(void)
{
di un assegnamento. Un'istruzione come:
int x, y, z;
(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*/
- -
- - .:..-_ --:
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---_-~-
-------------
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 */
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 - - -
Vi sono alcune cose molto importanti da ricordare che riguardano lefficienza e int sqr(int a)
l'usabilit delle funzioni. I
return a*a;
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:
Questa riga dichiara una variabile di tipo struct addr e chiamata addr_info. In
C++ si pu utilizzare una forma abbreviata. = -
----- ---------
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:
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:
_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;
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
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:
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.
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
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);
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
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()
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:
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
%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,... ~ - " - - ~ ~
return O;
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
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
#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,
-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;
return O; %[XYZ]
#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
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
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
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);
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);
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:
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
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, . .. );
#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);
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;
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
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:
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:
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);
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,
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
#ifdef nome_macro
sequenza istruzioni
#elif espressione N #endif
---~equenza istruzioni
#endif
-----_-_------------=..~..:---
#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--
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:
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:
- 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 * /
*/
Capitolo 11
Panoramica del
linguaggio C++
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
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
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;
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
--~--- - --~---~ ~-
~--
- - --==--- -- -
~
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.
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);
~----~ __ : . _
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
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.
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();
};
-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
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";
#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
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;
--~-
------- --
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;
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;
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); };
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> 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
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
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
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
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
myclass local_ob2(4);
return O;
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.
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).
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
#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
_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
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
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;
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-
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";
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
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);
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
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);
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;
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";
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.
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.
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;
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);
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.
Overloading di funzioni,
costruttori di copie
e argomenti standard
.,,..,,
"-~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.
!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.
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 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
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:
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:
:,_ - -
CAPITOLO 14
OVERLOADING DI FUNZIONI ... 385
Ora myfunc() pu essere richiamata in due modi, come illustrato dai seguenti return O;
esempi:
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;
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
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.
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
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:
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;
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.
-- ----- - -
406 CAPITOLO 15 O V E R L O A IJTITT>--D E <H r O P E RA T O R I
Ob + 100 // corretta
return op;
return O;
void show()
cout << 1ongi tu de << 11 11 :
cout << latitude "\n";
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
*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;
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};
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; ~-- ---~
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]
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);
In qu_esto programma, quando viene eseguita l'istruzione: loc(int lg, int lt)
1ongitude = l g;
ob[3] = 44; latitude = lt;
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 ");
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
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
;
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;
return temp;
int main()
ob 1. show() ;
ob2. show();
ob3.show();
cout << "\n";
D 20
s.30
l l
l:i 60
!. l
l l
: Capitolo 16
L'ereditariet
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-_:::.-.------------
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";}
}; };
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;
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
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
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.
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;
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
return O;
11 costruisce e di strugge ob
return O;
produce il seguente output:
- -- --=------~~ - ..
--442-=-- E~t. o 1 s
ob.show(}; Il visualizza 4 3 5 };
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: };
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;
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:
_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;
- ---
-- ----~ - -
452 CAPITOLO 16
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:
);
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
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
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
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;}
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
-------
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),
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.
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.
----- --------
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:
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;
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:
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.
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
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' ;
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
";
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.
};
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:
- - -=------ -
Capitolo 19
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.
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";
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;
int main()
return O; {
cout "Inizio\n";
------ - - - - ---
506 CAPITOLO 19 GESTIONE DELLE ECCEZIONI 507
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";
cout << 11
Fi ne 11 ; Xhandl er{O);
Xhandl er{l):
retu'"'.'1 O; Xhandl er{2);
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();
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:
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;
catch (double b) {
cout "Non si pu dividere un numero per zero. \n";
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
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
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.
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
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
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
return O; cout << 100 << '\n'; Il visualizza 100 e.non +100
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 ";
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 :_
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
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
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
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
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 << " ";
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
;
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:
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;
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
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;
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.
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();
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
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:
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;
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()
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
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;
if(!pb) {
--eout- "-Impossibile aprire il file della rubrica.\n";
return 1;
----- ------
: Capitolo 22
L'identificazione run-time
dei tipi e gli operatori cast
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;
-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
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;
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";
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.
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;
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.
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
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++
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.
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
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
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;
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); };
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;
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)
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:
'>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
}
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.
~- -
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;
Questo programma visualizza il seguente risultato:
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;
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
_ 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
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.
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
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:
Il inizializza v
---;for(i=O; i<lO; i++) v[i] i+ 'a';
--- - ~--
------~-
INTRODUZIONE ALLA LIBRERIA- STL 645
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:
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 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++;
11 Uso di end O.
#include <iostream>
return O;
lii nel ude <li st>
using namespace std;
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.
---------"""
----:::.-::.....=.....-- ~-.
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
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.
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: ;
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;
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.
Tabella 24.5 Gli algoritmi della libreria STL. (conflnua} Tabella 24.5 Gli algoritmi della libreria STL. (conffnuaJ
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;
return O;
Il programma visualizza il seguente output:
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().
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:
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
int main()
Ecco l'output prodotto dal programma:
{
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=--='----- ----
____ _,.
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
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;
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;
Output
11 concatenadue stringhe
str4 = strl + str2;
Input cout << str4 << "\n";
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:
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
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;
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
---- -------.
: Capitolo 25
Le funzioni di I/O
: basate sul e
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---
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()
--.::==:_ - - -
~--
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{)
--- ------
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);
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
: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()
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:
%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.
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 - - - --
--~
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
ungete()
lfinclude <cstdio>
int ungetc(int eh, FILE *stream);
Capitolo 26
!
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 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
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 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
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()
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 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()
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()
---=-:: 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().
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().
#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 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
#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().
---- ---- -
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()
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
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 */
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:
#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);
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
%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
--tinciude <ctime>
time_t time(time_t *time);
-=-=..._:__---- ,_
748 CA P I T O LO 2 8
calloc()
--- ----
-~
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()
malloc()
realloc()
Le funzioni di servizio
i
MACRO SIGNIFICATO
NULL Un puntatore nullo
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.
abort()
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 *));
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.
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{).
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
. 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);
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{)
----------
---- ----
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;
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.
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
- ---- ------" - --
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.
int wctob(wint_t eh) Converte eh nel suo equivalente multibyte a un solo byte. In caso
di errore restituisce EOF.
---- -
~
-------
-
--~'- - -- -
Capitolo 32
'~
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
(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
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:
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
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.
784 CAPITOLO 32
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().
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!__: __
- -- - -
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:
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().
#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().
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);
#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()
return O;
unsetf()
#include <iostream>
Ecco l'output.prodotto dal programma: voi d unsetf ( fmtfl ags flags);
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) ;
writeO
__ T~RMINE SIGNIFICATO
Bi Iter lteratore bidirezionale
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.
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-
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.
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.
(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.
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.
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.
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)
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.
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;
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.
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 ;,
(segue)
: Capitolo 34
Un tipo di dati
------
Size Un tipo intero
--.:.=...._:_-.:._ __ _
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);
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()
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{)
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.
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()
pop_he~p()
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
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
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);
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
---- -- - .. __ .
----- --
- -----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.
- - - ----
-- ----
. -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++ << " ";
--~ - ~
---- - -
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
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~
---- -~ --~-
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
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. };
&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> );
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> };
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.
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.
- .
: Capitolo 36
La classe stri ng
- -- ------
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
(segue) ---(segue)
868- CAPITOLO 36
ouo
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)
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
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);
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).
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
- class complex<float>-
class complex<double>
class complex<long double>
--- -- ---------
- ~---- - --------
876------c-A Pn ol o s 1
+ * 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);
- ---- -----
--------- ~ -- - - - ------ ____." --:" ,
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);
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;
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:
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
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.
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().
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
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++)
: Capitolo 38
38.1 Le eccezioni
38.2 La classe auto_ptr
38.3 La classe pair
38.4 La localizzazione
38.5 Altre classi interessanti
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>
class exception {
publ ic:
exception{) throw{);
exception(const bad_exception &ob) throw{);
virtual -exception() throw{);
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;
----- - - - -
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>.
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
---,
: Parte quinta
APPLICAZIONI C++
.i;:
l: . --- -------~
i
---:...--~ -----
-- - .-=::----
: Capitolo 39
-----
- - - - --=:::.. - - - -
. .- ------
---w - ---
-----~~--'-"-'c:__o_"_.:'=~='"'"""-'='"""'--~_:_
' -----~-----_---- ----
_______
- - - - - -_..:_:_-__ :.-.-= -- ~ -
-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>
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;
return temp;
918 CAPITOLO 3 9 --+N-HGR"AZIONE D~LLE-NUOVE CLASSI ... 919
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 - "C++"; 11 z conterr "Mi il " if(z=="tre") cout << "z uguale a tre":
~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 ~ ._,___
return *this;
return temp;
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;
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--
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];
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
--- --------
938 CAPITOLO 40 UN ANALIZZATORE DI ESPRES-SlONI ... 939
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++;
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:
(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
---- ----~---~-
946 CAPITOLO 40 ------ - -\J N ANALIZZA T--erftE-tJ-1 ESPRESSI ON I. . . -947
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
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;
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;
- - - - -------
954 CAPITOLO 40 U bLA.N.ALLZ ZA TOR E DI ESPRESSIONI ... 955
__ -~----
-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..
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';
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;
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;
966 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
-~-1-
di testo, 220 -------- __
qsort(), 760 setf(), 794 strerror(), 728 -
976 INDICE ANALITICO
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