関数ポインタ変換
.関数タイプと関数ポインタタイプ
C言語では、関数も1つのタイプであり、関数へのポインタを定義することができます.ポインタ変数のメモリセルにはアドレス値が格納され、関数ポインタには関数のエントリアドレス(
例23.3.関数ポインタ
変数
上記の
次に、関数タイプと関数ポインタタイプを区別する例をいくつか挙げます.まず、関数タイプFを定義します.
このタイプの関数はパラメータを持たず、戻り値は
宣言に相当:
次の関数宣言はエラーです.
関数は
関数
しかし、
これにより、関数を宣言するのではなく、関数ポインタが宣言されます.
関数ポインタで関数を呼び出すのは、直接呼び出すのと比較してどのようなメリットがありますか?例を研究します.第3節「データ型フラグ」の練習問題1を振り返ると、構造体に1つのタイプフィールドが増えているため、
現在、タイプフィールドには2つの値があります.
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
であり、ちょうどこのような関数であるため、f
はsay_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
である.では、f
とg
を宣言することができます.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_part
、img_part
、magnitude
、angle
、RECTANGULAR
などの関数を再実現する必要があります.多分そうでしょう.double real_part(struct complex_struct z)
{
if (z.t == RECTANGULAR)
return z.a;
else
return z.a * cos(z.b);
}
現在、タイプフィールドには2つの値があります.
POLAR
とif ... 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つの値が追加された場合、新しい関数のセットを追加し、関数ポインタ配列を修正するだけで、元の関数は変更せずに多重化することができる.