提问



你能用C编写面向对象的代码吗?特别是关于多态性。





另请参阅堆栈溢出问题 C 中的面向对象。

最佳参考


是。实际上,Axel Schreiner免费提供了他的书ANSI-C中的面向对象编程,它非常全面地介绍了这一主题。 [31]

其它参考1


既然你在谈论多态性,那么是的,你可以,我们在C ++出现之前的几年就做了那样的事情。


基本上你使用struct来保存数据和函数指针列表,以指向该数据的相关函数。


因此,在通信类中,您将进行开放,读取,写入和关闭调用,该调用将作为结构中的四个函数指针以及对象的数据维护,例如:


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 

// 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


所以你可以看到正在调用不同的函数,具体取决于子类。

其它参考2


命名空间通常通过以下方式完成:


stack_push(thing *)


代替


stack::push(thing *)


要使C结构成为类似C ++类的东西,你可以转:[32] [33]


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就像一个静态类变量 - 在一个类型的所有实例之间共享。所有方法都是静态的,除了有些人拿这个*

其它参考3


我相信除了有用之外,在C中实现OOP是亚博2018平台 OOP并理解其内部工作的绝佳方式。许多程序员的经验表明,要有效和自信地使用技术,程序员必须了解最终如何实现基础概念。在C中模拟类,继承和多态就是这样教的。


要回答原始问题,这里有几个资源,教授如何在C中进行OOP:


EmbeddedGurus.com博客文章基于对象的C语言编程展示了如何在便携式C中实现类和单继承:
http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c/[34]


应用说明C + - C语言中的面向对象编程展示了如何使用预处理器宏在C中实现类,单继承和后期绑定(多态):
http://www.state-machine.com/resources/cplus_3.0_manual.pdf,示例代码可从http://www.state-machine.com/resources/cplus_3.0.zip [35]获取]] [36]

其它参考4


我已经看过了。我不推荐它。 C ++最初是以这种方式开始的,它将C代码作为中间步骤生成。


基本上你最终要做的是为你存储函数引用的所有方法创建一个调度表。派生一个类需要复制这个调度表并替换你想要覆盖的条目,新的方法必须调用原始方法,如果它想要调用基本方法。最终,你最终重写了C ++。

其它参考5


当然有可能。这就是所有GTK +和GNOME所基于的框架GObject所做的。[37] [38] [39]

其它参考6


C stdio FILE子库是如何在纯粹的C中创建抽象,封装和模块化的一个很好的例子。


继承和多态 - 通常被认为对OOP至关重要的其他方面 - 不一定能提供他们所承诺的生产力增益,并且已经做出合理的论证,他们实际上可以阻碍开发和思考问题领域。[40] [41]]] [42]

其它参考7


动物和狗的简单例子:你镜像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编译器上工作。

其它参考8


看看GObject。它意味着在C中是OO,并且是你正在寻找的一个实现。如果您真的想要OO,请使用C ++或其他一些OOP语言。如果你习惯于处理OO语言,GObject可能真的很难处理,但就像任何事情一样,你会习惯惯例和流程。[43]

其它参考9


这很有趣。我自己一直在思考同样的问题,思考它的好处是这样的:



  • 试图想象如何用非OOP语言实现OOP概念有助于我理解OOp语言的优势(在我的例子中,C ++)。这有助于我更好地判断是否在给定类型的应用程序中使用C或C ++ - 其中一个的好处超过另一个。

  • 在我浏览网页以获取有关此信息和意见时,我找到了一位为嵌入式处理器编写代码并且只提供C编译器的作者:
    http://www.eetimes.com/discussion/other/4024626/Object-Oriented-C-Creating-Foundation-Classes-Part-1[44]



在他的案例中,分析和调整普通C中的OOP概念是一种有效的追求。由于尝试在C中实现它们导致的性能开销,他似乎愿意牺牲一些OOP概念。


我所接受的教训是,是的,它可以在一定程度上完成,是的,有一些很好的理由来尝试它。


最后,机器处理堆栈指针位,使程序计数器跳转并计算内存访问操作。从效率的角度来看,你的计划所做的这些计算越少越好......但有时我们必须支付这个税,这样我们才能以最不容易出现人为错误的方式组织我们的程序。 OOP语言编译器努力优化这两个方面。程序员必须更加谨慎地用C语言实现这些概念。

其它参考10


您可能会发现查看Apple的Core Foundation API集文档很有帮助。它是一个纯C API,但许多类型都是桥接到Objective-C对象的等价物。


您可能还会发现查看Objective-C本身的设计很有帮助。它与C ++略有不同,因为对象系统是根据C函数定义的,例如objc_msg_send来调用对象上的方法。编译器将方括号语法转换为那些函数调用,所以你不要我必须知道它,但考虑到你的问题,你可能会发现了解它是如何工作的有用的。

其它参考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 
...
#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的字段,并且甚至不需要重新编译另一个模块以使其仍然可用。


有了这种风格,我已经处理了OOP(数据封装)的很大一部分优势。通过使用函数指针,它甚至可以很容易地实现继承,但老实说,它实际上很少有用。

其它参考12


面向对象的C,可以完成,我已经看到了韩国生产的那种类型的代码,它是我多年来看到的最可怕的怪物(这就像去年(2007年)我看到的代码)。
所以是的,它可以做到,是的,人们之前已经做过,甚至在这个时代仍然这样做。但我推荐使用C ++或Objective-C,这两种语言都是C语言的诞生,其目的是提供具有不同范式的面向对象。

其它参考13


如果您确信OOP方法对于您要解决的问题更优越,那么您为什么要尝试使用非OOP语言来解决它?看起来你正在使用错误的工具来完成工作。使用C ++或其他一些面向对象的C变体语言。


如果您因为开始使用C编写的现有大型项目进行编码而要求,那么您不应该尝试将自己(或其他任何人)的OOP范例强制插入到项目的基础架构中。遵循指南已经存在于项目中。一般来说,干净的API和隔离的库和模块将大大有助于实现干净的OOP- ish 设计。


毕竟,如果你真的开始做OOP C,请阅读(PDF)。[45]

其它参考14


是的你可以。在C ++或Objective-C出现之前,人们正在编写面向对象的C语言。 C ++和Objective-C在某些方面都试图采用C中使用的一些OO概念,并将它们形式化为语言的一部分。[46]


这是一个非常简单的程序,它展示了如何制作看起来像/是方法调用的东西(有更好的方法来实现这一点。这只是语言支持概念的证据):


#include

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

其它参考15


当然,它只是不像使用内置支持的语言一样漂亮。我甚至写过面向对象的汇编程序。

其它参考16


要添加的小OOC代码:


#include 

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

其它参考17


你可以使用函数指针伪造它,事实上,我认为理论上可以将C ++程序编译成C.


但是,强制使用语言范式而不是选择使用范例的语言很少有意义。

其它参考18


我已经挖了一年了:


由于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,但是中文文档要好得多)。


OOC-GCC [47]

其它参考19


在Jim Larson 1996年在312节目午餐时间研讨会上发表的演讲中有一个继承的例子:高级和低级C。[48] [49]

其它参考20



??哪些文章或书籍在C中使用OOP概念是好的?



Dave Hanson的C接口和实现在封装和命名方面非常出色,并且在使用函数指针方面非常出色.Dave不会尝试模拟继承。[50]

其它参考21


OOP只是一种范例,它使数据比程序中的代码更重要。 OOP不是一种语言。因此,就像普通C是一种简单的语言一样,普通C中的OOP也很简单。

其它参考22


您可能想要做的一件事是研究X Window工具包的实现。当然它在牙齿中变长了,但是许多使用的结构被设计成在传统的C中以OO方式工作。通常这意味着在这里和那里添加额外的间接层并设计相互叠加的结构。[51] [52]


你可以通过这种方式对OO位于C的方式做很多事情,即使它感觉有点像,但是OO概念并没有从#include的思想中完全形成。它们确实构成了当时许多既定的最佳实践。 OO语言和系统只是提炼和放大了当时编程时代精神的一部分。

其它参考23


这个问题的答案是是的,你可以。


面向对象的C(OOC)工具包适用于那些希望以面向对象的方式进行编程的人,但也适用于那些优秀的旧C语言。 OOC实现了类,单个和多个继承,异常处理。


功能


?仅使用C宏和函数,不需要语言扩展! (ANSI-C)


?易于阅读的应用程序源代码。注意尽可能简化事情。


?单继承类


?接口和mixin的多重继承(自1.3版本起)


?实现异常(纯C!)


?类的虚函数


?外部工具,便于实现类


有关详细信息,请访问http://ooc-coding.sourceforge.net/。[53]

其它参考24


看起来人们正在尝试使用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的非常简洁的方法。

其它参考25


请参阅http://slkpg.byethost7.com/instance.html,了解C中OOP的另一个转折。它强调使用本机C进行重入的实例数据。多重继承是使用函数包装器手动完成的。保持类型安全。这是一个小样本:[54]


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

其它参考26


我有点迟到了,但我想分享我对这个主题的经验:我现在使用嵌入式东西,而我唯一的(可靠的)编译器是C,所以我想应用面向对象的方法在用C编写的嵌入式项目中


到目前为止我见过的大多数解决方案都使用了大量的类型转换,因此我们失去了类型安全性:如果你犯了错误,编译器不会帮助你。这是完全不可接受的。


我的要求:



  • 尽可能避免使用类型转换,因此我们不会失去类型安全性;

  • 多态性:我们应该能够使用虚方法,并且类的用户不应该知道某个特定方法是否是虚拟的;

  • 多重继承:我不经常使用它,但有时我真的想要一些类来实现多个接口(或扩展多个超类)。



我已经在本文中详细解释了我的方法:C语言中的面向对象编程;此外,还有一个用于自动生成基类和派生类的样板代码的实用程序。[55]

其它参考27


我建立了一个小图书馆,在那里我尝试过,对我来说它很有效。所以我想我分享经验。


https://github.com/thomasfuhringer/oxygen[56]


单继承可以使用结构很容易地实现,并为每个其他子类扩展它。对父结构的简单转换使得可以在所有后代上使用父方法。
只要您知道变量指向包含此类对象的结构,您就可以始终转换为根类并进行内省。


如前所述,虚拟方法有点棘手。但他们是可行的。为了简单起见,我只使用类描述结构中的一系列函数,每个子类复制并在需要时重新填充各个插槽。


多重继承实现起来相当复杂,并且会产生显着的性能影响。所以我离开了。我认为在很多情况下干净地模拟现实生活环境是可取和有用的,但在大多数情况下,单一继承可以满足需求。单一继承很简单,没有任何成本。


我也不关心类型安全。我认为你不应该依赖编译器来防止编程错误。而且无论如何它只会掩盖一小部分错误。


通常,在面向对象的环境中,您还希望实现引用计数,以尽可能自动化内存管理。所以我还将引用计数放入对象根类和一些封装堆内存分配和释放的功能。


这一切都非常简单和精益,给了我OO的基本要素,而不是强迫我处理C ++的怪物。而且我保留了留在C land的灵活性,除此之外,它还可以更容易地集成第三方库。

其它参考28


我建议使用Objective-C,它是C的超集。


虽然Objective-C已有30年历史,但它允许编写优雅的代码。


http://en.wikipedia.org/wiki/Objective-C[57]

其它参考29


是的,但我从未见过有人试图用C实现任何类型的多态性。