GObject是Gtk里非常重要的概念,几乎所有的Gtk代码都涉及到GObject,那到底这是何方神圣以致它在Gtk里应用如此广泛?
对GObject的初次了解
总的来说,GObject做的事情是:在c里实现面向对象。
众所周知,c是面向过程语言,可是可以通过一些手段实现面向对象。
从面向对象了解
来看一下这段C++代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include <iostream>
class CircleShape { constexpr static double PI = 3.1415926; int radius; private: void setRadius(int r) { radius = r; } public: CircleShape(int r) { setRadius(r); } double getArea() { return PI*radius*radius; } static double getPI() { return PI; } };
int main(int argc, char *argv[]) { CircleShape cs(2); std::cout << CircleShape::getPI() << "*2=" << cs.getArea() << std::endl; return 0; }
|
重点关注CircleShape这个类,里面有静态变量、局部变量和常量,有私有函数、公开函数、静态函数。
面向对象有三个特性:继承、封装、多态
结合GObject用C实现这个类
为了实现面向对象的三个特征,我们可以将其拆成三个部分。
如图,蓝色部分是类的定义,描述类的信息;红色部分是类内变量;绿色部分是各种函数定义。
按照这样拆分并且按照一定规则翻译成C即可,没有什么难度,是吗?完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
#ifndef __CIRCLESHAPE_HPP__ #define __CIRCLESHAPE_HPP__ #include <gtk-3.0/gtk/gtk.h> G_BEGIN_DECLS
#define CIRCLESHAPE_TYPE (circleshape_get_type()) #define CIRCLESHAPE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CIRCLESHAPE_TYPE, CircleShape)) #define CIRCLESHAPE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CIRCLESHAPE_TYPE, CircleShapeClass)) #define IS_CIRCLESHAPE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CIRCLESHAPE_TYPE)) #define IS_CIRCLESHAPE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CIRCLESHAPE_TYPE)) #define CIRCLESHAPE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CIRCLESHAPE_TYPE, CircleShapeClass))
const static double PI = 3.1415926;
typedef struct _CircleShape { GObject parent; } CircleShape;
typedef struct _CircleShapeClass { GObjectClass parent_class; } CircleShapeClass;
GDK_AVAILABLE_IN_ALL CircleShape* circleshape_new(int r); GDK_AVAILABLE_IN_ALL double circleshape_getArea(CircleShape *cs); GDK_AVAILABLE_IN_ALL double circleshape_getPI();
G_END_DECLS #endif
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
#include "CircleShape.hpp" #include <gtk-3.0/gtk/gtk.h>
typedef struct _CircleShapePrivate { int radius; } CircleShapePrivate;
G_DEFINE_TYPE_WITH_PRIVATE(CircleShape, circleshape, G_TYPE_OBJECT);
static void circleshape_init(CircleShape *cs) {} static void circleshape_class_init(CircleShapeClass *klass) {}
void setRadius(CircleShape* cs, int r) { CircleShapePrivate *priv = (CircleShapePrivate*)circleshape_get_instance_private(cs); priv->radius = r; }
double circleshape_getArea(CircleShape *cs) { CircleShapePrivate *priv = (CircleShapePrivate*)circleshape_get_instance_private(cs); double radius = priv->radius; return PI*radius*radius; }
double circleshape_getPI() { return PI; }
CircleShape* circleshape_new(int r) { CircleShape *cs = (CircleShape*)g_object_new(CIRCLESHAPE_TYPE, 0); setRadius(cs, r); return cs; }
|
哇,代码那么多!别怕,下面一一讲解。
头文件
G_BEGIN_DECLS
和G_END_DECLS
分别是extern "C" {
和}
,主要是用于兼容C++,这里跟GObject无关。
那么,这个是什么?
1 2 3 4 5 6
| #define CIRCLESHAPE_TYPE (circleshape_get_type()) #define CIRCLESHAPE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CIRCLESHAPE_TYPE, CircleShape)) #define CIRCLESHAPE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CIRCLESHAPE_TYPE, CircleShapeClass)) #define IS_CIRCLESHAPE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CIRCLESHAPE_TYPE)) #define IS_CIRCLESHAPE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CIRCLESHAPE_TYPE)) #define CIRCLESHAPE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CIRCLESHAPE_TYPE, CircleShapeClass))
|
类名字_TYPE:就是类名字_get_type的调用。
类名字(obj):调用G_TYPE_CHECK_INSTANCE_CAST
对obj进行检查和转换。
类名字_CLASS(klass):调用G_TYPE_CHECK_CLASS_CAST
对obj进行检查和转换。
IS_类名字(klass):就是检查是不是这个类,不转换。
类名字_GET_CLASS(obj):顾名思义,得到这个类的class。
聪明的你已经发现了,类名字_get_type()这个实现在哪里呢?其实在后面G_DEFINE_TYPE_WITH_PRIVATE
有定义,我们后面再讲。
1 2 3 4 5 6 7 8 9
| const static double PI = 3.1415926;
typedef struct _CircleShape { GObject parent; } CircleShape;
typedef struct _CircleShapeClass { GObjectClass parent_class; } CircleShapeClass;
|
将类写成两个结构休,要有类的描述CircleShapeClass,这个是类的实现原理。
这里每个结构体都要有一个父类,目的是?体现面向对象的继承性。
1 2 3 4 5 6
| GDK_AVAILABLE_IN_ALL CircleShape* circleshape_new(int r); GDK_AVAILABLE_IN_ALL double circleshape_getArea(CircleShape *cs); GDK_AVAILABLE_IN_ALL double circleshape_getPI();
|
这些就是你熟悉的各种函数了,除了GDK_AVAILABLE_IN_ALL
,这个就是extern
,呃,感觉这个宏变复杂了,不过有这个宏不是更容易看吗?
源文件
有意思的是,我们把私有成员放在源文件里,为了不让懵懵懂懂的程序员随便修改我们的私有成员,达到隐藏数据结构的目的。
1 2 3
| typedef struct _CircleShapePrivate { int radius; } CircleShapePrivate;
|
!最重要的地方在这里,G_DEFINE_TYPE
有很多形式,但是原理上是有互相调用的:
G_DEFINE_TYPE
G_DEFINE_TYPE_EXTEND
G_DEFINE_TYPE_WITH_CODE
G_DEFINE_TYPE_WITH_PRIVATE
这里使用的是G_DEFINE_TYPE_WITH_PRIVATE
,它实质上是调用G_DEFINE_TYPE_EXTENDED
实现的。
1 2 3 4
|
G_DEFINE_TYPE_WITH_PRIVATE(CircleShape, circleshape, G_TYPE_OBJECT);
|
它可以算是一个语法糖,用一个宏生成一堆代码,不用自己造轮子。
这个宏帮我们生成的函数有:
gtk_gadget_get_type,gtk_gadget_class_intern_init,gtk_gadget_get_instance_private
但是它生成的一些函数需要我们自定义的有:gtk_gadget_init,gtk_gadget_class_init
嗯,一个类的构造函数,一个是类描述的初始化,这些只能是根据你类的功能做出相应的改变,gtk没办法帮你生成。所以我们接着把这两个函数实现了吧:
1 2 3
| static void circleshape_init(CircleShape *cs) {} static void circleshape_class_init(CircleShapeClass *klass) {}
|
注意:
每次g_object_new都会调用gtk_gadget_init,因为这个是类的构造函数
但是gtk_gadget_class_init只有第一次才调用,其他时候都不会调用,因为这是类描述的初始化,相当于固定下来了,不需要进行改动。
然后就是我们的自定义函数啦:
1 2 3 4 5 6 7 8 9 10 11 12
| void setRadius(CircleShape* cs, int r) { CircleShapePrivate *priv = (CircleShapePrivate*)circleshape_get_instance_private(cs); priv->radius = r; }
double circleshape_getArea(CircleShape *cs) { CircleShapePrivate *priv = (CircleShapePrivate*)circleshape_get_instance_private(cs); double radius = priv->radius; return PI*radius*radius; }
double circleshape_getPI() { return PI; }
|
这些没什么好说的,注意两点:
还有别忘了这个哦 :
1 2 3 4 5
| CircleShape* circleshape_new(int r) { CircleShape *cs = (CircleShape*)g_object_new(CIRCLESHAPE_TYPE, 0); setRadius(cs, r); return cs; }
|
这个嘛,相当于自定义JAVA里面的new操作符,对象总得new一个嘛,对不对?就是靠这个new出来的,这个实际上是调用g_object_new
实现的,其实这里是调用了我们的circleshape_init
哦!对象初始化嘛。如果是第一次new呢?当然还要调用circleshape_class_init
啦,这样GObject才能知道这个类的存在嘛。
主程序
通过改写class,那我们怎么使用这个类啊?
1 2 3 4 5 6 7 8 9 10
| #include <iostream> #include "CircleShape.hpp"
int main(int argc, char *argv[]) { CircleShape* cs = circleshape_new(2); std::cout << circleshape_getPI() << "*2=" << circleshape_getArea(cs) << std::endl; return 0; }
|
这里就跟GTK的调用一样了,这么方便调用,我们都不用知道它内部怎么工作的了。
这体现了什么?面向对象的封装性!
前记
什么?前记?怎么才是前记?因为前面都是基础啊。GObject还有很多内容呢
你说它没有体现多态?那确实。但是GObject是把多态设计进去的了,我们以后将会遇到。
其实最近也没在搞GTK了
先鸽一下吧,到搞GTK的时候再更新…