Rubrica PROGRAMMAZIONE

Come funzionano i puntatori a funzione

L'ultimo scoglio da superare per diventare maestri del linguaggio C

Abstract: viene spiegato come utilizzare questi costrutti del linguaggio, per ottenere, al solito, un codice piu' efficiente. Si parte dalla dichiarazione, e si arriva all'utilizzo di funzioni che ritornano array di puntatori a funzioni. Eugenio e' avvisato!

di Stefano Casini



[Inizio articolo]Introduzione

Immaginate di sognare dei mostri che vi corrono dietro, con mille teste e la bocca aperta, cercando di mangiarvi: e' quello che succede ad Eugenio, programmatore ingenuo, quando la notte va a dormire dopo aver mangiato pesante, e sa che il giorno dopo dovra' fare una piccola modifica al codice scritto da Alberto, programmatore esperto, attualmente in vacanza alle Canarie. "Questi non sono i sorgenti, - sogna Eugenio - avete cambiato l'estensione al file .obj in .c per farmi uno scherzo!", e via una sfilza di *, ->, (**), *a.b->c, e chi piu' ne ha piu' ne metta. La mattina, al lavoro, con la faccia distrutta si siede davanti al computer, e si trascina lentamente analizzando tre righe di codice l'ora, e riscrivendole alla sua maniera (ogni riga si moltiplica per 10). Ma, se ormai i puntatori alle variabili non lo terrorizzano piu' di tanto, quello che non riesce a capire sono i puntatori alle funzioni. Le domande che si pone sono: funzionano veramente? quando conviene usarli? perche' hanno scelto una maniera cosi' difficile per dichiararli?


[Inizio articolo]Dichiarazione del puntatore a funzione

Dichiarare i puntatori a funzione non e' cosi difficile come pensa Eugenio; prendiamo una dichiarazione di funzione qualunque:

int Funzione(char carattere);
questa e' una funzione che prende come parametro un carattere e restituisce un intero.
Possiamo dichiarare un puntatore a tutto l'insieme di funzioni che prendono come parametro un carattere e restituiscono un intero seguendo questi passi:

1) mettere tra parentesi il nome della funzione
int (Funzione)(char);
2) mettere l'asterisco dentro la parentesi, prima del nome della funzione
int (* Funzione)(char);
3) cambiare il nome della funzione con il nome della variabile puntatore
int (* PuntatoreAFunzione)(char);
A mio avviso, e' molto piu' comodo usare una definizione di tipo di dati, secondo questo schema:
typedef int (modificatore * TIPODIPUNTATORE)(char);
TIPODIPUNTATORE PuntatoreAFunzione;
dove al posto di modificatore potremo mettere i vari modificatori del tipo di funzione (ad esempio _far, _pascal, eccetera). Abbiamo cosi' definito un nuovo tipo di dato, TIPODIPUNTATORE, che rappresenta appunto i puntatori a funzioni che prendono come parametro un carattere e restituiscono un intero, e dichiarato una variabile PuntatoreAFunzione, che potra' essere inizializzata con l'indirizzo di una funzione del tipo appena definito. Si, dice Eugenio, ma dove lo trovo l'indirizzo della funzione? Cosi' come per gli array (vedi articolo "Chi ha paura dei puntatori?" [1]) il nome dell'array viene interpretato a run-time come l'indirizzo di memoria dove e' stato allocato l'array, cosi' il nome della funzione viene ad essere visto come l'indirizzo della funzione. Potremo allora scrivere:
PuntatoreAFunzione = Funzione;
ed assegnare cosi' alla variabile PuntatoreAFunzione l'indirizzo della locazione di memoria in cui e' stata allocata a run-time Funzione; da adesso sara' indifferente eseguire Funzione o PuntatoreAFunzione: l'importante e' passare i parametri giusti. Per richiamare la funzione attraverso il suo puntatore occorre:

1) mettere un asterisco prima della variabile puntatore
* PuntatoreAFunzione;
2) racchiudere tutto in parentesi
(* PuntatoreAFunzione);
3) inserire dentro parentesi successive i parametri da passare alla funzione puntata
(* PuntatoreAFunzione)(parametri);
Alcuni compilatori moderni accettano anche la sintassi semplificata, dove non c'e' bisogno del derefenziamento del puntatore; basta scrivere:
PuntatoreAFunzione(parametri);
Naturalmente, il consiglio e' quello di usare la sintassi classica, per garantire la massima compatibilita'. Vediamo un esempio banale:
// definizione del nuovo tipo di dati
typedef int (* TIPODIPUNTATORE)(char);

void main(void)
{
	// dichiarazione della variabile PuntatoreAFunzione
	TIPODIPUNTATORE PuntatoreAFunzione;
	int valore;
	
	// assegnazione dell'indirizzo della funzione al puntatore
	PuntatoreAFunzione = Funzione;

	// queste due espressioni sono assolutamente equivalenti
	valore = Funzione('Z');
	valore = (* PuntatoreAFunzione)('Z');
	// questa espressione e' equivalente alle precedenti, ma non tutti 
	i compilatori l'accettano
	valore = PuntatoreAFunzione('Z');
}

int Funzione(char carattere)
{
	return((int)carattere - 32);
}


[Inizio articolo]Quando conviene usare i puntatori a funzione

Va bene, dice Eugenio, ma allora avevo ragione io che non servono a niente: tanto, o chiamo la funzione o chiamo il puntatore alla funzione il risultato e' lo stesso! Certo, risponde Alberto, perche' l'esempio fatto e' molto banale, ma per raffreddare il tuo entusiasmo ti dico che un puntatore a funzione puo' essere passato come argomento ad un'altra funzione, scrivendo cosi' un codice piu' elegante ed ottimizzato! Infatti un puntatore a funzione altro non e' che una variabile (a 32 bit, in genere), e come tale puo' essere passato come parametro in ingresso alle funzioni. Vediamo un esempio un poco piu' complesso, relativo a SynCrOPY, una DLL extension per il FileManager di Windows 3.1:

/* PSUBSTFUNC è un puntatore alle funzioni che prendono come parametri 
due puntatori a strutture FMS_GETFILESEL, e ritornano un intero */
typedef int (* PSUBSTFUNC)(LPFMS_GETFILESEL, LPFMS_GETFILESEL);

/* questo e' il prototipo di una funzione che accetta come terzo parametro 
un puntatore a funzione di tipo PSUBSTFUNC */
BOOL ExpandTo(HWND hwnd, LPSTR lpszDir, PSUBSTFUNC pSubstFunc);

/* questi sono prototipi di funzioni che possono essere assegnate a PSUBSFUNC */
int SostituisciFile(LPFMS_GETFILESEL lpfms_Source, LPFMS_GETFILESEL lpfms_Dest);
int SostituisciFileExp(LPFMS_GETFILESEL lpfms_Source, LPFMS_GETFILESEL lpfms_Dest);
int Confronta(LPFMS_GETFILESEL lpfms_GetFileSel, LPFMS_GETFILESEL lpfms_Dest);

/* questo e' un pezzo di codice della FileManager Extension Procedure dove 
si sceglie quale funzione passare alla funzione ExpandTo, in funzione del 
valore di wMsg */

HMENU FAR PASCAL __export FMExtensionProc(HWND hwnd, WORD wMsg, LONG lParam)
{

...
	switch(wMsg)
	{		
		case IDM_SYNCROPY:
		case IDM_EXPAND:
		{
			char szDir[256] = "C:\\";  
			
			ExpandTo(hwnd, szDir, (wMsg == 
			  IDM_SYNCROPY) ? SostituisciFile : 		
			SostituisciFileExp);
			break;
		}
		default:
			break;
	}

...

}

/* questa e' la funzione ExpandTo: il terzo parametro puntera' alla funzione 
SostituisciFile o alla funzione SostituisciFileExp, a seconda del valore che 
aveva wMsg nel pezzo di codice precedente */
BOOL ExpandTo(HWND hwnd, LPSTR lpszDir, PSUBSTFUNC pSubstFunc)
{
	WORD wIndex;
	FMS_GETFILESEL Fms_GetFileSel;
	
	wIndex = (WORD)SendMessage(hwnd, FM_GETSELCOUNT, 0, 0L);
	// prendiamo i file selezionati                           
	while(wIndex)
	{
		SendMessage(hwnd, FM_GETFILESEL, --wIndex, 
			(LPARAM)(LPFMS_GETFILESEL)&Fms_GetFileSel);
	    	if(ManipolaFile(hwnd, 
	    	    &Fms_GetFileSel, lpszDir, pSubstFunc) == IDCANCEL)
	    		break;
	}        
	
	return NULL;
}

/* la funzione ManipolaFile chiama finalmente la funzione puntata dal puntatore */
int ManipolaFile(HWND hwnd, LPFMS_GETFILESEL lpfms_GetFileSel, LPSTR lpszDir, 
	PSUBSTFUNC pSubstFunc)
{
	char szAppoDest[260];
	char szFileExpanded[260];
	char szFile[16];
	int result;
	FMS_GETFILESEL fms_Dest;
    
	if(pSubstFunc == SostituisciFileExp)
	{
		result = GetExpandedName(lpfms_GetFileSel->szName, szFileExpanded);
		if(result == TRUE)        
			GetFileTitle(szFileExpanded, szFile, sizeof(szFile));
	}
	else
		GetFileTitle(lpfms_GetFileSel->szName, szFile, sizeof(szFile));
	
	...    
	
	if(result == 1)	
		return (* pSubstFunc)(lpfms_GetFileSel, &fms_Dest);

	if(result == - 1)	
		return (* pSubstFunc)(&fms_Dest, lpfms_GetFileSel);
}                                     
Come si vede nell'esempio, una scelta fatta a monte, nello switch della FileManager Extension Procedure, si ripercuote a cascata nelle funzioni successive senza doversi portare appresso una variabile con il valore di wMsg ed eseguire di nuovo lo switch in ognuna delle funzioni successive. Vediamo adesso un altro esempio, dove viene definita una funzione, PrintNipts, che si occupa di stampare qualcosa che viene scelto a seconda dello stato del programma e dal menu attivo. Definiamo innanzitutto un tipo di puntatore a funzione, PFUNC, che rappresenta le funzioni che accettano come parametri l'handle al DC (device context) della stampante ed il puntatore ad una struttura di tipo RECT (rettangolo), e ritornano void.
typedef void (* PFUNC)(HDC, RECT *);
Definiamo poi la funzione PrintNipts, che funge da intermediaria: in pratica si limita a prendere il DC della stampante, a stampare nella pagina usando la funzione di stampa che gli viene passata con il puntatore contenuto nel secondo parametro, infine stampa delle note nella parte finale della pagina.
BOOL PrintNipts(LPSTR lpszTitle, PFUNC pfunc)
{
	// handle al DC della stampante    
	HDC hPr;                    
	RECT rc;
	RECT rcNotes;                           

	hPr = GetPrinterDC();
 	if (!hPr)
		return FALSE;

	// misuriamo l'altezza di una pagina della stampante
	SetRect(&rc, 0, 0, GetDeviceCaps (hPr, HORZRES), GetDeviceCaps (hPr, VERTRES));

	if (Escape(hPr, STARTDOC, strlen(lpszTitle), lpszTitle, NULL) < 0)
	{
		DeleteDC(hPr);
		return FALSE;
	}

	// stampiamo quello che interessa
	(* pfunc)(hPr, &rc);

	// settiamo il rettangolo per le note
	SetRect(&rcNotes, rc.left, rc.bottom - rc.bottom / 16, rc.right, rc.bottom);

	// stampiamo le note
	if(pszNotes)
	{
		int hTx; 
		RECT rcCalc;

		hTx = DrawText(hPr, pszNotes, - 1, &rcCalc, DT_CALCRECT | DT_WORDBREAK);
		if(hTx > (rcNotes.bottom - rcNotes.top))
		{
			Escape(hPr, NEWFRAME, 0, 0L, 0L);
			rcNotes.top = 0;
		}
		DrawText(hPr, pszNotes, - 1, &rcNotes, DT_LEFT | DT_WORDBREAK);
	}

	Escape(hPr, NEWFRAME, 0, 0L, 0L);
	Escape(hPr, ENDDOC, 0, 0L, 0L);

	DeleteDC(hPr);

	return TRUE;
}
Questa funzione, sempre utile, permette di ricavare dal file "win.ini" il nome della stampante.
HDC GetPrinterDC(void)
{
	static char szPrinter [80];
	char    * szDevice, * szDriver, * szOutput;
	HDC hPr;

	GetProfileString ("windows", "device", "", szPrinter, sizeof(szPrinter));

	if((szDevice = strtok (szPrinter, "," )) &&
		(szDriver = strtok (NULL, ", ")) &&
		(szOutput = strtok (NULL, ", ")))
	{
		hPr = CreateDC (szDriver, szDevice, szOutput, NULL);
		if(hPr)
			return hPr;
	}

	return NULL;
}
Da qualche parte avremo il codice delle funzioni di stampa, delle quali riporto solo i prototipi (notate che sono tutte dello stesso tipo):
void BuildPage(HDC hdc, LPRECT lprc);
void Curva(HDC hdc, LPRECT lprc);
void Audiogramma(HDC hdc, LPRECT lprc);
void Confronti(HDC hdc, LPRECT lprc);
void BuildConfrontiPage(HDC hdc, LPRECT lprc);
void AltriConfronti(HDC hdc, LPRECT lprc);
void BuildPeppePage(HDC hdc, LPRECT lprc);
void CurvaDiPeppe(HDC hdc, LPRECT lprc);
void BuildRiskPage(HDC hdc, LPRECT lprc);
void CurvaDiRischio(HDC hdc, LPRECT lprc);
Questo e' il frammento di codice della Window Procedure, dove viene processato il menu di stampa:
case IDM_STAMPA:
{
	HMENU hmenu;
	HMENU hSubMenu;

	hmenu = GetMenu(hwnd);
	hSubMenu = GetSubMenu(hmenu, VISUAL_MENU_POS);
	if(Stato == PERDITE)
	{
		if(GetMenuState(hSubMenu, IDM_TABELLA, MF_BYCOMMAND) &
			MF_GRAYED)
			PrintNipts("Tabella", BuildPage);
		if(GetMenuState(hSubMenu, IDM_GRAFO, MF_BYCOMMAND) &
			MF_GRAYED)
			PrintNipts("Grafico", Curva);
		if(GetMenuState(hSubMenu, IDM_AUDIO, MF_BYCOMMAND) &
			MF_GRAYED)
			PrintNipts("Audiogramma", Audiogramma);
	}
	if(Stato == PREVISIONI)
	{
		if(GetMenuState(hSubMenu, IDM_TABELLA, MF_BYCOMMAND) &
			MF_GRAYED)
			PrintNipts("Tabella", BuildConfrontiPage);
		if(GetMenuState(hSubMenu, IDM_GRAFO, MF_BYCOMMAND) &
			MF_GRAYED)
			PrintNipts("Grafico", Confronti);
		if(GetMenuState(hSubMenu, IDM_AUDIO, MF_BYCOMMAND) &
			MF_GRAYED)
			PrintNipts("Grafico", AltriConfronti);
	}
	if(Stato == PEPPECUR)
	{
		if(GetMenuState(hSubMenu, IDM_TABELLA, MF_BYCOMMAND) &
			MF_GRAYED)
			PrintNipts("Tabella", BuildPeppePage);
		if(GetMenuState(hSubMenu, IDM_GRAFO, MF_BYCOMMAND) &
			MF_GRAYED)
			PrintNipts("Grafico", CurvaDiPeppe);
	}
	if(Stato == RISK)
	{
		if(GetMenuState(hSubMenu, IDM_TABELLA, MF_BYCOMMAND) &
			MF_GRAYED)
			PrintNipts("Grafico", BuildRiskPage);
		if(GetMenuState(hSubMenu, IDM_GRAFO, MF_BYCOMMAND) &
			MF_GRAYED)
			PrintNipts("Grafico", CurvaDiRischio);
	}
	break;
}
Guarda caso, le stesse funzioni usate per stampare vengono usate anche per disegnare lo schermo, in risposta al messaggio WM_PAINT, pero' senza interporre la PrintNipts per recuperare il DC della stampante e scrivere le note.
case WM_PAINT:
{
	PAINTSTRUCT ps;
	RECT rc;
	HMENU hmenu, hSubMenu;

	BeginPaint(hwnd, &ps);
	GetClientRect(hwnd, &rc);

	hmenu = GetMenu(hwnd);
	hSubMenu = GetSubMenu(hmenu, VISUAL_MENU_POS);
	if(Stato == PERDITE)
	{
		if(GetMenuState(hSubMenu, IDM_TABELLA, MF_BYCOMMAND) &
			MF_GRAYED)
			BuildPage(ps.hdc, &rc);
		if(GetMenuState(hSubMenu, IDM_GRAFO, MF_BYCOMMAND) &
			MF_GRAYED)
			Curva(ps.hdc, &rc);
		if(GetMenuState(hSubMenu, IDM_AUDIO, MF_BYCOMMAND) &
			MF_GRAYED)
			Audiogramma(ps.hdc, &rc);
	}
	if(Stato == PREVISIONI)
	{
		if(GetMenuState(hSubMenu, IDM_TABELLA, MF_BYCOMMAND) &
			MF_GRAYED)
			BuildConfrontiPage(ps.hdc, &rc);
		if(GetMenuState(hSubMenu, IDM_GRAFO, MF_BYCOMMAND) &
			MF_GRAYED)
			Confronti(ps.hdc, &rc);
		if(GetMenuState(hSubMenu, IDM_AUDIO, MF_BYCOMMAND) &
			MF_GRAYED)
			AltriConfronti(ps.hdc, &rc);
	}
	if(Stato == PEPPECUR)
	{
		if(GetMenuState(hSubMenu, IDM_TABELLA, MF_BYCOMMAND) &
			MF_GRAYED)
			BuildPeppePage(ps.hdc, &rc);
		if(GetMenuState(hSubMenu, IDM_GRAFO, MF_BYCOMMAND) &
			MF_GRAYED)
			CurvaDiPeppe(ps.hdc, &rc);
	}

	if(Stato == RISK)
	{
		if(GetMenuState(hSubMenu, IDM_TABELLA, MF_BYCOMMAND) &
			MF_GRAYED)
			BuildRiskPage(ps.hdc, &rc);
		if(GetMenuState(hSubMenu, IDM_GRAFO, MF_BYCOMMAND) &
			MF_GRAYED)
			CurvaDiRischio(ps.hdc, &rc);
	}
	EndPaint(hwnd, &ps);
	break;
}
Senza usare i puntatori alle funzioni avremmo dovuto o riscrivere le funzioni per la stampa, incorporando in ognuna di esse il codice per recuperare il DC della stampante e stampare le note, oppure avremmo dovuto incorporare il codice di recupero del DC all'interno di quello che processa il menu di stampa, rendendo il tutto meno elegante e leggibile.


[Inizio articolo]Allarghiamoci: array di puntatori a funzione

Alberto chiede ad Eugenio: "Secondo te, si possono usare gli array di puntatori a funzione?". Eugenio, che stava mangiando un panino con la salsiccia, risponde alla Fantozzi: "Mah, veramente, non lo so, e poi e' l'ora della merenda, se mi fai queste domande non digerisco! Io, comunque, non saprei che farmene. Vuoi dare un morso al panino? Buona questa salsiccia!". La risposta e': si possono usare, e li useremo per compattare il codice sopra esposto. Facciamoci furbi e definiamo opportunamente le costanti numeriche da assegnare alla variabile Stato; per ogni valore di Stato abbiamo al massimo 3 possibilita' di scelta, legate all'item di menu correntemente disabilitato; per cui, ogni costante da assegnare sara' un multiplo di 3:

#define PERDITE	0
#define PREVISIONI	3
#define PEPPECUR	6
#define RISK	9
dichiariamo un array di puntatori a funzione del tipo PFUNC, e inizializziamolo con i nomi delle funzioni, messi in ordine opportuno, cosi':
void (* pFuncArray[12])(HDC, RECT *) = {BuildPage, Curva, Audiogramma, 
  BuildConfrontiPage, Confronti, AltriConfronti, BuildPeppePage, CurvaDiPeppe, 
  NULL, BuildRiskPage, CurvaDiRischio, NULL};
oppure cosi':
typedef void (* PFUNC)(HDC, RECT *);
PFUNC pFuncArray[12] = {BuildPage, Curva, Audiogramma, BuildConfrontiPage, Confronti, 
  AltriConfronti, BuildPeppePage, CurvaDiPeppe, NULL, BuildRiskPage, CurvaDiRischio, 
  NULL};
Con queste premesse, vediamo come di compatta il codice:
case IDM_STAMPA:
{
	HMENU hmenu;
	HMENU hSubMenu;
	PFUNC PuntatoreAFunzione;
	char * ArrayDiTitoli[12] = {"Tabella", "Grafico", "Audiogramma", 
	"Tabella", "Grafico", "Grafico", "Tabella", "Grafico", NULL, "Grafico", 
	  "Grafico", NULL};
	char * Titolo;

	/* controlliamo il valore della variabile Stato, per evitare che superi il 
	numero degli elementi dell'array */
	if(Stato > 9 || Stato < 0)
		break;

	hmenu = GetMenu(hwnd);
	hSubMenu = GetSubMenu(hmenu, VISUAL_MENU_POS);
	if(GetMenuState(hSubMenu, IDM_AUDIO, MF_BYCOMMAND) &
		MF_GRAYED)
	{
		PuntatoreAFunzione = pFuncArray[Stato + 2];
		Titolo = ArrayDiTitoli[Stato + 2];
	}
	if(GetMenuState(hSubMenu, IDM_GRAFO, MF_BYCOMMAND) &
		MF_GRAYED)
	{
		PuntatoreAFunzione = pFuncArray[Stato + 1];
		Titolo = ArrayDiTitoli[Stato + 1];
	}
	if(GetMenuState(hSubMenu, IDM_TABELLA, MF_BYCOMMAND) &
		MF_GRAYED)
	{
		PuntatoreAFunzione = pFuncArray[Stato];
		Titolo = ArrayDiTitoli[Stato];
	}

	if(PuntatoreAFunzione != NULL)
		PrintNipts(Titolo, PuntatoreAFunzione);

	break;
}

case WM_PAINT:
{
	PAINTSTRUCT ps;
	RECT rc;
	HMENU hmenu, hSubMenu;
	PFUNC PuntatoreAFunzione;
	
	/* controlliamo il valore della variabile Stato, per evitare che superi il 
	numero degli elementi dell'array */
	if(Stato > 9 || Stato < 0)
		break;

	BeginPaint(hwnd, &ps);
	GetClientRect(hwnd, &rc);

	hmenu = GetMenu(hwnd);
	hSubMenu = GetSubMenu(hmenu, VISUAL_MENU_POS);

	if(GetMenuState(hSubMenu, IDM_AUDIO, MF_BYCOMMAND) &
		MF_GRAYED)
		PuntatoreAFunzione = pFuncArray[Stato + 2];
	if(GetMenuState(hSubMenu, IDM_GRAFO, MF_BYCOMMAND) &
		MF_GRAYED)
		PuntatoreAFunzione = pFuncArray[Stato + 1];
	if(GetMenuState(hSubMenu, IDM_TABELLA, MF_BYCOMMAND) &
		MF_GRAYED)
		PuntatoreAFunzione = pFuncArray[Stato];

	if(PuntatoreAFunzione != NULL)
		(* PuntatoreAFunzione) (ps.hdc, &rc);
	EndPaint(hwnd, &ps);
	break;
}


[Inizio articolo]Funzioni che ritornano puntatori a funzione

Alberto chiede ad Eugenio: "Secondo te, possiamo compattare ancora il codice?". Eugenio, piangendo maionese dagli occhi, supplica: "No, ti prego, non ci sto capendo un cavolo!". Eppure la cosa e' molto semplice: ci sono due handler di messaggi che devono condividere lo stesso array di puntatori a funzione, per cui o mettiamo, come abbiamo fatto nell'esempio precedente, la definizione dell'array di puntatori a funzione esterno allo scope dei due handler (magari come variabile statica globale, Eugenio style), oppure creiamo una funzione, che verra' chiamata dai due handler (che un giorno potranno diventare 3, 4, .. 300), che terra' al suo interno l'array di puntatori, nascondendolo alle altre funzioni, e che ritorni il puntatore alla funzione voluta tra quelle presenti nell'array. Infatti, nulla vieta ad una funzione di ritornare il puntatore ad un'altra funzione. Scriviamo questa funzione che permette di fare la scelta:

PFUNC ScegliFunzione(int Stato, HMENU hSubMenu, int far * pInd)
{	
	PFUNC pFuncArray[12] = {BuildPage, Curva, Audiogramma, 
		BuildConfrontiPage, Confronti, AltriConfronti, 
		BuildPeppePage, CurvaDiPeppe, NULL, BuildRiskPage, 
		CurvaDiRischio, NULL};
	int indice;
			
	/* controlliamo il valore della variabile Stato, per evitare che superi il 
	numero degli elementi dell'array */
	if(Stato > 9 || Stato < 0)
		return NULL;

	if(GetMenuState(hSubMenu, IDM_AUDIO, MF_BYCOMMAND) &
		MF_GRAYED)
		indice = Stato + 2;
	if(GetMenuState(hSubMenu, IDM_GRAFO, MF_BYCOMMAND) &
		MF_GRAYED)
		indice = Stato + 1;
	if(GetMenuState(hSubMenu, IDM_TABELLA, MF_BYCOMMAND) &
		MF_GRAYED)
		indice = Stato;
    
	if(pInd)
		* pInd = indice;
    
	return pFuncArray[indice];
}
Il terzo parametro della funzione e' il puntatore ad una variabile di tipo intero, che verra' usata solo dall'handler della stampa: serve per far scegliere all'handler il titolo del documento da stampare; l'handler del WM_PAINT passera' per il terzo parametro il valore NULL. Vediamo ora come si semplificano gli handler:
case IDM_STAMPA:
{
	HMENU hmenu;
	HMENU hSubMenu;
	PFUNC PuntatoreAFunzione;
	char * ArrayDiTitoli[12] = {"Tabella", "Grafico", "Audiogramma", "Tabella", 
	  "Grafico", "Grafico", "Tabella", "Grafico", NULL, "Grafico", "Grafico", NULL};
	int indiceTitoli = 0;
	
	hmenu = GetMenu(hwnd);
	hSubMenu = GetSubMenu(hmenu, VISUAL_MENU_POS);

	PuntatoreAFunzione = ScegliFunzione(Stato, hSubMenu, &indiceTitoli);
	if(PuntatoreAFunzione != NULL)
		PrintNipts(ArrayDiTitoli[indiceTitoli], PuntatoreAFunzione);
	break;
}

case WM_PAINT:
{
	PAINTSTRUCT ps;
	RECT rc;
	HMENU hmenu, hSubMenu;
	PFUNC PuntatoreAFunzione;
	
	BeginPaint(hwnd, &ps);
	GetClientRect(hwnd, &rc);

	hmenu = GetMenu(hwnd);
	hSubMenu = GetSubMenu(hmenu, VISUAL_MENU_POS);

	PuntatoreAFunzione = ScegliFunzione(Stato, hSubMenu, NULL);
	if(PuntatoreAFunzione != NULL)
		(* PuntatoreAFunzione) (ps.hdc, &rc);
	EndPaint(hwnd, &ps);
	break;
}


[Inizio articolo]Funzioni che ritornano array di puntatori a funzione

I testi sacri del C dicono che una funzione non puo' ritornare un array, pero' chi mi ha seguito [1] [2] negli articoli precedenti conosce il trucco per aggirare questo limite: invece di ritornare l'array, ritorniamo il puntatore al primo elemento; naturalmente, se l'array viene inizializzato all'interno della funzione chiamata, il valore ritornato ha significato solo se l'array e' stato creato mediante un'allocazione dinamica di memoria. Se l'array fosse stato semplicemente dichiarato staticamente, il valore ritornato dalla funzione perderebbe di significato una volta usciti dallo scope della funzione [3]. Proviamo a modificare il codice dell'esempio pubblicato nell'articolo "Nei dintorni di malloc" [4] (a proposito, ne approfitto per segnalare un errore di trascrizione in [5], dove la chiamata alla funzione RiempiArray manca del secondo parametro: dovete mettere un carattere, per esempio 'A'). Le modifiche da fare sono semplici:
1) aggiungere una definizione di tipo

typedef void(* PFUNZIONEMATRICE)(char **);
2) scrivere una funzione che ritorna il puntatore all'array
PFUNZIONEMATRICE * CreaArrayDiPuntAFunzioni(void)
{
	PFUNZIONEMATRICE * ArrayDiPuntAFunzioni;
	
	ArrayDiPuntAFunzioni = malloc(sizeof(PFUNZIONEMATRICE) * 3);
	/* allochiamo la memoria per contenere 
	l'array di puntatori a funzione */

	if(!ArrayDiPuntAFunzioni)
			return NULL;

	ArrayDiPuntAFunzioni[0] = RiempiMatrice;
	ArrayDiPuntAFunzioni[1] = StampaMatrice;
	ArrayDiPuntAFunzioni[2] = LiberazioneMatrice;
	/* riempiamo l'array con gli indirizzi 
	delle funzioni */
	
	return ArrayDiPuntAFunzioni;
}
3) modificare la funzione main come segue
void main(void)
{
	char ** dinamicmatrice;
	unsigned int dimensioni[7] = {6, 12, 7, 3, 9, 5, 10};
	PFUNZIONEMATRICE * ppFuncMatr;
	int i;				

	dinamicmatrice = AllocazioneMatrice(dimensioni, sizeof(char *));
	if(!dinamicmatrice)
		return;
	/* allocazione dinamica di una matrice di 6 righe
	ciascuna riga da n caratteri */

	ppFuncMatr = CreaArrayDiPuntAFunzioni();
	/* allocazione dinamica di un array di 3 elementi,
	ciascun elemento e' un puntatore a funzione */

	for(i = 0; i < 3; i++)               
		(* ppFuncMatr[i])(dinamicmatrice);
	/* eseguiamo in cascata le 3 funzioni puntate 
	dagli elementi dell'array */

	free(ppFuncMatr);
	/* liberazione dell' array di puntatori a funzione
	allocato dinamicamente */
}
Il resto del codice rimane inalterato.


[Inizio articolo]Conclusioni

Il puntatore a funzione viene utilizzato raramente per due ordini di motivi: il primo e' che i casi in cui risulta effettivamente necessario non sono molto numerosi, all'interno di una programmazione "normale"; il secondo e' che la sintassi dichiaratoria puo' sembrare, a prima vista, difficile, e scoraggia i programmatori meno abituati all'uso intenso dei puntatori. Ne abbiamo illustrato alcuni esempi concreti di applicazione, tratti da programmi che girano tutti i giorni. I vantaggi che offre l'uso dei puntatori a funzione sono quelli di compattare e rendere il codice molto elegante. Inoltre, se si ha intenzione di passare alla programmazione in C++, diventa indispensabile conoscerne il principio di funzionamento; col C++, infatti, i dati vengono incapsulati all'interno delle classi, che esportano all'esterno solo le funzioni membro, e si lavora sulle classi usando appunto i puntatori alle funzioni dell'interfaccia.
Meditate, gente, meditate ...

__________________________________________

BIBLIOGRAFIA DELL'AUTORE E RIFERIMENTI



Stefano Casini, ingegnere, è articolista di BETA dal 1995 e svolge un lavoro che non ha nulla a che fare con la programmazione; è raggiungibile su Internet tramite la redazione.


Copertina Sommario Internet ID Informazioni Browser
Copyright © 1994-1998 BETA. Tutti i diritti sono riservati. BETA Sul Web: http://www.beta.it.