C言語:関数ポインタの型

三日坊主になるところだったので、久々に記事を書いてみる。

関数ポインタ

関数ポインタはC言語の最後の砦、使いこなせば圧倒的に便利になる機能です。ところが、C言語の型宣言、とくに関数ポインタは変な書き方をします。今回はこの関数ポインタの書き方についてです。

関数ポインタの型宣言のおさらい

C言語で関数ポインタを受け取る関数と言えば、qsort()が有名ですね。それではqsort()の宣言を見てみましょう。

void	 qsort(void *buffer, size_t count, size_t size, int (*comp)(const void *, const void *));

一番最後の引数compが、関数ポインタの宣言です。これは

int comp(const void *a, const void *b);

という型の関数ポインタを受け取ることを表します。

特徴的なのは、変数compの位置がおかしいことです。

関数ポインタを返す関数

変数名の場所がおかしいので、関数ポインタを返す関数は、非常に書きづらいものになります。でも書いてみましょう。
intを受け取り、char *を受け取り何も返さない(i.e. voidを返す)関数を返す関数はこうなります(←読みづらい・・・

void (*f1(int x))(char *);

お分かり頂けただろうか?

なぜこんな形になるのか

まず最初に、関数の宣言と、変数の宣言を比べてみましょう

戻り値の型 関数名(引数リスト); // 関数
変数の型 変数名; // 変数

ちょっと似てますね。変数名に引数リストを付けたものが、関数名に見えませんか?
ここで、戻り値の型を使って関数を宣言している。という風に考えてみましょう。

さて、関数ポインタの変数の型は

戻り値の型 (*変数名)(引数リスト);

となります。では、これらを組み合わせてみましょう。上の例で変数名としたところを、関数名に書き換えてみます。

戻り値の型 (*関数名(引数リスト))(引数リスト);

前のリストは、関数ポインタを返す関数の引数リスト、後のリストは、返された関数ポインタに与える引数リストです。

これをもうちょっと応用してみましょう。

関数ポインタの配列の宣言

配列の宣言は、

要素の型 配列名[要素数];

でしたよね。それでは、関数ポインタを要素とする配列 を宣言してみましょう。
さっきと同じ考えで、変数名の部分を配列っぽく書きます。

戻り値の型 (*配列名[要素数])(引数リスト);

これは結構効きます。例えば以下のようなスイッチ文を

switch (i) {
case 0:
  printf("%lf\n", sin(x));
  break;
case 1:
  printf("%lf\n", cos(x));
  break;
case 2:
  printf("%lf\n", tan(x));
  break;
}

関数ポインタの配列を用意すれば、

double (*funcs[])(double) = { sin, cos, tan };

printf("%lf\n", funcs[i](x));

と書き換えることができます。結構有名かも?



※結構適当に書いています。間違いがあったらご一報下さい