関数ポインタとコールバック関数
JavaScript のプログラムで、以下のようなコーディングがよく使われる。このプログラムでは、3と4を加えた結果が出てくるが、関数の引数の中に関数宣言で使われるfunctionキーワードが出てきているが、この意味を正しく理解しているだろうか?
このような (function()…)は、無名関数と呼ばれている。「関数を引数として渡す機能」と、「一度しか使わないような関数にいちいち名前を付けないで関数を使うための機能」であり、このような機能は、関数を引数で渡す機能はC言語では関数ポインタと呼ばれたり、新しいプログラム言語では一般的にラムダ式などと呼ばれる。
// JavaScriptの無名関数の例 3+4=7 を表示
console.log( (function( x , y ) {
return x + y ;
})( 3 , 4 ) ) ; // 無名関数
console.log( ((x,y) => {
return x + y ;
})( 3 , 4 ) ) ; // アロー関数
C言語の関数ポインタの仕組みを理解するために、以下のプログラムを示す。
int add( int x , int y ) {
return x + y ;
}
int mul( int x , int y ) {
return x * y ;
}
void main() {
int (*f)( int , int ) ; // fは2つのintを引数とする関数へのポインタ
f = add ; // f = add( ... ) ; ではないことに注意
printf( "%d¥n" , (*f)( 3 , 4 ) ) ; // 3+4=7
// f( 3 , 4 ) と書いてもいい
f = mul ;
printf( "%d¥n" , (*f)( 3 , 4 ) ) ; // 3*4=12
}
このプログラムでは、関数ポインタの変数 f を定義している。「 int (*f)( int , int ) ; 」 は、“int型の引数を2つ持つ、返り値がint型の関数”へのポインタであり、「 f = add ; 」では、f に加算する関数を覚えている。add に実引数を渡す()がないことに注目。
そして、「 (*f)( 3 , 4 ) ; 」により、実引数を3,4にて f の指し示す add を呼び出し、7 が答えとして求まる。
こういう、関数に「自分で作った関数ポインタ」を渡し、その相手側の関数の中で自分で作った関数を呼び出してもらうテクニックは、コールバックとも呼ばれる。コールバック関数を使うC言語の関数で分かり易い物は、クイックソートを行う qsort() 関数だろう。qsort 関数は、引数にデータを比較するための関数を渡すことで、様々な型のデータの並び替えができる。
#include <stdio.h>
#include <stdlib.h>
// 整数を比較するコールバック関数
int cmp_int( int* a , int* b ) {
return *a - *b ;
}
// 実数を比較するコールバック関数
int cmp_double( double* a , double* b ) {
double ans = *a - *b ;
if ( ans == 0.0 )
return 0 ;
else if ( ans > 0.0 )
return 1 ;
else
return -1 ;
}
// ソート対象の配列
int array_int[ 5 ] = { 123 , 23 , 45 , 11 , 53 } ;
double array_double[ 4 ] = { 1.23 , 12.3 , 32.1 , 3.21 } ;
void main() {
// 整数配列をソート
qsort( array_int , 5 , sizeof( int ) ,
(int(*)(const void*,const void*))cmp_int ) ;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~この分かりにくい型キャストが必要なのがC言語の面倒な所
for( int i = 0 ; i < 5 ; i++ )
printf( "%d\n" , array_int[ i ] ) ;
// 実数配列をソート
qsort( array_double , 4 , sizeof( double ) ,
(int(*)(const void*,const void*))cmp_double ) ;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for( int i = 0 ; i < 5 ; i++ )
printf( "%f\n" , array_double[ i ] ) ;
}
無名関数
コールバック関数を使っていると、データを比較するだけの関数とか簡単な短い処理が使われることが多い。こういった処理を実際に使われる処理と離れた別の場所に記述すると、プログラムが読みづらくなる。この場合には、その場で関数の名前を持たない関数(無名関数)を使用する。(C++の無名関数機能は、最近のC++の文法なのでテストには出さない)
void main() {
int (*f)( int , int ) ; // fは2つのintを引数とする関数へのポインタ
f = []( int x , int y ) { return x + y ; } ; // add を無名関数化
printf( "%d¥n" , (*f)( 3 , 4 ) ) ; // 3+4=7
// mul を無名関数にしてすぐに呼び出す3*4=12
printf( "%d¥n" , []( int x , int y ) { return x * y ; }( 3 , 4 ) ) ;
// メモ:C++11では、ラムダ式=関数オブジェクト
// C++14以降は、変数キャプチャなどの機能が追加されている。
}