next up previous contents index
Next: Introduzione alle classi Up: Approfondimenti. I files d'intestazione Previous: Gestione minimale dei files   Indice   Indice analitico

I files d'intestazione

Abbiamo visto che utilizzando le funzioni i programmi sono ``migliori'': una funzione è costituita da un numero di righe di codice relativamente esiguo, per cui essa è facile da controllare; è possibile inoltre riutilizzare più volte una stessa procedura all'interno di un programma. Tuttavia sarebbe ancora più bello se potessimo avere a disposizione le nostre funzioni non in un solo programma ma in tutta la nostra produzione; tale necessità è alla base del concetto di libreria, un insieme di funzioni che è possibile utilizzare in un qualunque nostro programma. Ma non solo: visto che questa possibilità esiste, perché non sfruttarla anche per dichiarazioni di strutture, o per definizioni di costanti? Vediamo come.

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.

ex-2
si includano in una libreria tutte le funzioni relative alle operazioni su liste semplici; le dichiarazioni siano contenute nel file listasemplice.h, le definizioni in listasemplice.cpp


next up previous contents index
Next: Introduzione alle classi Up: Approfondimenti. I files d'intestazione Previous: Gestione minimale dei files   Indice   Indice analitico
Claudio Cicconetti
2000-09-06