関数ポインタとコールバック関数
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 ) ; 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++の文法なのでテストには出さない)
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以降は、変数キャプチャなどの機能が追加されている。 }
演習(ハッシュ法)
ハッシュ法のプログラム(オープンアドレス法もしくはチェイン法)を用いて、
(1)名前と電話番号,(2)名前と住所,(3)名前と誕生日について、名前をキーとして検索するプログラムを作成せよ。
原則として「出席番号 % 3 + 1」の番号のテーマに取り組むこと。
レポートを作成する際には、ハッシュ関数を変更してどういった変化があるか確認せよ。
ハッシュサイズは、10〜20件程度で良い。