当前位置:  开发笔记 > 编程语言 > 正文

你能用C编写面向对象的代码吗?

如何解决《你能用C编写面向对象的代码吗?》经验,为你挑选了19个好方法。

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


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



1> mepcotterell..:

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


虽然本书中的概念是固体,但您将失去类型安全性.
在我们所知的设计模式之前,设计模式被称为"面向对象"; 与垃圾收集相同,等等.他们现在根深蒂固,我们往往会忘记,当他们第一次被设计时,它与我们今天所认为的设计模式大致相同
您可以直接从作者的网站上获取它:http://www.cs.rit.edu/~ats/books/ooc.pdf来自同一作者的其他论文:http://www.cs.rit.edu/~ats /books/index.html
这个rit.edu索引[**使用ANSI-C**进行面向对象编程]提供了正确的集合(Book +源代码示例)(https://ritdml.rit.edu/handle/1850/8544)
这本书是同行评审的吗?第一页第一段的第一句有错字。

2> paxdiablo..:

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

基本上,您使用a 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

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


封装非常容易,多态性是可行的 - 但遗传是棘手的
+1好例子!虽然如果有人真的想走这条路,那么"实例"结构更合适的是*单个字段*指向它们的"虚拟表"实例,在一个地方包含该类型的所有虚函数.即你的`tCommClass`将被重命名为`tCommVT`,而`tCommClass`结构只有数据字段和单个`tCommVT vt`字段指向"唯一的"虚拟表.在每个实例中携带所有指针会增加不必要的开销,并且更像是你在JavaScript中的工作方式而不是C++,恕我直言.
lwn.net最近发表了一篇题为[内核中的面向对象设计模式](http://lwn.net/SubscriberLink/444910/a13771395ae01342/)的文章,主题类似于上面的答案 - 也就是说,包含一个结构函数指针,或指向结构的指针,该结构具有使用我们正在处理的数据作为参数的指向结构的指针的函数.

3> nategoose..:

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

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


我喜欢为类提供结构的概念.但是通用的`Class`结构怎么样?这将使OO C*比C++更具动态性.那个怎么样?顺便说一句,+ 1.

4> Miro Samek..:

我相信除了有用之外,在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获得.


C +手册的新网址:http://www.state-machine.com/doc/cplus_3.0_manual.pdf

5> tvanfosson..:

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

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


或者,您最终可能会重写Objective C,这将是一个更有吸引力的结果.
"最终,你最终重写了C++"我想知道是否/担心会出现这种情况.
OOP没有类别的味道,例如[在Javascript](http://javascript.crockford.com/prototypal.html),其中大师说:"我们不需要类来制作许多类似的对象".但是我担心这在C中并不容易实现.但是(但是)还不能说明.(是否有克隆结构的clone()例程?)

6> Johannes Sch..:

当然有可能.这就是所有GTK +和GNOME所基于的框架GObject所做的.



7> msw..:

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

继承和多态-其他方面往往被认为OOP必不可少的-不一定提供他们承诺的生产力的提高和合理的 争论已经进行了,他们实际上可以阻碍发展和思考的问题域.



8> Jasper Bekke..:

动物与狗的简单例子:你镜像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编译器上运行.



9> NG...:

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



10> 小智..:

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

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

在我浏览网页以获取有关此信息和意见时,我找到了一位为嵌入式处理器编写代码的作者,并且只提供了一个C编译器:http: //www.eetimes.com/discussion/other/4024626/Object-Oriented -C-创建基金会-类-部分- 1

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

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

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



11> benzado..:

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

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



12> Patrick Schl..:

有几种技术可以使用.最重要的是如何拆分项目.我们在项目中使用一个接口,该接口在.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(数据封装)的很大一部分优势.通过使用函数指针,它甚至可以很容易地实现继承,但老实说,它实际上很少有用.


如果在标题中执行`typedef struct FOO_type FOO_type`而不是typedef为void,则可以获得类型检查的额外好处,同时仍然不会暴露您的结构.

13> Uri..:

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

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


第一个C++编译器就是这样做的 - 它将C++代码转换为等效(但丑陋且非人类可读)的C代码,然后由C编译器编译.
EDG,Cfront和其他一些人仍然能够做到这一点.有一个很好的理由:并非每个平台都有C++编译器.
您也可以使用LLVM和C后端执行相同的操作.

14> Robert Gould..:

面向对象的C,可以做到的,我见过这种类型的代码在生产在韩国,这是最可怕的怪物我看到在年(这是像去年(2007年),我看到的代码).所以,是的这是可以做到的,是的人已经做到了,并且仍然这样做,即使在这个时代.但我建议C++或Objective-C,由C出生都是语言,以提供面向对象的不同范式的目的.


如果Linus看到你的评论.他肯定会笑或诅咒你

15> RarrRarrRarr..:

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

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

如果,这一切后,你真的是做OOP C中,读这个(PDF).


没有真正回答这个问题......
@Brian&Tim Ring:关于某个主题的*书推荐*的问题; 我给了他一本专门针对这个主题的*书*的链接.我也就为什么解决问题的方法可能不是最优的(我认为这里的许多人似乎同意,基于投票和其他评论/答案)给出了我的看法.你对改进我的答案有什么建议吗?
PDF的链接似乎是关于这个主题的整本教科书...一个漂亮的证明,但它不符合边际......
是的,回答这个问题.询问如何以特定方式使用语言是完全有效的.没有其他语言的意见请求....
@Brian,PDF的链接似乎直接回答了问题,虽然我没有时间自己检查.

16> Alan Storm..:

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

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

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



17> Darron..:

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



18> 小智..:

要添加的小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;
}



19> 小智..:

我一直在挖这个一年:

由于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

推荐阅读
依然-狠幸福
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有