私はCのポインターをかなりうまく処理しているように感じます。ほとんどの場合、それらを使用して配列を関数に渡します。
しかし、多くの異なるコード例を見て、関数へのポインターを見るのは珍しいことではないことに気づきました。なぜこれが役立つのか、私にはまったくわかりません。関数モデルへのポインターを実装するのに便利または不可欠ないくつかの古典的な例はありますか?
私はCのポインターをかなりうまく処理しているように感じます。ほとんどの場合、それらを使用して配列を関数に渡します。
しかし、多くの異なるコード例を見て、関数へのポインターを見るのは珍しいことではないことに気づきました。なぜこれが役立つのか、私にはまったくわかりません。関数モデルへのポインターを実装するのに便利または不可欠ないくつかの古典的な例はありますか?
qsort
典型的な例は、関数を使用して要素の配列を並べ替えることです。最後の引数は、voidポインターとして渡される、2つの要素を比較するルーチンへの関数ポインターです。
void qsort(void *base,
size_t nmemb,
size_t size,
int(*compar)(const void *, const void *));
ここで関数ポインタを使用することは不可欠です。なぜなら、ソートルーチンは要素を比較する方法を知るのに十分な汎用性を持たせることができないためです。したがって、関数ポインタは本質的に、ソート関数がそれを拡張するために使用する「コールバック」です。
[編集]たとえば、次の構造が定義されているとします。
struct president {
char * fname;
char * lname;
unsigned int number;
};
それらの配列を「number」フィールドでソートしたい場合は、次のように「compar」(コンパレータ)関数を実装できます。
int compare_presidents(const void * v1, const void * v2) {
struct president p1 = * ((struct president *) v1);
struct president p2 = * ((struct president *) v2);
return (p1.number - p2.number); // Compare by number ascending.
}
そして、そのような構造の配列を次のように並べ替えることができます。
qsort(presidents, num_presidents, sizeof(struct president), compare_presidents);
関数へのポインターは、現在私たちがエレガントにコールバックと呼んでいるものを実装する(まあ、説明する)「石のナイフとクマの皮」の方法と考えてください。類推は完全ではありませんが、それがアイデアです。ポインター参照を介して完全に異なる関数を呼び出す、かなり危険な方法をすばやく提供します。
明らかな例の1つはqsort
です。アイテムの比較に使用する関数へのポインターを渡します。並べ替えているデータの種類がわからないため、実際にはその部分を単独で実行することはできません。
もう1つの一般的な例は、数値積分を行う関数です。統合する関数へのポインターを下限と上限とともに渡し、関数を呼び出して、それらの境界の間のさまざまなポイントの値を見つけて、答えを取得します。
例1:5つの異なるソートアルゴリズムがあると想像してください。あらゆる種類の場所で並べ替えるだけですが、すべての場所で5つから選択する必要はありません。代わりに、並べ替えアルゴリズムへのポインターがあります(これらはすべて同じパラメーター、タイプなどを取ります)。アルゴリズムを1回選択し(ポインターを設定)、ポインターを使用してあらゆる場所で並べ替えるだけです。
例2:コールバック...関数はループ内で何かを実行しますが、反復ごとにアプリケーションのフィードバックが必要です。その関数へのコールバックは、引数として渡される関数ポインターです。
非常に一般的な使用法は、イベントハンドラーのコールバック関数です。たとえば、ボタンプレスハンドラーについて考えてみます。button_update()は次のようになります。
void button_update(struct button* button, void* data) {
if (button_pressed(button)) {
button.callback(data);
}
}
この場合、button.callbackは関数ポインターになります。
スレッドの開始は別の例です。POSIXと同様に、C11にはスレッドを開始するためのインターフェースがあります。
typedef int (*thrd_start_t)(void*);
int thrd_create(thrd_t *, thrd_start_t, void *);
ここで、thrd_start_t
関数ポインタは新しいスレッドによって実行される関数を指し、void*
引数はその関数に渡される引数です。
関数ポインタのもう1つの一般的な使用例は、プラグインです。プラグインは、動的にロード可能なライブラリとして実装できます。アプリケーションがプラグインをロードすると、事前定義された関数を呼び出して、アプリケーション関数ポインターを使用して構造体を渡し、プラグイン関数ポインターを使用して構造体を取得できます。これで、プラグインとアプリケーションは互いの関数を呼び出すことができます。
さらに:関数ポインタには、関数シグネチャによって指定された型もあります。[注意深く]特定の関数ポインタ型にキャストすることもできます。
qsortの例では、予期されるコンパレータの署名にconstvoid*が含まれています。
int (*comparator)(const void *, const void *)
。
ただし、特定の(void *ではない)ptr型の比較関数がある場合は、それをqsortの期待される署名に単純にキャストできます。
(int (*)(const void *, const void *))cmpr_specific
同様に、typedefを使用して関数ポインターの署名を定義できるため、親を数える手間が省けます。
typedef int (*MYFUNC_T)(int arg);
MYFUNC_T callptr = myfunc; callptr(10);
、myfuncが具体的である場合int myfunc(int arg)
ここで、それが何として定義されているかを考えてくださいint (*fptr_arr[10])(int (*)(int));
。
さて、typedefはそれを読みやすくします:typedef int (*FUNC_T)(int arg); typedef int (*FUNC2_T)(FUNC_T func);
-関数ポインタ引数を取る関数。
FUNC2_T fptr_arr[10]; fptr_arr[0]=myfunc;
さらに別の例は、いくつかのヘビーデューティーシステムプログラミングまたはリアルタイム組み込みプログラミングです。プロセッサ割り込みベクタテーブルは、関数ポインタの配列として実装できます。これらはアセンブラでそのように実装され、関数テーブルのメモリ位置を指定する(非C標準)方法がある場合はCでも実行できます。