Preprocessore
Cos’è il preprocessore?
Sezione intitolata “Cos’è il preprocessore?”Prima della compilazione, il preprocessore esegue alcune direttive e prepara il testo sorgente.
Le direttive possono includere file, sostituire macro e scegliere parti del codice.
Le istruzioni che dai al preprocessore si chiamano direttive e iniziano sempre con il simbolo #.
Aggiungere altri file con #include
Sezione intitolata “Aggiungere altri file con #include”#include chiede al preprocessore di inserire il contenuto di un altro file.
Il file incluso viene elaborato come parte del sorgente corrente.
Librerie del sistema
Sezione intitolata “Librerie del sistema”Per includere le librerie già pronte che vengono con C++, si usano le parentesi angolari <>:
#include <iostream> // per stampare e leggere dati#include <string> // per usare le stringhe#include <cmath> // per le funzioni matematiche#include <vector> // per usare i vettoriIl compilatore sa già dove trovare questi file nel tuo computer.
File tuoi
Sezione intitolata “File tuoi”Per includere file che hai scritto tu, si usano le virgolette "":
#include "studente.h" // file nella stessa cartella#include "modelli/persona.h" // file in una sottocartellaIl compilatore cerca prima nella cartella del tuo progetto, poi nelle cartelle standard.
Creare una sostituzione automatica con #define
Sezione intitolata “Creare una sostituzione automatica con #define”#define associa un testo a un nome che il preprocessore sostituirà nel sorgente.
Costanti con #define
Sezione intitolata “Costanti con #define”#define PI 3.14159265358979#define MAX_STUDENTI 30#define SALUTO "Ciao, mondo!"
int main() { // Il preprocessore sostituisce PI prima ancora di compilare: // double area = 3.14159265358979 * 5.0 * 5.0; double area = PI * 5.0 * 5.0; return 0;}Oggi si preferisce usare const al posto di #define per le costanti, perché è più sicuro. Ma #define si vede spesso nel codice esistente.
Macro con parametri (scorciatoie con calcoli)
Sezione intitolata “Macro con parametri (scorciatoie con calcoli)”Puoi creare scorciatoie che accettano valori:
#define MASSIMO(a, b) ((a) > (b) ? (a) : (b))#define QUADRATO(x) ((x) * (x))
int main() { int x = 5, y = 3; cout << MASSIMO(x, y) << endl; // stampa 5 cout << QUADRATO(4) << endl; // stampa 16 return 0;}Attenzione: queste “macro-funzioni” possono dare risultati strani. Preferisci le funzioni normali:
// PROBLEMA: n viene incrementato due volte!int n = 5;int r = QUADRATO(n++); // diventa: ((n++) * (n++)), comportamento imprevedibile
// SOLUZIONE: usa una funzione normaleint quadrato(int x) { return x * x; }Cancellare una scorciatoia con #undef
Sezione intitolata “Cancellare una scorciatoia con #undef”#define VALORE 42cout << VALORE << endl; // 42
#undef VALORE// Da qui in poi VALORE non esiste più// cout << VALORE << endl; // ERRORE!Compilazione condizionale: includere codice solo in certi casi
Sezione intitolata “Compilazione condizionale: includere codice solo in certi casi”Puoi dire al preprocessore: “includi questa parte di codice solo se questa condizione è vera”.
È come avere due versioni di un documento e scegliere quale stampare:
#define DEBUG // commenta questa riga per disabilitare i messaggi di debug
int main() { int x = 42;
#ifdef DEBUG // Questa riga viene inclusa SOLO se DEBUG è definito cout << "Debug: x = " << x << endl;#endif
cout << "Valore: " << x << endl; return 0;}#ifdef NOME= “se NOME è definito, includi il codice che segue”#ifndef NOME= “se NOME NON è definito, includi il codice che segue”#endif= “fine del blocco condizionale”
Include guard: evitare di includere lo stesso file due volte
Sezione intitolata “Include guard: evitare di includere lo stesso file due volte”Immagina di copiare e incollare lo stesso capitolo due volte in un libro: sarebbe un problema. Stessa cosa in C++.
Quando scrivi un file .h, usa sempre questa protezione:
#ifndef STUDENTE_H // se STUDENTE_H non è ancora definito...#define STUDENTE_H // ...definiscilo (ed esegui il codice qui sotto)
// Tutto il contenuto del file va quivoid salutaStudente();struct Studente { int eta; };
#endif // fine della protezioneLa seconda volta che questo file viene incluso (magari attraverso un altro file), STUDENTE_H è già definito, quindi tutto il contenuto viene saltato automaticamente.
In alternativa, puoi usare #pragma once, che fa la stessa cosa in modo più semplice:
#pragma once // includi questo file al massimo una volta
void salutaStudente();#pragma once è supportato da quasi tutti i compilatori moderni, anche se non è parte dello standard ufficiale.
Generare un errore personalizzato con #error
Sezione intitolata “Generare un errore personalizzato con #error”Puoi far fallire la compilazione con un messaggio tuo:
#ifndef MIA_LIBRERIA_H#error "Devi includere mia_libreria.h prima di questo file!"#endifSe la condizione si verifica, il compilatore si ferma e mostra il tuo messaggio.
Macro predefinite: informazioni automatiche
Sezione intitolata “Macro predefinite: informazioni automatiche”Il preprocessore definisce alcune parole speciali in automatico:
cout << __FILE__ << endl; // nome del file correntecout << __LINE__ << endl; // numero della riga correntecout << __DATE__ << endl; // data in cui è stato compilato il programmacout << __TIME__ << endl; // ora in cui è stato compilatoSono molto utili per il debug, per sapere esattamente dove è successo un problema:
#ifdef DEBUG// Crea un messaggio di log con il nome del file e il numero di riga#define LOG(msg) cout << "[" << __FILE__ << ":" << __LINE__ << "] " << msg << endl#else#define LOG(msg) // in modalità normale, LOG non fa nulla#endif
int main() { int x = 42; LOG("Il valore di x è: " << x); // messaggio visibile solo in modalità debug return 0;}