関数へのポインタ

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
mkm

関数へのポインタ

#1

投稿記事 by mkm » 5年前

プログラミング初心者です
関数へのポインタの勉強です。

コード:

#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 ()
 {
 puts("+");
 kuku (sum);
 
 puts ("multiply");
 kuku(mul);
 return(0);
}/[code]


追って行くと、途中からわかりません。

kuku(sum) でkuku関数を呼んで引数としてsumを渡す。

kuku()は(*calc)(int,int)となっていますが ここあたりから疑問が多すぎます。

void kukuが実行される前に引数がsumだからsumが実行されるんじゃないんですか?そうなるとsumの引数、x1,x2が宣言されてないから変なことになるんじゃないんですか?
この場合、kukuの引数=sumとなっていて、どう代入されるのかわかりません。

box
記事: 2002
登録日時: 13年前

Re: 関数へのポインタ

#2

投稿記事 by box » 5年前

関数sumは、int型の引数を2つ受け取って、その和を呼び出し元へ返します。
関数mulは、int型の引数を2つ受け取って、その積を呼び出し元へ返します。
関数kukuは、「引数が2つあり、int型の結果を返す関数」へのポインターを引数としています。
関数はメモリー中のどこかにありますから、何らかのアドレス値を持っています。

main関数で、sumを引数として関数kukuを呼び出しています。
このとき何が起きるかというと、「関数kukuを実行しろ!引数はsumだ!」という指令が出ています。
よって、kuku関数の引数であるcalcは、今回はsumのことを指します。
9×9回のループの中で実行するcalcは、実際にはsumです。
mulの場合も同様です。
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

box
記事: 2002
登録日時: 13年前

Re: 関数へのポインタ

#3

投稿記事 by box » 5年前

関数kukuは、「引数が2つあり、int型の結果を返す関数」へのポインターを引数としています。
下記が、より正確です。
関数kukuは、「int型の引数が2つあり、int型の結果を返す関数」へのポインターを引数としています。
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

かずま

Re: 関数へのポインタ

#4

投稿記事 by かずま » 5年前

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;
}

返信

“C言語何でも質問掲示板” へ戻る