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:

#include 
typedef 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! 🙂

4 commenti
  1. Antonio Barba
    Antonio Barba dice:

    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 ^_^

    Rispondi
  2. Antonio Barba
    Antonio Barba dice:

    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!)…

    Rispondi

Lascia un Commento

Vuoi partecipare alla discussione?
Sentitevi liberi di contribuire!

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *