関数ポインタとコールバック
例年より授業の進度が速いので、 全体の総括の前に、追加ネタを加える。
関数ポインタは、オブジェクト指向の仮想関数の実装であったり、 イベント駆動型のプログラミングで重要なテクニックなので紹介する。
関数ポインタの基礎
基本的には、関数ポインタは以下のように使用する。 以下のサンプルプログラムでは、(*f)( 2 , 3 ) にて、fに代入されている関数を、 引数(2,3) にて呼び出す。
int fadd( int x , int y ) { return x + y ; } int fmul( int x , int y ) { return x * y ; } void main() { // 型は2つのintを引数を持つintを返り値とする関数へのポインタ int (*f)( int , int ) ; f = fadd ; // 関数名の後ろに丸カッコがない点に注目 printf( "%d¥n" , (*f)( 2 , 3 ) ) ; // 2+3=5 f = fmul ; printf( "%d¥n" , (*f)( 2 , 3 ) ) ; // 2*3=6 }
関数ポインタは、ライブラリを利用する際に、自分なりの変更を加えたい部分があったら、 その処理を関数で記述しておき、その関数のポインタをライブラリに渡す。 ライブラリは、必要に応じて関数ポインタを使って、関数を呼び出す。 このように、ライブラリの中から自分の渡した関数を呼び出してもらうのは、 「コールバック関数」と呼ばれる。
以下に簡単な例として、配列の最大データの添字を返すプログラムの例を示す。 この方法であれば、異なるデータの型でもコールバック関数を専用に作るだけで良い。
int intcmp( int* x , int* y ) { // 整数比較関数 if ( *x > *y ) return 1 ; else if ( *x < *y ) return -1 ; else return 0 ; } int vmax( void* array , // 配列先頭アドレス int size , // 配列データ件数 int sizeofdata , // 1件あたりのbyte数 int(*f)( void*,void* ) ) { // 比較関数 int i , max = 0 ; for( i = 0 ; i < size ; i++ ) if ( (*f)( array + max * sizeofdata , array + i * sizeofdata ) < 0 ) max = i ; return max ; } int idata[ 4 ] = { 11 , 33 , 22 , 44 } ; char sdata[ 4 ][ 4 ] = { "ab" , "bc" , "aa" , "c" } ; void main() { int m ; // intcmp関数を使って、idata から最大値を探す m = vmax( idata , 4 , sizeof(int) , intcmp ) ; printf( "%d" , idata[ m ] ) ; // strcmp関数を使って、sdata から最大値を探す m = vmax( sdata , 4 , sizeof(sdata[0]) , strcmp ) ; printf( "%s" , sdata[ m ] ) ; }
この方式をそのまま使った便利な関数が、C言語のクイックソートの標準関数 qsort( void* , sizt_t , size_t , int(*)( void*,void* ) ) である。この関数を使えば、データ比較用のコールバック関数を書くだけで、 配列の高速ソートが可能となる。
// LLVMなどの新しいC言語では、無名関数みたいな // block pointer ってぇのが使えるらしい。 int (^f)( int , int ) ; f = ^( int x , int y ) { return x + y ; } ; printf( "%d¥n" , f( 2 , 3 ) ) ; f = ^( int x , int y ) { return x * y ; } ; printf( "%d¥n" , f( 2 , 3 ) ) ;
オブジェクト指向での仮想関数
関数ポインタは、オブジェクト指向での仮想関数機能の実現に利用されている。
// 純粋仮想基底クラス class A { public: virtual void print() { printf( "A¥n" ) ; } } ; // 派生クラス A_int class A_int : public A { private: int data ; public: A_int( int x ) { data = x ; } virtual void print() { printf( "A_int : %d¥n" , data ) ; } } ; // 派生クラス A_str class A_str : public A { private: const char* data ; public: A_str( const char* x ) { data = x ; } virtual void print() { printf( "A_str : %s¥n" , data ) ; } } ; int main() { A aa ; A_int ai( 10 ) ; A_str as( "hello" ) ; // array[] には、異なる型のデータ(同じ基底クラス)が入ってる。 A* array[] = { &aa , &ai , &as } ; for( int i = 0 ; i < 3 ; i++ ) { array[ i ]-> print() ; } // 実行結果 // A // A_int : 10 // A_str : hello return 0 ; }
JavaScriptと無名関数
JavaScript では、イベント駆動のプログラムを実現するために、無名関数を利用することが多い。
var func ; func = function( x , y ) { return x + y ; } ; func( 2 , 3 ) ; // 答えは5 function foo( func ) { alert( func( 2 , 3 ) ) ; } foo( function(int x,int y) { return x * y ; } ) ; // alertで6が表示される