Cerca nel sito:
ricerca
avanzata

Frasi Celebri...

Quando sei in dubbio, usa la forza bruta.

Ken Thompson 

Sondaggio:

Secondo voi quale squadra "ladra" di pi??

Juventus
Roma
Inter
Lazio
Chievo
Milan
Altro

visualizza risultati


 

Librerie di funzioni

I file header sono molto utili quando si vuole partizionare un programma in piu` moduli, tuttavia la potenza dei file header si esprime meglio quando si vuole realizzare una libreria di funzioni.
L'idea e` quella di separare l'interfaccia della libreria dalla sua implementazione: nel file header vengono dichiarati (ed eventualmente definiti) gli identificatori che devono essere visibili anche a chi usa la libreria (costanti, funzioni, tipi...), tutto cio` che e` privato (implementazione di funzioni non inline, variabili...) viene invece messo in un altro file che include l'interfaccia. Vediamo un esempio di semplicissima libreria per gestire date (l'esempio vuole essere solo didattico); ecco il file header:

 

// Date.h
struct Date {
  unsigned short dd;     // giorno
  unsigned short mm;     // mese
  unsigned yy;           // anno
  unsigned short h;      // ora
  unsigned short m;      // minuti
  unsigned short s;      // secondi
};

void PrintDate(Date);

 

ed ecco come sarebbe il file che la implementa:

 

// Date.cpp
#include "Date.h"
#include < iostream >
using namespace std;

void PrintDate(Date dt) {
  cout << dt.dd << '/' << dt.mm << '/' << dt.yy;
  cout << " " << dt.h << ':' << dt.m;
  cout << ':' << dt.s;
}

 

A questo punto la libreria e` pronta, per distribuirla basta compilare il file Date.cpp e fornire il file oggetto ottenuto ed il file header Date.h. Chi deve utilizzare la libreria non dovra` far altro che includere nel proprio programma il file header e linkarlo al file oggetto contenente le funzioni di libreria. Semplicissimo!
Esistono tuttavia due problemi, il primo e` illustrato nel seguente esempio:

 

// Modulo1.h
#include < iostream >
using namespace std;

/* altre dichiarazioni */


// Modulo2.h
#include < iostream >
using namespace std;

/* altre dichiarazioni */

// Main.cpp
#include < iostream >
using namespace std;

#include < Modulo1.h >
#include < Modulo2.h >

int main(int, char* []) {
  /* codice funzione */
}

 

Si tratta cioe` di un programma costituito da piu` moduli, quello principale che contiene la funzione main() e altri che implementano le varie routine necessarie. Piu` moduli hanno bisogno di una stessa libreria, in particolare hanno bisogno di includere lo stesso file header (nell'esempio iostream) nei rispettivi file header.
Per come funziona il preprocessore, poiche` il file principale include (direttamente e/o indirettamente) piu` volte lo stesso file header, il file che verra` effettivamente compilato conterra` piu` volte le stesse dichiarazioni (e definizioni) che daranno luogo a errori di definizione ripetuta dello stesso oggetto (funzione, costante, tipo...). Come ovviare al problema?
La soluzione ci e` fornita dal precompilatore stesso ed e` nota come compilazione condizionale; consiste cioe` nello specificare quando includere o meno determinate porzioni di codice. Per far cio` ci si avvale delle direttive #define SIMBOLO, #ifndef SIMBOLO e #endif: la prima ci permette di definire un simbolo, la seconda e` come l'istruzione condizionale e serve a testare un simbolo (la risposta e` positiva se SIMBOLO non e` definito, negativa altrimenti), l'ultima direttiva serve a capire dove finisce l'effetto della direttiva condizionale. Le ultime due direttive sono utilizzate per delimitare porzioni di codice; se #ifndef e verificata il preprocessore lascia passare il codice (ed esegue eventuali direttive) tra l'#ifndef e #endif, altrimenti quella porzione di codice viene nascosta al compilatore.
Ecco come tali direttive sono utilizzate (l'errore era dovuto all'inclusione multipla di iostream):

 

// Contenuto del file iostream.h
#ifndef __IOSTREAM_H
#define __IOSTREAM_H

/* contenuto file header */

#endif

 

si verifica cioe` se un certo simbolo e` stato definito, se non lo e` (cioe` #ifndef e` verificata) si definisce il simbolo e poi si inserisce il codice C/C++, alla fine si inserisce l'#endif. Ritornando all'esempio, ecco cio` che succede quando si compila il file Main.cpp:

  1. Il preprocessore inizia a elaborare il file per produrre un unico file compilabile;
  2. Viene incontrata la direttiva #include < iostream > e il file header specificato viene elaborato per produrre codice;
  3. A seguito delle direttive contenute inizialmente in iostream, viene definito il simbolo __IOSTREAM_H e prodotto il codice contenuto tra #ifndef __IOSTREAM_H e #endif;
  4. Si ritorna al file Main.cpp e il precompilatore incontra #include < Modulo1.h > e quindi va ad elaborare Modulo1.h;
  5. La direttiva #include < iostream > contenuta in Modulo1.h porta il precompilatore ad elaborare di nuovo iostream, ma questa volta il simbolo __IOSTREAM_H e` definito e quindi #ifndef __IOSTREAM_H fa si` che nessun codice venga prodotto;
  6. Si prosegue l'elaborazione di Modulo1.h e viene generato l'eventuale codice;
  7. Finita l'elaborazione di Modulo1.h, la direttiva #include < Modulo2.h > porta all'elaborazione di Modulo2.h che e` analoga a quella di Modulo1.h;
  8. Elaborato anche Modulo2.h, rimane la funzione main() di Main.cpp che produce il corrispondente codice;
  9. Alla fine il precompilatore ha prodotto un unico file contenete tutto il codice di Modulo1.h, Modulo2.h e Main.cpp senza alcuna duplicazione e contenente tutte le dichiarazioni e le definizioni necessarie;
  10. Il file prodotto dal precompilatore e` passato al compilatore per la produzione di codice oggetto;

Utilizzando il metodo appena previsto in tutti i file header (in particolare quelli di libreria) si puo` star sicuri che non ci saranno problemi di inclusione multipla. Tutto il meccanismo richiede pero` che i simboli definiti con la direttiva #define siano unici.

 

successivo
–«  INDICE  »–

 

 

 

 
Powered by paper&pencil (carta&matita ) - Copyright © 2001-2019 Cataldo Sasso