最後は関数ポインタの配列について説明します。
順を追って説明したいので、少しずつ書いていきます。
まずは、↓のプログラムを作ってみてください。
<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文はプログラムが長くなる傾向があります。
配列の添え字を変える事で、呼び出す関数が変更出来るので、使いどころは色々ありそうですね。