sum の後に、関数呼出し演算子 ( ) があると、
それは関数呼び出しになり、値は関数の実行結果になります。
sum の後に、関数呼出し演算子 ( ) がないと、
その値は関数のアドレス(型は関数へのポインタ)になります。
関数は実行されません。
関数呼び出し演算子の ( ) の中には、関数の定義に応じて
0個以上の引数(値を持つ式)を書くことになります
関数 kuku の引数 calc は関数へのポインタです。
kuku(sum) で関数 kuku を呼び出すと、
calc は、関数 sum のアドレスで初期化されます。
(*calc)(i,j) という式で、*calc はポインタの指すものなので
関数になり、その後に関数呼び出し演算子 (i,j) があるので、
ポインタ calc の指す関数を実行し、結果の値を得ます。
calc が sum のアドレスを持っていると、sum(i,j) を実行します。
JIS X3010:2003 プログラム言語C (ISO/IEC 9899:1999)
コード:
6.3.2.1 左辺値,配列及び関数指示子
...
関数指示子(function designator)は,関数型をもつ式とする。
関数指示子が sizeof 演算子又は単項&演算子のオペランドである場合を
除いて,型“~型を返す関数”をもつ関数指示子は,型“~型を返す関数
へのポインタ”をもつ式に変換する。
関数は、sizeof と単項& の後である場合以外は、常に「関数へのポインタ」
に変換されるのです。
だから、関数へのポインタを引数として要求する kuku を呼び出すとき、
kuku(&sum) と書かなくてよいのです。
でも、kuku(&sum) と書いても構いません。
単項& の後では sum は「関数へのポインタ」になりません。
&sum で「関数へのポインタ」になります。
コード:
6.5.2 後置演算子
構文規則
後置式:
一次式
後置式 [ 式 ]
後置式 ( 実引数式並びopt )
後置式 . 識別子
後置式 -> 識別子
後置式 ++
後置式 --
( 型名 ) { 初期化子並び }
( 型名 ) { 初期化子並び , }
実引数式並び:
代入式
実引数式並び , 代入式
「後置式 ( 実引数式並びopt )」は、関数呼出しです。
コード:
6.5.2.2 関数呼出し
制約 呼び出される関数を表す式は,void を返す関数へのポインタ型,
又は配列型以外のオブジェクト型を返す関数へのポインタ型を
もたなければならない。
後置演算子である関数呼び出し演算子「( 実引数式並びopt )」の前は
「関数へのポインタ」でないといけないのです。
「関数」を書いてもよいのは、「関数」が常に「関数へのポインタ」に
変換されるからです。
ということは、(*calc)(i,j) は、calc(i,j) と書いてもよいのです。
コード:
6.7.5.3 関数宣言子 (関数原型を含む)
...
仮引数を“~型を返却する関数”とする宣言は,6.3.2.1 の規定に従い,
“~型を返却する関数へのポインタ”に型調整する。
これにより、void kuku(int calc(int, int)) と書くこともできます。
ということで、元のプログラムは次のようにも書けます。
コード:
#include <stdio.h>
int sum(int x1, int x2)
{
return x1 + x2;
}
int mul(int x1, int x2)
{
return x1 * x2;
}
void kuku(int calc(int, int))
{
int i, j;
for (i = 1; i <= 9; i++) {
for (j = 1; j <= 9; j++)
printf("%3d", calc(i, j));
putchar('\n');
}
}
int main(void)
{
puts("+");
kuku(sum);
puts("multiply");
kuku(mul);
return 0;
}