関数ポインタと関数テーブルは、現場でのコードにはよく出てきますが、C言語の入門書には載っていないことが多いです。
そのため、この記事では関数ポインタと関数テーブルについて詳しく説明していきます!
【前提知識】関数にもアドレスが存在する
※タイトルを見て意味が分かる人は飛ばしてください
変数はアドレスが存在しており、ポインタでよくアドレスを指定しますよね。
実は変数にアドレスが存在するように、関数自体にもアドレスが存在しています。
試しに以下のソースコードを書いて、アドレスを表示させてみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
#include<stdio.h> /* 関数のプロトタイプ宣言 */ int SumFunc(int x, int y); int SubtractionFunc(int x, int y); int MultiplicationFunc(int x, int y); int DivisionFunc(int x, int y); int main() { printf("SumFuncのアドレス...%p\n",SumFunc); printf("SubtractionFuncのアドレス...%p\n",SubtractionFunc); printf("MultiplicationFuncのアドレス...%p\n",MultiplicationFunc); printf("DivisionFuncのアドレス...%p\n",DivisionFunc); return 0; } /* 足し算の関数 */ int SumFunc(int x, int y) { return (x + y); } /* 引き算の関数 */ int SubtractionFunc(int x, int y) { return (x - y); } /* かけ算の関数 */ int MultiplicationFunc(int x, int y) { return (x * y); } /* 割り算の関数 */ int DivisionFunc(int x, int y) { return (x / y); } |
ぼくの環境だと、以下のようなアドレスが表示されました。
1 2 3 4 |
SumFuncのアドレス...0x7f282b0006b5 SubtractionFuncのアドレス...0x7f282b0006c9 MultiplicationFuncのアドレス...0x7f282b0006db DivisionFuncのアドレス...0x7f282b0006ee |
ところで、ポインタの変数にアドレスを指定すれば、ポインタの変数経由で値を変えたり参照することができましたよね。
以下のサンプルコードだと、ポインタの変数ptrが変数aaaのアドレスを持っているため、ptrの値を変えることでaaaの値を変えることができました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include<stdio.h> int main() { int aaa; int* ptr; /* 変数aaaに10を代入 */ aaa = 10; /* 変数aaaのアドレスをptrに代入 */ ptr = &aaa; /* ptrの参照先を77を代入 */ *ptr = 77; printf("aaa...%d\n",aaa); printf("ptr...%d\n",*ptr); return 0; } |
1 2 |
aaa...77 ptr...77 |
関数ポインタも、同じような考え方です。
関数のアドレスを格納する変数(ポインタ)を作って関数のアドレスを格納すれば、変数(ポインタ)を呼ぶことで関数が実施されそうですよね。
では実際に関数のアドレスを入れる変数を作成して、実行してみましょう!
関数ポインタの書き方について
関数ポインタ(関数のアドレスを入れる変数)の作成方法ですが、ちょっとややこしくて、以下のように書きます。
戻り値の型 (*ポインタの変数名)(引数)
これだけ見ても、パッと分かりにくいと思うので、まずはサンプルコードを見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
#include<stdio.h> /* 関数のプロトタイプ宣言 */ int SumFunc(int x, int y); int SubtractionFunc(int x, int y); int MultiplicationFunc(int x, int y); int DivisionFunc(int x, int y); int main() { int (*ptr_func)(int, int); /* 関数のアドレスを格納する変数 */ int answer; /* 演算結果 */ /* 足し算の関数のアドレスを代入 */ ptr_func = SumFunc; /* 関数をコール */ answer = ptr_func(10,2); printf("answer...%d\n",answer); /* 引き算の関数のアドレスを代入 */ ptr_func = SubtractionFunc; /* 関数をコール */ answer = ptr_func(10,2); printf("answer...%d\n",answer); /* かけ算の関数のアドレスを代入 */ ptr_func = MultiplicationFunc; /* 関数をコール */ answer = ptr_func(10,2); printf("answer...%d\n",answer); /* 割り算の関数のアドレスを代入 */ ptr_func = DivisionFunc; /* 関数をコール */ answer = ptr_func(10,2); printf("answer...%d\n",answer); return 0; } /* 足し算の関数 */ int SumFunc(int x, int y) { return (x + y); } /* 引き算の関数 */ int SubtractionFunc(int x, int y) { return (x - y); } /* かけ算の関数 */ int MultiplicationFunc(int x, int y) { return (x * y); } /* 割り算の関数 */ int DivisionFunc(int x, int y) { return (x / y); } |
1 2 3 4 |
answer...12 answer...8 answer...20 answer...5 |
このように関数のポインタにアドレスを入れることで、直接関数を呼ばなくても関数を実行することができます。
では、少しずつ解説していきますね!
手順1. 関数ポインタ(関数のアドレスを代入する変数)を作る
まずは、関数ポインタ(関数のアドレスを代入する変数)を定義する必要があります。
定義の方法は、先ほど説明した通り、以下のような形で作ります。
戻り値の型 (*ポインタの変数名)(引数)
先ほどのサンプルコードだと、以下の部分ですね。
1 |
int (*ptr_func)(int, int); /* 関数のアドレスを格納する変数 */ |
int型の戻り値を返す関数のアドレスを代入する予定なので、まずはint型を指定します。
その後のptr_funcというのが変数名で、(int, int)の部分が引数の部分です。引数は、(int a, int b)のように変数名の記載は必要なく、省略可能です。
これで変数名ptrには「戻り値はint型で、int型の変数2つを引数とする関数のアドレスを格納する変数」という意味を持たせることができました。
手順2. 変数に関数のアドレスを代入
関数ポインタを作成したら、続いては関数のアドレスを実際に代入しましょう。
先ほどのサンプルコードだと、以下のような箇所になります。
1 |
ptr_func = SumFunc; |
関数のアドレスを指定する場合は、関数の前に”&”付けても付けなくてもどちらでも大丈夫です。
これで関数ポインタptr_funcには、SumFunc関数のアドレスを代入することができました。
これで後は、関数ポインタptr_funcを使って関数を呼び出すだけですね。
手順3. 関数ポインタで関数を呼び出す
関数ポインタで関数を呼び出すときは、関数ポインタに引数を指定するだけです。
先ほどのサンプルコードだと、以下のような箇所になります。
1 |
answer = ptr_func(10,2); |
今までの関数の呼び出し方と同じで、関数名が関数ポインタ名に変わっただけですね。
このように、関数ポインタにアドレスを代入して、関数を呼び出すことが出来ます。
関数ポインタのメリット
以下、先ほどのサンプルコードを抜粋したものです。黄色に塗られているところに注目してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
int main() { int (*ptr_func)(int, int); /* 関数のアドレスを格納する変数 */ int answer; /* 演算結果 */ /* 足し算の関数のアドレスを代入 */ ptr_func = SumFunc; /* 関数をコール */ answer = ptr_func(10,2); printf("answer...%d\n",answer); /* 引き算の関数のアドレスを代入 */ ptr_func = SubtractionFunc; /* 関数をコール */ answer = ptr_func(10,2); printf("answer...%d\n",answer); /* かけ算の関数のアドレスを代入 */ ptr_func = MultiplicationFunc; /* 関数をコール */ answer = ptr_func(10,2); printf("answer...%d\n",answer); /* 割り算の関数のアドレスを代入 */ ptr_func = DivisionFunc; /* 関数をコール */ answer = ptr_func(10,2); printf("answer...%d\n",answer); return 0; } |
実際に関数を呼び出す箇所は全て「answer = ptr_func(10,2);」というように、同じ記載になっていますね。
関数ポインタptr_funcのアドレスが、どの関数を指定しているかによって呼び出す関数が変わってきますが、関数の呼び出し方法は全く同じになります。
ここが、関数ポインタのメリットです。
そして、この関数ポインタのメリットを応用したのが、「関数テーブル」になります。
今のサンプルコードだと、足し算・引き算・かけ算・割り算の4つの関数を呼んでいますが、処理がすごく似ていますよね。
それぞれ違っているところは、関数ポインタptr_funcに各関数のアドレスを指定する処理ぐらいです。
「じゃあ、それぞれの関数のアドレスを配列にして、ループ処理で回せばコードが短くなるんじゃないか?」
こう考えたくなりますよね。なので、各関数のテーブル(配列)を作って、コードを短くしてみましょう。
関数テーブルの書き方について
関数テーブルとは、関数のアドレスが配列で定義されたものになります。
先ほどのサンプルコードだと、足し算・引き算・かけ算・割り算の4つの関数がありますので、これを配列で定義するとこうなります。
1 2 |
/* 関数テーブルの作成 */ int (*ptr_funcArray[4])(int,int) = {SumFunc, SubtractionFunc, MultiplicationFunc, DivisionFunc}; |
変数名の部分がすごくややこしいですが、先ほどの関数ポインタの書き方に配列の”[]”が付いただけです。
この変数ptr_funcArrayが、各関数のアドレスを配列で持ったもの、つまり関数テーブルになります。
ptr_FuncArray[0]を指定すればSumFunc関数のアドレスが、ptr_FuncArray[1]を指定すればSubtractionFunc関数のアドレスが指定されます。
なので、これをループ処理で全て呼び出そうとすると、以下のようなサンプルコードになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
#include<stdio.h> /* 関数のプロトタイプ宣言 */ int SumFunc(int x, int y); int SubtractionFunc(int x, int y); int MultiplicationFunc(int x, int y); int DivisionFunc(int x, int y); /* 関数テーブルの作成 */ int (*ptr_funcArray[4])(int,int) = {SumFunc, SubtractionFunc, MultiplicationFunc, DivisionFunc}; int main() { int answer; /* 演算結果 */ int i; /* ループ処理用変数 */ for(i = 0; i < 4; i++) { answer = ptr_funcArray[i](10,2); printf("answer...%d\n",answer); } return 0; } /* 足し算の関数 */ int SumFunc(int x, int y) { return (x + y); } /* 引き算の関数 */ int SubtractionFunc(int x, int y) { return (x - y); } /* かけ算の関数 */ int MultiplicationFunc(int x, int y) { return (x * y); } /* 割り算の関数 */ int DivisionFunc(int x, int y) { return (x / y); } |
実行結果は同じですが、かなり短くなりましたね。
黄色になっているところが、関数テーブルからアドレスを引っ張ってきた後、引数の(10,2)を指定して関数を呼び出している箇所になります。
最後に
関数ポインタと関数テーブルについて解説しました。
関数ポインタのコードをいざ書こうとすると、書き方を忘れてしまうと思いますが、あんまり自分で書くことはないと思います。
どちらかと言うと、「ソースコードを追っていたら、関数ポインタになっていた」みたいな場面が多いはずです。
なので、まずは関数ポインタについて理解できれば十分かなと思います。