Beh, il titolo è un pochino esagerato, lo so. Vediamo comunque un ambito interessante di programmazione forse un pochino inusuale ma molto utile. Le macro sono sicuramente note a chi studia C/C++, fanno parte delle cosiddette direttive al preprocessore e iniziano col simbolo cancelletto. Solitamente usiamo #include, #define.
Siamo sicuramente abituati e leggere questi due codici, come equivalenti:
#define N 100
int N=100;
È interessante però sapere che si possono definire anche funzioni! La gestione non è proprio uguale, diventa un po' più complesso, diciamo che l'ideale è questo:
- funzioni abbastanza semplici da implementare (no cicli, ecc)
- importanza dell'efficienza del codice (quindi funzione semplice richiamata molte volte)
Un esempio semplice di funzione definita sia in modo standard che come macro, è il seguente:
int f(int x){
return x*x;
}
#define f(x) x*x
Si tratta semplicemente dell'elevazione al quadrato, vediamo quindi che è possibile implementarla in questi due modi diversi.
Un esempio molto più interessante e versatile, riguarda le istruzioni condizionali! Qui occorre fare uso dell'operatore ternario ovvero: condizione?se_vero:se_falso
, vediamo un esempio sempre con funzione standard e macro:
int f(int x){
if(x%2==0){
return 1;
}else{
return 0;
}
}
#define f(x) (x%2==0?1:0)
Questo possiamo dire che è molto interessante, permette di implementare in modo efficiente varie tipologie di funzioni (salvo neccessità di usare cicli, ricorsioni). Quest'ultimo esempio, come facile intuire, restituisce 1 se il numero è pari mentre 0 se è dispari.
Macro vs funzioni: benchmark performance
Vediamo secondo quest'ultimo caso, la differenza in termini di performance (altrimenti stiamo a parlare solo di teoria 😄), quindi il diverso tempo impiegato da 1.000.000.000 di iterazioni (verifica se l'i-esimo numero è pari o dispari) fra la funzione implementata in modo standard e definita tramite macro. Scriviamo il codice un'unica volta, commentando il codice alternativo (quindi facciamo compilare ed eseguire il codice opportuno, macro o funzione a seconda di ciò che ci interessa).
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define f(x) (x%2==0?1:0)
int funzione(int x);
int main() {
register unsigned int N=1000000000;
register unsigned int i;
register unsigned int temp;
clock_t start = clock();
for(i=0;i<N;i++){
//if(i%2==0){temp=1;}else{temp=0;}
//f(i);
funzione(i);
}
clock_t end = clock();
printf("%f s\n", (float)(end - start) / CLOCKS_PER_SEC);
return 0;
}
int funzione(int x){
if(x%2==0){return 1;}else{return 0;}
}
Risultati del benchmark prestazioni
- macro:
#define f(x) (x%2==0?1:0)
,f(i);
: 0,231s - codice scritto direttamente, con variabile temporanea (occorre, altrimenti non avrebbe alcun senso poiché non memorizzerebbe alcuna operazione):
int temp;
if(i%2==0){temp=1;}else{temp=0;}
: 0,359s - codice scritto tramite funzione:
int funzione(int x){ if(x%2==0){return 1;}else{return 0;} }
,funzione(i);
: 0,75s
Era un esempio semplice, ma già utile per rendere l'idea dell'importanza dellle macro, delle direttive al preprocessore. L'efficienza del codice parla chiaro se valutiamo il diverso tempo di esecuzione. Quindi imparate a farne uso, provate a fare qualche implementazione. Sarò esagerato io, ma l'efficienza del codice è il fine ultimo (almeno, uno dei) da raggiungere! 😀