【C】——コールバック関数のメリット


ポインタへの応用はC言語プログラミングの精髄であり、コールバック関数はC言語における関数ポインタへの高度な応用である.簡単に言えば、コールバック関数は、関数ポインタによって呼び出される関数です.関数ポインタ(関数のエントリアドレス)を別の関数に渡すと、この関数ポインタが指す関数を呼び出すために使用される場合、この関数はコールバック関数だと言います.なぜコールバック関数を使用するのでしょうか.まず、小さな例を見てみましょう.
 1         Node * Search_List (Node * node, const int value)
 2         {
 3                 while (node != NULL)
 4                 {
 5                         if (node -> value == value)
 6                         {
 7                                 break;
 8                         }
 9                         node = node -> next;
10                 }
11                 return node;
12         }

この関数は、一方向チェーンテーブルで指定した値を検索し、この値を保存するノードを返すために使用します.そのパラメータは、このチェーンテーブルの最初のノードを指すポインタと、検索する値です.この関数は簡単に見えますが、整数のチェーンテーブルにしか適用できません.文字列チェーンテーブルを検索すると、もう1つの関数を書かなければなりません.実際には、ほとんどのコードは現在の関数と同じですが、2番目のパラメータのタイプと比較方法が違います.
実際には、タイプに関係なく検索関数を使用することを望んでいます.これにより、任意のタイプの値が格納されているチェーンテーブルを検索することができます.そのため、比較方法を変更する必要があります.コールバック関数を使用すると、この目的を達成できます.同じタイプの2つの値を比較するための関数(コールバック関数)を作成し、この関数を指すポインタをパラメータとして検索関数に渡し、検索関数はこの比較関数を呼び出して比較を実行します.この方法では、どのタイプの価値も比較できます.
また、値自体ではなく、比較対象の値を指すポインタ、すなわちvoid*タイプのパラメータを検索関数に渡す必要があります.このポインタはコールバック関数に渡され、最終的な比較を行います.このような変更により、任意のタイプを指すポインタを検索関数に渡すことができ、任意のタイプの比較を完了することができます.これがポインタのメリットであり、文字列、配列、または構造体をパラメータとして関数に渡すことはできませんが、それらを指すポインタはできます.
検索関数は次のように実現されます.
 1         NODE *Search_List(NODE *node, int (*compare)(void const *, void const *) , \
 2         void const *desired_value);
 3         {
 4                 while (node != NULL)
 5                 {
 6                         if (compare((node->value_address), desired_value) == 0)
 7                         {
 8                                 break;
 9                         }
10                         node = node->next;
11                 }
12                 return node;
13         }

 
ユーザは関数ポインタを検索関数に渡し、後者はこの関数をコールバックすることがわかります.
ここで、チェーンテーブルノードは次のように定義されています.
1 typedef struct list
2         {
3                 void *value_address;
4                 struct list *next;
5         }NODE;

 
このように、NODE*タイプのポインタを任意のタイプのデータを格納するチェーンテーブルノードに向けることができるように定義される.そしてvalue_addressは特定のデータを指すポインタであり、void*として定義され、未知のタイプを指すポインタを表し、チェーンテーブルに任意のタイプのデータを格納することができ、検索関数Search_に渡されます.リストの最初のパラメータは、NODE*と統一的に表すことができます.そうでなければ、異なるデータ型を格納するチェーンテーブルに適応するために、検索関数を別々に書く必要があります.
実際の比較を行わないため、検索関数はタイプに関係なく作成する必要があります.これは、呼び出し元がチェーンテーブルに含まれる値のタイプを知っているため、異なるタイプの値をそれぞれ含むチェーンテーブルをいくつか作成すると、容易に実現できます.各タイプに対して比較関数を作成すると、すべてのタイプのチェーンテーブルに単一の検索関数が作用します.
次は、チェーンテーブル全体を検索する比較関数です.
注意強制タイプ変換では、比較関数のパラメータをvoid*と宣言して検索関数のプロトタイプに一致させ、強制的に(int*)タイプに変換して比較整数に使用する必要があります.
 1         int int_compare(void const *a, void const *b)
 2         {
 3                 if (*(int *)a == *(int *)b)
 4                 {
 5                         return 0;
 6                 }
 7                 else
 8                 {
 9                         return -1;
10                 }
11         }

 
この関数は次のように使用できます.
desired_node = Search_List(root, int_compare, &desired_int_value);
文字列チェーンテーブルで検索する場合は、次のコードでタスクを完了できます.
desired_node = Search_List(root, strcmp, “abcdefg”);
ちょうどライブラリ関数strcmpが実行する比較は、void const*ではなくconst char*として宣言されるため、gccは警告メッセージを発行します.
上記の例はコールバック関数の基本原理と用法を示し,コールバック関数の応用は非常に広範である.通常、統合インタフェースで異なるコンテンツを実装したい場合は、コールバック関数で実装するのが適切です.いつでも、作成した関数が異なる時点で異なるタイプの作業を実行したり、関数呼び出し者によってしか定義できない作業を実行したりしなければならない場合は、コールバック関数を使用して実現することができます.多くのウィンドウシステムでは、マウスをドラッグしたりボタンをクリックしたりして、呼び出しユーザプログラムの特定の関数を指定するなど、コールバック関数を使用して複数のアクションを接続しています.