In questo articolo vi mostrerò come sfruttare praticamente i puntatori a funzione e le enumerazioni, o enum, in C. Se non sapete ancora di cosa stiamo parlando vi consiglio di leggere un manuale di C o altri tutorial a livello più basilare. Il mio solito consiglio è l’ottimo Kernighan & Ritchie.
Partiamo da un programma completo in C e analizziamolo in tutte le sue parti:
#includetypedef enum { HEX_MODE, DEC_MODE, BIN_MODE, OUTPUT_MODE_MAX } OutputMode; typedef void (*printFuncCallback)(int c); void printDecMode(int c) { printf("%d ", c); }
void printBinMode(int c) { int i; for (i=7; i>=0; i--) { printf(((c >> i) & 1) ? "1" : "0"); } printf(" "); } void printHexMode(int c) { printf("%x ", c); } printFuncCallback printFunc[OUTPUT_MODE_MAX] = { printHexMode, printDecMode, printBinMode }; int main(int argc, char **argv) { int c; // set the default output mode to Hex OutputMode mode = HEX_MODE; if (argc == 2) { if (!strcmp(argv[1], "-h")) { // activate hex mode mode = HEX_MODE; } else if (!strcmp(argv[1], "-d")) { // activate decimal mode mode = DEC_MODE; } else if (!strcmp(argv[1], "-b")) { // activate binary mode mode = BIN_MODE; } } for (;;) { c = getchar(); if (c == EOF) break; if (c == '\n') { printf("\n"); continue; } // print the char in the selected mode printFunc[mode](c); } return 0; }
Questo programmino è una versione estesa di quello sviluppato nell’articolo sugli operatori bitwise, e pertanto vi consiglio, se non l’avete ancora fatto, di seguire anche il tutorial precedente.
Partiamo dalla spiegazione della struttura del programma.
Abbiamo un parser per la riga di comando che seleziona la modalità di funzionamento in base al flag passato come parametro:
if (argc == 2) { if (!strcmp(argv[1], "-h")) { // activate hex mode mode = HEX_MODE; } else if (!strcmp(argv[1], "-d")) { // activate decimal mode mode = DEC_MODE; } else if (!strcmp(argv[1], "-b")) { // activate binary mode mode = BIN_MODE; } }
Come potete notare, in questa fase viene inizializzata la variabile “mode”. I valori HEX_MODE, DEC_MODE e BIN_MODE sono i membri della enum OutputMode che abbiamo definito in questo modo:
typedef enum { HEX_MODE, DEC_MODE, BIN_MODE, OUTPUT_MODE_MAX } OutputMode;
Il compilatore tradurrà questi valori in interi progressivi da 0 a salire, quindi HEX_MODE equivale a zero, mentre BIN_MODE equivale a 2, (notare che OUTPUT_MODE_MAX corrisponde a 3, quindi rispecchia esattamente il numero di elementi che costituiscono la enum, cioè HEX, DEC e BIN).
Dal momento che le enum vengono trattatate esattamente come numeri interi (costanti), è possibile utilizzarle per indicizzare gli elementi di un generico array, in questo modo:
printFuncCallback printFunc[OUTPUT_MODE_MAX] = { printHexMode, printDecMode, printBinMode };
L’array si chiama printFunc, contiene OUTPUT_MODE_MAX ( = 3) elementi, di tipo printFuncCallback. Il tipo degli elementi è un tipo definito dall’utente, dichiarato come segue:
typedef void (*printFuncCallback)(int c);
Tradotto in italiano, significa printFuncCallback è un puntatore (*printFuncCallback) ad una funzione di tipo void che prende come parametro un int. Il nome di tale parametro è ininfluente, infatti avremmo potuto scrivere equivalentemente:
typedef void (*printFuncCallback)(int);
In sostanza abbiamo dichiarato un array con 3 puntatori a funzione. I puntatori contenuti in questo array devono essere di tipo printFuncCallback, ciò significa che possiamo inserire nell’array solo funzioni void che accettano in input un int. Ecco le nostre 3 funzioni, scritte secondo questa convenzione:
void printDecMode(int c) { printf("%d ", c); } void printBinMode(int c) { int i; for (i=7; i>=0; i--) { printf(((c >> i) & 1) ? "1" : "0"); } printf(" "); } void printHexMode(int c) { printf("%x ", c); }
La prima stampa il parametro c in formato decimale ( printf(“%d “, c); ), la seconda stampa il parametro c in binario (vedi l’articolo precedente sulle operazioni bitwise) e la terza stampa il proprio parametro in formato esadecimale (la %x sta per heXadecimal).
Infine abbiamo un loop che legge ogni carattere dallo standard input (generalmente la tastiera, ma su Unix può essere redirezionato da qualunque sorgente, tipicamente un file, tramite l’operazione di pipeline: cat nomefile.txt | ./nomeprogramma ), esce quando incontra un End Of File, e stampa a video il carattere letto tramite una delle 3 funzioni:
for (;;) { c = getchar(); if (c == EOF) break; // se sono arrivato qui salto a FINE if (c == '\n') { printf("\n"); continue; // se sono arrivato qui salto al prossimo ciclo, // cioè torno al getchar() } // print the char in the selected mode printFunc[mode](c); } // FINE: arrivo qui soltanto dopo il break
Il valore della variabile mode fornisce l’indice della funzione di stampa contenuta nell’array di puntatori a funzione, quindi se mode vale BIN_MODE, sarà richiamata la funzione printBinMode, perchè BIN_MODE corrisponde al numero 1 e in quella posizione dell’array abbiamo memorizzato proprio l’indirizzo di quella funzione (ricordiamo che in C gli array sono indicizzati a partire da 0).
Da notare anche l’utilizzo delle istruzioni break e continue. Entrambe servono ad interrompere il ciclo for ma con modalità diverse: break serve per terminare l’intero ciclo, mentre continue serve per terminare l’iterazione in corso, ma il ciclo riprende dall’iterazione successiva. Questo significa che se il programma incontra il carattere EOF (End Of File) esegue l’istruzione break, e termina il ciclo, terminando in questo caso anche l’esecuzione del programma; invece quando incontra il carattere di andata a capo \n il programma stampa un’andata a capo (invece di convertirla in forma numerica come accade con tutti gli altri caratteri) e interrompe il ciclo corrente, riprendendo dal prossimo carattere.
Salvate il sorgente in un file chiamato multimessage.c, poi potete compilare ed eseguire il sorgente usando questo comando sotto GNU/Linux:
gcc -o multimessage multimessage.c
Poi eseguitelo e questo è ciò che otterrete:
$ echo "ciao a tutti da OScene.net" | ./multimessage -h 63 69 61 6f 20 61 20 74 75 74 74 69 20 64 61 20 4f 53 63 65 6e 65 2e 6e 65 74 $ echo "ciao a tutti da OScene.net" | ./multimessage -b 01100011 01101001 01100001 01101111 00100000 01100001 00100000 01110100 01110101 01110100 01110100 01101001 00100000 01100100 01100001 00100000 01001111 01010011 01100011 01100101 01101110 01100101 00101110 01101110 01100101 01110100 $ echo "ciao a tutti da OScene.net" | ./multimessage -d 99 105 97 111 32 97 32 116 117 116 116 105 32 100 97 32 79 83 99 101 110 101 46 110 101 116
Potete anche convertire interi file in questo modo:
$ cat file1.txt | ./multimessage -h > file_destinazione.txt
e qualunque altra cosa vi venga in mente di fare!
Buon divertimento! 🙂
Ehi Antonio, finalmente hai fatto ripartire il blog! Ottimo!
Un affezionato lettore 🙂
eh si! il taglio è decisamente diverso… non lo considero un blog, ma una raccolta di guide tecniche di livello base… Ho anche aperto altri 2 siti, li trovi nella mia pagina personale nella sezione Staff 🙂
cmq, grazie di essere passato, anch’io ti leggo ogni tanto, anche se spesso non lascio commenti ^_^
Bell’articolo ancora la tecnica dei vettori di puntatori a funzione non la conoscevo, continua a scrivere che noi ti seguiamo ^_^
Grazie Marcello, sto preparando altri tutorial di base da mettere sul sito, e sto anche organizzando qualche guida di programmazione avanzata da mettere sul wiki tecnico, ancora in fase di costruzione (trovi il link nella sezione Staff, dentro la mia pagina personale), ma in lingua inglese (so che per te non è un problema!)…