Fino ad ora abbiamo volutamente lasciato correre la differenza esistente tra una dichiarazione ed una definizione. In C++ dichiarare una variabile, una funzione, una struttura, un enumerato non necessita che si allochi della memoria, per cui è possibile dichiarare quante volte si vuole un qualsiasi dato o una qualsiasi struttura dati; al contrario, quando definiamo un oggetto, automaticamente il compilatore alloca la memoria necessaria a contenere tale oggetto, per cui non è possibile definire due volte il medesimo oggetto in un programma. Vediamo un esempio:
// ex8_7_1 double funzione (int, int); // OK: solo dichiarazione double funzione (int a, int b); // OK: solo dichiarazione double funzione (int c, int d); // MAH: ok ma diabolico // int funzione (int a, int b); // NO: ambiguita` con le altre double funzione (int a, int b) { return double(a) + double(b); } // double funzione (int a, int b) { // NO: definizione // return double(a) - double(b); // } void main() { double x = 1.5; // double x; // NO: definizione funzione (5, 7); }
Come si vede, è possibile facilmente dichiarare una funzione senza definirla: basta non fornire il corpo di essa; inoltre è possibile in tal caso omettere i nomi degli argomenti, ma non il loro tipo! Si noti che è possibile teoricamente anche specificare nomi diversi degli argomenti nella dichiarazione e nella definizione di una funzione; inutile far notare che tale pratica tende quantomeno a confondere un utilizzatore del nostro codice. Come è possibile però dichiarare una variabile senza definirla, ovvero senza allocare memoria per essa? Dobbiamo utilizzare la parola chiave extern; attenzione: solo le variabili globali possono essere definite senza essere dichiarate, come vediamo nel seguente esempio:
// ex8_7_2 extern int a; // solo dichiarazione int a; // OK // int a; // NO: definizione void main() { // extern int b; // NO: solo variabili globali a = 7; // OK }
Alla luce di queste nuove scoperte, vediamo cosa è un file d'intestazione; si tratta di un file, solitamente avente lo stesso nome del corrispondente file .cpp
ma estensione .h
, nel quale sono contenute tutte le definizioni correlate al file .cpp
in questione: enum, struct, dichiarazioni di funzioni (prototypes), dichiarazioni di variabili globali non costanti, definizioni di variabili costanti, typedef. Normalmente per una libreria si utilizzano due files: uno .h
di intestazione e uno .cpp
da compilare; se vogliamo poi utilizzare la nostra libreria in un programma, non dobbiamo fare altro che utilizzare il comando
#include "nome_libreria" |
Si faccia bene attenzione: se il nome della libreria è racchiusa da parentesi angolate (`< >'
) allora si tratta di una libreria di sistema, per cui il calcolatore sa esattamente dove essa si trovi; nel caso invece si usino i doppi apici, dobbiamo specificare il percorso dove si trova il file .h
incluso, in mancanza del quale viene presa in considerazione solo la cartella contenente il file con la funzione main
. Vediamo un semplice esempio di libreria:
// mialibreria.h enum Colori { ROSSO, GIALLO, BLU }; struct Circonferenza { double raggio; Colori colore; Circonferenza (double Raggio, Colori Colore = ROSSO) { raggio = Raggio; colore = Colore; } }; const double PI = 3.14159265358979323846264338327; extern int var_esterna; double area (const Circonferenza& c); void stampa (const Circonferenza& c);
Il precedente file costituisce l'intestazione di:
// mialibreria.cpp #include "mialibreria.h" #include <iostream.h> double area (const Circonferenza& c) { return c.raggio * c.raggio * PI; } void stampa (const Circonferenza& c) { char col[7]; switch (c.colore) { case ROSSO: strcpy (col, "ROSSO"); break; case GIALLO: strcpy (col, "GIALLO"); break; case BLU: strcpy (col, "BLU"); break; } cout << "colore: " << col << "\n" << "raggio: " << c.raggio << "\n"; } int var_esterna = 8; // definizione // la dichiarazione e` in `mialibreria.h'
Tale file contiene le definizioni degli oggetti (variabile intera e due funzioni) esclusivamente dichiarate nel file mialibreria.h
. Un file di esempio che sfrutta la nostra minilibreria è il seguente8.8:
// ex8_7_3.cpp #include <iostream.h> #include "mialibreria.h" // !! void main() { // dichiarazione in `mialibreria.cpp' // definizione in `mialibreria.h' cout << var_esterna << "\n"; double r; cout << "raggio? "; cin >> r; Circonferenza c (r); stampa (c); cout << "area: " << area (c) << "\n"; }
Si noti che var_esterna
non viene mai dichiarata né definita nel file ex8_7_3.cpp
, eppure essa viene utilizzata senza lamentele da parte del compilatore. Vediamo dunque, finalmente, il vero significato del comando #include
: si tratta di una direttiva8.9 che ha come unico scopo quello di copiare tutto il file specificato nel punto esatto in cui essa si trova. Quindi ogni volta che includiamo una libreria, il nostro file prima di essere compilato diventa (molto) più lungo di quanto esso non ci appaia normalmente; infatti vengono copiate tutte le definizioni della libreria inclusa. Ad esempio includendo la libreria math.h
vengono copiati tutte le dichiarazioni delle funzioni sin
, cos
, pow
e tutte quelle che abbiamo studiato; talvolta una libreria ne include un'altra, per cui non sappiamo in genere quanti file il compilatore sfrutti per un programma anche molto semplice.
Si pensi al seguente semplice esempio: un programma che stampi la frase ``hello world!'' necessita la libreria iostream.h
; essa8.10 include a sua volta la libreria streambuf
, la quale richiede la libio.h
e così via fino a quando un bel numero di libreria non sono tutte incluse; ciascuna di esse porta con sé molte definizioni che non verranno affatto utilizzate; esse tuttavia, siccome non occupano memoria, non incidono sulla velocità del nostro programma ma solo sul tempo di compilazione, il quale è puramente marginale data la potenza elevata e la velocità strepitosa dei calcolatori attuali. Addirittura capita spesso che una stessa libreria venga inclusa più di una volta, con una evidente perdita di tempo: è inutile(ma non dannoso) definire più di una volta uno stesso oggetto! Se vogliamo evitare le inclusioni multiple di un file d'intestazione, possiamo utilizzare il comune stratagemma che vediamo nel seguente esempio:
// libreriadiprova.h #ifndef _LIBRERIA_DI_PROVA_H_ #define _LIBRERIA_DI_PROVA_H_ /* ...definizioni... */ #endif
Abbiamo introdotto tre nuove direttive: #ifndef
, #define
e #endif
. Si tratta di un costrutto condizionale del tipo: ``se non è definito (IF Not DEFined) il seguente identificatore (nell'esempio _LIBRERIA_DI_PROVA_H_
) allora definiscilo ed esegui tutto ciò che incontri fino alla fine del costrutto (segnata da #endif
); altrimenti vai alla riga successiva all'#endif
''. La prima volta che la nostra libreria viene inclusa l'identificatore _LIBRERIA_DI_PROVA_H_
non esiste, per cui esso viene definito (#define
_LIBRERIA_DI_PROVA_H_
) e vengono elaborate tutte le definizioni contenute fino a #endif
; tutte le successive volte che la libreria viene inclusa _LIBRERIA_DI_PROVA_H_
è già definita per cui il corpo del costrutto condizionale viene ignorato.
listasemplice.h
, le definizioni in listasemplice.cpp