Menu
Coddy logo textTech

構造体における関数ポインタ

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型から異なる振る舞いを生み出すことができます。

challenge icon

チャレンジ

簡単

関数ポインタを通じて構造体が独自の振る舞いを持つ方法を示す 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))を忘れないでください。

チートシート

関数ポインタは構造体の中に直接埋め込むことができ、各インスタンスに独自の動作を持たせることができます。これはC言語におけるポリモーフィズム(多態性)の基礎となります。

関数ポインタを持つ構造体の定義

まず、関数ポインタ型を定義し、それを構造体のメンバとして含めます。

typedef void (*PrintFunc)(const char*);

typedef struct {
    const char* name;
    PrintFunc print;  // Function pointer as a member
} Printer;

構造体の使用

初期化時に関数をポインタに割り当て、構造体を介してその関数を呼び出します。

void loud_print(const char* msg) {
    printf("!!! %s !!!\n", msg);
}

int main() {
    Printer p;
    p.name = "Alert";
    p.print = loud_print;
    
    p.print("Hello");  // Output: !!! Hello !!!
    return 0;
}

p.print("Hello") という構文は、他の言語でオブジェクトのメソッドを呼び出すのと同様に、構造体を介して関数を呼び出します。

ポリモーフィックな動作

同じ構造体型の異なるインスタンスに異なる関数を割り当てることができ、同じ構造体型から異なる動作を生み出すことができます。

自分で試してみよう

#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;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Object Oriented Programmingのすべてのレッスン