在C中的面向对象编程
在C中的面向对象编程
可能的重复问题:
我记得很久以前读到过有人(我想是Linus Torvalds)谈论过C++是一种可怕的语言,你可以用C来编写面向对象的程序。现在有时间反思,我真的看不出所有的面向对象概念如何在C中实现。有些东西是相当明显的。例如:
- 要模拟成员函数,可以在结构体中放置函数指针。
- 要模拟多态性,可以编写一个接受可变数量参数的函数,并根据参数的
sizeof
执行某些操作。
但是如何模拟封装和继承呢?
我想封装可能可以通过嵌套结构体来实现私有成员的存储。虽然很容易绕过,但可以起一个明显的名字,比如PRIVATE
,以表示它不应该从结构体外部使用。那继承呢?
面向对象编程(Object oriented programming,OOP)是一种常用的编程范式,它可以通过使用虚函数和虚表(vtable)来实现多态性。但是,在C语言中实现OOP是一项具有挑战性的任务。下面将从代码实现的角度分析OOP在C语言中的出现原因和解决方法。
在C语言中,我们可以通过创建虚函数指针数组来实现多态性。首先,我们需要为每个类定义一个初始化函数(init function),在其中分配内存并初始化对象。每个初始化函数还应包含一个静态的虚表结构体(vtable),其中包含虚函数指针(对于纯虚函数,指针为NULL)。在派生类的初始化函数中,需要在执行其他操作之前调用父类的初始化函数。
为了创建一个更好的API,我们可以实现虚函数的包装函数,代码如下(如果在头文件中实现,请在前面加上static inline
):
int playerGuess(Player* this) { return this->vtable->guess(this); }
在C语言中,单继承可以通过滥用结构体的二进制布局来实现。然而,多继承会更加复杂,因为在不同类型之间进行转换时通常需要调整指针的值。
除了虚函数指针,虚表还可以包含其他类型特定的数据,比如运行时类型信息(例如类型名称的字符串)、指向父类虚表的链接以及析构函数链。在使用这种实现方式时,通常会使用虚析构函数,其中派生类的析构函数将对象降级为其父类对象,并递归调用父类的析构函数,直到达到基类的析构函数,最终释放结构体。
在C语言中,封装性可以通过将结构体定义在头文件中,并在源文件中实现虚函数(通过虚表指针调用)来实现。然而,这种实现方式相对笨拙,并且会降低性能(因为虚函数包装函数无法放在头文件中),因此不推荐使用。
尽管C语言本身不支持面向对象编程,但通过使用虚函数和虚表的技术,我们可以在C语言中模拟实现面向对象编程的一些特性。这种实现方式虽然相对复杂,但在某些情况下可能是必要的,特别是在需要在C语言中实现多态性和继承等概念时。
有人问到了一个关于(Object oriented programming in C)的问题,并提到了一本名为《Object Oriented C》的书籍,作者未知。他还提到自己在Chrome浏览器上安装了PDF扩展。
从上述内容中可以得出以下信息:
- 有人对(Object oriented programming in C)这个问题感兴趣。
- 《Object Oriented C》是一本与问题相关的书籍。
- 问题中提到的书籍作者未知。
- 提问者在Chrome浏览器上安装了PDF扩展。
文章整理如下:
在某个讨论中,有人提出了关于(Object oriented programming in C)的问题。对于这个问题,有人建议阅读一本名为《Object Oriented C》的书籍,该书探讨了在C语言中进行面向对象编程的方法。然而,这本书的作者并未在讨论中提及。
除了提到书籍之外,还有人注意到提问者在Chrome浏览器上安装了PDF扩展。这意味着提问者可能在查看PDF文件时遇到了问题,或者他可能希望通过该扩展来更方便地阅读PDF文件。
对于这个问题,最好的解决方法可能是通过点击链接Object Oriented C来获取《Object Oriented C》这本书的详细信息。在阅读这本书的过程中,提问者可能会找到关于问题的答案,以及更多关于在C语言中进行面向对象编程的知识。
另外,提问者还可以检查他所使用的PDF扩展是否正常工作,或者尝试使用其他PDF阅读器来查看PDF文件。
通过获取相关书籍的信息并进一步学习,以及检查和调整PDF扩展的设置,提问者很有可能找到关于(Object oriented programming in C)这个问题的答案,并解决他在使用PDF扩展时遇到的问题。
面向对象编程(Object oriented programming,简称OOP)在C语言中出现的原因是因为C语言本身并不直接支持面向对象的特性,如封装(encapsulation)和继承(inheritance)。然而,尽管C语言不支持这些特性,但仍然可以通过一些设计方法来模拟实现。
封装是OOP中最容易实现的部分,它是一种设计思想,与语言无关,与如何思考问题相关。例如,Windows的FILE API就完全封装了。当你打开一个文件时,你会得到一个不透明的对象,其中包含了该文件对象的所有状态信息。你可以将这个句柄传递给每个文件IO API。这种封装实际上比C++更好,因为没有公共的头文件可以让人们查看你的私有变量的名称。
继承比较困难,但并不是为了让你的代码成为面向对象而必需的。在某些情况下,聚合(aggregation)比继承更好,并且在C语言中实现聚合和在C++中一样容易。可以参考这个链接了解更多信息。
对于Neil的回应,可以参考维基百科的解释,说明为什么继承对于多态性并不是必需的。
我们这些老手在C++编译器出现之前就已经写了很多面向对象的代码,这是一种思维方式,而不是一种工具。
如果没有继承,就没有运行时多态性,也没有面向对象。
对于你的错误,我很少见到你犯错误,但这次你完全错了。继承不是面向对象的必需特性。你可以通过聚合和委托实现多态性。
你不会认为Ada83是面向对象的,尽管它具备除了继承和多态性以外的所有特性。我同意Neil的观点。否则就完全抛弃了OOP范式被发明的整个目的:代码重用。
最被重用的代码片段是标准C运行时库,而大多数C++代码实际上是“病毒式”的,你不能只使用其中的一部分而不带入整个代码库。
Jacinto:在ADA中完全可以编写面向对象的代码,但是不要将其视为面向对象的语言。我也不认为C++是一种面向对象的语言,因为它太多地渗透了系统编程的特性。