関数ポインタ

関数を変数に格納する

今までのポインタでは何らかの値(整数や文字列など)を扱ってきました。

ポインタは「データのメモリ上の位置とデータ型(のサイズ)」の情報です。
変数はもちろんメモリ上に存在しますが、関数もメモリ上に存在します。
ということは、関数もポインタで扱うことが可能ということです。
それが関数ポインタです。
関数ポインタは「関数の情報を格納するポインタ変数」です。

#include <stdio.h>

void Echo(const char *str)
{
	printf("%s\n", str);
}

int main()
{
	//普通に関数を呼び出す
	Echo("Hello");

	//関数ポインタの宣言
	void (*func)(const char*);

	//関数ポインタに関数を代入
	func = Echo;

	//関数ポインタを通して関数Echoを実行
	func("World");

	getchar();
}
Hello
World

自作関数Echoは引数で受け取った文字列を表示して改行するだけの関数です。

14行目が関数ポインタの宣言です。
ポインタ変数なので間接演算子(*)を使用しますが、変数名は丸括弧で囲う必要があります。
さらに、変数名に続いて引数リストを指定します。
ここでは自作関数Echoと同じ、const char*型ひとつを引数に指定します。
さらに、関数ポインタ自体のデータ型にはvoid型を指定します。

この関数ポインタfuncには「戻り値がvoid型で、const char*型をひとつ引数に取る関数」のポインタを格納することができます。
17行目で実際に自作関数Echoを代入しています。
このとき、関数名には関数呼び出し演算子(丸括弧)はつけてはなりません。
こうすることで関数のポインタを得ることができます。

これで、関数ポインタfuncは関数Echoを指している「関数Echoの別名」となります。
普通に関数を実行する場合と同じように、関数ポインタfuncに関数呼び出し演算子と実引数を指定すれば関数Echoを実行できます。

書式

関数ポインタは宣言の形式は見慣れないものですが、それほど難しいものではありません。
丸括弧と間接演算子が付く以外は関数のプロトタイプ宣言とほとんど同じです。

//関数のプロトタイプ宣言

//char*型ひとつ、int型ひとつを引数に取り
//int型を返す関数
int Func1(char*, int);

//引数なし
//char*型を返す関数
char *Func2();

//↓は↑に対応する関数ポインタの宣言

//char*型ひとつ、int型ひとつを引数に取り
//int型を返す関数ポインタ
int (*func1)(char*, int);

//引数なし
//char*型を返す関数ポインタ
char *(*func2)();

func1 = Func1;
func2 = Func2;

宣言と同時に関数を代入することもできます。

int (*func1)(char*, int) = Func1;
char *(*func2)() = Func2;

関数ポインタの実行側では、普通の関数と同じように関数呼び出し演算子で関数を実行することができます。
戻り値も受け取ることができます。

int a = func1("ABC", 3);
char *b = func2();

普通の関数と関数ポインタとの見分けをつけるために、以下のような形式で呼び出すこともできます。

int a = (*func1)("ABC", 3);
char *b = (*func2)();

関数を差し替えてみる

ただ関数を実行するだけならこんなややこしい手順を踏む意味はありません。
関数ポインタの最大の特徴は、同じ呼び出し方なのに条件によって呼び出す関数を差し替えることができる点です。

#include <stdio.h>

//引数strを表示する関数
void Echo(const char *str)
{
	printf("%s\n", str);
}

//引数strの文字数を表示する関数
void ShowLength(const char *str)
{
	int count = 0;
	while (*str++ != '\0')
		count++;

	printf("文字数: %d\n", count);
}

int main()
{
	int cond = 1;

	void (*func)(const char*);

	//条件によって実行する関数を変える
	if(cond == 0)
		func = Echo;
	else
		func = ShowLength;

	func("Hello");

	getchar();
}
文字数: 5

このコードは、関数の呼び出し方は同じなのに変数condの値によって実際に呼び出される関数が変わることになります。
例えばユーザーからの入力やファイルから読み込んだデータによって処理を差し替えることができるのが関数ポインタです。

NULLポインタ

関数ポインタもポインタなので、0やNULLを代入してNULLポインタにすることができます。

void(*func)(const char*) = Echo;

if (func == NULL)
    printf("funcは使用できない\n");
else
    printf("funcは使用可能\n");

//NULLポインタ
func = NULL;

if (func == NULL)
    printf("funcは使用できない\n");
else
    printf("funcは使用可能\n");
funcは使用可能
funcは使用できない

NULL(または0)と比較することで、関数ポインタが使用可能かどうかを判別できます。
ただし初期化前の変数は値を代入するまではNULLとの比較すらもできないので注意してください。

関数ポインタの配列

関数ポインタは配列にして使用することもできます。

//関数ポインタの配列
void(*func[2])(const char*);

func[0] = Echo;
func[1] = ShowLength;

//実行
func[0]("ABC");
(*func[1])("ABC");

//宣言と同時に初期化
void(*func2[])(const char*) = { Echo, ShowLength };