這一系列文章,主要是整理一下 Heresy 自己知道,可以用來儲存、傳遞一個函式(function)的一些方法;當然,主要就是 function pointer 和 function object 了~為什麼要做這件事呢?其實如果妥善利用的,這類的功能是相當實用,不但可以減少不少程式碼的量的,同時更可以減少程式碼維護所需要的時間。
而首先,這一篇就先來大概介紹一下 C 的 funciton pointer(函式指標、pointer to function)了~
Function Pointer 基本範例
顧名思義,他就是指到 function 的指標。下面是一個簡單的範例:
int power2( int v ) { return v * v; } void custom_for_each( int *pArray, unsigned int size, int (*op)( int ) ) { for( unsigned int i = 0; i < size; ++ i ) pArray[i] = (*op)( pArray[i] ); } int main( int argc, char* argv[] ) { int size = 10; int* pArray = new int[ size ]; for( unsigned int i = 0; i < size; ++ i ) pArray[i] = i; custom_for_each( pArray, size, &power2 ); for( unsigned int i = 0; i < size; ++ i ) printf( "%d, ", pArray[i] ); delete [] pArray; return 0; }
在這個例子裡,除了 main() 以外,還有定義了兩個 function:power2() 和 custom_for_each()。
其中,power2() 相當簡單,就是傳入一個整數,然後計算他的平方後傳回來。
custom_for_each() 這個函式則就是這邊的重點,它的用途是傳入一個整數的陣列 pArray 和他的大小 size,以及一個 function pointer op;而這邊這個 function pointer 的寫法,就是「int (*op)( int )」,它代表了一個名為 op 的 pointer,指到一個「接受一個整數當作參數、並回傳一個整數的 function」。
在custom_for_each() 的內容部分,則就是一個迴圈,把 pArray 裡的每一項,都經過 op 做計算,並寫回 pArray 裡;而呼叫 op 的方法,基本上就是「(*op)( pArray[i] )」,也就是像一般的指標一樣,在前面加上「*」了~
而實際上要怎麼使用這兩個函式呢?只要在呼叫 custom_for_each() 的時候,把 power2() 的位址當作第三個參數傳進去就可以了!完整寫出來,就是「custom_for_each( pArray, size, &power2 );」了。而實際上,只要是「接受一個整數當作參數、並回傳一個整數的 function」,都是可以以這樣的形式當作參數傳進 custom_for_each() 使用的~
這樣寫的好處,在於可以把 function 拆開成更小的元件,讓他們可以更方便地組合、使用,有效地增加程式碼的可重複使用性。現在範例裡只有 power2() 一個函式或許感覺不太出來,但是如果我們又去額外寫了其他他的函式的話,就比較看的出來了~比如說,假設我們現在有下面這六個 function:
int plus_one( int v ); int power2( int v ); int power3( int v ); int power4( int v ); void custom_for_each( int *pArray, unsigned int size, int (*op)( int ) ); void custom_for_each_2D( int **pArray, int sizeX, int sizeY, int (*op)( int ) );
應該就比較感覺得出來這樣寫的好處了!因為前面四個計算用的 function 可以任意傳給 custom_for_each() 或來當作 custom_for_each_2D() 參數,在這個狀況下,就是有 4 * 2、共有八種組合了~而如果函式的內容複雜、數量在更多的話,這樣不但可以減少許多本來需要重複撰寫的程式碼,也可以有效降低程式碼維護的時間。
使用 typedef 加強可讀性
雖然這樣就可以使用 function pointer 了,但是說實話看起來實在很醜、很難閱讀;「int (*op)(int)」這樣的寫法,真的很難一眼就看出來哪個才是變數。
所以,如果要經常性地使用某種形式的 function pointer 的時候,其實可以考慮透過「typedef」的方式,幫這種比較複雜的 function pointer 型別,定義出一個比較簡單、一般的型別名稱來用。例如以上面的例子來說,就可以改寫成:
typedef int (*CustomOperator)( int ); //void custom_for_each( int *pArray, unsigned int size, int (*op)( int ) ) void custom_for_each( int *pArray, unsigned int size, CustomOperator op )
也就是可以先透過第一行的「typedef int (*CustomOperator)( int );」,讓 CustomOperator 變成是一個 function pointer 的型別,而之後就可以把它當作一般的型別來使用了~像 custom_for_each() 的宣告,就可以直接用「CustomOperator op」來代替本來的「int (*op)( int )」!這樣看起來,就簡單易懂多了~
Function Pointer 的陣列
而在某些情況下,我們或許會需要使用到 function pointer 的陣列,這時候該怎麼寫呢?基本的寫法,是如同下面這樣的形式:
int (*op[10])( int );
for( int i = 0; i < 10; ++ i )
op[i] = &power2;
在這裡,「int (*op[10])( int );」就是會宣告出一個大小是 10 的 function pointer 陣列。在指定它裡面每一項的值的時候,都和本來的 function pointer 寫法一樣;不過在使用的時候,只要寫「op[1]( 10 )」這樣就可了,不用再加上星號(其實就和一般的陣列意義一樣)。
當然,這樣看起來還是很難閱讀,所以我們還是可以用 typedef 的方法,來加強程式的可讀性~也就是可以將上面的程式碼修改為:
typedef int (*CustomOperator)( int ); CustomOperator op[10]; for( int i = 0; i < 10; ++ i ) op[i] = &power2;
這樣看起來,function pointer 就更像一般的變數、更容易閱讀、使用了!
[…] function pointer 是因為他要處理 class 的 member function […]
讚讚
[…] 之前也有在《在 C++ 裡傳遞、儲存函式 Part 1:Function Pointer》介紹過,算是一個很典型的方法,但是由於語法上的關係,Heresy […]
讚讚
這段是不是寫錯了?
custom_for_each( pArray, size, &square );
應該是
custom_for_each( pArray, size, &power2 );
讚讚
感謝指正,已修正
讚讚
[…] 在 C++ 裡傳遞、儲存函式 Part 1:Function Pointer -11,329 […]
讚讚
typedef int (AFX_CDEL *CustomOperator)( int );
謝謝您的文章,還想請問您,如果在CustomOperator這個型別名稱之前加了AFX_CDEL這東西,是什麼意思呢?
讚讚
那是在指定 calling convention
http://msdn.microsoft.com/en-us/library/984x0h58.aspx
AFX 應該是某種 library 自行定義的 prefix
讚讚
[…] C 的 function pointer(請參考《在 C++ 裡傳遞、儲存函式 Part 1:Function Pointer》)、拿來註冊成 callback […]
讚讚
[…] Pingback: 在 C++ 裡傳遞、儲存函式 Part 1:Function Pointer « Heresy's Space […]
讚讚
寫得很清楚!感謝!
讚讚
感謝 給我清楚的關念
讚讚
寫的很棒!function pointer 就相當於軟體上的irq function一樣(^○^)
讚讚
謝謝 :)
讚讚
[…] 前一篇大致介紹了 C 語言裡的 function pointer 了,而這一篇,則是來大概介紹 C++ 的 function object 了! […]
讚讚