<関数へのポインタ>
関数は変数ではありませんが、プログラム実行時にはあるメモリ領域を占めています。C言語では、メモリ内での関数の場所(アドレス)を指すためのポインタ、すなわち「関数へのポインタ」を使うことができます。
int add(int, int); /* 関数addのプロトタイプ宣言 */
int sub(int, int); /* 関数subのプロトタイプ宣言 */
int main(void) {
int
(* func)(int, int); /*
関数へのポインタfuncの宣言 */
int
n, m, ans;
...
func
= add; /*
関数addのアドレスをfuncに代入 */
ans
= (* func)(n, m); /*
funcが指す関数(add)を呼び出し */
func
= sub; /*
関数subのアドレスをfuncに代入 */
ans
= (* func)(n, m); /*
funcが指す関数(sub)を呼び出し
...
}
/* 関数addの定義 */
int add(int n1, int n2) {
return
n1+n2;
}
/* 関数subの定義 */
int sub(int n1, int n2) {
return
n1-n2;
}
関数へのポインタを宣言する時には、「どのような引数をとり、どのような返却値をかえす関数へのポインタか」を明確にする必要があります。
int
(* func)(int, int);
この例では、funcを「int型の引数を2つとり、int型の返却値を返す関数へのポインタ」として宣言しています。funcには、この型に合致する関数のアドレスを代入することができます。また、代入した関数を呼び出すには、(* func)と書きます。
func
= add;
ans
= (* func)(n, m);
func
= sub;
ans
= (* func)(n, m);
この場合、2行目では関数addが、4行目では関数subが呼び出されます。実は単に、
ans
= func(n, m);
と書いてもいいのですが、funcがポインタ変数であることをはっきりさせるため、(* func)のように書くのが普通です。
<関数へのポインタの配列>
上の例は関数へのポインタの使い方の基本を示すためだけのもので、実際的な意味はありませんが、状況によって異なる関数を呼び出す必要がある時などは、関数へのポインタが役立つケースがあります。
レポート課題の全体構造の例として示したプログラムは、入力に対してswitch文で複数の関数に分岐させていましたが、同じことが「関数へのポインタの配列」をつかうと下の例のようにかけます。
int main(void) {
void (* func[5])(void)={do_load, do_save, do_process, do_display, do_abort};
/* 関数へのポインタの配列funcを宣言し、それぞれの要素を各関数のアドレスで初期化 */
int
op;
....
scanf("%d",
&op);
(*
func[op-1])(); /*
入力された整数値に対応する関数を呼び出し */
...
}
funcの宣言文はややこしい構文ですが、ここではfuncを「引数無し、返却値無しの関数」を指すポインタ5つからなる配列として宣言し、それを5つのユーザー関数のアドレスで初期化しています。呼び出す時は配列の要素を指定して使えばよいわけです。
この例のように分岐の数が少ない時はswitch文で十分なのですが、例えば100個もの関数に分岐させるようなケースだとすると、関数へのポインタを使った方がすっきりするでしょう。
<関数へのポインタを関数の引数として渡す>
#include <stdio.h>
#include <math.h>
#define NFUNC 5
double func_add(double, double,
double(*)(double));
double invert(double);
int main(void) {
double
(* func[NFUNC])(double)={sin, cos, exp, log, invert};
/*
関数へのポインタの配列funcを宣言し、ライブラリ関数やユーザー関数のアドレスで初期化 */
int
n;
double
x1, x2;
printf("Input
two numbers: ");
scanf("%lf%lf",
&x1, &x2); /*
二つの実数値を入力 */
printf("Input
func (0:sin, 1:cos, 2:exp, 3:log, 4:invert): ");
scanf("%d",
&n); /*
演算を指定 */
if
((n>=0) && (n<NFUNC))
/*
入力値と指定された関数(へのポインタ)を引数に関数func_addを呼び出し、結果を表示 */
printf("Sum
= %f\n", func_add(x1, x2, func[n]));
return
0;
}
/* 関数func_add */
/* 引数として指定された関数の、指定された2点での値の和を返す */
double func_add(double x, double
y, double (* f)(double)) {
return
((* f)(x) + (* f)(y));
}
/* 関数invert */
/* 逆数を返す */
double invert(double x) {
return
1/x;
}
この例で、func_addという関数は、引数として渡された任意の関数(ただし、double型の引数をとりdouble型の値を返すもの)の2点での値の和を返します。例えば、x1とx2とsin関数へのポインタを引数にして呼び出すと、sin(x1)+sin(x2)を返す、というわけです。渡す関数は、型さえ一致していれば標準ライブラリ関数でもユーザー関数でも構いません。
この方法を使うと、ある処理をするための関数を、汎用性の高いものにすることができます。数値演算のためのプログラムでも、例えば数値積分をする関数を作るときに、「sin関数を積分する関数」、「log関数を積分する関数」などとバラバラにせず、「任意の関数を積分する関数」として書くことができることになります。
例として、任意のデータを任意の基準でクイックソートを行う関数qsort(標準ライブラリに備わっている)があります。qsort関数は、ソートすべきデータの配列、要素数、要素のサイズとともに、「二つの要素の大小関係」を示す「比較関数」を引数として渡して、呼び出します。
#include <stdlib.h>
void qsort(void *base, size_t n,
size_t width, int (*fcmp)(void *e1, void *e2));
比較関数は、2つの任意の型(変数、構造体など何でも良い)のポインタ(void *e1, void *e2)を引数として受け取り、その2つの大小関係を判断した結果を、以下のようにint型整数として返す関数として作成します。
*e1 > *e2 なら正の値
*e1 = *e2 なら0
*e1 < *e2 なら負の値
例えば、独自に定義した構造体の複数のオブジェクトを、別々の基準で比較してソートすることもできます。
typedef strunct test_rec { /* 学生の各科目の試験の点を記録するための構造体 */
char
name[20]; /*
学生の名前 */
int
math; /*
数学の点数 */
int
eng; /*
英語の点数 */
int
phys; /*
物理の点数 */
int
chem; /*
化学の点数 */
} RECORD;
...
int cmp_math(const void *s1, const
void *s2) { /*
数学の点の比較関数 */
return
((RECORD *)s2)->math - ((RECORD *)s1)->math;
}
int cmp_eng(const void *s1, const
void *s2) { /*
英語の点の比較関数 */
return
((RECORD *)s2)->eng - ((RECORD *)s1)->eng ;
}
int cmp_phys(const void *s1, const
void *s2) { /*
物理の点の比較関数 */
return
((RECORD *)s2)->phys - ((RECORD *)s1)->phys;
}
int cmp_chem(const void *s1, const
void *s2) { /*
化学の点の比較関数 */
return
((RECORD *)s2)->chem - ((RECORD *)s1)->chem;
}
比較関数の仮引数はvoid *にして、関数内で使う時に適切なキャストをしていることに注意して下さい。
このような比較関数を準備して、これらをqsortの引数として渡せば、RECORD構造体オブジェクトの配列studentsの各要素を、それぞれの科目の点数でソートすることができます。
int main(void) {
RECORD
students[NSTUDENTS];
/*
データ入力 */
...
/*
qsort関数を使って数学の点でソート */
qsort(students,
NSTUDENTS, sizeof(RECORD), cmp_math);
...
/*
qsort関数を使って英語の点でソート */
qsort(students,
NSTUDENTS, sizeof(RECORD), cmp_eng);
...
}
qsort関数を用いたプログラムは、【課題4−3】の解答例にもあります。