第9章 関数ポインタ

 関数もアドレスが取れると言いました。アドレスはただの数値です。となると関数のアドレスを入れることのできる変数があってもいいじゃないですか。今回はそういう変数についてのお話です。


 今回の要点...はちょっと終わりのみにします。推測と試行錯誤によって文法を学んでいくという「独学スタイル」に触れ、分からないことがあったときに自分の力だけで解決するという力をつけてみましょう。

 では、いってみましょう。


 今回は関数のアドレスを入れられる変数、関数ポインタのお話です。

 さて、ポインタ型は参照先の型に * をつけたものとなります。しかし、今回の参照先は関数なのです。「参照先の型」というのはどうなるのでしょうか?

 ...ちょっと違う観点から考えてみましょう。ポインタの宣言は、変数の宣言文で変数名の頭に * につけるというものでした。もしかしたら忘れている人もいるかもしれませんが、

char* p1, p2;

では p2 はポインタにはなりません。

 これと同じように考えてみましょう。つまり、関数のプロトタイプで、関数名の頭に * をつけてみてはどうでしょうか? 仮引数名はいらなそうなので、そこまでは書かないでよさそうです。プロトタイプでも書かなくて構いませんしね。

double *fpFunc(double, double);

 ...よく考えたら、これだと戻り値の型が double* の関数 fpFunc のプロトタイプになってしまいますね。

 * が戻り値の型の情報として利用されるからいけないのです。ということは、カッコでもつけてやって、int と * を分断してしまえばどうなんでしょうか?

double (*fpFunc)(double, double);

 う〜ん、なんとも不気味な文ですね。こんなので本当にいいんでしょうか?


 これが本当にいいかはちょっと置いておいて、さらに配列関数ポインタにも挑戦してみましょう。大胆ですね...。

 配列変数の宣言は、変数名の後ろに [ ] をつけるというものでした。なら、配列関数ポインタも同じようにすればできるのではないでしょうか?

double (*afpFunc[4])(double, double);

 う〜ん、さらに不気味な文になってしまいました。だんだん心配になってきました。


 そして、これも本当にいいかは置いておいて、今度は関数ポインタを使う方を考えてみましょう。

 第1部第31章で関数のアドレスは関数名で取得できると言いました。なので fpFunc が正しい関数ポインタとすると、代入は

fpFunc = Func;

で良さそうです。では、このポインタを使って Func 関数を呼ぶにはどうすればいいのでしょうか?

 配列で要素にアクセスするとき、実は <配列名>[インデックス] ではなく <アドレス>[インデックス] であると言いました。

 もしかしたら、関数でも同じことが言えるのかもしれません。つまり、関数の呼び出しは <関数名>(<実引数リスト>) ではなく、実はこれも <アドレス>(<実引数リスト>) なのではないでしょうか?


 では、これらの推測があっているのか、プログラムを組んで試してみましょう。

 そうですね。演算子と2つの値を入力させて、計算結果を表示するプログラムなんかがいいでしょうね。普通は各演算を switch 文で分岐してやるのでしょうが、ここは配列関数ポインタなんかを使ってみましょう。これができたら、何かかっこいいですね。

 もうこうなったら、何でもやってみましょう。エラーが出たらそのときはそのときですよ。この配列関数ポインタは一旦初期化した以降は全く変更しないので、変えられないように const をつけておきましょう。ポインタ自体の値を変更させないのですから、* の後に const をつけるのでしたね。

プログラム実行結果
// FuncPtr1.cpp
#include <iostream.h>

double Add(double a, double b){ return a + b; }
double Sub(double a, double b){ return a - b; }
double Mul(double a, double b){ return a * b; }
double Div(double a, double b){ return a / b; }

double (* const afpOps[])(double, double) = {
    Add, Sub, Mul, Div,
};

enum EOperator
{
    ADD, SUB, MUL, DIV,
};

bool InputOp(EOperator& rfOp)
{
    char letter;

    cout << "演算子を指定して下さい > " << flush;
    cin >> letter;

    switch(letter)
    {
    case '+': rfOp = ADD; break;
    case '-': rfOp = SUB; break;
    case '*': rfOp = MUL; break;
    case '/': rfOp = DIV; break;
    default :      return false;
    }

    return true;
}

void InputVals(double& ra, double& rb)
{
    cout << "2つの数値を入力して下さい > " << flush;
    cin >> ra >> rb;
}

int main()
{
    static const char* s_apszOps[] = {
        " + ", " - ", " * ", " / ",
    };
    EOperator fOp;
    double     a, b;

    while(InputOp(fOp))
    {
        InputVals(a, b);

        cout << a << s_apszOps[fOp] << b << " = "
             << afpOps[fOp](a, b) << endl;
    }

    cout << "終了します。" << endl;
    return 0;
}
演算子を指定して下さい > +
2つの数値を入力して下さい > 1.2 2.4
1.2 + 2.4 = 3.6
演算子を指定して下さい > -
2つの数値を入力して下さい > 3.45 2.34
3.45 - 2.34 = 1.11
演算子を指定して下さい > *
2つの数値を入力して下さい > 1.23 2
1.23 * 2 = 2.46
演算子を指定して下さい > /
2つの数値を入力して下さい > 6.28 2
6.28 / 2 = 3.14
演算子を指定して下さい > %
終了します。

 何と、あんないい加減に考えた推測が当たっています。動作には何の問題もありません。

 所詮は同じ人間の考えたこと。訳の分からない超越言語ではなかったんですね。怖がる必要など何もないのです。

 いろいろな言語の機能がありますが、それらも人間にとって分かりやすいような形になってくれているはずです。こういう風に動作の意味、文法の意味を考えるクセをつけておけば、プログラミング言語も結構簡単に理解できるということです。


 最後に、この関数ポインタの型についての話で終わりにします。

 関数ポインタの「型」はどうなるか、それはやはり簡単。変数の宣言の変数名をなくすと変数の型になるように、関数ポインタの宣言の変数名をなくすと関数ポインタの型になります。

 例えば、

double (*fpFunc)(double);

の型は double(*)(double) であり、

void (*fpFunc)(const char*, int);

の型は void (*)(const char*, int) なのです。

 このように、関数のポインタ型には戻り値の型と引数の型、そしてその個数、順番の全てが必要なのです。これらの異なる関数のアドレスを代入しようとしてもエラーが出てしまいます。

 よくよく考えてみれば、戻り値や引数の型、個数、順番がなければ、関数を呼ぶときに困りますよね。それに、引数の異なる関数なんかを代入できるとしたら、その関数ポインタを使って関数を呼ぶとき危険ですね。これも、当たり前の話なのです。


 また、関数ポインタの型ははっきり言って鬱陶しいです。なので、その型をよく使う時、その型に見た目の意味を持たせたい時などには、typedef してしまうといいでしょう

typedef double (*FPOP2)(double, double);  // 2項演算関数の型

const FPOP2 afpOps[] = {
    ADD, SUB, MUL, DIV,
};

 今回はこれで終わりです。関数ポインタは好きなので、その思い入れが出た章になったかもしれません。

 では、今回の要点です。


 次回も似たようなことをしたいと思います。それでは、また。


第8章 アドレスを返す関数 | 第10章 続・ポインタ天国

Last update was done on 2000.8.5

この講座の著作権はロベールが保有しています