最後は関数ポインタの配列について説明します。
順を追って説明したいので、少しずつ書いていきます。
まずは、↓のプログラムを作ってみてください。
<sample program 176-01>
#include <stdio.h> #include <stdlib.h> #include <time.h> void Func1(void); void Func2(void); int main(void) { void(*pFunc)(void); srand((unsigned int)time(NULL)); if (rand() % 2) { pFunc = Func1; } else { pFunc = Func2; } pFunc(); return 0; } void Func1(void) { printf("Enter Func1\n"); } void Func2(void) { printf("Enter Func2\n"); } |
<実行結果>
Enter Func2 続行するには何かキーを押してください・・・
以前、関数ポインタは同じ引数と戻り値を持つ関数であれば、どの関数のアドレスも代入できると説明しました。
それを使って、乱数で2つの関数を呼び出すプログラムを作りました。
例えば、同じプログラムでも関数ポインタを使わなければ↓こうなりますよね。
<sample program 176-02>
#include <stdio.h> #include <stdlib.h> #include <time.h> void Func1(void); void Func2(void); int main(void) { srand((unsigned int)time(NULL)); if (rand() % 2) { Func1(); } else { Func2(); } return 0; } void Func1(void) { printf("Enter Func1\n"); } void Func2(void) { printf("Enter Func2\n"); } |
<実行結果>
Enter Func1 続行するには何かキーを押してください・・・
なぜ、わざわざ関数ポインタを使って作ったのかというと・・・
関数を呼び出す箇所が1つで済む
これがポイントになります。
では、これを関数ポインタ配列を使ったプログラムに変えてみましょう。
<sample program 176-03>
#include <stdio.h> #include <stdlib.h> #include <time.h> #define ARRAY_MAX 2 void Func1(void); void Func2(void); int main(void) { void(*pFunc[ARRAY_MAX])(void); srand((unsigned int)time(NULL)); pFunc[0] = Func1; pFunc[1] = Func2; pFunc[rand() % 2](); return 0; } void Func1(void) { printf("Enter Func1\n"); } void Func2(void) { printf("Enter Func2\n"); } |
<実行結果>
Enter Func1 続行するには何かキーを押してください・・・
関数ポインタ名の後に配列の要素数を書く事で、関数ポインタ配列になります。
普通に添え字を使ってアクセスし、関数のアドレスを格納出来ます。
そして、
pFunc[0](); |
のように書く事で、要素に入っている関数を呼び出すことが出来ます。
次に、例を変えて使い道について書きます。
やりたい事を↓に書きます。
ゲームにおける敵の動きについてプログラムを組みたい ・敵は10体 ・パラメータに動きの種類を表す変数kindを用意する ・敵の動きの種類は3種類 ・変数kindに0、1、2を格納することにより区別する ・10体分のkindの値は乱数でセットする ・3種類の動きはそれぞれ関数で用意する ・変数kindの値によって、それぞれの関数を呼び出す |
さて、これを実際に作ってみましょう。
最初は関数ポインタ配列を使わずに書いてみます。
<sample program 176-04>
#include <stdio.h> #include <stdlib.h> #include <time.h> #define ENEMY_MAX 10 #define ENEMY_KIND_MAX 3 typedef struct { int kind; } Enemy; void MoveEnemy1(void); void MoveEnemy2(void); void MoveEnemy3(void); int main(void) { Enemy enemy[ENEMY_MAX]; int i; srand((unsigned int)time(NULL)); for (i = 0; i < ENEMY_MAX; i++) { enemy[i].kind = rand() % ENEMY_KIND_MAX; } for (i = 0; i < ENEMY_MAX; i++) { switch (enemy[i].kind) { case 0: MoveEnemy1(); break; case 1: MoveEnemy2(); break; case 2: MoveEnemy3(); break; } } return 0; } void MoveEnemy1(void) { printf("Enemy1 : 右に移動します。\n"); } void MoveEnemy2(void) { printf("Enemy2 : 下に移動します。\n"); } void MoveEnemy3(void) { printf("Enemy3 : 回転します。\n"); } |
<実行結果>
Enemy3 : 回転します。 Enemy3 : 回転します。 Enemy2 : 下に移動します。 Enemy2 : 下に移動します。 Enemy3 : 回転します。 Enemy2 : 下に移動します。 Enemy1 : 右に移動します。 Enemy1 : 右に移動します。 Enemy2 : 下に移動します。 Enemy3 : 回転します。 続行するには何かキーを押してください・・・
敵のパラメータはkindのみとしました。
switch文を使って、配列に格納されているkindを確認し、値に合った関数を呼び出しています。
これを、関数ポインタ配列を使った形に変更します。
<sample program 176-05>
#include <stdio.h> #include <stdlib.h> #include <time.h> #define ENEMY_MAX 10 #define ENEMY_KIND_MAX 3 typedef struct { int kind; } Enemy; void MoveEnemy1(void); void MoveEnemy2(void); void MoveEnemy3(void); int main(void) { Enemy enemy[ENEMY_MAX]; void(*pMoveEnemy[ENEMY_KIND_MAX])(void) = { MoveEnemy1, MoveEnemy2, MoveEnemy3, }; int i; srand((unsigned int)time(NULL)); for (i = 0; i < ENEMY_MAX; i++) { enemy[i].kind = rand() % ENEMY_KIND_MAX; } for (i = 0; i < ENEMY_MAX; i++) { pMoveEnemy[enemy[i].kind](); } return 0; } void MoveEnemy1(void) { printf("Enemy1 : 右に移動します。\n"); } void MoveEnemy2(void) { printf("Enemy2 : 下に移動します。\n"); } void MoveEnemy3(void) { printf("Enemy3 : 回転します。\n"); } |
<実行結果>
Enemy2 : 下に移動します。 Enemy3 : 回転します。 Enemy1 : 右に移動します。 Enemy1 : 右に移動します。 Enemy1 : 右に移動します。 Enemy2 : 下に移動します。 Enemy3 : 回転します。 Enemy3 : 回転します。 Enemy1 : 右に移動します。 Enemy2 : 下に移動します。 続行するには何かキーを押してください・・・
関数ポインタ配列を用意し、3つの関数のアドレスを格納しました。
※今回は初期値として設定しました。
メンバ変数kindの値によって、
pMoveEnemy[0](); ← MoveEnemy1関数を呼び出す pMoveEnemy[1](); ← MoveEnemy2関数を呼び出す pMoveEnemy[2](); ← MoveEnemy3関数を呼び出す
のどれかが実行される仕組みです。
関数ポインタ配列を使ったプログラムの方が、プログラム的にもすっきりして見えます。
敵の動きを「追加」する際にも、関数ポインタ配列の方が楽に出来ます。
※switch文はプログラムが長くなる傾向があります。
配列の添え字を変える事で、呼び出す関数が変更出来るので、使いどころは色々ありそうですね。