Menu
Coddy logo textTech

メソッドのシミュレーション

CoddyのCジャーニー「Object Oriented Programming」セクションの一部 — レッスン 39/61。

構造体の中に関数ポインタを配置する方法を見てきました。同じ構造体型の異なるインスタンスに異なる関数を割り当てるときに、真の力が発揮されます。— これが、異なる振る舞いを持つメソッドをシミュレートする方法です。

カスタマイズ可能なロジックを持つアクションを表す構造体を考えてみましょう。

typedef void (*ActionFunc)(void);

typedef struct {
    const char* name;
    ActionFunc execute;
} Action;

インスタンスを作成するとき、それぞれに異なる関数を関連付けます:

void say_hello(void) {
    printf("Hello!\n");
}

void say_goodbye(void) {
    printf("Goodbye!\n");
}

int main() {
    Action greet = { "Greet", say_hello };
    Action farewell = { "Farewell", say_goodbye };
    
    greet.execute();    // 出力: Hello!
    farewell.execute(); // 出力: Goodbye!
    return 0;
}

greetfarewell はどちらも同じ型(Action)ですが、それぞれに対して execute を呼び出すと、まったく異なる結果が生成されます。この構造体は単にデータを保持するだけでなく、インスタンスごとに異なる振る舞いを保持します。

このパターンは、他の言語におけるオブジェクトの仕組みを反映しています。各オブジェクトは、同じインターフェースを共有していても、メソッドの独自の実装を持つことができます。

challenge icon

チャレンジ

簡単

同じ構造体型の異なるインスタンスが、どの関数がワイヤリングされているかに基づいて、どのように全く異なる振る舞いを示すかを示す Speaker システムを構築しましょう。

コードを整理するために2つのファイルを作成します:

  • speaker.h: パラメータを取らず何も返さない SpeakFunc という名前の関数ポインタ型を定義します。次に、name (const char*) と SpeakFunc 型の speak 関数ポインタを含む Speaker 構造体を定義します。
  • main.c: ヘッダーをインクルードし、3つの異なる発声関数を実装します:
    • barkWoof! を出力します
    • meowMeow! を出力します
    • chirpChirp! を出力します
    次に、2つの Speaker インスタンスを作成し、入力に基づいてそれぞれを異なる関数にワイヤリングし、それらの speak メソッドを呼び出してポリモーフィックな動作を確認します。

プログラムは2つの入力を受け取ります:1つ目のスピーカーのタイプ (1は bark、2は meow、3は chirp) と 2つ目のスピーカーのタイプ (同様に1、2、または3) です。

2つの Speaker インスタンスを作成します。名前は好きなもの("Dog"、"Cat"、"Bird"、あるいは単に "Speaker1"、"Speaker2" など)を付けて構いません。入力コードに基づいてそれぞれを適切な関数にワイヤリングし、各インスタンスの speak を順番に呼び出します。

1つ目のタイプが 1 で、2つ目のタイプが 2 の場合の出力例:

Woof!
Meow!

1つ目のタイプが 3 で、2つ目のタイプが 3 の場合の出力例:

Chirp!
Chirp!

ここでの重要な洞察は、両方のスピーカーが同じ Speaker 型であるにもかかわらず、それぞれの speak メンバに異なる関数が割り当てられているため、異なる出力が生成されるということです。ヘッダーファイルではインクルードガードを使用することを忘れないでください。

チートシート

同じ構造体型の異なるインスタンスが異なる関数ポインタを保持できるため、ポリモーフィックな動作が可能になります。

関数ポインタ型と、それを使用する構造体を定義します:

typedef void (*ActionFunc)(void);

typedef struct {
    const char* name;
    ActionFunc execute;
} Action;

異なる関数を割り当てたインスタンスを作成します:

void say_hello(void) {
    printf("Hello!\n");
}

void say_goodbye(void) {
    printf("Goodbye!\n");
}

int main() {
    Action greet = { "Greet", say_hello };
    Action farewell = { "Farewell", say_goodbye };
    
    greet.execute();    // Output: Hello!
    farewell.execute(); // Output: Goodbye!
    return 0;
}

両方のインスタンスは同じ型を共有していますが、関数ポインタが呼び出されたときに異なる動作を示します。

自分で試してみよう

#include <stdio.h>
#include "speaker.h"

// TODO: "Woof!" と出力する bark 関数を実装してください

// TODO: "Meow!" と出力する meow 関数を実装してください

// TODO: "Chirp!" と出力する chirp 関数を実装してください

int main() {
    int type1, type2;
    scanf("%d", &type1);
    scanf("%d", &type2);
    
    // TODO: 2つの Speaker インスタンスを作成してください
    
    // TODO: type1 に基づいて、1つ目のスピーカーの speak 関数を割り当ててください
    // (1 = bark, 2 = meow, 3 = chirp)
    
    // TODO: type2 に基づいて、2つ目のスピーカーの speak 関数を割り当ててください
    // (1 = bark, 2 = meow, 3 = chirp)
    
    // TODO: 各スピーカーの speak を順番に呼び出してください
    
    return 0;
}
quiz icon腕試し

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

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