你如何在C中实现一个类?

假设我必须使用C(没有C ++或面向对象的编译器),而且我没有dynamic内存分配,那么我可以使用一些技术来实现一个类,或者一个类的很好的近似值? 将“class级”隔离到一个单独的文件中总是一个好主意? 假设我们可以通过假设固定数量的实例来预先分配内存,甚至在编译时间之前将每个对象的引用定义为一个常量。 随意对我将需要实现哪个OOP概念做出假设(将会有所不同),并为每个概念提供最佳方法。

限制:

  • 我必须使用C而不是OOP,因为我正在为embedded式系统编写代码,并且编译器和预先存在的代码库都在C中。
  • 没有dynamic内存分配,因为我们没有足够的内存来合理地假设如果我们开始dynamic分配它,我们不会用完。
  • 我们使用的编译器在函数指针方面没有问题

这取决于你想要的确切的“面向对象”function集。 如果你需要像重载和/或虚拟方法的东西,你可能需要在结构中包含函数指针:

 typedef struct { float (*computeArea)(const ShapeClass *shape); } ShapeClass; float shape_computeArea(const ShapeClass *shape) { return shape->computeArea(shape); } 

这将让你实现一个类,通过“inheritance”基类,并实现一个合适的function:

 typedef struct { ShapeClass shape; float width, height; } RectangleClass; static float rectangle_computeArea(const ShapeClass *shape) { const RectangleClass *rect = (const RectangleClass *) shape; return rect->width * rect->height; } 

这当然要求你也实现一个构造函数,确保函数指针设置正确。 通常你会为实例dynamic地分配内存,但你也可以让调用者这样做:

 void rectangle_new(RectangleClass *rect) { rect->width = rect->height = 0.f; rect->shape.computeArea = rectangle_computeArea; } 

如果你需要几个不同的构造函数,你将不得不“装饰”函数名称,你不能有多个rectangle_new()函数:

 void rectangle_new_with_lengths(RectangleClass *rect, float width, float height) { rectangle_new(rect); rect->width = width; rect->height = height; } 

以下是一个显示用法的基本示例:

 int main(void) { RectangleClass r1; rectangle_new_with_lengths(&r1, 4.f, 5.f); printf("rectangle r1's area is %f units square\n", shape_computeArea(&r1)); return 0; } 

我希望这至less给你一些想法。 对于C语言中一个成功和丰富的面向对象的框架,请看glib的GObject库。

还要注意,上面没有显式的“类”,每个对象都有自己的方法指针,这比你在C ++中find的要灵活一些。 另外,它也花费内存。 您可以通过在class结构中填充方法指针来避开这种情况,并且为每个对象实例创build一个引用类的方法。

我也必须做一次功课。 我遵循这个方法:

  1. 在一个结构中定义你的数据成员。
  2. 定义你的函数成员,把指针指向你的结构作为第一个参数。
  3. 做一个头和一个c这些。 头结构定义和函数声明,C实现。

一个简单的例子是:

 /// Queue.h struct Queue { /// members } typedef struct Queue Queue; void push(Queue* q, int element); void pop(Queue* q); // etc. /// 

如果你只想要一个类,使用一个struct数组作为“对象”数据,并将指针传递给“成员”函数。 你可以使用typedef struct _whatever Whatever在声明struct _whatever之前的typedef struct _whatever Whatever struct _whatever来隐藏客户端代码的实现。 这样的“对象”和C标准库FILE对象没有区别。

如果你需要一个以上的inheritance和虚拟函数类,那么通常把指向函数的指针作为结构的成员,或者指向一个虚函数表的共享指针。 GObject库同时使用这个和typedef技巧,并被广泛使用。

还有一本关于这种可用的在线面向对象编程与ANSI C技术的书。

你可以看看GOBject。 这是一个操作系统库,给你一个详细的方式来做一个对象。

http://library.gnome.org/devel/gobject/stable/

Miro Samek为他的状态机框架开发了一个面向对象的C框架: http : //sourceforge.net/projects/qpc/ 。 他还写了一本关于它的书: http : //www.state-machine.com/psicc2/ 。

C接口和实现:创build可重用软件的技术 David R. Hanson

http://www.informit.com/store/product.aspx?isbn=0201498413

这本书在覆盖你的问题方面做得非常出色。 它在Addison Wesley Professional Computing系列中。

基本的范例是这样的:

 /* for data structure foo */ FOO *myfoo; myfoo = foo_create(...); foo_something(myfoo, ...); myfoo = foo_append(myfoo, ...); foo_delete(myfoo); 

使用一个struct来模拟一个类的数据成员。 在方法范围方面,您可以通过将.c文件中的私有函数原型和.h文件中的公用函数放置到模拟私有方法。

我将举一个简单的例子来说明如何在C中完成OOP。我意识到这个是从2009年开始的,不过我想补充一点。

 /// Object.h typedef struct Object { uuid_t uuid; } Object; int Object_init(Object *self); uuid_t Object_get_uuid(Object *self); int Object_clean(Object *self); /// Person.h typedef struct Person { Object obj; char *name; } Person; int Person_init(Person *self, char *name); int Person_greet(Person *self); int Person_clean(Person *self); /// Object.c #include "object.h" int Object_init(Object *self) { self->uuid = uuid_new(); return 0; } uuid_t Object_get_uuid(Object *self) { // Don't actually create getters in C... return self->uuid; } int Object_clean(Object *self) { uuid_free(self->uuid); return 0; } /// Person.c #include "person.h" int Person_init(Person *self, char *name) { Object_init(&self->obj); // Or just Object_init(&self); self->name = strdup(name); return 0; } int Person_greet(Person *self) { printf("Hello, %s", self->name); return 0; } int Person_clean(Person *self) { free(self->name); Object_clean(self); return 0; } /// main.c int main(void) { Person p; Person_init(&p, "John"); Person_greet(&p); Object_get_uuid(&p); // Inherited function Person_clean(&p); return 0; } 

基本的概念是将“inheritance的类”放在结构的顶部。 这样,访问结构中的前4个字节也访问“inheritance类”(Asuming non-crazy optimalisations)中的前4个字节。 现在,当结构的指针被转换为“inheritance类”时,“inheritance类”可以像访问其成员一样访问“inheritance的值”。

这个和一些命名约定的构造函数,析构函数,分配和释放函数(我build议初始化,干净,新,免费)将让你很长的路要走。

至于虚函数,在结构中使用函数指针,可能用Class_func(…); 包装也。 至于(简单)模板,添加一个size_t参数来确定大小,需要一个void *指针,或者需要一个只有你所关心的function的“类”types。 (例如int GetUUID(Object * self); GetUUID(&p);)

在你的情况下,这个类的好的近似可能是一个ADT 。 但还是不一样。

我的策略是:

  • 在一个单独的文件中定义类的所有代码
  • 在一个单独的头文件中定义该类的所有接口
  • 所有成员函数都采用一个“ClassHandle”来代表实例名(而不是o.foo(),调用foo(oHandle)
  • 根据内存分配策略,将构造函数replace为函数void ClassInit(ClassHandle h,int x,int y,…)或ClassHandle ClassInit(int x,int y,…)
  • 所有成员variables都作为静态结构的一个成员存储在类文件中,封装在文件中,防止外部文件访问
  • 对象被存储在上面的静态结构的数组中,具有预定义的句柄(在界面中可见)或者可以被实例化的对象的固定限制
  • 如果有用的话,这个类可以包含公用函数,这些公用函数将遍历数组并调用所有实例化对象的函数(RunAll()调用每个Run(oHandle)
  • Deinit(ClassHandle h)函数释放dynamic分配策略中分配的内存(数组索引)

有没有人看到这种方法的变化的任何问题,漏洞,潜在的陷阱或隐藏的好处/缺点? 如果我正在重新devise一个devise方法(我想我一定是),你能指出我的名字吗?

关于这个问题有一个非常广泛的书,这可能是值得一试的:

面向对象编程ANSI-C

也看到这个答案 , 这一个

有可能的。 这在当时似乎总是一个好主意,但事后却成了维护的噩梦。 你的代码变得乱七八糟,所有代码都捆绑在一起。 如果你使用函数指针,一个新的程序员将会在阅读和理解代码时遇到很多问题,因为调用函数并不明显。

使用get / set函数隐藏数据很容易在C中实现,但在这里停止。 我在embedded式环境中看到过多次尝试,最后总是一个维护问题。

既然你们都准备好了维修问题,我会明白的。

 #include <stdio.h> #include <math.h> #include <string.h> #include <uchar.h> /** * Define Shape class */ typedef struct Shape Shape; struct Shape { /** * Variables header... */ double width, height; /** * Functions header... */ double (*area)(Shape *shape); }; /** * Functions */ double calc(Shape *shape) { return shape->width * shape->height; } /** * Constructor */ Shape _Shape() { Shape s; s.width = 1; s.height = 1; s.area = calc; return s; } /********************************************/ int main() { Shape s1 = _Shape(); s1.width = 5.35; s1.height = 12.5462; printf("Hello World\n\n"); printf("User.width = %f\n", s1.width); printf("User.height = %f\n", s1.height); printf("User.area = %f\n\n", s1.area(&s1)); printf("Made with \xe2\x99\xa5 \n"); return 0; }; 

可能是这本在线书籍可以帮助你解决你的问题。

我的方法是将struct和所有主要关联的函数移动到一个单独的源文件中,以便“可移植”使用它。

根据你的编译器,你可能可以在struct包含函数,但这是一个非常特定的编译器扩展,与我经常使用的最后一个版本的标准无关:)

第一个c ++编译器实际上是一个将C ++代码翻译成C的预处理器。

所以很可能有C语言的类。您可以尝试挖掘一个旧的C ++预处理器,并查看它创build的解决scheme。

你想要虚拟方法吗?

如果没有,那么你只需在结构体中定义一组函数指针即可。 如果你把所有的函数指针分配给标准的C函数,那么你将能够以非常相似的语法调用C语言中的函数,以及如何使用C ++。

如果你想拥有虚拟方法,它会变得更加复杂。 基本上你需要为每个结构实现自己的VTable,并根据调用哪个函数分配函数指针给VTable。 然后你需要在结构本身中设置一组函数指针,然后调用VTable中的函数指针。 这基本上就是C ++的function。

TBH虽然…如果你想要后者,那么你可能会更好,只需find一个你可以使用的C ++编译器并重新编译该项目。 我从来没有理解C ++的痴迷不能在embedded式中使用。 我用了很多次,工作速度快,没​​有内存问题。 当然,你必须对自己所做的事情有一点小心,但实际上并不复杂。

GTK完全build立在C上,它使用了许多OOP概念。 我已经阅读了GTK的源代码,这是相当令人印象深刻的,而且更容易阅读。 基本的概念是每个“类”只是一个结构和相关的静态函数。 静态函数都接受“实例”结构作为参数,根据需要做任何事情,并在必要时返回结果。 例如,你可能有一个函数“GetPosition(CircleStruct obj)”。 该函数将简单地挖掘结构,提取位置数字,可能会构build一个新的PositionStruct对象,将x和y粘在新的PositionStruct中,并返回它。 GTK甚至通过在结构中embedded结构来实现这种inheritance。 很聪明。

C不是一个OOP语言,正如你正确地指出的那样,所以没有内build的方法来写一个真正的类。 你最好打赌是看看结构和函数指针 ,这些可以让你构build一个类的近似值。 但是,由于C是程序性的,你可能需要考虑编写更多类似于C的代码(即不用试图使用类)。

另外,如果你可以使用C,你可以使用C ++并获得类。