関数ポインタ
関数にも番地がある
ここまでポインタは、変数や確保した領域を指すものとして扱ってきました。関数も機械語としてメモリに置かれているので、やはり先頭の番地を持っています。配列名が先頭要素の番地になったのと同じように、printf や自作の関数も、名前だけ書くとその番地になります。
その番地を入れておく変数が関数ポインタです。
void print_plain(const Student *s);
void (*style)(const Student *) = print_plain;読み方は内側からです。(*style) で「style は何かを指すポインタ」、その外側の (const Student *) と先頭の void で「指す先は const Student * を 1 つ受け取って何も返さない関数」となります。style のまわりのカッコは外せません。外して void *style(const Student *) と書くと、「void * を返す関数の宣言」という別物になります。
呼ぶときは普通の関数と同じ
代入したあとは、普通の関数名のように書けば呼べます。
style(&list[i]);古い書き方の (*style)(&list[i]) も同じ意味で通ります。今は前者が普通です。
何がうれしいか
一覧を表示する処理を考えてください。素の一行で出す形と、点数を星の本数で出す形の 2 通りが欲しくなったとします。関数ポインタが無いと、表示のしかたを if で分けた走査ループを 2 つ書くか、ループの中に if を毎回置くことになります。表示のしかたが 3 つ 4 つと増えるたびに、走査のコードを触ることになります。
関数ポインタを使うと、走査は 1 つで済みます。
void print_all(const Student *list, int n, void (*style)(const Student *)) {
for (int i = 0; i < n; i++) {
style(&list[i]);
}
}print_all は「1 人ぶんをどう出すか」を知りません。知らなくてよいことを知らないまま書けるので、表示の種類が増えても print_all は一文字も変わりません。呼ぶ側が渡すものを差し替えるだけです。
print_all(list, 3, print_plain);
print_all(list, 3, print_bar);「処理そのものを引数として渡す」という考え方は、C に限った話ではありません。他の言語でコールバックや高階関数と呼ばれているものと、やっていることは同じです。次回使う標準ライブラリの qsort も、並べ替えの手順だけを持っていて、大小の決め方はこちらが関数で渡す、という作りになっています。
要件
- print_all の第3引数は void (*)(const Student *) 型の関数ポインタにする
- print_all の中では if で表示を分けず、受け取った関数を呼ぶだけにする
- 入力が 2 なら print_bar、それ以外なら print_plain を渡す
入出力例
main("1") → "佐藤 80
鈴木 95
田中 72"
main("2") → "佐藤 ********
鈴木 *********
田中 *******"