假设我必须使用C(没有C++或面向对象的编译器)并且我没有动态内存分配,我可以使用哪些技术来实现类,或者类的良好近似?将"类"隔离到单独的文件中总是一个好主意吗?假设我们可以通过假定固定数量的实例来预分配内存,或者甚至在编译时将每个对象的引用定义为常量.随意假设我将需要实施哪个OOP概念(它会有所不同)并为每个概念提出最佳方法.
限制:
我必须使用C而不是OOP,因为我正在编写嵌入式系统的代码,编译器和预先存在的代码库位于C语言中.
没有动态内存分配,因为我们没有足够的内存来合理地假设如果我们开始动态分配它就不会用完.
我们使用的编译器没有函数指针的问题
unwind.. 83
这取决于您想要的确切的"面向对象"功能集.如果你需要重载和/或虚方法之类的东西,你可能需要在结构中包含函数指针:
typedef struct { float (*computeArea)(const ShapeClass *shape); } ShapeClass; float shape_computeArea(const ShapeClass *shape) { return shape->computeArea(shape); }
这将允许您通过"继承"基类并实现一个合适的函数来实现一个类:
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; }
这当然要求您还要实现一个构造函数,以确保正确设置函数指针.通常,您会为实例动态分配内存,但您也可以让调用者也这样做:
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; }
我希望至少可以给你一些想法.对于C中成功且丰富的面向对象框架,请查看glib的GObject库.
另请注意,上面没有明确的"类"建模,每个对象都有自己的方法指针,比通常在C++中找到的更灵活.此外,它需要内存.您可以通过在class
结构中填充方法指针来摆脱这种情况,并为每个对象实例创建一种引用类的方法.
这取决于您想要的确切的"面向对象"功能集.如果你需要重载和/或虚方法之类的东西,你可能需要在结构中包含函数指针:
typedef struct { float (*computeArea)(const ShapeClass *shape); } ShapeClass; float shape_computeArea(const ShapeClass *shape) { return shape->computeArea(shape); }
这将允许您通过"继承"基类并实现一个合适的函数来实现一个类:
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; }
这当然要求您还要实现一个构造函数,以确保正确设置函数指针.通常,您会为实例动态分配内存,但您也可以让调用者也这样做:
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; }
我希望至少可以给你一些想法.对于C中成功且丰富的面向对象框架,请查看glib的GObject库.
另请注意,上面没有明确的"类"建模,每个对象都有自己的方法指针,比通常在C++中找到的更灵活.此外,它需要内存.您可以通过在class
结构中填充方法指针来摆脱这种情况,并为每个对象实例创建一种引用类的方法.
我不得不做一次做作业.我遵循这种方法:
在结构中定义数据成员.
定义函数成员,将指向结构的指针作为第一个参数.
在一个标题和一个c中执行这些操作.结构定义和函数声明的标题,c表示实现.
一个简单的例子是这样的:
/// Queue.h struct Queue { /// members } typedef struct Queue Queue; void push(Queue* q, int element); void pop(Queue* q); // etc. ///
如果您只需要一个类,请使用struct
s 数组作为"对象"数据,并将指针传递给"成员"函数.您可以typedef struct _whatever Whatever
在声明struct _whatever
从客户端代码隐藏实现之前使用.这样的"对象"和C标准库FILE
对象之间没有区别.
如果您需要具有继承和虚函数的多个类,则通常将指向函数的指针作为结构的成员,或指向虚函数表的共享指针.该GObject的库同时使用这一点,typedef的把戏,而被广泛使用.
还有一本关于在线可用技术的书 - 使用ANSI C进行面向对象编程.
你可以看看GOBject.它是一个操作系统库,为您提供了一个简单的方法来完成一个对象.
http://library.gnome.org/devel/gobject/stable/
C接口和实现:创建可重用软件的技术, David R. Hanson
http://www.informit.com/store/product.aspx?isbn=0201498413
这本书很好地涵盖了你的问题.这是Addison Wesley专业计算系列.
基本范例是这样的:
/* for data structure foo */ FOO *myfoo; myfoo = foo_create(...); foo_something(myfoo, ...); myfoo = foo_append(myfoo, ...); foo_delete(myfoo);
我将给出一个简单的示例,说明如何在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; }
基本概念涉及将“继承的类”放在结构的顶部。这样,访问结构中的前4个字节也将访问“继承的类”中的前4个字节(假定非疯狂的优化)。现在,当将结构体的指针强制转换为“继承的类”时,“继承的类”可以像通常访问其成员一样访问“继承的值”。
对于构造函数,析构函数,分配和释放函数的命名约定以及一些命名约定(我建议使用init,clean,new,free)将使您大有帮助。
对于虚函数,可以在结构中使用函数指针,可能使用Class_func(...); 包装器也。对于(简单的)模板,添加size_t参数来确定大小,需要void *指针或仅具有您所关注的功能的“类”类型。(例如int GetUUID(Object * self); GetUUID(&p);)