524

Cでオブジェクト指向コードを書く方法は何ですか?特にポリモーフィズムに関して。


このスタックオーバーフローの質問Cでのオブジェクト指向も参照してください。

4

32 に答える 32

378

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

于 2008-12-09T04:12:20.740 に答える
371

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

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

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

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

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

于 2008-12-09T04:06:09.987 に答える
94

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

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 * を取ることを除いて、すべてのメソッドは実際には静的です

于 2010-04-28T20:42:29.897 に答える
59

OOP を C で実装することは、それ自体が有用であることに加えて、OOP を学び、その内部の仕組みを理解するための優れた方法であると私は信じています。多くのプログラマーの経験から、技術を効率的かつ自信を持って使用するには、プログラマーは基本的な概念が最終的にどのように実装されるかを理解する必要があることが示されています。C でクラス、継承、ポリモーフィズムをエミュレートすると、まさにこのことがわかります。

元の質問に答えるために、C で OOP を実行する方法を教えるいくつかのリソースを次に示します。

EmbeddedGurus.com のブログ投稿「C でのオブジェクト ベースのプログラミング」では、移植可能な C でクラスと単一継承を実装する方法を示しています 。 /

アプリケーション ノート「"C+"—C でのオブジェクト指向プログラミング」では、プリプロセッサ マクロを使用して C でクラス、単一継承、遅延バインディング (ポリモーフィズム) を実装する方法について説明しています: http://www.state-machine.com/resources/cplus_3. 0_manual.pdf、サンプル コードはhttp://www.state-machine.com/resources/cplus_3.0.zipから入手できます。

于 2010-04-29T00:25:37.677 に答える
33

私はそれが行われるのを見てきました。私はそれをお勧めしません。C ++は元々、中間ステップとしてCコードを生成するプリプロセッサとしてこのように開始されました。

基本的に、最終的に行うことは、関数参照を格納するすべてのメソッドのディスパッチテーブルを作成することです。クラスを派生させるには、このディスパッチテーブルをコピーし、オーバーライドするエントリを置き換える必要があります。新しい「メソッド」は、基本メソッドを呼び出す場合は元のメソッドを呼び出す必要があります。最終的には、C++を書き直すことになります。

于 2008-12-09T04:04:02.880 に答える
27

確かにそれは可能です。これは、 GTK+GNOMEのすべてが基づいているフレームワークであるGObjectが行うことです。

于 2008-12-09T04:16:45.303 に答える
18

C stdio FILE サブライブラリは、純粋な C で抽象化、カプセル化、およびモジュール性を作成する方法の優れた例です。

継承とポリモーフィズム (OOP に不可欠と見なされることが多いその他の側面) は、それらが約束する生産性の向上を必ずしももたらすとは限らず、開発や問題領域についての思考を実際に妨げる可能性があるという合理的な 議論なされてきました。

于 2010-04-29T03:09:35.157 に答える
16

Animal と Dog の簡単な例: C++ の vtable メカニズムを (大部分はともかく) ミラーリングします。また、割り当てとインスタンス化 (Animal_Alloc、Animal_New) を分離して、malloc() を複数回呼び出さないようにします。thisまた、ポインターを明示的に渡す必要があります。

非仮想関数を実行する場合、それは些細なことです。それらをvtableに追加しないだけで、静的関数はthisポインターを必要としません。多重継承では一般に、あいまいさを解決するために複数の vtable が必要です。

また、setjmp/longjmp を使用して例外処理を実行できる必要があります。

struct Animal_Vtable{
    typedef void (*Walk_Fun)(struct Animal *a_This);
    typedef struct Animal * (*Dtor_Fun)(struct Animal *a_This);

    Walk_Fun Walk;
    Dtor_Fun Dtor;
};

struct Animal{
    Animal_Vtable vtable;

    char *Name;
};

struct Dog{
    Animal_Vtable vtable;

    char *Name; // Mirror member variables for easy access
    char *Type;
};

void Animal_Walk(struct Animal *a_This){
    printf("Animal (%s) walking\n", a_This->Name);
}

struct Animal* Animal_Dtor(struct Animal *a_This){
    printf("animal::dtor\n");
    return a_This;
}

Animal *Animal_Alloc(){
    return (Animal*)malloc(sizeof(Animal));
}

Animal *Animal_New(Animal *a_Animal){
    a_Animal->vtable.Walk = Animal_Walk;
    a_Animal->vtable.Dtor = Animal_Dtor;
    a_Animal->Name = "Anonymous";
    return a_Animal;
}

void Animal_Free(Animal *a_This){
    a_This->vtable.Dtor(a_This);

    free(a_This);
}

void Dog_Walk(struct Dog *a_This){
    printf("Dog walking %s (%s)\n", a_This->Type, a_This->Name);
}

Dog* Dog_Dtor(struct Dog *a_This){
    // Explicit call to parent destructor
    Animal_Dtor((Animal*)a_This);

    printf("dog::dtor\n");

    return a_This;
}

Dog *Dog_Alloc(){
    return (Dog*)malloc(sizeof(Dog));
}

Dog *Dog_New(Dog *a_Dog){
    // Explict call to parent constructor
    Animal_New((Animal*)a_Dog);

    a_Dog->Type = "Dog type";
    a_Dog->vtable.Walk = (Animal_Vtable::Walk_Fun) Dog_Walk;
    a_Dog->vtable.Dtor = (Animal_Vtable::Dtor_Fun) Dog_Dtor;

    return a_Dog;
}

int main(int argc, char **argv){
    /*
      Base class:

        Animal *a_Animal = Animal_New(Animal_Alloc());
    */
    Animal *a_Animal = (Animal*)Dog_New(Dog_Alloc());

    a_Animal->vtable.Walk(a_Animal);

    Animal_Free(a_Animal);
}

PS。これは C++ コンパイラでテストされていますが、C コンパイラで簡単に動作するはずです。

于 2008-12-09T10:45:21.587 に答える
13

これは興味深く読ませていただきました。私自身も同じ質問を熟考しており、それについて考える利点は次のとおりです。

  • OOP の概念を非 OOP 言語で実装する方法を想像しようとすると、OOP 言語 (私の場合は C++) の強みを理解するのに役立ちます。これにより、特定のタイプのアプリケーションに C と C++ のどちらを使用するかについて、より適切な判断を下すことができます。一方の利点が他方よりも重要な場合です。

  • これに関する情報と意見を求めて Web をブラウジングしていると、組み込みプロセッサ用のコードを書いていて、C コンパイラしか利用できない作者を見つけました 。 -C-Creating-Foundation-Classes-Part-1

彼の場合、プレーンな C で OOP の概念を分析して適応させることは、有効な追求でした。OOP の概念を C で実装しようとした結果、パフォーマンスのオーバーヘッドが発生したため、いくつかの OOP の概念を犠牲にすることを厭わなかったようです。

私が得た教訓は、はい、ある程度は可能であり、そうしようとする正当な理由がいくつかあるということです。

最終的に、マシンはスタック ポインタ ビットをいじり、プログラム カウンタをジャンプさせ、メモリ アクセス操作を計算します。効率の観点からは、プログラムによって実行されるこれらの計算が少ないほど良いのですが、人為的エラーの影響を最小限に抑える方法でプログラムを編成できるようにするために、単純にこの税金を支払わなければならない場合があります。OOP 言語コンパイラは、両方の側面を最適化しようとします。プログラマーは、これらの概念を C のような言語で実装することに、より注意を払う必要があります。

于 2011-06-16T20:56:28.297 に答える
13

GObjectをチェックしてください。これは、C の OO であり、探しているものの 1 つの実装であることを意図しています。ただし、本当に OO が必要な場合は、C++ またはその他の OOP 言語を使用してください。OO 言語の扱いに慣れていると、GObject を扱うのが非常に難しい場合がありますが、他のものと同様に、規則や流れに慣れることができます。

于 2010-04-28T19:53:40.463 に答える
11

使用できるテクニックはいくつかあります。最も重要なのは、プロジェクトを分割する方法です。プロジェクトでは、.h ファイルで宣言されたインターフェイスと、.c ファイルでのオブジェクトの実装を使用します。重要な部分は、.h ファイルを含むすべてのモジュールはオブジェクトのみを としてvoid *認識し、.c ファイルは構造の内部を知っている唯一のモジュールであるということです。

例として FOO と名付けたクラスは次のようになります。

.h ファイル内

#ifndef FOO_H_
#define FOO_H_

...
 typedef struct FOO_type FOO_type;     /* That's all the rest of the program knows about FOO */

/* Declaration of accessors, functions */
FOO_type *FOO_new(void);
void FOO_free(FOO_type *this);
...
void FOO_dosomething(FOO_type *this, param ...):
char *FOO_getName(FOO_type *this, etc);
#endif

C 実装ファイルはそのようなものになります。

#include <stdlib.h>
...
#include "FOO.h"

struct FOO_type {
    whatever...
};


FOO_type *FOO_new(void)
{
    FOO_type *this = calloc(1, sizeof (FOO_type));

    ...
    FOO_dosomething(this, );
    return this;
}

そのため、そのモジュールのすべての関数にオブジェクトへのポインターを明示的に渡します。C++ コンパイラはこれを暗黙的に行いますが、C では明示的に書き出します。

私は自分のプログラムで実際に使用thisして、プログラムが C++ でコンパイルされないようにし、構文強調表示エディターで別の色になるという優れた特性を持っています。

FOO_struct のフィールドは 1 つのモジュールで変更でき、別のモジュールを再コンパイルする必要さえありません。

そのスタイルで、私はすでに OOP (データのカプセル化) の利点の大部分を処理しています。関数ポインターを使用すると、継承などを簡単に実装できますが、正直なところ、ほとんど役に立ちません。

于 2010-04-28T20:26:05.913 に答える
10

Core Foundation の API セットに関する Apple のドキュメントを参照すると役立つ場合があります。これは純粋な C API ですが、型の多くは Objective-C オブジェクトの同等物にブリッジされています。

また、Objective-C 自体の設計を調べることも役立つ場合があります。オブジェクト システムが C 関数の観点から定義されている点で、C++ とは少し異なります。たとえばobjc_msg_send、オブジェクトのメソッドを呼び出します。コンパイラは角かっこの構文をこれらの関数呼び出しに変換するため、それを知る必要はありませんが、あなたの質問を考えると、それが内部でどのように機能するかを学ぶと役立つ場合があります。

于 2010-04-28T19:58:36.117 に答える
9

関数ポインタを使用して偽造することができます。実際、C++プログラムをCにコンパイルすることは理論的には可能だと思います。

ただし、パラダイムを使用する言語を選択するのではなく、言語にパラダイムを強制することはほとんど意味がありません。

于 2008-12-09T04:05:56.243 に答える
7

オブジェクト指向Cを実行できます。韓国でそのタイプのコードが作成されているのを見て、これは私がここ数年で見た中で最も恐ろしいモンスターでした(これは昨年(2007年)のようにコードを見ました)。そうです、そうすることができます、そしてそうです、人々は以前にそれをしました、そして今でもそれをします。ただし、C ++またはObjective-Cをお勧めします。どちらも、さまざまなパラダイムでオブジェクト指向を提供することを目的として、Cから生まれた言語です。

于 2008-12-09T04:09:17.983 に答える
7

はい、できます。C ++またはObjective-Cが登場する前は、人々はオブジェクト指向Cを書いていました。C ++とObjective-Cはどちらも、部分的には、Cで使用されているオブジェクト指向の概念の一部を取り入れて言語の一部として形式化しようとしたものです。

これは、メソッド呼び出しのように見える/メソッド呼び出しであるものを作成する方法を示す非常に単純なプログラムです(これを行うためのより良い方法があります。これは、言語が概念をサポートしていることの証明にすぎません)。

#include<stdio.h>

struct foobarbaz{
    int one;
    int two;
    int three;
    int (*exampleMethod)(int, int);
};

int addTwoNumbers(int a, int b){
    return a+b;
}

int main()
{
    // Define the function pointer
    int (*pointerToFunction)(int, int) = addTwoNumbers;

    // Let's make sure we can call the pointer
    int test = (*pointerToFunction)(12,12);
    printf ("test: %u \n",  test);

    // Now, define an instance of our struct
    // and add some default values.
    struct foobarbaz fbb;
    fbb.one   = 1;
    fbb.two   = 2;
    fbb.three = 3;

    // Now add a "method"
    fbb.exampleMethod = addTwoNumbers;

    // Try calling the method
    int test2 = fbb.exampleMethod(13,36);
    printf ("test2: %u \n",  test2);

    printf("\nDone\n");
    return 0;
}
于 2008-12-09T04:17:14.677 に答える
6

解決しようとしている問題に対してOOPアプローチが優れていると確信している場合、なぜOOP以外の言語で解決しようとしているのでしょうか。仕事に間違ったツールを使用しているようです。C++またはその他のオブジェクト指向Cバリアント言語を使用します。

Cで記述された既存の大規模なプロジェクトでコーディングを開始しているために質問している場合は、独自の(または他の誰かの)OOPパラダイムをプロジェクトのインフラストラクチャに強制しようとしないでください。プロジェクトにすでに存在するガイドラインに従ってください。一般に、クリーンなAPIと分離されたライブラリおよびモジュールは、クリーンなオブジェクト指向設計を実現するのに大いに役立ちます。

この後、本当にOOP Cを実行する準備ができている場合は、これを読んでください PDF)。

于 2010-04-28T19:43:35.487 に答える
6

もちろん、サポートが組み込まれている言語を使用するほどきれいではありません。「オブジェクト指向アセンブラ」も書きました。

于 2009-01-06T18:51:37.020 に答える
5

私はこれを1年間掘り下げてきました:

GObject システムは純粋な C では使いにくいため、C でオブジェクト指向スタイルを簡単にするためにいくつかの優れたマクロを作成しようとしました。

#include "OOStd.h"

CLASS(Animal) {
    char *name;
    STATIC(Animal);
    vFn talk;
};
static int Animal_load(Animal *THIS,void *name) {
    THIS->name = name;
    return 0;
}
ASM(Animal, Animal_load, NULL, NULL, NULL)

CLASS_EX(Cat,Animal) {
    STATIC_EX(Cat, Animal);
};
static void Meow(Animal *THIS){
    printf("Meow!My name is %s!\n", THIS->name);
}

static int Cat_loadSt(StAnimal *THIS, void *PARAM){
    THIS->talk = (void *)Meow;
    return 0;
}
ASM_EX(Cat,Animal, NULL, NULL, Cat_loadSt, NULL)


CLASS_EX(Dog,Animal){
    STATIC_EX(Dog, Animal);
};

static void Woof(Animal *THIS){
    printf("Woof!My name is %s!\n", THIS->name);
}

static int Dog_loadSt(StAnimal *THIS, void *PARAM) {
    THIS->talk = (void *)Woof;
    return 0;
}
ASM_EX(Dog, Animal, NULL, NULL, Dog_loadSt, NULL)

int main(){
    Animal *animals[4000];
    StAnimal *f;
    int i = 0;
    for (i=0; i<4000; i++)
    {
        if(i%2==0)
            animals[i] = NEW(Dog,"Jack");
        else
            animals[i] = NEW(Cat,"Lily");
    };
    f = ST(animals[0]);
    for(i=0; i<4000; ++i) {
        f->talk(animals[i]);
    }
    for (i=0; i<4000; ++i) {
        DELETE0(animals[i]);
    }
    return 0;
}

これが私のプロジェクト サイトです (英語のドキュメントを書く時間がありませんが、中国語のドキュメントの方がはるかに優れています)。

OOC-GCC

于 2011-12-05T12:02:27.227 に答える
4

セクション312プログラミングランチタイムセミナーで行われたJimLarsonの1996年の講演で、Cを使用した継承の例があります:高レベルCと低レベルC。

于 2008-12-09T04:12:48.697 に答える
4

OOP は、プログラム内のコードよりもデータを重要視するパラダイムにすぎません。OOP は言語ではありません。したがって、プレーン C が単純な言語であるように、プレーン C の OOP も単純です。

于 2012-01-15T23:50:59.277 に答える
4

やりたいことの 1 つは、 X Window用のXtツールキットの実装を調べることです。確かに長くなりつつありますが、使用されている構造の多くは、従来の C 内でオブジェクト指向のように機能するように設計されています。一般に、これはあちこちに間接的なレイヤーを追加し、構造を相互に重ねるように設計することを意味します。

このように、C にある OO の方法で実際に多くのことを行うことができます。そのように感じることもあるかもしれませんが、OO の概念は の心から完全に形成されたわけではありません#include<favorite_OO_Guru.h>。それらは、当時の確立されたベスト プラクティスの多くを構成していました。オブジェクト指向の言語とシステムは、当時のプログラミングの時代精神の一部を抽出して増幅しただけです。

于 2010-04-28T21:17:23.433 に答える
4

人々は C を使用して C++ スタイルをエミュレートしようとしているようです。私の見解では、C でオブジェクト指向プログラミングを行うことは、実際には構造体指向プログラミングを行うことです。ただし、遅延バインディング、カプセル化、継承などを実現できます。継承の場合、サブ構造体の基本構造体へのポインターを明示的に定義します。これは明らかに多重継承の形式です。また、

//private_class.h
struct private_class;
extern struct private_class * new_private_class();
extern int ret_a_value(struct private_class *, int a, int b);
extern void delete_private_class(struct private_class *);
void (*late_bind_function)(struct private_class *p);

//private_class.c
struct inherited_class_1;
struct inherited_class_2;

struct private_class {
  int a;
  int b;
  struct inherited_class_1 *p1;
  struct inherited_class_2 *p2;
};

struct inherited_class_1 * new_inherited_class_1();
struct inherited_class_2 * new_inherited_class_2();

struct private_class * new_private_class() {
  struct private_class *p;
  p = (struct private_class*) malloc(sizeof(struct private_class));
  p->a = 0;
  p->b = 0;
  p->p1 = new_inherited_class_1();
  p->p2 = new_inherited_class_2();
  return p;
}

    int ret_a_value(struct private_class *p, int a, int b) {
      return p->a + p->b + a + b;
    }

    void delete_private_class(struct private_class *p) {
      //release any resources
      //call delete methods for inherited classes
      free(p);
    }
    //main.c
    struct private_class *p;
    p = new_private_class();
    late_bind_function = &implementation_function;
    delete_private_class(p);

でコンパイルしc_compiler main.c inherited_class_1.obj inherited_class_2.obj private_class.objます。

したがって、純粋な C スタイルに固執し、C++ スタイルに強制しようとしないことをお勧めします。また、この方法は、API を構築する非常にクリーンな方法に役立ちます。

于 2014-11-05T20:10:46.167 に答える
4

質問に対する答えは「はい、できます」です。

オブジェクト指向 C (OOC) キットは、オブジェクト指向の方法でプログラミングしたいが、古き良き C にもこだわりたい人向けです。OOC は、クラス、単一および多重継承、例外処理を実装します。

特徴

• C マクロと関数のみを使用し、言語拡張は必要ありません! (ANSI-C)

• アプリケーションの読みやすいソース コード。物事をできるだけシンプルにするように注意が払われました。

• クラスの単一継承

• インターフェイスと mixin による多重継承 (バージョン 1.3 以降)

• 例外の実装 (純粋な C で!)

• クラスの仮想関数

• クラスの実装を容易にする外部ツール

詳細については、http://ooc-coding.sourceforge.net/を参照してください。

于 2012-03-27T08:27:21.633 に答える
4

C で OOP の概念を使用するのに適した記事や書籍はどれですか?

Dave Hanson のC Interfaces and Implementationsは、カプセル化と命名に優れており、関数ポインターの使用に非常に優れています。Dave は継承をシミュレートしようとしません。

于 2010-04-29T02:38:33.617 に答える
2

C の OOP のさらに別のひねりについては、 http://slkpg.byethost7.com/instance.htmlを参照してください。ネイティブ C のみを使用して、再入可能性のためにインスタンス データを強調しています。複数の継承は、関数ラッパーを使用して手動で行われます。型安全性は維持されます。ここに小さなサンプルがあります:

typedef struct _peeker
{
    log_t     *log;
    symbols_t *sym;
    scanner_t  scan;            // inherited instance
    peek_t     pk;
    int        trace;

    void    (*push) ( SELF *d, symbol_t *symbol );
    short   (*peek) ( SELF *d, int level );
    short   (*get)  ( SELF *d );
    int     (*get_line_number) ( SELF *d );

} peeker_t, SlkToken;

#define push(self,a)            (*self).push(self, a)
#define peek(self,a)            (*self).peek(self, a)
#define get(self)               (*self).get(self)
#define get_line_number(self)   (*self).get_line_number(self)

INSTANCE_METHOD
int
(get_line_number) ( peeker_t *d )
{
    return  d->scan.line_number;
}

PUBLIC
void
InitializePeeker ( peeker_t  *peeker,
                   int        trace,
                   symbols_t *symbols,
                   log_t     *log,
                   list_t    *list )
{
    InitializeScanner ( &peeker->scan, trace, symbols, log, list );
    peeker->log = log;
    peeker->sym = symbols;
    peeker->pk.current = peeker->pk.buffer;
    peeker->pk.count = 0;
    peeker->trace = trace;

    peeker->get_line_number = get_line_number;
    peeker->push = push;
    peeker->get = get;
    peeker->peek = peek;
}
于 2012-07-05T18:21:29.310 に答える
2

私はそれを試した小さなライブラリを構築しましたが、私にとっては本当にうまく機能します。だから私は経験を共有すると思いました。

https://github.com/thomasfuhringer/oxygen

単一の継承は、構造体を使用して非常に簡単に実装でき、それを他のすべての子クラスに拡張できます。親構造への単純なキャストにより、すべての子孫で親メソッドを使用できるようになります。変数がこの種のオブジェクトを保持する構造体を指していることがわかっている限り、いつでもルート クラスにキャストしてイントロスペクションを行うことができます。

前述したように、仮想メソッドはややトリッキーです。しかし、それらは実行可能です。簡単にするために、すべての子クラスが必要に応じて個々のスロットをコピーして再設定するクラス記述構造で関数の配列を使用するだけです。

多重継承は実装がかなり複雑になり、パフォーマンスに大きな影響を与えます。だから私はそれを残します。実際の状況をきれいにモデル化することは、かなりの数のケースで望ましく有用であると私は考えていますが、おそらく 90% のケースでは、単一継承がニーズをカバーしています。また、単一継承は単純でコストもかかりません。

また、型の安全性についても気にしません。プログラミングの間違いを防ぐために、コンパイラに頼るべきではないと思います。とにかく、それはエラーのごく一部からのみあなたを保護します。

通常、オブジェクト指向環境では、メモリ管理を可能な限り自動化するために参照カウントも実装する必要があります。そこで、「オブジェクト」ルート クラスに参照カウントを追加し、ヒープ メモリの割り当てと割り当て解除をカプセル化するいくつかの機能を追加しました。

それはすべて非常にシンプルで無駄がなく、C++ というモンスターに対処することを強制することなく、OO の本質を教えてくれます。また、私は C 言語にとどまるという柔軟性を保持しています。これにより、とりわけ、サードパーティ ライブラリの統合が容易になります。

于 2016-03-02T08:38:39.817 に答える
2

私はパーティーに少し遅れましたが、トピックに関する私の経験を共有したいと思います: 私は最近組み込みのものを扱っており、私が持っている唯一の (信頼できる) コンパイラは C であるため、オブジェクト指向を適用したいと考えています。 Cで書かれた私の組み込みプロジェクトでのアプローチ。

これまで見てきた解決策のほとんどは型キャストを多用しているため、型の安全性が失われています。間違いを犯した場合、コンパイラは役に立ちません。これは完全に容認できません。

私が持っている要件:

  • 型キャストをできるだけ避けて、型の安全性を失わないようにします。
  • ポリモーフィズム: 仮想メソッドを使用できる必要があり、クラスのユーザーは特定のメソッドが仮想であるかどうかを認識してはなりません。
  • 多重継承: 頻繁には使用しませんが、いくつかのクラスに複数のインターフェースを実装する (または複数のスーパークラスを拡張する) ことが本当に必要な場合があります。

私のアプローチについては、次の記事で詳しく説明しました。 C でのオブジェクト指向プログラミング。さらに、基本クラスと派生クラスのボイラープレート コードを自動生成するためのユーティリティがあります。

于 2015-03-19T01:10:54.430 に答える
1

CのスーパーセットであるObjective-Cを使用することを提案します。

Objective-Cは30年前のものですが、エレガントなコードを書くことができます。

http://en.wikipedia.org/wiki/Objective-C

于 2010-08-27T22:42:13.957 に答える
1

はい、可能です。

これは純粋な C であり、マクロの前処理はありません。継承、ポリモーフィズム、データのカプセル化 (プライベート データを含む) があります。同等の保護された修飾子がありません。これは、プライベート データが継承チェーンでもプライベートであることを意味します。

#include "triangle.h"
#include "rectangle.h"
#include "polygon.h"

#include <stdio.h>

int main()
{
    Triangle tr1= CTriangle->new();
    Rectangle rc1= CRectangle->new();

    tr1->width= rc1->width= 3.2;
    tr1->height= rc1->height= 4.1;

    CPolygon->printArea((Polygon)tr1);

    printf("\n");

    CPolygon->printArea((Polygon)rc1);
}

/*output:
6.56
13.12
*/
于 2012-07-24T10:05:25.717 に答える
0

はい、しかし私は誰もがCで何らかのポリモーフィズムを実装しようとするのを見たことがありません。

于 2008-12-09T04:06:03.843 に答える
0

最初に言うべきことは、(少なくとも私見では) C の関数ポインタの実装は本当に使いにくいということだと思います。関数ポインターを避けるために、たくさんのフープを飛び越えます...

そうは言っても、他の人が言ったことはかなり良いと思います。あなたは構造を持っています、あなたはモジュールを持ってfoo->method(a,b,c)method(foo,a,b,c) ますFOO_method(foo,a,b,c)。プライベートとパブリックなどを取得できる .h ファイルの

さて、このテクニックでは得られないことがいくつかあります。プライベート データ フィールドは提供されません。それは、意志力と優れたコーディングの衛生状態に関係していると思います...また、これで継承を行う簡単な方法はありません。

それらは少なくとも簡単な部分です...残りは、90/10のような状況だと思います. 利益の 10% には、作業の 90% が必要です。

于 2010-04-28T21:37:54.513 に答える