用 C 进行 OOP 编程? 在本文中,我们将研究 Glib 对象系统,也称为“GObject”,直到最近它还是 GTK+ 的一部分。但是在研究 Glib 2.0 中的这个新对象系统之前,我们需要解决一个更为基本的问题 - “对象系统”到底是什么以及它为何存在?究竟,C 是一种非面向对象的语言。是有可能用 C 编写面向对象的程序,还是必须使用 C++ 编写面向对象的程序?
答案是有可能用 C 编写面向对象的程序。但是,由于对象的概念不属于 C 语言规范,因此需要用外部库来提供这方面的支持。在本文中,我们使用术语“对象系统”来描述一个提供 OOP 编程所需基础的库,而 Glib 便是这种库的一个示例。Glib 提供了类、继续、引用计数、信号、接口和对象特性的 C 实现。通过使用 Glib,C 程序员就可以轻松地编写面向对象的程序。
因此,有可能用 C 编写面向对象的程序。但是,您可能会感到迷惑:为何 GTK+ 开发人员不直接使用 C++。这里我们不讨论每种可能的解释,而只解释为什么拥有一个用于 C 的对象系统是有意义的。其一,比起 C++,有许多开发人员更喜欢用 C。另外,由于项目或平台的限制,可能不会选择使用 C++ 编译器。无论是什么原因,拥有了用于 C 的对象系统,可以使更多的潜在开发人员也进行 OOP 编程(尤其是 GNOME 编程),我们对此表示感谢。
C++ 包装器 可以这么说,所有那些 C++ 的支持者也不必担心 - 您也可以用 C++ 编写 GNOME 程序。由于 C++ 是 C 语言的超集,因此您可以方便地将 C 样式的 Glib/GTK+ 代码和现有的 C++ 项目结合在一起。另外,您也可以使用 Glib/GTK+ C++ 包装器。Glib/GTK+ C++ 包装器将答应您使用本机的 C++ 类和对象与 Glib 对象交互。
现在,让我们把 Glib 对象系统(也称为“GObject”)与 C++ 和 java 语言的对象系统作一下比较。首先,让我们进行语法比较。使用 C++,您可以通过对象指针调用方法,如下所示:
object->function(arg_a, arg_b); 通过使用 C++ 对象引用,您可以输入:
object.function (arg_a, arg_b); Java 语言只有引用,没有指针。Java 方法调用语法与 C++ 引用对象方法调用完全一样:
object.function (arg_a, arg_b); 与此相反,GObject 使用标准 C 函数调用语法。其对象指针作为函数的第一个参数被传递。还要注重的是,为了防止名称空间冲突,函数名用类名作为前缀:
classname_function (object, arg_a, arg_b); 您可能会感到迷惑:调用 GObject 方法与调用简单的 C 函数到底有何区别?嗯,从 C 程序语言本身的角度而言,没有什么区别。就 C 编译器而言,它只是调用一个函数,而该函数的第一个参数正好是指向标准 C 结构的指针。
因此您的 C 编译器甚至不知道我们正在编写面向对象的程序。但是,别让这个事实愚弄了您,让您误以为 GObject OOP 编程只是将良好的旧式 C 编程进行了一番改头换面。GObject 在幕后确实作了许多工作,答应您创建现有类的子类、创建类的接口(我们将在本文的后面对此进行讨论)等等。不过这个 OOP 的所有功能都旨在与标准的 C 编程构造完全兼容。
接口给这个问题提供了解决方案,它答应我们给全异类添加公共功能。因此回到上面的示例,我们只要为 Horse、Car 和 House 类编写“Talk”接口。忽然之间这三个不相关的类都“能够通信”了,并且能够使用我们所创建的与通信相关的新函数。并且这些与通信有关的新函数使用“Talk”接口本身与我们的对象“愉快”地进行交互。因此,由于接口,这三个不相关的类现在“讲同一种语言了”。
在 Glib 中,您可以为一个类创建任意数量的接口。因此,假如我们为 House 类创建了一个 Talk 接口,我们可以如下定义 say() 函数:
say (TALK(myhouse), "hello there!"); 可以在 GTK+ 2 的 GtkEditable 接口中找到更加切实可信的接口示例(请参阅参考资料以获取链接)。文本窗口小部件和条目窗口小部件都实现了这个接口。
GObject 信号 一般而言,由事件驱动的 GUI 程序包含一个主循环。在该循环中,程序一直等待从 X 服务器发出的新消息。这些消息(称为事件)由程序进行解释,并答应程序对用户选择菜单项、单击按钮等操作作出反应。
信号除了将对象相互连接之外,与事件非常相似。它们答应对象在不需要显式的事件循环的情况下自动对另一个对象状态的变化作出“反应”。只需要将一个对象的信号连接到另一个对象的方法。然后,当第一个对象“发出”信号(由于状态的内部变化)时,第二个对象“捕捉”该变化并作出适当的反应。之所以不需要事件循环,是由于信号是用回调实现的 - 发出信号的结果只是调用一个 C 函数。信号是一种有效和灵活的“粘合剂”,它们把程序中的对象“粘合”在一起。