假设我必须使用C(没有C ++或面向对象的编译器)并且我没有动态内存分配,我可以使用哪些技术来实现类或类的良好近似?将“类”隔离到单独的文件中总是一个好主意吗?假设我们可以通过假定固定数量的实例来预分配内存,或者甚至在编译时将每个对象的引用定义为常量。请随意假设我需要实施哪个OOP概念(它会有所不同),并为每个概念提出最佳方法。
限制:
答案 0 :(得分:79)
这取决于您想要的确切的“面向对象”功能集。如果你需要重载和/或虚方法之类的东西,你可能需要在结构中包含函数指针:
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
结构中填充方法指针来远离它,并为每个对象实例创建一种引用类的方法。
答案 1 :(得分:23)
我也必须做一次做作业。我遵循这种方法:
一个简单的例子是:
/// Queue.h
struct Queue
{
/// members
}
typedef struct Queue Queue;
void push(Queue* q, int element);
void pop(Queue* q);
// etc.
///
答案 2 :(得分:12)
如果您只需要一个类,请使用struct
的数组作为“对象”数据,并将指针传递给“成员”函数。您可以在声明typedef struct _whatever Whatever
之前使用struct _whatever
来隐藏客户端代码中的实现。这样的“对象”和C标准库FILE
对象之间没有区别。
如果你想要多个具有继承和虚函数的类,那么通常将指向函数的指针作为结构的成员,或指向虚函数表的共享指针。 GObject库使用this和typedef技巧,并且被广泛使用。
还有一本关于这方面技术的书籍 - Object Oriented Programming with ANSI C。
答案 3 :(得分:7)
C接口和实现:创建可重用软件的技术 , David R. Hanson
这本书非常适合报道你的问题。这是Addison Wesley专业计算系列。
基本范例是这样的:
/* for data structure foo */
FOO *myfoo;
myfoo = foo_create(...);
foo_something(myfoo, ...);
myfoo = foo_append(myfoo, ...);
foo_delete(myfoo);
答案 4 :(得分:7)
你可以看看GOBject。它是一个操作系统库,为您提供了一个简单的方法来完成一个对象。
答案 5 :(得分:4)
我将举一个简单的例子,说明如何在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个字节。 (假设非疯狂的最优化)。现在,当结构的指针被强制转换为继承的类时,'继承的类'可以访问“继承的值”'就像它通常会访问它的成员一样。
这个以及一些构造函数,析构函数,分配和deallocarion函数的命名约定(我推荐使用init,clean,new,free)将为你提供很长的路要走。
对于虚函数,使用struct中的函数指针,可能使用Class_func(...);包装也是。 对于(简单)模板,添加size_t参数以确定大小,需要void *指针,或者需要一个'类'键入只有你关心的功能。 (例如,int GetUUID(Object * self); GetUUID(& p);)
答案 6 :(得分:4)
使用struct
来模拟班级的数据成员。在方法范围方面,您可以通过将私有函数原型放在.c文件中,将 public 函数放在.h文件中来模拟私有方法。
答案 7 :(得分:3)
在你的情况下,类的良好近似可以是ADT。但它仍然不一样。
答案 8 :(得分:3)
我的策略是:
有没有人看到这种方法的变化有任何问题,漏洞,潜在的陷阱或隐藏的利弊?如果我正在重新设计一种设计方法(我认为我必须这样做),你能指出我的名字吗?
答案 9 :(得分:3)
关于这个主题有一本非常广泛的书,可能值得一试:
答案 10 :(得分:3)
有可能。它在当时似乎总是一个好主意,但之后它变成了维护的噩梦。您的代码中充斥着将所有内容捆绑在一起的代码片段。如果使用函数指针,新程序员在阅读和理解代码时会遇到很多问题,因为调用哪些函数并不明显。
使用get / set函数隐藏数据很容易在C中实现但是停在那里。我已经在嵌入式环境中看到了多次尝试,最终它总是一个维护问题。
由于你们都准备好了维护问题,我会明确指出。
答案 11 :(得分:3)
可能this online book可以帮助您解决问题。
答案 12 :(得分:3)
#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;
};
答案 13 :(得分:2)
第一个c ++编译器实际上是一个预处理器,它将C ++代码翻译成C。
所以很有可能在C中有课。 您可以尝试挖掘旧的C ++预处理器,看看它创建了哪种解决方案。
答案 14 :(得分:2)
GTK完全基于C构建,它使用了许多OOP概念。我已经阅读了GTK的源代码,它非常令人印象深刻,而且更容易阅读。基本概念是每个“类”只是一个结构和相关的静态函数。静态函数都接受“instance”结构作为参数,做任何需要的事情,并在必要时返回结果。例如,您可能有一个函数“GetPosition(CircleStruct obj)”。该函数将简单地挖掘结构,提取位置编号,可能构建一个新的PositionStruct对象,将x和y粘贴到新的PositionStruct中,然后返回它。 GTK甚至通过在结构体中嵌入结构来实现这种继承。很聪明。
答案 15 :(得分:2)
我的方法是将struct
和所有主要关联的函数移动到单独的源文件中,以便可以“移植”使用它。
根据您的编译器,可能能够将函数包含到struct
中,但这是非常特定于编译器的扩展,并且没有任何内容使用常规使用的标准的最新版本:)
答案 16 :(得分:1)
你想要虚拟方法吗?
如果没有,那么你只需在struct本身中定义一组函数指针。如果将所有函数指针分配给标准C函数,那么您将能够以与C ++中非常相似的语法调用C中的函数。
如果你想拥有虚拟方法,它会变得更加复杂。基本上,您需要为每个结构实现自己的VTable,并根据调用的函数为VTable分配函数指针。然后,您需要在结构本身中使用一组函数指针,这些函数指针又调用VTable中的函数指针。这基本上就是C ++的作用。
但是TBH虽然......如果你想要后者,那么你最好找一个可以使用的C ++编译器并重新编译项目。我从未理解对C ++的痴迷不能在嵌入式中使用。我已经使用了很多次,它工作速度快,没有内存问题。当然,你必须更加小心你所做的事情,但它真的不那么复杂。答案 17 :(得分:0)
C不是一种OOP语言,正如你正确指出的那样,所以没有内置的方法来编写一个真正的类。你最好的选择是structs和function pointers,这些可以让你建立一个类的近似值。但是,由于C是程序性的,您可能需要考虑编写更多类似C的代码(即不尝试使用类)。
另外,如果你可以使用C,你可以使用C ++并获得类。