web-dev-qa-db-ja.com

関数ポインター定義がアンパサンド「&」またはアスタリスク「*」をいくつでも使用できるのはなぜですか?

なぜ次のように動作しますか?

void foo() {
    cout << "Foo to you too!\n";
};

int main() {
    void (*p1_foo)() = foo;
    void (*p2_foo)() = *foo;
    void (*p3_foo)() = &foo;
    void (*p4_foo)() = *&foo;
    void (*p5_foo)() = &*foo;
    void (*p6_foo)() = **foo;
    void (*p7_foo)() = **********************foo;

    (*p1_foo)();
    (*p2_foo)();
    (*p3_foo)();
    (*p4_foo)();
    (*p5_foo)();
    (*p6_foo)();
    (*p7_foo)();
}
201
Jimmy

これには、これらの演算子の組み合わせすべてが同じように機能することを可能にするいくつかの要素があります。

これらすべてが機能する根本的な理由は、関数(fooなど)が関数へのポインターに暗黙的に変換可能であることです。これがvoid (*p1_foo)() = foo;が機能する理由です:fooは暗黙的にそれ自体へのポインターに変換され、そのポインターは_p1_foo_に割り当てられます。

単項_&_は、関数に適用されると、オブジェクトに適用されるときにオブジェクトのアドレスを生成するように、関数へのポインターを生成します。通常の関数へのポインターの場合、暗黙的な関数から関数へのポインター変換のため、常に冗長です。いずれにせよ、これがvoid (*p3_foo)() = &foo;が機能する理由です。

単項_*_は、関数ポインターに適用されると、オブジェクトへの通常のポインターに適用されたときにポイント先のオブジェクトを生成するように、ポイント先の関数を生成します。

これらのルールは組み合わせることができます。最後から2番目の例_**foo_を考えてみましょう。

  • 最初に、fooが暗黙的にそれ自体へのポインターに変換され、最初の_*_がその関数ポインターに適用されて、関数fooが再び生成されます。
  • 次に、結果は再び暗黙的にそれ自体へのポインターに変換され、2番目の_*_が適用され、再び関数fooが生成されます。
  • 次に、暗黙的に再び関数ポインターに変換され、変数に割り当てられます。

好きなだけ_*_ sを追加できます。結果は常に同じです。 _*_ sが多いほど、陽気になります。

5番目の例_&*foo_も検討できます。

  • まず、fooは暗黙的にそれ自体へのポインターに変換されます。単項_*_が適用され、fooが再び生成されます。
  • 次に、_&_がfooに適用され、変数に割り当てられたfooへのポインターが生成されます。

_&_は関数にのみ適用できますが、関数ポインターに変換された関数には適用できません(もちろん、関数ポインターが変数である場合は、結果はポインターへのポインタです) -a-pointer-to-a-function;たとえば、リストにvoid (**pp_foo)() = &p7_foo;)を追加できます。

これが、_&&foo_が機能しない理由です。_&foo_は関数ではありません。右辺値である関数ポインタです。ただし、これらの式の両方で_&*&*&*&*&*&*foo_は常に右辺値関数ポインターではなく関数に適用されるため、_&******&foo_は、_&_と同様に機能します。

また、関数ポインタを介して呼び出しを行うために、単項_*_を使用する必要がないことに注意してください。 _(*p1_foo)();_と_(p1_foo)();_は、関数から関数へのポインター変換のため、同じ結果になります。

208
James McNellis

Cは単に基礎となるマシンの抽象化であり、これはその抽象化が漏れている場所の1つであることを覚えておくと役立つと思います。

コンピューターの観点から見ると、関数は実行されると他の命令を実行する単なるメモリーアドレスです。したがって、Cの関数自体はアドレスとしてモデル化され、おそらく、関数が指すアドレスと「同じ」であるという設計につながります。

6
madumlao