2012年9月23日日曜日

Callback関数を知らん人がまず理解すべきことのまとめ。

未だにへっぽこプログラマーの私が、「Callback関数なにそれ美味しいの?」状態から、
Callback関数、「それはとっかえひっかえされる関数だお(キリッ」ってなるまでにとった行動のまとめ。
ちなみに私、C言語とPythonくらいしか喋れませんので、今回はC言語に特化した内容でお送りします。

※Webから漁ってきた情報から手っ取り早くまとめたものなので間違いもあると思われます。間違った点はゴリゴリ指摘していただけるとマンモスウレピーです。


Callback関数とは?

■プログラム中で、呼び出し先の関数の実行中に実行されるようにあらかじめ指定しておく関数(IT用語辞典より引用)

初心者が読んでもわけわかめだと思います。いきなり定義が頭に入る奴なんていません。
これはまだマシな方でWikipediaの解説なんてクソイミフです。
アホだと自覚してる私は読む気さえ起きません。

完全理解は無理で、ある程度の理解水準に素早く到達して、
「とりあえず実装できる」状態になりましょう。
その後、徐々に理解を深めていく方が学習は早いです。
ここでは3段階で解説して、「とりあえず実装できる」状態になりましょう。

Callback関数を理解するためには、
1:ポインタの理解、2:関数ポインタの理解、3:callback関数のメリットと実装方法
を学ぶ必要があります。


1:ポインタとは?

C言語入門者を悩ませるポインタです。
まずは軽くポインタって何?ってところから。

ポインタ⇔指す人ってわけで、C言語の入門書では以下の様なコメントが書かれてます。

int *p;    // int型を変数を指すポインタ
int data; // int型の変数
p = &data; // pはdataを指す

1行目:
いつも「指す」って何やねんってなります。
私は、ポインタはアドレスを格納する変数だと思ってます。
だからpはアドレスを格納するための変数です。
指すとか意味分かんないからアドレスを格納するただの変数だと思うのです。

2行目:
int data; って書いた瞬間に、メモリ上でプログラムが使える領域に対して「sizeof(int)分の領域をdataが使います!」って宣言したことになります。
イメージは、「空いてる席を見つけて、これ俺の席!」って宣言した感じ。

3行目:
&dataはその領域の先頭を表します。
&は実体の前にくっつけてアドレスに変換し、 *はアドレスの前にくっつけて実体に変換します。
つまり、ここでは pと&dataはアドレス、*pは実体となります。
ポインタに躓いたときはよくコンパイルエラー吐いたんですが、
簡単な話、代入時に両辺がアドレスか、両辺が実体かをチェックすればいいだけなのです。


2:関数ポインタとは?

次に、本題の関数ポインタに移ります。
先ほどint *p;ってかくと「pさんはint型のアドレスを格納する変数だお!」宣言だって言いました。

そこで私のようなアホはこう考えます。
「関数ポインタ」って、読んで字の如く解釈すると関数のアドレスを格納する変数だろ?
じゃぁそれの真似して、
kansuu *p;って書いたら「pさんはkansuu型のアドレスを格納するお!」ってなるんじゃないかって。
でも残念ながらコンパイラさんはkansuu型?なにそれ?しらんわクソが!ってなります。
そもそも関数って返り値、引数、お名前って要素がありますやん?
なんで名前しか教えてくれへんの?あほちゃう?
コンパイラさんはこんな気持ちになることでしょう。
じゃぁそれら3つを定義したkansuu型を定義してあげよう!

架空の関数kansuuさんが以下のように実装されてたとして~、、、
int kansuu(double hoge){
  return 1;
}
C言語では以下のように定義します。
 int (* p)(double); // 返り値 (* ポインタ変数名)(引数の型)
これ覚えるとこです!とりあえず、ここだけは覚えるとこです!
重要なことだから二回言いました。

で、世間ではこういった仰々しい書き方がうざいからtypedefを先っちょにくっつけて、
typedef int (* FUNC_POINTER)(double);
しちゃってから
FUNC_POINTER p; //
とか書きます。
これで、関数kansuuを指すポインタpが定義されました。
ポインタっつーのはアドレスを格納する変数だから、
p = kansuu; は許されます!

これは以下の理由によります。
Cで定義された関数って、プログラムを実行するとメモリ上のどこかに関数の実装部分を確保します。例えば、 void hogehoge(void)って関数を定義すると、その関数の実装部分のアドレスは hogehogeでアクセスできます。
とりあえず、引数・返り値とか関係なく、関数名が実装部分へのアドレスだと思ってください!

てなわけで、p = kansuu;って書くと、
pはkansuu型のアドレスを格納する変数ポインタで、kansuuはkansuuへのポインタ。
両辺はアドレスを意味するからこの式は正しい。
pにkansuuへのアドレスが格納されたんだな!って解釈になります。
まとめると、関数ポインタは、関数の実装部分へのアドレスを格納するための変数なのです。





3:callback関数のメリットと実装方法

とりあえずサンプルプログラム。





後にくっそ重要なこと3つ。

1.引数、返り値が同じ関数のアドレスだったら関数ポインタはなんでも格納できる
2.関数ポインタが指してる関数は、”ポインタ(引数)”で呼び出せる
3.1、2から関数ポインタに格納されてる関数のアドレスによって関数を呼び分けられる

ってことです。
func1,func2は「とっかえひっかえできる関数」だけどそれだとカッコ悪いから我々はそれを
「コールバック関数」と呼ぶのです。
main関数部ではpが格納してる関数アドレスによってfuncは、関数を呼び分けております。
同じように呼ぶだけで処理を変えられるのはメリットだし、
汎用性という観点でも使える技術です。

※ 当ブログは以下のサイトに移転しました。

ここまで読んで参考になったという方は、ローベル本をおすすめします。
良質かつ簡潔にコールバック関数についてまとまっていました!


例えば、アプリ側の開発者に対して、Aボタン押下時に好きな処理を記述させられるようなSDKを設計したい!!とか言う要望にはもってこいですよね。