由于 C 编程语言在设计时并没有考虑面向对象编程(OOP)的理念,因此它并不直接支持类、继承、多态和其他面向对象的概念。它也没有类似 C++、Java 和 C# 等面向对象语言中的虚拟表(Virtual Table)。因此,使用仅有的 C 语言特性和标准库来实现面向对象编程范式可能并不容易。然而,通过使用包含函数指针和数据的结构体,或者使用第三方库,仍然可以实现这一点。

有许多第三方库专门为 C 添加了对面向对象编程的支持。其中最通用且广泛使用的是 GObject 系统,它是 Glib 的一部分。GObject 系统自带虚拟表。要在 C 中使用 GObject 系统创建对象,必须从 GObject 结构体继承。

对象创建

在此示例中,将直接从 GObject 派生实现一个新对象。为了简便起见,命名该对象为 MyObject。

声明对象

要创建一个简单的不可继承(最终)对象,需要声明两个结构体:实例和类。它们通过宏声明:

/* 在 myobject.h 中 */
G_DECLARE_FINAL_TYPE (MyObject, my_object, MY, OBJECT, GObject)

这将声明两个结构体:MyObject 和 MyObjectClass。MyObject 必须在 C 实现中定义,而 MyObjectClass 已由宏定义。

样板代码

由于 GObject 系统只是一个第三方库,因此无法对 C 语言本身进行修改,创建新对象需要大量的样板代码。这大部分由上述宏处理。然而,以下代码也是必须的:

/* 在 myobject.h 中 */
#define MY_TYPE_OBJECT my_object_get_type ()

该宏定义了多个函数,包括 MY_OBJECT() 和 MY_OBJECT_CLASS(),用于类型转换;MY_IS_OBJECT() 和 MY_IS_OBJECT_CLASS() 用于测试对象或类是否为正确的类型;MY_OBJECT_GET_CLASS() 用于从实例获取类结构体。

定义对象

在使用之前,必须定义新创建的对象以及实例结构体。

/* 在 myobject.c 中 */

struct _MyObject
{
    GObject parent_instance;

    /* 其他成员 */
};

G_DEFINE_TYPE (MyObject, my_object, G_TYPE_OBJECT)

静态函数

根据你的对象,可能需要定义一些静态函数。对于一个最小的对象,以下函数是必需的:

/* 在 myobject.c 中 */
static void
my_object_class_init (MyObjectClass *klass) 
{
     /* 代码 */
}

static void
my_object_init (MyObject *self)
{
     /* 代码 */
}

构造函数

在 C 中,没有内部的方式为对象分配内存。因此,必须显式声明构造函数。

/* 在 myobject.c 中 */

GObject *
my_object_new (void)
{
     return g_object_new (MY_TYPE_OBJECT,
                          0);
}

对象使用

虽然使用对象的指针类型来创建对象是完全有效的,但建议使用层次结构顶部的对象指针类型,即最远的基类。现在,可以像这样使用新创建的对象:

/* 在 main.c 中 */

/* 注意:GObject 是层次结构的顶部。 */

/* 声明和构造 */
GObject *myobj = my_object_new (); 

/* 销毁 */
g_object_unref (myobj);

继承

概念

继承是最广泛使用且最有用的面向对象概念之一。它通过将现有代码封装到对象中并继承它,为代码的重用提供了一种有效的方式。新的类称为派生类。可以通过继承创建许多对象层次结构。继承也是抽象代码的一种最有效的方式。

实现

在 GObject 系统中,继承可以通过从 GObject 派生来实现。由于 C 不提供用于继承的关键字或运算符,因此通常通过将基类实例和基类作为成员分别声明在派生类和派生类的实例中来实现派生对象。在 C 代码中:

/* 派生对象实例 */
struct _DerivedObject
{
     /* 基类实例是派生类实例的成员 */
     BaseObject parent_instance;
};

进一步阅读

  • Hanser. 《用 ANSI-C 进行面向对象编程》。1994年。Hanser 描述了在标准 ANSI C 中实现类、继承、实例、方法、对象、虚拟表、多态、延迟绑定等的另一种方式。
  • Gregory Naču - C64OS.com. 《6502中的面向对象编程(第二版)》。2019年。Greg Nacu 描述了在 6502 汇编语言中使用极少的内存实现类、继承、实例、方法、对象等的另一种方式。
  • Greg Kroah-Hartman. 《你从未想知道的关于 kobjects、ksets 和 ktypes 的一切》。镜像:《你从未想知道的关于 kobjects、ksets 和 ktypes 的一切》。2007年。
Last modified: Sunday, 12 January 2025, 1:33 PM