ポインタ、覚えていますか?怪しい場合は基礎編を履修するようにしてから本稿を読んでください。今回は関数ポインタです。覚えるのはさほど難しくはないんですが、ちょっと間違えると簡単に暴走するので難しいと思われてたりします。コレを使いこなすと上級者に間違えられてしまったりするので、是非間違えられてしまいましょう😂
--
いきなりここに飛んで来ちゃった人は、よろしければ下記からご覧ください。


  • 関数はどこにある
変数やプログラムはメモリの何処かに配置されます。ポインタは変数の位置を指し示していました。位置とはアドレスです。ところでプログラムもメモリのどこかに配置されていますので、やはりその場所はアドレスで指し示すことが出来ます。これが関数ポインタです。

通常、関数がどこにあるかなんて意識したりはしません。ですが、関数の位置を管理することで便利なことがあります。それが状態遷移です。以前、状態遷移は管理する変数の値で switch で処理を分岐すると説明してます。これは、現在の状態番号に依って、個別の関数にジャンプするだけなので、いっそのこと関数の位置をまんま持ってしまえば簡単じゃないのかってのが、関数ポインタを使おうかと思うきっかけになります。

状態遷移は処理の流れを管理する技法なので、直接処理の飛び先をポインタとして持ってしまったら簡単に出来そうな気がしませんか。そして、ポインタが示す先にジャンプする動作のため、ポインタの値が不適切なら一発で暴走するのも理解できるかと思います。ヤバイんです。


  • 関数の型
ポインタ変数は、その型に対してアスタリスク(*)を付与することで宣言しました。int 型変数のポインタであれば int* でしたね。では、関数の型はどう記述するのでしょうか?関数はどういう構造をしてましたっけ?

戻り値の型 関数名(引数の型 引数名, ...)
こんな感じですよね。関数ポインタは関数の位置を示すので、関数名の部分が欲しいアドレスとなります。必要なのは型だけなので、名前は不要です。…が、ポインタ変数としての名前は欲しいです。とりあえず関数(function)のポインタ(pointer)なので、pfunc というポインタ変数名とすると、

戻り地の型 (*pfunc)(引数の型 引数名, ...)
この書き方が関数ポインタ変数の宣言となります。pfunc周囲の括弧は必須です。括弧を外すと格好がつかないのです(オヤジギャグ)。一番シンプルな void deployShips(void) という戻り値も引数もない関数の場合は、

void (*pGame)(void);
このように記述すると pGame ポインタ変数が宣言されます。引数がある例えば void Sleep(int msec) の関数ポインタを受け取る変数宣言は、

void (*pSleep)(int);
このような記述となります。このポインタ変数宣言では変数の値は不定値となります。同じ型のポインタであれば確認できます。先の pGame であれば、

pGame = deployShips;
このように、関数名を代入することでその関数の位置(ポインタ)が格納されます。型が違うと格納できないため、例えば

pGame = Sleep;
このような記述を行うとコンパイルエラーとなります。ポインタ変数宣言と同時に初期化子を与えることも出来ます。上記の pGame であれば

void (*pGame)(void) = deployShips;
このような記述が可能です。
※ 首周りの日焼け防止に効果を発揮してます…が、欠点としては見栄えはあまり良くないことでしょうか。私は白色を選択してますが、ワイシャツが出ているように印象が良くないかと。ただ、そんな事は言っていられないので使っています。
【あす楽対応】「直送」おたふく手袋 4970687605163 JW-613 黒 ストレッチ カバー付ヘッドキャップ
【楽天】

【あす楽対応】「直送」おたふく手袋 4970687605163 JW-613 黒 ストレッチ カバー付ヘッドキャップ

  • ポインタが示す関数を実行する
さて、pGame や pSleep に関数ポインタの値が入った状態で、その関数を実行するにはどうすればよいでしょうか。例えば、既に存在する deployShips という関数を呼び出すには

deployShips();
このように関数名を記述するだけでした。deployShips は定数ポインタです。配列名が定数ポインタであるのと同じです。そして C言語では、それが定数でも変数でも動作としては全く同じことが出来ます。deployShips のポインタ値を格納した pGame 関数ポインタ変数であっても、呼び出す時は

pGame();
このように記述するだけです。大変簡単です。簡単であるが故に怖い部分でもありますよね。何しろ pGame は変数なので、ここの値が null でも呼び出そうとしてしまいます。null 呼び出しは通常良くてエラーです。マシンによってはあっさり暴走します。


  • 関数ポインタ配列
さて、ポインタも変数ですから配列に出来ます。int 型配列などでは宣言時に配列名の後ろに [] を付与して指定しましたが、関数ポインタの場合でも全く同じです。配列名は pGame ですので、この名前の後ろに [] を付与することなります。

void (*pGame[3])(void);
初期化子を記述することで配列数を省略することも出来ます。

void (*pGame[])(void) = { drawGameFiled, sureDeploy, attackPlayer, };
当然、全て同じ型を持つ関数しか初期化子に指定できません。そして、配列から関数を呼び出すことも出来ます。

pGame[1]();
このように記述することで、sureDeploy を呼び出すことが出来ます。添字 1 の部分はこれまた当然変数も記述できますので、

pGame[num]();
等のように、num の値によって呼び出し関数を変更することが出来ます。この記述は

switch (num) { case 0: drawGameFiled(); break; case 1: sureDeploy(); break; case 2: attackPlayer(); break; }
この記述と大差ありませんが、実行速度はおそらく関数ポインタ配列からの実行のほうが速い(気がします)。まあ、C言語の最適化が素晴らしければ、殆ど測定誤差レベルで速度差は無い気もします…。
※ 外出時はこのタブレットを水分と一緒に必ず持ち歩いています。大量に汗をかくので、500ml 飲み干したらこれを一粒かじるみたいな感じです。熱中症怖いですからねぇ…
カバヤ食品 塩分チャージタブレッツ 塩レモン 81g ×6個賞味期限2025/12
【楽天】

カバヤ食品 塩分チャージタブレッツ 塩レモン 81g ×6個賞味期限2025/12