構造体における関数ポインタ
CoddyのCジャーニー「Object Oriented Programming」セクションの一部 — レッスン 38/61。
これまで、関数ポインタを独立した変数として使用したり、引数として渡したりしてきました。ポリモーフィズムへの次のステップは、関数ポインタを構造体の中に直接埋め込むことです。これにより、構造体に独自の振る舞いを持たせることができます。
構造体に関数ポインタが含まれている場合、各インスタンスは異なる関数を保持することができます。これは、同じ型の2つのオブジェクトであっても、その関数ポインタを呼び出したときに異なる動作をする可能性があることを意味します。
typedef void (*PrintFunc)(const char*);
typedef struct {
const char* name;
PrintFunc print; // メンバとしての関数ポインタ
} Printer;Printer構造体には、データ(name)と振る舞い(print)の2つのメンバが含まれるようになりました。これを使用するには、初期化時に関数をポインタに割り当てます:
void loud_print(const char* msg) {
printf("!!! %s !!!\n", msg);
}
int main() {
Printer p;
p.name = "Alert";
p.print = loud_print;
p.print("Hello"); // 出力: !!! Hello !!!
return 0;
}構造体を介して関数を呼び出していることに注目してください:p.print("Hello")。これは、他の言語でオブジェクトのメソッドを呼び出すのと非常によく似ています。構造体は、そのデータとそのデータを操作する関数の両方を保持しています。
このパターンは、C言語におけるポリモーフィズムの基礎です。異なるインスタンスに異なる関数を割り当てることができ、同じstruct型から異なる振る舞いを生み出すことができます。
チャレンジ
簡単構造体が関数ポインタを通じて独自の振る舞いを持つ方法を示す Notifier システムを構築しましょう。
コードを整理するために、2つのファイルを作成します:
notifier.h:const char*パラメータを受け取り、何も返さないNotifyFuncという名前の関数ポインタ型を定義します。次に、name(const char*) とNotifyFunc型のnotify関数ポインタを含むNotifier構造体を定義します。main.c: ヘッダーをインクルードし、2つの通知関数を実装します:alert_notify— メッセージを[ALERT] messageの形式で出力します。info_notify— メッセージを[INFO] messageの形式で出力します。
Notifierインスタンスを作成し、入力に基づいて適切な関数を関連付け、構造体の関数ポインタを介して通知を呼び出します。
プログラムは2つの入力を受け取ります:通知タイプ (1 は alert、2 は info) と、表示するメッセージです。
タイプに基づいて、対応する関数を notifier の notify メンバに割り当て、構造体を介してそれを呼び出してメッセージを表示します。
タイプが 1 でメッセージが "System starting" の場合の出力例:
[ALERT] System startingタイプが 2 でメッセージが "All systems normal" の場合の出力例:
[INFO] All systems normalヘッダーファイルでインクルードガードを使用すること、および構造体メンバを介して関数を呼び出すこと(n.notify(message))を忘れないでください。
自分で試してみよう
#include <stdio.h>
#include "notifier.h"
// TODO: alert_notify 関数を実装する
// 出力形式: [ALERT] message
// TODO: info_notify 関数を実装する
// 出力形式: [INFO] message
int main() {
int type;
char message[256];
// 入力を読み込む
scanf("%d", &type);
getchar(); // 改行文字を消費する
fgets(message, sizeof(message), stdin);
// メッセージの末尾に改行があれば削除する
int len = 0;
while (message[len] != '\0') len++;
if (len > 0 && message[len-1] == '\n') message[len-1] = '\0';
// TODO: Notifier インスタンスを作成する
// TODO: type (1 は alert、2 は info) に基づいて、
// 適切な関数を notifier の notify メンバに割り当てる
// TODO: 構造体の関数ポインタを介して通知を呼び出す
// 使用例: n.notify(message)
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。