関数ポインタ変換


.関数タイプと関数ポインタタイプ
C言語では、関数も1つのタイプであり、関数へのポインタを定義することができます.ポインタ変数のメモリセルにはアドレス値が格納され、関数ポインタには関数のエントリアドレス(.textセグメント)が格納されていることがわかります.簡単な例を見てみましょう.
例23.3.関数ポインタ
#include <stdio.h>

void say_hello(const char *str)
{
	printf("Hello %s/n", str);
}

int main(void)
{
	void (*f)(const char *) = say_hello;
	f("Guys");
	return 0;
}

変数fのタイプ宣言void (*f)(const char *)を分析します.fはまず*号と結合しているので、ポインタです.(*f)の外側は関数プロトタイプのフォーマットであり、パラメータはconst char *であり、戻り値はvoidであるため、fはこのような関数を指すポインタである.一方、say_helloのパラメータはconst char *であり、戻り値はvoidであり、ちょうどこのような関数であるため、fsay_helloを指すことができる.注意、say_helloは1種の関数のタイプで、関数のタイプと配列のタイプは類似して、右の値をして使用する時自動的に関数のポインタのタイプに変換して、だから直接fに与えることができて、もちろんvoid (*f)(const char *) = &say_hello;に書くことができて、関数say_helloを先に住所を取ってからfに与えることができて、自動のタイプの変換を必要としません.
 
上記のf("Guys")のように、直接関数ポインタで関数を呼び出すこともできます.また、*fで指定した関数タイプを取り出してから、関数、すなわち(*f)("Guys")を呼び出すこともできます.関数呼び出し演算子()は、オペランドが関数ポインタであることを要求するため、f("Guys")が最も直接的な書き方であり、say_hello("Guys")または(*f)("Guys")は、関数タイプを自動的に関数ポインタに変換して関数呼び出しを行う.
次に、関数タイプと関数ポインタタイプを区別する例をいくつか挙げます.まず、関数タイプFを定義します.
typedef int F(void);

このタイプの関数はパラメータを持たず、戻り値はintである.では、fgを宣言することができます.
F f, g;

宣言に相当:
int f(void);
int g(void);

次の関数宣言はエラーです.
F h(void);

関数はvoidタイプ、スカラータイプ、構造体、連合体を返すことができますが、関数タイプを返すことも配列タイプを返すこともできません.次の関数の宣言は正しいです.
F *e(void);

関数eは、F *タイプの関数ポインタを返します.e複数のカッコを付けると、同じ意味になります.
F *((e))(void);

しかし、*号も括弧に入れると違います.
int (*fp)(void);

これにより、関数を宣言するのではなく、関数ポインタが宣言されます.fpは、次のように宣言することもできます.
F *fp;

関数ポインタで関数を呼び出すのは、直接呼び出すのと比較してどのようなメリットがありますか?例を研究します.第3節「データ型フラグ」の練習問題1を振り返ると、構造体に1つのタイプフィールドが増えているため、real_partimg_partmagnitudeangleRECTANGULARなどの関数を再実現する必要があります.多分そうでしょう.
double real_part(struct complex_struct z)
{
	if (z.t == RECTANGULAR)
		return z.a;
	else
		return z.a * cos(z.b);
}

現在、タイプフィールドには2つの値があります.POLARif ... else ...は、各関数にif ... else if ... elseが必要です.タイプフィールドに3つの値がある場合は?各関数は、switch ... case ...またはreal_part(z)でなければなりません.このようなメンテナンスコードは理想的ではありません.今、関数ポインタで実装します.
double rect_real_part(struct complex_struct z)
{
	return z.a;
}

double rect_img_part(struct complex_struct z)
{
	return z.b;
}

double rect_magnitude(struct complex_struct z)
{
	return sqrt(z.a * z.a + z.b * z.b);
}

double rect_angle(struct complex_struct z)
{
	double PI = acos(-1.0);

	if (z.a > 0)
		return atan(z.b / z.a);
	else
		return atan(z.b / z.a) + PI;
}

double pol_real_part(struct complex_struct z)
{
	return z.a * cos(z.b);
}

double pol_img_part(struct complex_struct z)
{
	return z.a * sin(z.b);
}

double pol_magnitude(struct complex_struct z)
{
	return z.a;
}

double pol_angle(struct complex_struct z)
{
	return z.b;
}

double (*real_part_tbl[])(struct complex_struct) = { rect_real_part, pol_real_part };
double (*img_part_tbl[])(struct complex_struct) = { rect_img_part, pol_img_part };
double (*magnitude_tbl[])(struct complex_struct) = { rect_magnitude, pol_magnitude };
double (*angle_tbl[])(struct complex_struct) = { rect_angle, pol_angle };

#define real_part(z) real_part_tbl[z.t](z)
#define img_part(z) img_part_tbl[z.t](z)
#define magnitude(z) magnitude_tbl[z.t](z)
#define angle(z) angle_tbl[z.t](z)
z.tが呼び出されると、タイプフィールドreal_part_tblでインデックスを作成し、ポインタ配列if ... else ...から対応する関数ポインタを取り出して呼び出すこともできますが、if ... else ...rect_real_partのような効果を達成することもできます.これらのコードを関数に結合する必要はありません.「低結合、高集約」(Low Coupling,High Cohesion)はプログラム設計の基本原則であり、既存のコードをよりよく多重化し、コードのメンテナンスを容易にすることができる.タイプフィールドpol_real_partに1つの値が追加された場合、新しい関数のセットを追加し、関数ポインタ配列を修正するだけで、元の関数は変更せずに多重化することができる.