你能用C编写面向对象的代码吗?特别是关于多态性.
另请参阅堆栈溢出问题C中的面向对象问题.
是.实际上,Axel Schreiner免费提供了他的书 "ANSI-C中的面向对象编程",它非常全面地介绍了这一主题.
既然你在谈论多态性,那么是的,你可以,我们在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
所以你可以看到正在调用不同的函数,具体取决于子类.
命名空间通常通过以下方式完成:
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就像一个静态类变量 - 在一个类型的所有实例之间共享.所有方法都是静态的,除了有些人拿这个*
我相信除了有用之外,在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++最初是以这种方式开始的,它将C代码作为中间步骤生成.
基本上你最终要做的是为存储函数引用的所有方法创建一个调度表.派生一个类需要复制这个调度表并替换你想要覆盖的条目,新的"方法"必须调用原始方法才能调用基本方法.最终,你最终重写了C++.
当然有可能.这就是所有GTK +和GNOME所基于的框架GObject所做的.
C stdio FILE子库是如何在纯粹的C中创建抽象,封装和模块化的一个很好的例子.
继承和多态-其他方面往往被认为OOP必不可少的-不一定提供他们承诺的生产力的提高和合理的 争论已经进行了,他们实际上可以阻碍发展和思考的问题域.
动物与狗的简单例子:你镜像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编译器上运行.
看看GObject.它意味着在C中是OO,并且是您正在寻找的一个实现.如果您真的想要OO,请使用C++或其他一些OOP语言.如果你习惯于处理OO语言,GObject可能真的很难处理,但是就像任何事情一样,你会习惯惯例和流程.
这很有趣.我自己一直在思考同样的问题,思考它的好处是这样的:
试图想象如何用非OOP语言实现OOP概念有助于我理解OOp语言的优势(在我的例子中,C++).这有助于我更好地判断是否在给定类型的应用程序中使用C或C++ - 其中一个的好处超过另一个.
在我浏览网页以获取有关此信息和意见时,我找到了一位为嵌入式处理器编写代码的作者,并且只提供了一个C编译器:http: //www.eetimes.com/discussion/other/4024626/Object-Oriented -C-创建基金会-类-部分- 1
在他的案例中,分析和调整普通C中的OOP概念是一种有效的追求.由于试图在C中实现它们导致的性能开销,他似乎愿意牺牲一些OOP概念.
我所接受的教训是,是的,它可以在一定程度上完成,是的,有一些很好的理由来尝试它.
最后,机器处理堆栈指针位,使程序计数器跳转并计算内存访问操作.从效率的角度来看,你的程序执行的这些计算越少越好......但有时我们必须支付这个税,因此我们可以组织我们的程序,使其最不容易受到人为错误的影响.OOP语言编译器努力优化这两个方面.程序员必须更加谨慎地用C语言实现这些概念.
您可能会发现查看Apple的Core Foundation API集文档很有帮助.它是纯C API,但许多类型都桥接到Objective-C对象等价物.
您可能还会发现查看Objective-C本身的设计很有帮助.它与C++略有不同,因为对象系统是根据C函数定义的,例如objc_msg_send
调用对象上的方法.编译器将方括号语法转换为那些函数调用,因此您不必知道它,但考虑到您的问题,您可能会发现它有助于了解它是如何工作的.
有几种技术可以使用.最重要的是如何拆分项目.我们在项目中使用一个接口,该接口在.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(数据封装)的很大一部分优势.通过使用函数指针,它甚至可以很容易地实现继承,但老实说,它实际上很少有用.
你可以使用函数指针伪造它,事实上,我认为理论上可以将C++程序编译成C.
然而,强制使用语言范式而不是选择使用范式的语言很少有意义.
面向对象的C,可以做到的,我见过这种类型的代码在生产在韩国,这是最可怕的怪物我看到在年(这是像去年(2007年),我看到的代码).所以,是的这是可以做到的,是的人已经做到了,并且仍然这样做,即使在这个时代.但我建议C++或Objective-C,由C出生都是语言,以提供面向对象的不同范式的目的.
如果您确信OOP方法对于您尝试解决的问题更优越,那么您为什么要尝试使用非OOP语言来解决它?看起来你正在使用错误的工具来完成工作.使用C++或其他一些面向对象的C变体语言.
如果您因为开始使用C编写的现有大型项目进行编码而要求,那么您不应该尝试将自己的(或任何其他人的)OOP范例强制插入到项目的基础结构中.遵循项目中已存在的准则.通常,干净的API和隔离的库和模块将大大有助于实现干净的OOP- ish设计.
如果,这一切后,你真的是做OOP C中,读这个(PDF).
是的你可以.在C++或Objective-C出现之前,人们正在编写面向对象的C语言.C++和Objective-C在某些方面都试图采用C中使用的一些OO概念,并将它们形式化为语言的一部分.
这是一个非常简单的程序,它展示了如何制作看起来像/是方法调用的东西(有更好的方法来实现这一点.这只是语言支持概念的证据):
#includestruct 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; }
当然,它不会像使用内置支持的语言一样漂亮.我甚至写过"面向对象的汇编程序".
要添加的小OOC代码:
#includestruct 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; }
我一直在挖这个一年:
由于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