- コールバック関数って一体何なんだろう?
- 色んなサイトで解説されてるけど、説明が複雑すぎて理解出来ない。
- もっとシンプルかつ簡単に説明してほしい!
本記事ではサンプルコードを示しつつ、可能な限り簡単に解説し、初心者でも理解できるような説明をします。
基本的にはC言語ベースで解説を行いますが、非常にシンプルなコードを例にしつつ説明しますので、他の言語をやられている方でも理解できるかと思います。
まずはポインタの説明(C言語ユーザー以外は飛ばしてもOK)
変数のポインタについて
まずはこちらのサンプルプログラムをご覧ください。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <stdio.h> int main(void){ char abc = 100; char *v_ptr; v_ptr = &abc; printf("%d\n", *v_ptr); return 0; } |
このプログラムの実行結果は以下の通りです。
1 |
100 |
プログラム的な仕組みを解説すると、ポインタ変数v_ptrに変数abcのアドレスを格納します。
そこでv_ptrに「*」をつけるとアドレスに格納されている値を見に行けるので、100という数値が表示されるのです。
関数のポインタについて
今度は関数のポインタについて解説します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <stdio.h> void func(void){ printf("Hello World"); } int main(void){ void (*f_ptr)(void); //ポインタ関数f_ptrを宣言 f_ptr = &func; //f_ptrに関数funcのアドレスを格納する f_ptr(); //ポインタの指し示す関数funcの処理を実施する return 0; } |
実行結果は以下の通り。
1 |
Hello World |
変数ポインタが特定の変数のアドレスを格納し、そこで「*」をつければ値を参照できるのと同様、関数もポインタを使って同じことが出来ます。
図では便宜上f_ptrで関数funcの処理を行う際「*」が必要かのような書き方をしましたが、実際のプログラムでは「*」は不要です。(サンプルプログラムを参照のこと)
コールバック関数について
コールバック関数を簡単に説明すると
コールする関数の引数に関数アドレスを渡すことで、コール先で関数を実施する
というものです。
なんだか言葉の説明だけだと分かりづらいのでサンプルコードを見ていきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <stdio.h> void calc_func(char *abc){ (*abc)++; } int main(void){ char a = 0; calc_func(&a); return 0; } |
こちらはmain関数の中でcalc_funcを呼ぶことで変数aをインクリメントしています。
さてこの関数calc_funcや変数aですが、Windowsでソフトウェア開発をしているときは問題ないのですが、電子工作などでディスプレイがない状態ですと本当に処理が実施されているか分かりません。
そこでcalc_funcに関数の引数を設けます。
1 2 3 4 5 6 7 |
void calc_func(char *abc, void (*cbk_func)(void)){ (*abc)++; cbk_func(); } |
これでcalc_funcが関数コールされた際に、引数として別の関数がセットされるとcalc_funcの中でその関数が使えるようになるのです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <stdio.h> void calc_func(char *abc, void (*cbk_func)(void)){ (*abc)++; cbk_func(); } void ledon(void){ //LEDを光らせる処理(コードは割愛) } int main(void){ char a = 0; calc_func(&a, ledon); //関数を引数に指定するときは「&」は不要 return 0; } |
ここでledonというLEDを光らせる処理を追加しました。
もし電子工作をやっていてディスプレイが無いような環境の場合、calc_funcにledonの関数を渡すことで処理が正しく進んでいれば最後にLEDが光るはずです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#include <stdio.h> void calc_func(char *abc, void (*cbk_func)(void)){ (*abc)++; cbk_func(); } void ledon(void){ //LEDを光らせる処理(コードは割愛) } void buzzer(void){ //ブザーを鳴らす処理(コードは割愛) } void motor(void){ //モーターを動かす処理(コードは割愛) } int main(void){ char a = 0; calc_func(&a, ledon); //calc_funcの最後にLEDを光らせる calc_func(&a, buzzer);//calc_funcの最後にブザーを鳴らす calc_func(&a, motor); //calc_funcの最後にモーターを駆動させる return 0; } |
今度はブザーを鳴らしたり、モーターを駆動させる処理を追加しました。
calc_func側で関数を受ける構えができていると、状況に応じてLEDを光らせたり、ブザーを鳴らしたり、モーターを駆動させるということが出来ます。このようにコールバック関数は「コールする関数の引数に関数アドレスを渡すことで、コール先で関数を実施する」ことができるのです。
関数を受ける構えができているとそこに様々な関数を渡せるので、多様な処理を相手の関数の中で行うことができるんですね。
最後に:C言語の曖昧さについて
- なぜ関数を引数として渡すときは「&」は不要なの?(変数のときは「&」が必要なのに)
- なぜ関数ポインタで処理を実施するときは「*」は不要なの(変数のときは「*」が必要なのに)
今回の解説を読んで途中で疑問を持たれかと思います。
これには答えがなくて「C言語はそういうものだから」というのがひとまずの結論となります。
C言語というのはもともと少人数が自分たちの作業を楽にするために開発されたプログラミング言語であり、行き当たりばったりで都度拡張を行ってきたためルールが曖昧なところがあります。
始めのうちは慣れないかもしれませんが「そういうもの」として諦めて覚えていきましょう。