こんにちは!こんばんは!
ちゃとらです(・ω・)/
関数ポインタって聞いたことがあるでしょうか?
その名の通り関数のアドレスを代入できるポインタ変数であり、変数ポインタと同じような使い方です。
関数ポインタについて分かりやすく解説していきます!
関数ポインタとは
説明
関数ポインタとは、ポインタ変数の一種であり、関数が保存されているアドレスを格納することができます。
関数のアドレスを参照することで、関数を呼び出すことが可能です。
しかし、関数ポインタの宣言方法は少し独特です。
なので、サンプルコードを基に具体的な解説をしていきます。
サンプルコード
まずは、関数ポインタを使って関数(showString)を動かすプログラムです。
#include <stdio.h>
void showString(void)
{
printf("ABCDEFG\n");
}
int main(void)
{
void(*fp)(void); // 関数ポインタfpの宣言
fp = showString; // fpにshowString関数のアドレスを代入
fp(); // 参照先の関数を実行
return 0;
}
ABCDEFG
10行目、11行目で関数ポインタを宣言しshowString関数のアドレスを代入しています。
ここでの注目点は関数ポインタの宣言方法についてです。
// 関数ポインタの宣言方法
戻り値のデータ型 (*変数名)(引数のデータ型...)
関数ポインタを宣言するには、戻り値・引数のデータ型を設定しなければなりません。また、関数ポインタには宣言した戻り値・引数のデータ型が合致する関数のアドレスしか入れることができません。
13行目で関数ポインタfpに格納されている関数のアドレスを参照してshowString関数を動かしています。
イメージは以下のようになります。

関数ポインタを使うことで次のようなこともできます。
#include <stdio.h>
void showString(void)
{
printf("ABCDEFG\n");
}
int add(int a, int b)
{
return a + b;
}
int mul(int a, int b)
{
return a * b;
}
int main(void)
{
void(*fp1)(void) = showString; // fp1にshowString関数のアドレスを代入
int(*fp2)(int, int) = add; // fp2にadd関数のアドレスを代入
int n1 = 100, n2 = 200;
int ans;
fp1(); // showString関数を実行
ans = fp2(n1, n2); // add関数を実行し結果をansに代入
printf("%dと%dの和:%d\n", n1, n2, ans);
fp2 = mul; // fp2にmul関数のアドレスを代入
ans = fp2(n1, n2); // mul関数を実行し結果をansに代入
printf("%dと%dの積:%d\n", n1, n2, ans);
return 0;
}
ABCDEFG
100と200の和:300
100と200の積:20000
ここで注目してほしいのは、21行目でfp2にadd関数のアドレスを代入し26行目で実行していますが、29行目と30行目でfp2にmul関数のアドレスを代入し実行していることです。
このように、関数ポインタの戻り値・引数のデータ型が関数と合致するならば、他の関数のアドレスも代入可能となります。
関数ポインタの使い道
まぁ、何となく関数ポインタがどんなものなのか分かったけど、
どんな時に使うの?
主に、コールバックや一部の標準関数の呼び出しですね!
関数ポインタの用途は様々ですが、代表的な使い方を2つ紹介していきます!
コールバック
コールバックとは、電話のかけ直しが言葉の由来となっていて、関数の引数に関数ポインタを設定することでその関数内で他の関数を実行することが可能となります。
つまり、自作関数内で他の自作関数を実行させることができます。
以下のプログラムで、実際にコールバックさせてみます。
#include <stdio.h>
void showString(void)
{
printf("showStringの実行開始\n");
printf("ABCDEFG\n");
printf("showStringの実行終了\n");
}
void func(void(*fp)(void))
{
printf("funcの実行開始\n");
fp(); // 引数で受け取った関数アドレスを参照して関数を実行
printf("funcの実行終了\n");
}
int main(void)
{
func(showString); // showString関数のアドレスを渡してfunc関数を実行
return 0;
}
funcの実行開始
showStringの実行開始
ABCDEFG
showStringの実行終了
funcの実行終了
19行目でfunc関数にshowString関数のアドレスを渡して実行しています。
なので、func関数の始まりを実行→showString関数の実行→func関数の終わりを実行という流れの処理となっています。
このソースコードでは単純な使い方のコールバックですが、もちろん応用した使い方もできます。
コールバックを使用することによって、状況に応じて関数内で動かす他の関数を変えたり、多くの関数を動かすことができます。
意外と便利かも!
一部の標準関数の呼び出し
C言語に用意されている標準関数の中には、引数に関数ポインタ(関数アドレス)を渡して呼び出すことのできるものがいくつかあります。
代表的なものだと、atexit関数やsignal関数などです。
以下のプログラムでは、atexit関数を使って関数を登録し、main関数の処理が終わったら最後に登録された関数から順番に呼び出されます。
#include <stdio.h>
#include <stdlib.h>
void showString1(void)
{
printf("ABCDEFG\n");
}
void showString2(void)
{
printf("HTJKLMN\n");
}
int main(void)
{
void(*fp)(void);
int result;
fp = showString2;
result = atexit(fp); // showString2関数を登録して結果をresultに代入
// 関数の登録判定
if (result == 0)
{
printf("関数の登録に成功しました\n");
}
else
{
printf("関数の登録に失敗しました\n");
}
fp = showString1;
result = atexit(fp); // showString1関数を登録して結果をresultに代入
// 関数の登録判定
if (result == 0)
{
printf("関数の登録に成功しました\n");
}
else
{
printf("関数の登録に失敗しました\n");
}
return 0;
}
// main関数の処理が終了したら
// showString2 → showString1の順番で関数が実行される
関数の登録に成功しました
関数の登録に成功しました
ABCDEFG
HTJKLMN
atexit関数の説明は以下の通りです。
// atexit関数の形式
int atexit(void(*func)(void));
- 引数として渡された関数ポインタの中身の関数を登録し、登録成功ならば1を返し、登録失敗ならば0を返す。
- main関数の終了やexit関数が呼び出されてプログラムが終了したら、最後に登録された関数から順番に呼び出される。
- コンパイラによって登録できる関数の制限が変わるが、最低でも32個の関数を登録することが可能。
atexit関数は最後に登録された関数から順番に関数を呼び出します。
なので、main関数の処理が終了したらshowString2→showString1の順番で関数が呼び出されています。
注意点としてatexit関数の引数には、戻り値・引数が共にvoid型の関数アドレスしか入らないことです。
まとめ
変数のアドレスを代入して参照することができれば、
同じように関数のアドレスを代入して参照することができます!
今回はここまでです。
ちゃとら(・ω・)/