Cでオブジェクト指向のコードをどのように記述しますか?[閉まっている]


500

Cでオブジェクト指向コードを書く方法は何ですか?特に多型に関して。


このStack Overflowの質問Cのオブジェクト指向も参照してください。


1
<a href=" ldeniau.web.cern.ch/ldeniau/html/oopc.html"> Cのオブジェクト指向プログラミング</a> by Laurent Deniau


25
@Camilo Martin:私は意図的にできないはずだと尋ねました。私は実際にCでOOPを使用することに興味はありません。しかし、CでOOソリューションを見ると、Cの制限や柔軟性、さらにはポリモーフィズムを実装して使用するための創造的な方法について学ぶことができます。
Dinah

5
OOは単なるパターンです。ここを確認してください。それは.batファイル でも行うことができます:dirk.rave.org/chap9.txt(十分な興味があれば、どのプログラミング言語にも任意のパターンを適用できます)。しかし、これは考え抜かれた食べ物です。そして、おそらく私たちがそれらを持たない言語に当たり前のように取るこのようなパターンを適用することから多くを学ぶことができます。
Camilo Martin

6
GTK-'すみません、GObject-は、実際にはCでのOOP(sorta)のかなり良い例です。
new123456 2010

回答:


362

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


28
この本の概念はしっかりしていますが、タイプセーフを失うことになります。
ダイアピル

22
デザインパターンとして知られるようになる前は、「オブジェクト指向」と呼ばれるデザインパターンでした。ガベージコレクションなどと同じです。それらは現在非常に根付いており、私たちは忘れがちですが、それらが最初に考案されたとき、それは今日のデザインパターンと私たちが考えるものとほとんど同じでした
Dexygen

11
著者のサイトから直接入手できます:cs.rit.edu/~ats/books/ooc.pdf 同じ著者の他の論文:cs.rit.edu/~ats/books/index.html
pakman

10
適切なコレクション(本+ソースコードの例)は、このrit.eduインデックスから入手できます。ANSI -Cを
David

3
この本は査読済みですか?最初のページの最初の段落の最初の文にタイプミスがあります。
ダグルームズ2015

343

あなたは多態性について話しているので、はい、できます。C++が登場する数年前に、私たちはそのようなことを行っていました。

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

したがって、通信クラスでは、次のようなオブジェクトのデータとともに、構造内の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

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


52
カプセル化は非常に簡単で、ポリモーフィズムは実行可能ですが、継承はトリッキーです
Martin Beckett

5
lwn.netは最近、上記の回答と同様の構造体をテーマに、カーネル内のオブジェクト指向設計パターンというタイトルの記事を公開しました。つまり、関数ポインターを含む構造体、またはパラメータとして使用しているデータを持つ構造体。
ラジカルマット

11
+1良い例!誰もが本当にこの道を進みたければ、「インスタンス」構造体が「仮想テーブル」インスタンスを指す単一のフィールドを持ち、そのタイプのすべての仮想関数を1か所に含む方が適切です。つまり、tCommClass名前はに変更されtCommVTtCommClass構造体にはデータフィールドとtCommVT vt「唯一の」仮想テーブルを指す単一のフィールドのみが含まれます。各インスタンスですべてのポインタを運ぶと、不要なオーバーヘッドが追加され、C ++やIMHOよりもJavaScriptで行う方法に似ています。
Groo、2013年

1
これは単一のインターフェースの実装を示していますが、複数のインターフェースの実装についてはどうでしょうか?または多重継承?
weberc2 14

Weber、C ++のすべての機能が必要な場合は、おそらくC ++を使用する必要があります。質問は、ポリモーフィズム、オブジェクトが異なる「形」を取る能力について具体的に尋ねました。Cでインターフェイスと多重継承を実行することは確かに可能ですが、それはかなりの追加作業であり、C ++組み込みのものを使用するのではなく、自分でスマートを管理する必要があります。
paxdiablo 2014

86

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

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 *


1
@nategoose- st->my_type->push(st, thing2);代わりにst->my_type.push(st, thing2);
Fabricio

@nategoose:または struct stack_type my_type;代わりにstruct stack_type * my_type;
Fabricio

3
クラスの構造体を持つというコンセプトが気に入っています。しかし、一般的なClass構造体はどうですか?それはOO Cはなるだろうより C ++よりもダイナミック。どのようにそのことについて?ちなみに+1。
Linuxios 2012

54

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

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

EmbeddedGurus.comのブログ記事「Cでのオブジェクトベースのプログラミング」は、ポータブルCでクラスと単一継承を実装する方法を示しています。http//embeddedgurus.com/state-space/2008/01/object-based-programming-in-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から入手できます


4
C +マニュアルの新しいURL:state-machine.com/doc/cplus_3.0_manual.pdf
Liang

32

私はそれが終わったのを見てきました。私はそれをお勧めしません。C ++は最初、この方法を、中間ステップとしてCコードを生成するプリプロセッサーとして開始しました。

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


5
「結局、あなたは結局C ++を書き直すことになる」そうなるのではないかと恐れた。
ダイナ

39
または、はるかに魅力的な結果になるObjective Cを書き換えることになるかもしれません。
ファルケン教授の契約が2010年

3
JavaScriptのように、クラスを必要としないOOPのフレーバーがあり、「同様のオブジェクトを多数作成するのにクラスは必要ありません」とグルが言っています。しかし、Cでこれを実現するのは簡単ではないのではないかと心配しています。(構造体を複製するclone()ルーチンはありますか?)
Lumi

1
実際にそれを実装し、その実装を高速にする(Google、V8エンジン)必要があった別の賢い人たちは、JavaScriptに(非表示の)クラスを追加するすべてを実行しました。
cubuspl42 2013

glibCで客観的に書かれていませんか?
クラベミール2018

26

もちろん可能です。これは、GTK +GNOMEのすべてがベースにしているフレームワークであるGObjectが行っていることです。


そのようなアプローチの長所/短所は何ですか?つまり。C ++を使用して書く方がはるかに簡単です。
クラベミール2018

@kravemirまあ、C ++はCほど移植性が低く、C ++を別のC ++コンパイラーでコンパイルされる可能性のあるコードにリンクするのは少し難しいです。しかし、はい。C++でクラスを記述する方が簡単ですが、GObjectもそれほど難しくありません(少しボイラープレートを気にしないと仮定した場合)。
エドウィンバック

17

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

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


stdioはカーネルレイヤーで抽象化されていませんか?私が間違っていない場合、Cライブラリはそれらを文字ファイル/デバイスとして扱い、カーネルドライバーが仕事をします...
kravemir

15

動物と犬の簡単な例: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コンパイラで動作させるのは簡単です。


typedef内部のstructC.は不可能である
マソウド

13

GObjectをチェックしてください。それはCのオブジェクト指向であり、あなたが探しているものの1つの実装です。ただし、本当にOOが必要な場合は、C ++またはその他のOOP言語を使用してください。OO言語の扱いに慣れている場合、GObjectを使用するのは非常に難しい場合がありますが、他の方法と同様に、規則とフローに慣れます。


12

これは興味深い内容です。私は同じ質問を自分で考えていましたが、それについて考えることの利点は次のとおりです。

  • 非OOP言語でOOPの概念を実装する方法を想像してみると、OOp言語(私の場合はC ++)の長所を理解するのに役立ちます。これにより、特定の種類のアプリケーションでCを使用するかC ++を使用するかについて、より良い判断を下すことができます。

  • これに関する情報と意見をウェブで閲覧したところ、組み込みプロセッサ用のコードを書いていて、Cコンパイラしか利用できない作成者を見つけました。http//www.eetimes.com/discussion/other/4024626/Object-Oriented -C-Creating-Foundation-Classes-Part-1

彼の場合、プレーンなCでOOPの概念を分析し、適合させることは有効な追求でした。Cで実装しようとしたことによるパフォーマンスオーバーヘッドの影響により、OOPの概念を犠牲にしていたようです。

私が受けた教訓は、確かにある程度は可能であり、そうすることを試みるにはいくつかの正当な理由があります。

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


10

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

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


10

使用できるテクニックはいくつかあります。最も重要なのは、プロジェクトを分割する方法です。.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(データのカプセル化)の利点の大部分をすでに処理しています。関数ポインタを使用することで、継承などを実装することも簡単ですが、正直なところ、それが実際に役立つことはほとんどありません。


6
その場合はtypedef struct FOO_type FOO_type、ヘッダ内の空隙へのtypedefのではなく、まだあなたの構造を露出していない間、あなたは、型チェックの追加利益を得ます。
スコットウェールズ

8

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

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


5
まさに最初のC ++コンパイラはまさにそれを行いました-それはC ++コードを同等の(しかし醜くて人間が読めない)Cコードに変換し、それはその後Cコンパイラによってコンパイルされました。
Adam Rosenfield、

2
EDG、Cfrontおよび他のいくつかは、まだこれを行うことができます。非常に正当な理由があります。すべてのプラットフォームにC ++コンパイラがあるわけではありません。
Jasper Bekkers 2008

何らかの理由で、C-frontは特定のC ++拡張(たとえば、参照)のみをサポートしていて、完全なOOP /動的ディスパッチエミュレーションはサポートしていないと思いました。
ウリ

2
LLVMとCバックエンドでも同じことができます。
Zifre 2009年

7

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


3
Linusがあなたのコメントを見た場合。彼は間違いなく笑うか、あなた
アンダースリンド

7

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

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

結局のところ、あなたが本当にOOP Cを始めることにしているなら、これ(PDF)を読んでください。


36
実際には質問に答えていません...
Brian Postow 2010

2
@ Brian、PDFへのリンクは質問に直接答えるように見えますが、自分で確認する時間はありませんでした。
Mark Ransom

5
PDFへのリンクは、主題に関する完全な教科書のように見えます...美しい証明ですが、マージンに収まりません...
Brian Postow

5
はい、質問に答えます。特定の方法で言語を使用する方法を尋ねることは完全に有効です。他の言語についての意見はありませんでした...
ティムリング

9
@ブライアン&ティムリング:トピックに関する本の推奨事項を尋ねる質問。私は彼に具体的にこのトピックを扱っているへのリンクを与えました。また、問題へのアプローチが最適ではない理由についても意見を述べました(投票やその他のコメント/回答に基づいて、ここにいる多くの人々が同意しているようです)。私の答えを改善するための提案はありますか?
RarrRarrRarr 2010

6

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

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

#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;
}

6

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


6

追加する小さなOOCコード:

#include <stdio.h>

struct Node {
    int somevar;
};

void print() {
    printf("Hello from an object-oriented C method!");
};

struct Tree {
    struct Node * NIL;
    void (*FPprint)(void);
    struct Node *root;
    struct Node NIL_t;
} TreeA = {&TreeA.NIL_t,print};

int main()
{
    struct Tree TreeB;
    TreeB = TreeA;
    TreeB.FPprint();
    return 0;
}

5

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

GObjectシステムは純粋なCでは使いづらいので、CでOOスタイルを簡単にするために、いくつかの素晴らしいマクロを書いてみました。

#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;
}

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

OOC-GCC


CLASS STATIC ASM NEW DELETE ST ... OOC-GCCでマクロである
dameng



4

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


3
よく言ったが、これはコメントでなければなりません。
pqsk 2015年

4

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

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


4

質問への答えは「はい、できます」です。

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

特徴

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

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

•クラスの単一継承

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

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

•クラスの仮想関数

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

詳細については、http://ooc-coding.sourceforge.net/にアクセスしてください。


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を構築する非常にクリーンな方法に役立ちます。


継承の場合、通常、基本クラスまたはインスタンス構造は派生クラスに埋め込まれ、個別に割り当てられたり、ポインターを使用して参照されたりすることはありません。このように、最上位のベースは常にその派生型の構造の先頭にあるため、簡単に相互にキャストできます。これは、オフセットにある可能性のあるポインターでは実行できません。
underscore_d

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;
}

2

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

これまでに見たほとんどのソリューションはタイプキャストを多用しているため、タイプセーフが失われます。間違いを犯した場合、コンパイラーは役に立ちません。これはまったく受け入れられません。

私が持っている要件:

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

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


2

私はそれを試してみた小さなライブラリを構築しました。だから私は経験を共有すると思いました。

https://github.com/thomasfuhringer/oxygen

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

すでに述べたように、仮想メソッドはややトリッキーです。しかし、彼らは実行可能です。物事をシンプルに保つために、クラスの説明構造で関数の配列を使用します。これは、すべての子クラスが必要に応じて個々のスロットをコピーして再投入します。

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

また、タイプセーフについても気にしません。プログラミングの間違いを防ぐためにコンパイラーに依存すべきではないと私は思います。とにかく、それはエラーのかなり小さな部分からのみあなたを守ります。

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

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


1

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

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

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


その場合、実際にはオブジェクト指向なので、代わりにC ++をお勧めします...
yyny

これは答えではありません。しかし、とにかく、@ YoYoYonnY:私はObjective-Cを使用せず、C ++を使用していますが、そのようなコメントは根拠なしでは役に立たず、何も提供していません。Objective-Cが「実際にオブジェクト指向である」とは言えないのはなぜだと思いますか?そして、なぜObjective-Cが失敗したのにC ++が成功するのですか?おもしろいのは、Objective-Cは文字通りその名前にObjectという単語が含まれているのに対し、C ++はそれ自体をOOP言語ではなくマルチパラダイム言語として販売している(つまり、主にOOPではなく、極端なフォークビューではOOPではない)まったく)...だから、あなたはそれらの名前を間違った方法で取得していないのですか?
underscore_d

0

はい。しかし、Cを使用してポリモーフィズムを実装しようとする人を見たことがありません。


6
もっと見回す必要があります:)たとえば、MicrosoftのDirect XにはポリモーフィックCインターフェイスがあります。
AShelly 2008

8
たとえば、Linuxカーネルの実装を調べます。これは、Cで非常に一般的で広く使われている練習である
イリヤ

3
また、glibはポリモーフィックであるか、またはポリモーフィズムを許可する方法で使用できます(これはC ++のように、どの呼び出しが仮想であるかを明示的に言わなければなりません)
Spudd86

1
Cでは多型はそれほど珍しくありませんが、多重継承はまれです。
JohanBjäreholt18年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.