探索GObject(1):封装

GObject?

GObject 是一个基于C语言的面向对象的实现。它为C语言提供了一整套面向对象的接口,其特性有封装、继承、重载、类定义、接口、信号、引用计数等。它与 GLib 一并为 GTK+ 的两大基石。

C vs C++

C和面向对象,这不就是C++么?为什么要搞出另一套东西,而不直接使用C++呢?关于C与C++之争是一个大坑。Linux之父Linus就是力挺C而批判C++的。讨厌C++的人似乎认为C++过于复杂,内部机制陷阱过多等等。自己的经历不多,用C++也很少,达不到大牛们的境界,如果让我给个非要用C而不用C++的理由,我也给不出一个有说服力的。

为什么研究GObject

最原始的动力是,我在使用GTK+进行开发,而GObject是GTK+的基石。如果基础不牢,上层一定不会稳,因此很有必要把GObject给过一遍。知道了它的内部,才知道该如何使用它,明白它的机制与原理,做到心中有数。

但是研究GObject能带来更多。由于C里没有任何面向对象机制,因此GObject把这些机制全部实现了一遍。从中可以看到一些机制的实现原理,从而对面向对象有更多的理性了解。

第一步:封装

面向对象的最基本需求就是封装。所谓封装,按我的理解,就是将一系列相关数据,及对这些数据有关的操作,有序的组织在一个结构中。一个圆形有x坐标、y坐标、半径三个参数,我们可以用这三个变量表示一个圆:

double x, y, radius;

这没什么问题。现在多了一个圆,我们又要用三个变量:

double x1, y1, radius1;

当我们有很多个圆的时候,可能要用到数组:

double x[100], y[100], radius[100];

问题在哪?x、y和radius是相互独立的。我完全可以定义100个x,200个y,150个radius。如果不只有圆,还有矩形,那么矩形的坐标叫什么呢?xx、yy?等你写了一堆代码之后回来看,到底x和y是圆的坐标,还是xx和yy是圆的坐标?

所以有了struct。一个struct对数据进行了很自然的封装:

struct Circle {
  double x, y, radius;
};

好了,现在我们有了Circle这个类型。这个类型将圆的三个参数封装到了一起,从现在开始它们就是一个整体了。我可以很自然的声明一个圆,而不是它的三个参数:

struct Circle c;

我们也不用担心x、y、z的数量不等了,更不用担心坐标和矩形坐标命名冲突——它们定义在Rectangle这个struct里呢:)。

事情还没有完。有了圆这个类型,那么对圆的操作呢?假设一个圆的操作之一为移动(move)。我们可以定义如下函数:

void circle_move (struct Circle *self, double x, double y) {
  self->x = x;
  self->y = y;
}

我们输入一个圆的指针,以及新的x、y坐标,移动操作帮助我们把指定的圆移动到新的坐标上。注意第一个参数self,是不是有点眼熟?它就是C++里的this。记得学C++时很多同学对this理解相当困难,如果看这个self就不难理解了:self就是我们要操作的那个变量,它是一个指针。C++在对象方法调用时省略了这个参数,它可以被编译器自动设置。在C里面,这个工作要我们自己做。因此移动一个圆要这么调用:

struct Circle cir;
circle_move (&cir, 10.0, 5.0);

注意self是个指针,因为C里没有引用,所以我们只能使用指针来达到传递一个对象,而不是传递它的复制品的效果。

这个方法……不就是普通的函数调用嘛,根本就没把操作给封装呀。好,现给一个看起来像C++中的方法:

struct Circle {
  double x, y, radius;
  void (*move) (struct Circle *self, double x, double y);
};
...
struct Circle cir;
cir.move = circle_move;
cir.move (&cir, 10.0, 5.0);

通过函数指针,可以让move调用看起来更像C++了。但是,有两个不爽的地方。其一,要显式地将circlemove函数赋值给move函数指针,如果有5个圆,那就要5行指定的代码(除非用数组+循环)。更为严重的是我们可以为不同的变量指定不同move操作。其二,调用时依然要显示地指定self,这带来的一个后果是,我们完全可以调用cir1的move,但是传入的是cir2的指针。

对于第一点,可以使用类结构+初始化函数来解决。对于第二点,C语言是没法避免显示的传入self指针(如果可以的话请告诉我)。因此这种写法只是“像”C++而已,没啥实际的好处。不过在之后我们会看到,GObject会在类结构中使用函数指针来表示对象的操作。

小结

  • 研究C下的面向对象实现,可以让我们更深入地了解面向对象的机理;
  • 要将数据封装,可以使用struct;
  • 要表示对某种对象的操作,定义一组函数,其第一个参数为要操作对象的指针。

话说,很有可能懒筋发作,然后这篇又留下一个坑-_-。希望不要如此吧……

探索GObject Comments(128) 2010年5月26日 18:37