一个人如何用C编写面向对象的代码?[关闭]


499

用C编写面向对象的代码有哪些方法?特别是关于多态性。


另请参见此堆栈溢出问题,以C语言中的面向对象问题。


1
Laurent Deniau的<a href=" ldeniau.web.cern.ch/ldeniau/html/oopc.html"> C语言中的面向对象编程</a>


25
@Camilo马丁:我故意问不能。我实际上对在C中使用OOP并不感兴趣。但是,通过查看C中的OO解决方案,我/我们将了解更多关于C的局限性和/或灵活性,以及​​有关实现和使用多态性的创新方法的更多信息。
黛娜2010年

5
OO只是一种模式。在这里检查,甚至可以在.bat文件: dirk.rave.org/chap9.txt中完成(我想,如果您足够感兴趣的话,任何模式都可以应用于任何编程语言)。不过,这是值得深思的食物。通过将这样的模式应用到我们认为不存在的语言上,可能会学到很多东西。
卡米洛·马丁

6
GTK-'打扰我了,GObject-实际上是C语言中OOP(排序)的一个很好的例子。
new123456 2010年

Answers:


361

是。实际上,Axel Schreiner免费提供了他的书 “ ANSI-C中的面向对象程序设计”,其中相当全面地涵盖了该主题。


28
尽管本书中的概念是实体,但您将失去类型安全性。
diapir 2009年

22
在我们称为设计模式之前,是称为“面向对象”的设计模式。与垃圾收集相同,其他类似。现在,它们是如此根深蒂固,我们往往会忘记,当它们最初被设计时,其方式与我们今天所认为的设计模式
几乎相同-Dexygen

11
您可以直接从作者的网站获得它:cs.rit.edu/~ats/books/ooc.pdf 同一作者的其他论文:cs.rit.edu/~ats/books/index.html
pakman 2012年

10
可以从此rit.edu索引获得适当的集合(书籍+源代码示例) 与ANSI-C面向对象的编程
大卫C.兰金

3
这本书是同行评审的吗?第一页第一段的第一句有错字。
新郎

343

因为您正在谈论多态性,所以可以,我们可以在C ++出现之前的几年中进行此类工作。

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

因此,在通信类中,您将拥有一个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

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


52
封装非常容易,多态是可行的-但是继承却很棘手
Martin Beckett 2010年

5
lwn.net最近在内核上发表了一篇标题为“ 面向对象设计模式 ”的文章,涉及类似于上述答案的结构-即,包含函数指针的结构,或指向具有将指针指向函数的结构的结构的指针。使用我们正在处理的数据作为参数的struct。
radicalmatt

11
+1好例子!尽管如果有人真的想走这条路,那么“实例”结构只有一个指向其“虚拟表”实例的字段会更合适,该实例包含该类型的所有虚拟函数。也就是说,您tCommClass将被重命名为tCommVT,而一个tCommClass结构将仅具有数据字段和tCommVT vt指向“一个且唯一的”虚拟表的单个字段。与每个实例一起携带所有指针会增加不必要的开销,并且比起C ++,恕我直言,您在JavaScript中的工作方式更多。
Groo

1
因此,这说明了单个接口的实现,但是实现多个接口又如何呢?还是多重继承?
weberc2 2014年

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

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

32

我已经看到了。我不推荐它。C ++最初以这种方式作为预处理器开始,该预处理器生成C代码作为中间步骤。

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


5
“最终,您最终将重写C ++”,我想知道是否/害怕如此。
黛娜2010年

39
或者,您可能最终重写了目标C,这将是一个更具吸引力的结果。
Falken教授的合同

3
OOP具有无类的OOP风格,例如Javascript中的专家说:“我们不需要类来制作许多相似的对象。” 但是我担心在C语言中要实现这一目标并不容易。不过,现在还不能说清楚。(是否有一个clone()例程来克隆一个结构?)
Lumi

1
另一个聪明的家伙,必须实际实现该目标并使其快速实施(Google,V8引擎),他们所做的一切都确实向JavaScript添加(隐藏)了类。
cubuspl42

难道不是glib用C语言写的吗?
克拉韦米尔,

26

当然可以。这就是GObject,所有GTK +GNOME都基于的框架。


这种方法的优点/缺点是什么?就是 使用C ++编写它要容易得多。
克拉韦米尔,

@kravemir好吧,C ++并不像C那样可移植,因此将C ++链接到可能由其他C ++编译器编译的代码要困难一些。但是,是的,用C ++编写类更容易,尽管GObject也不是那么困难(假设您不介意一点样板)。
Edwin Buck

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内的struct在C是不可能的
马苏德

13

GObject。它的意思是成为C语言中的OO,以及您所需要的一种实现。如果您确实想要OO,请使用C ++或其他OOP语言。如果您习惯于使用OO语言,有时有时很难使用GObject,但是像任何东西一样,您将习惯于约定和流程。


12

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

  • 尝试想象如何以非OOP语言实现OOP概念,这有助于我理解OOp语言(在我的情况下为C ++)的优势。这有助于使我更好地判断对于给定类型的应用程序是使用C还是C ++,在这种情况下,一个应用程序的收益大于另一个应用程序的收益。

  • 在浏览网络以获取有关此方面的信息和观点时,我发现一位作者正在为嵌入式处理器编写代码,并且只有一个C编译器可用:http : //www.eetimes.com/discussion/other/4024626/Oriented -C-Creation-Foundation-Classes-Part-1

在他的案例中,分析和适应纯C语言中的OOP概念是一个正确的追求。似乎他愿意牺牲一些OOP概念,因为尝试在C中实现它们会降低性能开销。

我吸取的教训是,是的,可以做到一定程度,是的,有一些充分的理由尝试它。

最终,机器将旋转堆栈指针位,使程序计数器跳来跳去并计算内存访问操作。从效率的角度来看,您的程序执行的计算越少越好……但是有时我们仅需支付此税,就可以以最不易受人为错误影响的方式组织程序。OOP语言编译器致力于优化这两个方面。程序员必须更加小心地用C之类的语言来实现这些概念。


10

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

您可能还会发现对Objective-C本身的设计有所帮助。与C ++的不同之处在于,对象系统是根据C函数定义的,例如objc_msg_send,调用对象上的方法。编译器将方括号语法转换为这些函数调用,因此您不必了解它,但是考虑到您的问题,您可能会发现了解它在幕后的工作方式很有用。


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的字段,而无需重新编译另一个模块即可使用。

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


6
如果您不typedef struct FOO_type FOO_type使用typedef来使标头中的内容无效,那么您将获得类型检查的额外好处,同时仍不暴露结构。
斯科特·威尔士

8

您可以使用函数指针进行伪造,实际上,我认为理论上可以将C ++程序编译为C。

但是,将某种范式强加于一种语言而不是选择一种使用范式的语言几乎是没有道理的。


5
最早的C ++编译器正是这样做的-将C ++代码转换为等效的(但丑陋且难以理解的)C代码,然后由C编译器进行编译。
亚当·罗森菲尔德

2
EDG,Cfront和其他一些公司仍然可以做到这一点。有一个很好的理由:并非每个平台都有C ++编译器。
贾斯珀·贝克斯

由于某种原因,我认为C-front仅支持某些C ++扩展(例如,引用),而不支持完整的OOP /动态调度仿真。
Uri

2
您也可以使用LLVM和C后端执行相同的操作。
Zifre

7

可以实现面向对象的C,我已经在韩国看到过这种类型的代码,这是我多年来见过的最可怕的怪物(就像去年(2007年)我看到的代码)。所以是可以做到的,是的,人们以前曾经做过,而且即使在当今时代也仍然这样做。但是我建议使用C ++或Objective-C,它们都是C语言诞生的,目的是为对象提供不同的范例。


3
如果Linus看到您的评论。他肯定会笑或诅咒您
Anders Lind

7

如果您确信OOP方法可以解决您要解决的问题,那么为什么要使用非OOP语言来解决呢?看来您使用的工具不正确。使用C ++或其他一些面向对象的C变体语言。

如果您因开始使用C编写的现有大型项目进行编码而提出要求,则不应尝试将自己(或其他任何人)的OOP范式强加到项目的基础架构中。遵循项目中已经存在的准则。在一般情况下,清洁API和隔离库和模块将很长的路要走朝着具有净OOP- 十岁上下的设计。

如果毕竟,您确实准备好进行OOP C,请阅读(PDF)。


36
没有真正回答这个问题……
Brian Postow

2
@Brian,指向PDF的链接似乎可以直接回答问题,尽管我没有时间检查自己。
Mark Ransom

5
PDF链接似乎是一本关于该主题的完整教科书...一个漂亮的证明,但并不合适...
Brian Postow 2010年

5
是的,回答问题。询问如何以特定方式使用语言是完全有效的。有对其他语言的意见不要求....
蒂姆环

9
@Brian&Tim Ring:这个问题要求有关某个主题的书籍推荐;我给他一个指向专门解决该主题的的链接。我还提出了关于为什么解决问题的方法可能不是最佳方法的意见(我认为,根据投票和其他评论/答案,这里的很多人似乎都同意这一观点)。您对改善我的答案有什么建议吗?
RarrRarrRarr

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

我已经挖掘了一年:

由于纯C语言很难使用GObject系统,因此我尝试编写一些不错的宏来简化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更好。)

海湾合作委员会


类的静态ASM新的删除ST ...都在OOC-GCC宏
大锰



4

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


3
说得好,但这应该是评论。
pqsk

4

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

您确实可以通过这种方式在C中放置OO的方式做很多事情,尽管有时感觉OO概念并没有完全从的思想中形成#include<favorite_OO_Guru.h>。它们确实构成了当时许多公认的最佳实践。OO语言和系统仅蒸馏和放大了当天编程的时代精神。


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%的情况下,单继承可以满足需求。单一继承很简单,并且不花任何钱。

我也不在乎类型安全。我认为您不应该依赖编译器来防止编程错误。无论如何,它只保护您避免一小部分错误。

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

这一切都非常简单和精益,它为我提供了OO的基本知识,而没有强迫我处理C ++这个怪兽。而且,我保留了留在C地带的灵活性,这尤其使集成第三方库变得更加容易。


1

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

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

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


在那种情况下,我建议使用C ++,因为它实际上是面向对象的……
yyny

这不是答案。但是无论如何,@YoYoYonnY:我不使用Objective-C而是使用C ++,但是没有基础就没有这样的注释,而您没有提供任何注释。为什么您声称Objective-C缺乏“实际上是面向对象的……”?为什么C ++在Objective-C失败的地方成功?有趣的是,Objective-C 的名字确实有“ Object ”一词,而C ++却将自己作为一种多范式语言进行市场营销,而不是一种OOP语言(即,主要不是OOP,在某些极端的人看来不是OOP)。根本没有...)所以您确定没有以错误的方式获得这些名称吗?
underscore_d

0

是的,但是我从未见过有人尝试使用C实现任何形式的多态性。


6
您需要查看更多信息:)例如,Microsoft的Direct X具有多态C接口。
AShelly

8
以Linux内核实现为例。这是在C中非常普遍且广泛使用的做法
。– Ilya

3
glib也是多态的,或者可以以允许多态的方式使用(就像C ++,您必须明确地说出哪些调用是虚拟的)
Spudd86 2010年

1
在C中,多态并不罕见,另一方面,多重继承却很常见。
JohanBjäreholt18年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.