Guida al C/C++
autore: Orebla
xx/xx/xx
Direttive per il preprocessore: la direttiva #define, #if e #ifdef:
La direttiva #define
La direttiva serve a definire una MACRO, ovvero un simbolo.
La sintassi con cui utilizzare la direttiva è la seguente:
#define nome_macro valore_macro
convenzionalmente i nomi delle macro vengono scritti con delle lettere
MAIUSCOLE. Il preprocessore legge la definizione della MACRO e, ogni volta che
ne incontra il nome all'interno del file sorgente
SOSTITUISCE al simbolo
il corrispondente valore, SENZA verificare la correttezza
sintattica dell'espressione risultante.
// esempio di utilizzo di una macro
#include <stdio.h>
#define ULTIMO 30
int main()
{int i = 0;
for(i = 0; i< ULTIMO;i++)
printf("il numero e\' %d ed il massimo valore e\' %d",i,ULTIMO);
return 0; }
// fine esempio
Dopo l'azione del preprocessore, la funzione main() avrà questo aspetto :
int main()
{int i = 0;
for(i = 0; i< 30;i++)
printf("il numero e\' %d ed il massimo valore e\' %d",i,30);
return 0; }
Ovvero il preprocessore ha sostituito il simbolo ULTIMO
con il corrispondente valore.
Il valore della MACRO può essere formato da un numero arbitrario di caratteri;
se risultasse necessario andare a capo, basta inserire un carattere \
al termine della riga.
Ad esempio, nel caso fosse necessario stampare molte volte il medesimo
messaggio, si può definire una macro di questo tipo:
#include <stdio.h>
#define AVVISO printf("\nil
programma, fino a qui, funziona in modo corretto");
int main()
{char c;
c = getchar();
... // istruzioni
AVVISO
... // altre istruzioni
AVVISO
... // istruzioni
}
grazie al preprocessore, ovvero effettuando la sostituzione, questo codice si
trasforma in :
int main()
{char c;
c = getchar();
... // istruzioni
printf("\nil programma, fino a qui, funziona in modo
corretto");
... // altre istruzioni
printf("\nil programma, fino a qui, funziona in modo
corretto");
... // istruzioni
}
Grazie alla
direttiva #define si possono definire anche delle
macro che svolgono operazioni un po' più complesse. Ad esempio
#include <stdio.h>
#define STAMPA(a) printf("\nil valore della
variabile e\' pari a %d",a);
int main()
{ int a=6, b=9;
STAMPA(a)
STAMPA(b)
STAMPA(a+b)
return 0;}
produce il seguente output:
il valore della variabile e' pari a 6
il valore della variabile e' pari a 9
il valore della variabile e' pari a 15
ATTENZIONE:
la sostituzione può nascondere dei problemi.
#include <stdio.h>
#define MOLTIPLICA(a,b) a*b
int main()
{int a=5,b=3,c;
c = MOLTIPLICA(a,b);
// tutto OK ! diventa c = a * b;
c = MOLTIPLICA( a + b, b );
// ?? diventa c = a + b * b;
return 0; }
Per ovviare al problema basta inserire delle parentesi ...
#define MOLTIPLICA(a,b) (a) * (b)
Direttive
per la compilazione condizionale: #if ed #ifdef
Così come, mediante il costrutto if() era possibile controllare il flusso del
programma, grazie alle direttiva #if e #ifdef
è possibile controllare il flusso della compilazione, ovvero stabilire quali
porzioni del codice verranno compilate e quali no. La sintassi con cui
utilizzare le due direttive è la seguente:
#if
espressione_costante
... codice
#else
... codice
#endif
|
#ifdef
nome_macro
... codice
#endif
|
Se l'espressione costante è vera, allora viene compilata la prima
porzione del codice; altrimenti viene compilata la seconda
|
Se la macro è stata definita, allora viene compilata la porzione di
codice compresa tra #ifdef ed #endif; altrimenti tale porzione di codice
viene saltata
|
NOTA:
le due macro vanno SEMPRE concluse da #endif
// esempio :
programma per convertire euro in sterline o dollari
#include <stdio.h>
#define MONETA STERLINA
#define UK
int main()
{double coeff = 1;
int numero ;
#if MONETA==STERLINA
coeff = 0.6943;
#else
coeff = 1.2509;
#endif
printf("\n inserisci la valuta in euro");
numero = getchar();
printf("\n %d euro corrispondono a %f", numero , numero/coeff);
#ifdef UK
printf(" sterline");
#else
printf(" dollari");
#endif
return 0; }
// fine esempio
Provate a
riprodurre questo esempio usando l'opzione -E del compilatore. A seconda del
valore della macro MONETA vedrete che l'output del preprocessore cambia, ovvero
cambia il codice che di volta in volta viene compilato.
Viceversa, usando il costrutto if() TUTTO il codice
viene sempre compilato, ma solo una porzione viene eseguita.
IMPORTANTE:
non è necessario definire una macro all'interno del codice !!! lo
possiamo tranquillamente fare anche dalla riga di comando.
gcc -Wall -o test -DNOMEMACRO[=VALOREMACRO]
programma.c
-DNOMEMACRO[=VALOREMACRO]
NOMEMACRO rappresenta il nome della macro
VALOREMACRO è opzionale e
rappresentail valore della macro
Prendiamo come esempio il programma di prima. Modificando il codice in questo
modo:
#include <stdio.h>
int main()
{double coeff = 1;
int numero ;
#if MONETA==STERLINA
coeff = 0.6943;
#else
coeff = 1.2509;
#endif
printf("\n inserisci la valuta in euro");
numero = getchar();
printf("\n %d euro corrispondono a %f", numero , numero/coeff);
#ifdef UK
printf(" sterline");
#else
printf(" dollari");
#endif
return 0; }
// fine esempio
e compilando con questo comando
gcc -Wall -o test
-DMONETA=STERLINA
-DUK
programma.c
otteniamo il medesimo risultato ottenuto prima.
Nota:
chi usa il compilatore Dev-Cpp, per poter usare la stessa metodologia, deve
scegliere l'opzione "Opzioni di compilazione" sotto il menù strumenti
ed inserire, assieme al -Wall queste nuove istruzioni per il compilatore
Esercizio:
Per mettere in pratica tutto questo, costruiamo un programma in cui la medesima
procedura viene implementata sia in modo ricorsivo che in modo iterativo. La
scelta di quale procedura utilizzare avverà in fase di compilazione e NON in
fase di esecuzione, ovvero nel codice eseguibile NON CI SARA' TRACCIA
dell'implementazione della funzione che non è stata selezionata.
La funzione da implementare è quella che ci serve a
visualizzare il valore dei bit che compongono la rappresentazione binaria di una
variabile di tipo intero.
Il metodo da utilizzare è quello visto qualche giorno fa: data una variabile di
tipo int, per visualizzare il valore del bit della sua rappresentazione che si
trova nella posizione corrispondente alla potenza di 2 di ordine n è
sufficiente dividere il numero stesso per la medesima potenza di 2 e poi
applicare l'operatore modulo %2.
10011000010100101011001010100001
mi interessa il bit corrispondente a 2
elevato a 5
------> passo 1
10011000010100101011001010100001
/ 2^5
------> passo 2
00000100110000101001010110010101
------> passo 3
00000100110000101001010110010101
% 2 = 1 ! ecco il valore del bit
Se abbiamo a che fare con interi, dovremmo dividere il numero per potenze di 2
che variano da 0 a 31.
// Implementazione iterativa
#include <stdio.h>
#include <math.h>
void
show_bits_i(int);
void
show_bits_i(int
numero)
{int i=0;
for(i = 31; i >=0; i--)
printf("%d",((int) (numero/pow(2,i)))
%2 );
} // non serve l'istruzione return, la funzione è di tipo void
// Implementazione ricorsiva (1)
// questa implementazione riprende il metodo presentato alcune lezioni fa per la
visualizzazione dei bit di
// una variabile
void
show_bits_r(int);
void show_bits_r(int
numero)
{
if(numero>0) // sarebbe stato equivalente
scrivere if(numero)
{printf("%d", numero
%2 );
i++;
show_bits_r(numero/2);}
}
In questo modo, però, stampo solo alcuni bit
della sequenza di 32, non tutti !
// Implementazione ricorsiva (2)
void
show_bits_r(int);
void show_bits_r(int
numero)
{static int i=31;
if(i>0) // sarebbe stato equivalente scrivere
if(i)
{printf("%d", (int)
(numero/pow(2,i)) %2 );
i++;
show_bits_r(numero);}
}
Quale differenza notiamo rispetto all'implementazione ricorsiva presentata ieri
?
/*
codice finale - inizio . supponiamo di aver inserito le due implementazioni
descritte sopra all'interno del file personale myfun.h
*/
#include "myfun.h"
#include <stdlib.h>
int main()
{char car1,car2;
int inizio,i;
printf("\n inserisci un carattere");
car1 = mygetchar()
printf("\n inserisci un altro carattere");
car2 = mygetchar()
srand(car1*car2); // inizializzo il generatore di numeri casuali
for(i=0;i<20;i++)
{
#ifdef RICORSIVO
show_bits_r( rand() ); //
implementazione ricorsiva
#else
show_bits_i( rand() ); //
implementazione iterativa
#endif
// codice finale - fine
Se l'utente definisce la macro RICORSIVO allora viene compilata SOLO LA PRIMA
PARTE mentre se l'utente non la definisce viene automaticamente compilata la
seconda.