面向对象编程C
可能重复:
你能用C编写面向对象的代码吗?
面向对象的模式在C?
我记得前一段时间读过一些关于某人(我认为是Linus Torvalds)的文章,谈论C ++如何是一种可怕的语言,以及如何用C编写面向对象的程序。有时间反思,我真的不知道所有面向对象的概念都转化为C.有些东西相当明显。 例如:
- 为了模仿成员函数,你可以把函数指针放在结构体中。
- 为了模拟多态性,你可以编写一个函数来获取可变数量的参数,并根据参数的
sizeof
(例如,
你将如何模仿封装和inheritance?
我想封装可以通过嵌套结构来存储私有成员来模拟。 这将是相当容易解决,但也许可以被命名为PRIVATE
或者同样明显的信号,表明它不是从结构外部使用。 那么inheritance呢?
您可以使用常规函数和虚拟表(vtable)来实现多态。 这是一个非常整洁的系统,我发明了一个编程练习(基于C ++):
构造函数分配内存,然后调用初始化内存的类的init函数。 每个init函数还应该包含一个静态的vtable结构,其中包含虚拟函数指针(纯虚拟的NULL)。 派生类的init函数在做任何事情之前调用超类的init函数。
一个非常好的API可以通过实现虚函数包装器(不要与vtables指向的函数混淆)来创build,如下所示(如果你在头文件中这样做,在它前面添加static inline
):
int playerGuess(Player* this) { return this->vtable->guess(this); }
单一inheritance可以通过滥用结构的二进制布局来完成:
注意多重inheritance是混乱的,因为在层次结构types之间转换时,经常需要调整指针值。
其他types特定的数据也可以添加到虚拟表中。 示例包括运行时types信息(例如,types名称作为string),链接到超类vtable和析构函数链。 您可能需要虚拟析构函数,其中派生类析构函数将对象降级到其超类,然后recursion调用析构函数等等,直到达到基类析构函数,并最终释放该结构。
封装是通过在player_protected.h中定义结构并在player_protected.c中实现函数(由vtable指向的)完成的,类似于派生类,但是这样做相当笨拙,并且降低了性能(因为虚拟包装不能放到头),所以我会build议反对它。
你读过关于这个主题的“圣经”了吗? 查看面向对象的C …
你将如何模仿封装和inheritance?
其实,封装是最简单的部分。 封装是一种devise理念,它与语言和所有事情无关,与你如何思考问题完全没有关系。
例如,Windows FILE API是完全封装的。 当你打开一个文件,你会得到一个不透明的对象,其中包含文件“对象”的所有状态信息。 你把这个句柄交给每个文件io apis。 封装实际上比C ++ 要好得多,因为没有公共头文件,人们可以查看并查看私有variables的名称。
inheritance是比较困难的,但是为了让你的代码是面向对象的,这并不是必须的。 在某些方面,聚合总比inheritance好,聚合在C中和在C ++中一样简单。 看到这个例子。
为了回应尼尔,请参阅维基百科 ,了解为什么遗传不是多态性所必需的。
在C ++编译器可用之前,我们的老人编写了面向对象的代码年,这是一个思维集而不是工具集。
苹果基于C的CoreFoundation框架实际上是为了使它的“对象”可以作为一个实际的面向对象语言Objective-C中的对象而加倍。 这个框架的一个相当大的子集是作为CF-Lite在苹果网站上开源的。 在这样做的一个主要的操作系统级框架中可能是一个有用的案例研究。
从较高的海拔高度,考虑到比OOP主stream认为的开放性问题更为开放,面向对象编程(Object-Oriented Programming)是指将对象作为具有相关function的数据进行思考。 这并不一定意味着一个函数必须物理地连接到一个对象上,因为它是支持OOP范例的stream行语言,例如在C ++中:
struct T { int data; int get_data() const { return data; } };
我build议仔细看一下GTK +对象和types系统 。 这是用C编程语言实现的OOP的一个很好的例子:
GTK +实现了自己的自定义对象系统,它提供了inheritance和虚函数等标准的面向对象的function
协会也可以是契约式和传统式的。
关于封装和数据隐藏技术,stream行和简单的可能是不透明的指针 (或不透明的数据types) – 你可以传递它,但为了加载或存储任何信息,你必须调用相关的函数,知道如何与隐藏在不透明指针后面的对象。
另一个类似但不同的是阴影数据types – 检查Jon Jagger对这个不太知名的技术的很好的解释。
gtk和glib库使用macros将对象转换为各种types。
add_widget(GTK_WIDGET(myButton的));
我不能说如何做,但你可以阅读他们的来源,以确切地了解它是如何完成的。
查看VFS层在Linux内核中的工作方式,以获得inheritance模式的示例。 各种文件系统的文件操作“inheritance”了一组通用文件操作函数(例如generic_file_aio_read()
, generic_file_llseek()
…),但是可以用它们自己的实现(例如ntfs_file_aio_write()
)覆盖它们。
绝对看Objective-C。
typedef struct objc_object { Class isa; } *id; typedef struct objc_class { struct objc_class *isa; struct objc_class *super_class const char *name; long version; long info long instance_size; struct objc_ivar_list *ivars; struct objc_method_list **methodLists; struct objc_cache *cache; struct objc_protocol_list *protocols; } *Class;
正如你所看到的,inheritance信息和其他细节一起被保存在一个类结构中(通常这个类也可以被当作一个对象)。
Objective-C与C ++封装相同,因此需要公开声明variables。 直C是更灵活的,你可以返回空指针,只有你的模块有内部访问,所以在这方面封装更好。
我曾经写过一个基本的OO风格的C绘图程序作为graphics学课程的一部分 – 我没有深入到类的声明中,我简单地使用了一个vtable指针作为结构的第一个元素,并实现了手工编码的inheritance。 关于在如此低水平上使用vtables的精妙之处在于,您可以通过更改几个指针来更改运行时的类行为,或dynamic更改对象类。 创build各种混合对象,假多重inheritance等是很容易的。
关于Objective-C的好文章和讨论:
http://cocoawithlove.com/2009/10/objective-c-niche-why-it-survives-in.html
对于C语言中面向对象编程的一个很好的例子,看看几年前POV-Ray的来源–3.1g版本特别好。 当然,“对象”是带有函数指针的结构体。 macros被用来为抽象对象提供核心方法和数据,派生类是以macros开始的结构。 然而,并没有试图与私人/公众打交道。 要看的东西是在.h文件中,实现细节主要在.c文件中,除了很多例外。
有一些巧妙的技巧,我不知道怎么可以转到C ++ – 例如通过重新分配函数指针,将一个类转换为一个不同但类似的类。 简单的今天的dynamic语言。 我忘了细节; 我认为这可能是CSG交集和联合对象。
一个有趣的历史。 Cfront ,最初的C ++实现输出C代码,然后需要一个C编译器来实际构build最终的代码。 所以,任何可以用C ++expression的东西都可以写成C
处理inheritance的一种方法是嵌套结构:
struct base { ... }; void method_of_base(base *b, ...); struct child { struct base base_elements; ... };
然后你可以这样做:
struct child c; method_of_base(&c.b, ...);
你可能想看看Objective-C,那就是它的function。 这只是一个将Objective-C OO代码编译到C的前端。