オブジェクト指向のコードを C で書くにはどうすればよいでしょうか?

はい。実際、Axel Schreiner は彼の著書「ANSI-C でのオブジェクト指向プログラミング」を無料で提供しており、この主題を徹底的にカバーしています。


あなたがポリモーフィズムについて話しているのなら、そうです。C++ が登場する何年も前に、私たちはそのようなことをしていました.

基本的に struct を使用します データと、そのデータに関連する関数を指す関数ポインタのリストの両方を保持します。

したがって、通信クラスでは、open、read、write、および close 呼び出しがあり、オブジェクトのデータと共に、構造内の 4 つの関数ポインターとして維持されます。

typedef struct {
    int (*open)(void *self, char *fspec);
    int (*close)(void *self);
    int (*read)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    int (*write)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    // And data goes here.
} tCommClass;

tCommClass commRs232;
commRs232.open = &rs232Open;
: :
commRs232.write = &rs232Write;

tCommClass commTcp;
commTcp.open = &tcpOpen;
: :
commTcp.write = &tcpWrite;

もちろん、上記のコード セグメントは、実際には rs232Init() などの「コンストラクタ」に含まれます。 .

そのクラスから「継承」するときは、ポインターを独自の関数を指すように変更するだけです。これらの関数を呼び出した人は誰でも、関数ポインターを介してそれを実行し、ポリモーフィズムを提供します:

int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000");

手動 vtable のようなものです。

ポインターを NULL に設定することで、仮想クラスを作成することもできます。動作は C++ とは少し異なります (コンパイル時のエラーではなく、実行時のコア ダンプ)。

これを示すサンプル コードを次に示します。まずトップレベルのクラス構造:

#include <stdio.h>

// The top-level class.

typedef struct sCommClass {
    int (*open)(struct sCommClass *self, char *fspec);
} tCommClass;

次に、TCP 'サブクラス' の関数があります:

// Function for the TCP 'class'.

static int tcpOpen (tCommClass *tcp, char *fspec) {
    printf ("Opening TCP: %s\n", fspec);
    return 0;
}
static int tcpInit (tCommClass *tcp) {
    tcp->open = &tcpOpen;
    return 0;
}

HTTP も同様です:

// Function for the HTTP 'class'.

static int httpOpen (tCommClass *http, char *fspec) {
    printf ("Opening HTTP: %s\n", fspec);
    return 0;
}
static int httpInit (tCommClass *http) {
    http->open = &httpOpen;
    return 0;
}

そして最後に、動作を示すテスト プログラム:

// Test program.

int main (void) {
    int status;
    tCommClass commTcp, commHttp;

    // Same 'base' class but initialised to different sub-classes.

    tcpInit (&commTcp);
    httpInit (&commHttp);

    // Called in exactly the same manner.

    status = (commTcp.open)(&commTcp, "bigiron.box.com:5000");
    status = (commHttp.open)(&commHttp, "http://www.microsoft.com");

    return 0;
}

これにより、出力が生成されます:

Opening TCP: bigiron.box.com:5000
Opening HTTP: http://www.microsoft.com

サブクラスに応じて、さまざまな関数が呼び出されていることがわかります。


名前空間は、多くの場合、次のようにして行われます:

stack_push(thing *)

の代わりに

stack::push(thing *)

C 構造体を C++ クラスのようなものにするには、次のようにします:

class stack {
     public:
        stack();
        void push(thing *);
        thing * pop();
        static int this_is_here_as_an_example_only;
     private:
        ...
};

struct stack {
     struct stack_type * my_type;
     // Put the stuff that you put after private: here
};
struct stack_type {
     void (* construct)(struct stack * this); // This takes uninitialized memory
     struct stack * (* operator_new)(); // This allocates a new struct, passes it to construct, and then returns it
     void (*push)(struct stack * this, thing * t); // Pushing t onto this stack
     thing * (*pop)(struct stack * this); // Pops the top thing off the stack and returns it
     int this_is_here_as_an_example_only;
}Stack = {
    .construct = stack_construct,
    .operator_new = stack_operator_new,
    .push = stack_push,
    .pop = stack_pop
};
// All of these functions are assumed to be defined somewhere else

struct stack * st = Stack.operator_new(); // Make a new stack
if (!st) {
   // Do something about it
} else {
   // You can use the stack
   stack_push(st, thing0); // This is a non-virtual call
   Stack.push(st, thing1); // This is like casting *st to a Stack (which it already is) and doing the push
   st->my_type.push(st, thing2); // This is a virtual call
}

デストラクタや削除はしませんでしたが、同じパターンに従います。

this_is_here_as_an_example_only は、型のすべてのインスタンス間で共有される静的クラス変数のようなものです。すべてのメソッドは実際には静的ですが、一部のメソッドは this * を取ります。