如何在C中模拟OO风格的多态性?


73

有没有办法用C编程语言编写类似于OO的代码?


也可以看看:

通过搜索“ [c] oo”找到。


感谢方括号中的提示。不知道
prinzdezibel,

3
是的,这需要变得更容易发现。我一直在尝试为uservoice提出一个好的建议,但是并没有达成共识。
dmckee ---前主持人小猫,

Answers:


60

第一个C ++编译器(“带有类的C”)实际上会生成C代码,因此这绝对是可行的。

基本上,您的基类是一个struct;派生结构必须在第一个位置包括基本结构,以便指向“派生”结构的指针也将是指向基本结构的有效指针。

函数可以作为函数指针作为结构的一部分,因此像p-> call(p)这样的语法成为可能,但是您仍然必须将指向结构的指针显式传递给函数本身。


8
这不能解释方法重写在C语言中的工作方式。如何像在C ++多态调用中那样重写function_on_base来访问派生的memeber_y?
约翰K

压倒一切是不可能的C.
帕特里克·柯林斯

10
这个答案是不正确的。传递struct derived*function_on_base不会编译;即使地址正确,struct derived*类型struct base*也不同;但是,如果将指针从强制转换derived*base*,它将起作用(但是您会错过编译时类型检查,而在运行时会崩溃)。@PatrickCollins重写可能在C:pastebin.com/W5xEytbv
weberc2

@JohnK参见上面的评论。
weberc2 2014年

@ weberc2是的,我不确定我写这本书时在想什么。我可能会想到“超载”,您的粘贴内容也暗示了这一点。
帕特里克·柯林斯

52

常见的方法是使用指向函数的指针定义结构。这定义了可以在任何类型上调用的“方法”。然后,子类型在此公共结构中设置自己的函数,然后将其返回。

例如,在Linux内核中,有struct:

每个注册的类型的文件系统的随后登记其自己的功能createlookup和其余功能。其余代码则可以使用通用的inode_operations:


1
基本上,这就是cfront(原始C ++编译器)将C ++转换为随后由pcc编译的C的方式。我从混乱中学到了很多有关如何处理核心文件的知识。
Paul Tomblin,2009年

这不是C ++风格的多态性。与函数指针相比​​,多态性的一大优势在于,多态性允许数据绑定,而函数指针则不允许。在这种情况下,没有机会进行数据绑定,而只是函数指针的结构。另外,我认为该注释中的结构是多余的。
user2445507

30

C ++与C相距不远。

类是具有隐藏指针的结构,该指针指向称为VTable的函数指针表。Vtable本身是静态的。当类型指向具有相同结构的Vtable但指针指向其他实现时,您将得到多态性。

建议将调用逻辑封装在以struct作为参数的函数中,以避免代码混乱。

您还应该在函数(等效于C ++构造函数)和删除(C ++中的析构函数)中封装结构的实例化和初始化。无论如何,这些都是好的做法。

调用方法:


该示例是不完整的。没有关于如何使用不同成员实现新类的说明(仅int除外)。在这种情况下,似乎所有“多态”对象都将具有相同的int成员。如果要定义具有不同成员的类怎么办?我认为在这种情况下,您的结构应具有指向数据成员的void指针,而不是具体的int。
user2445507

@ user2445507该示例实现了一个vtable,该vtable在运行时调用函数,而如果您创建一个类,则c ++会在后台执行该操作。
尼古拉斯·拉尔森

@NiclasLarsson在C ++中,可以创建另一个类来继承纯虚函数,但具有不同的成员变量。然后,在该类中实现的虚函数可以使用那些成员变量。这是多态性的关键方面。该示例没有演示如何执行此操作。对于此示例,您最好只使用函数指针。
user2445507

17

我查看了其他人的答案,并提出了以下建议:

我希望能回答一些问题。


3
好的例子。如果您有2个具有不同init / get / set的“派生”类,那就更好了。“私有”成员/函数可以使用不透明的结构来完成。命名约定也很重要:mylib_someClass_aMethod(this)很可能。
西罗Santilli郝海东冠状病六四事件法轮功2016年

这看起来很整洁。我将其放入一个ac文件中,然后为ARM微控制器编译好了。我还没走远,只是想在red_hax0r的帖子上致谢。
比较者,

1
多态性在哪里?
Ebru Yener

12

VPRI的Ian Piumarta和Alessandro Warth撰写的文章Open Reusable Object Models的附录B是GNU C中一个对象模型的实现,大约140行代码。这是一本有趣的书!

这是使用C的GNU扩展(语句表达式)将消息发送到对象的宏的未缓存版本:

在同一个文档,有一个看看objectvtablevtable_delegatedsymbol结构,以及_bindvtable_lookup功能。

干杯!


1

文件函数fopen,fclose,fread是C中OO代码的示例。它们代替了类中的私有数据,而是在FILE结构上工作,该FILE结构用于封装数据,并且C函数充当成员类函数。 http://www.amazon.com/File-Structures-Object-Oriented-Approach-C/dp/0201874016


3
本书所指的只是C ++。注意:我有一份副本,今天不会推荐。如果我今天在C中提出一个面向对象的建议,那将是David Hanson(amzn.com/0201498413)的C接口和实现:创建可重用软件的技术。这本书绝对是一本绝妙的书,大多数程序员都会很好地理解它,其中的大多数示例都来自编译器后端,因此该代码是示例性的。
哈利

1

输出为:

因此可以正常工作,它是一种多态代码。

UncleZeiv在一开始就对此进行了解释。


0

摘自Wikipedia:在编程语言和类型理论中,多态性(来自希腊语πολύς,polys,“很多”和μορφή,morphē,“形式,形状”)是对不同类型实体的单一接口。

所以我想说在C中实现它的唯一方法是使用可变参数以及一些(半)自动类型信息管理。例如,在C ++中,您可以编写(很琐碎):

在C语言中,除其他解决方案外,您可以做的最好的事情是这样的:

然后,您需要:

  1. 用枚举/宏实现“ typeinfo”
  2. 用stdarg.h的东西实现后一个功能
  3. 向C静态类型检查说再见

我几乎可以肯定,多态性的任何其他实现都应该非常类似于这种实现。上面的答案似乎比多态性更着重于解决继承问题!


在C11中,新的_Generic关键字大大简化了此设计模式。对于那些选择这种方法的人,我强烈推荐它。
卡莱布·格雷

0

为了在C语言中也建立OO功能,您可以查看以前的答案。

但是,(如在其他重定向到该问题的其他问题中所提出的那样)是否要通过C语言示例理解多态性。也许我错了,但是我想不出任何像C指针算术那样容易理解的东西。在我看来,指针算法在C中固有地是多态的。在下面的示例中,根据输入结构的属性,相同的函数(OO中的方法),即加法(+),将产生不同的行为。

例:

免责声明:我是C语言的新手,非常期待得到纠正,可以从其他用户的评论中学习,或者完全删除此答案(如果有的话)。非常感谢,


0

我通常想做的是将结构包装在另一个包含有关包装类的元信息的结构中,然后构造作用在通用结构上的类似访问者的函数列表。这种方法的优点是您不需要修改现有结构,并且可以为结构的任何子集创建访问者。

采取通常的示例:

typedef struct {
    char call[7] = "MIAO!\n";
} Cat;
    
typedef struct {
    char call[6] = "BAU!\n";
} Dog;

我们可以将2个结构包裹在这个新结构中:

typedef struct {
    const void * animal;
    AnimalType type;
} Animal;

该类型可以是一个简单的int,但不要懒惰:

typedef enum  {
    ANIMAL_CAT = 0,
    ANIMAL_DOG,
    ANIMAL_COUNT
} AnimalType;

最好有一些包装功能:

Animal catAsAnimal(const Cat * c) {
    return (Animal){(void *)c, ANIMAL_CAT};
}

Animal dogAsAnimal(const Dog * d) {
    return (Animal){(void *)d, ANIMAL_DOG};
}

现在,我们可以定义“访问者”:

void catCall ( Animal a ) {
    Cat * c = (Cat *)a.animal;
    printf(c->call);
}

void dogCall ( Animal a ) {
    Dog * d = (Dog *)a.animal;
    printf(d->call);
}

void (*animalCalls[ANIMAL_COUNT])(Animal)={&catCall, &dogCall};

那么实际用法将是:

Cat cat;
Dog dog;

Animal animals[2];
animals[0] = catAsAnimal(&cat);
animals[1] = dogAsAnimal(&dog);

for (int i = 0; i < 2; i++) {
    Animal a = animals[i];
    animalCalls[a.type](a);
}

这种方法的缺点是,每次要将其用作泛型类型时,都必须包装结构。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.