可派生类型 根据继承性,GObject中可分为两种类型:最终类型(final type)和可派生类型(derivable type)。最终类型没有任何子对象,而可派生类型可以有子对象。
这两种对象的主要区别在于它们的类:
最终类型没有自己的类区域,即其类结构体中唯一成员是其父类的类结构体。
可派生类型的对象则拥有自己的类区域,在这个类区域中可以定义自己特有的函数、属性等,这使得可派生类型的类可以被进一步继承和扩展。其类结构体对子类是开放的。
G_DECLARE_DERIVABLE_TYPE
可以用来在头文件中声明一个可派生类型。
抽象类型 抽象类型没有实例。此类型是可派生的,其子类可以使用抽象类型的函数和信号。
本节将使用三个类型PawNumber
,PawInt
和PawDouble
。
PawDouble
在前一节中已定义,PawInt
的定义类似PawDouble
。PawInt
和PawDouble
分别表示整数和浮点数。
而PawNumber
代表数字,数字是比整数和浮点数更抽象的概念。所以PawNumber
被设计为PawInt
和PawDouble
的父类,并且它不可实例化,因为他是抽象类型。
PawInt
和PawDouble
有三个操作:乘法、除法、一元取反操作和(为了方便展示,省去加法和减法),除此以外还会定义一个to_s
操作,它可以将PawNumber
代表的值转化为字符串。这些操作可以在PawNumber
类型上定义。
上述代码都会在后文给出
宏 G_BEGIN_DECLS和G_END_DECLS G_BEGIN_DECLS
和G_END_DECLS
是GLib提供的宏,用于在C++环境中保持C语言链接约定。这两个宏主要用于确保当GObject库的头文件被C++编译器包含时,内部声明的C函数不会被当作C++函数进行名称修饰,从而可以在C++代码中正常调用C库函数。
等价于:
1 2 3 4 5 6 7 #ifdef __cplusplus #define G_BEGIN_DECLS extern "C" { #define G_END_DECLS } #else #define G_BEGIN_DECLS #define G_END_DECLS #endif
G_DECLARE_DERIVABLE_TYPE 1 #define G_DECLARE_DERIVABLE_TYPE(ModuleObjName, module_obj_name, MODULE, OBJ_NAME, ParentName)
其中:
ModuleObjName:新类型的名称,驼峰形式(如PawDouble
)
module_obj_name:新类型名称,全小写,单词之间使用下划线分隔(如paw_double
)
MODULE:模块的名称,全大写(如PAW
)
OBJ_NAME:新类型的名称,去除修饰,全大写如(DOUBLE
)
ParentName:父类型的名称,驼峰形式(如GObject
)
G_DECLARE_DERIVABLE_TYPE
内部完成以下操作:
声明返回类型为GType
的<moudle>_<name>_get_type()
函数: 这只是声明,用户需要定义它。但其实可以使用G_DEFINE_TYPE
,其扩展包括该函数的定义。所以,实际上用户不需要手动编写定义。
定义_<Moudle><Name>
实例结构体: 其唯一的成员是其父类型的实例结构体。用户可以在源文件中使用私有结构体来存储实例的变量,这一行为(将不需要公开的实例的变量存储在一个私有的结构体中)是为了封装和隐藏实现细节。然后typedef struct _<Moudle><Name> <Moudle><Name>
定义<Moudle><Name>Class
: 简化类结构体的使用,即typedef struct _<Moudle><Name>Class <Moudle><Name>Class
。但是_<Moudle><Name>Class
结构体本身并没有在宏中定义,用户应该在使用此宏之后在头文件中定义。
定义<MOUDLE>_<NAME>()
和<MOUDLE>_<NAME>_CLASS()
静态内联函数:<MOUDLE>_<NAME>()
将参数转换为指向该类型实例的指针。<MOUDLE>_<NAME>_CLASS()
同理。
定义<MOUDLE>_IS_<NAME>()
和<MOUDLE>_IS_<NAME>_CLASS()
函数:<MOUDLE>_IS_<NAME>()
会检查参数是否为指向<Moudle><Name>
类型实例的指针。如果参数指向<Moudle><Name>
及其后代,则宏返回真。<MOUDLE>_IS_<NAME>_CLASS()
同理。
定义<MOUDLE>_<NAME>_GET_CLASS()
函数: 接受一个指向<Moudle><Name>
类型(或其子类型)的实例的指针,获取指向<Moudle><Name>Class
的指针。
在使用G_DECLARE_DERIVABLE_TYPE
之前,我们还要手动定义<MOUDLE>_TYPE_<NAME>
。
对于一个类型的声明,从使用G_DECLARE_FINAL_TYPE()
变成使用G_DECLARE_DERIVABLE_TYPE()
,是不会破坏API或ABI的。所以在我们平时使用中,如果一开始无法判断类型的可派生性,建议先使用G_DECLARE_FINAL_TYPE()
,直到我们确定该类型的派生是有意义的,再使用相应的宏。
G_DECLARE_FINAL_TYPE 详见上一节
G_DEFINE_ABSTRACT_TYPE 类似于G_DEFINE_TYPE
,但是是定义一个抽象类型。
示例代码 PawNumber 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 #ifndef PAW_NUMBER_H #define PAW_NUMBER_H #include <glib-object.h> G_BEGIN_DECLS #define PAW_TYPE_NUMBER (paw_number_get_type()) G_DECLARE_DERIVABLE_TYPE(PawNumber, paw_number, PAW, NUMBER, GObject) struct _PawNumberClass { GObjectClass parent_class; PawNumber* (*mul)(PawNumber* self, PawNumber* other); PawNumber* (*div)(PawNumber* self, PawNumber* other); PawNumber* (*uminus)(PawNumber* self); char * (*to_s)(PawNumber* self); }; PawNumber* paw_number_mul (PawNumber* self, PawNumber* other) ; PawNumber* paw_number_div (PawNumber* self, PawNumber* other) ; PawNumber* paw_number_uminus (PawNumber* self) ; char * paw_number_to_s (PawNumber* self) ;G_END_DECLS #endif
7,33:宏,用于在C++环境下保持C语言链接约定,确保C函数不会被当作C++函数进行名称修饰。
10:声明可派生类型PawNumber。
13-21:定义类结构体PawNumberClass
。
25-31:定义公共函数。
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include "PawNumber.h" G_DEFINE_ABSTRACT_TYPE(PawNumber, paw_number, G_TYPE_OBJECT) PawNumber* paw_number_mul (PawNumber* self, PawNumber* other) { g_return_val_if_fail(PAW_IS_NUMBER(self), NULL ); g_return_val_if_fail(PAW_IS_NUMBER(other), NULL ); PawNumberClass* class = PAW_NUMBER_GET_CLASS(self); return class->mul ? class->mul(self, other) : NULL ; } PawNumber* paw_number_div (PawNumber* self, PawNumber* other) { g_return_val_if_fail(PAW_IS_NUMBER(self), NULL ); g_return_val_if_fail(PAW_IS_NUMBER(other), NULL ); PawNumberClass* class = PAW_NUMBER_GET_CLASS(self); return class->div ? class->div(self, other) : NULL ; } PawNumber* paw_number_uminus (PawNumber* self) { g_return_val_if_fail(PAW_IS_NUMBER(self), NULL ); PawNumberClass* class = PAW_NUMBER_GET_CLASS(self); return class->uminus ? class->uminus(self) : NULL ; } char * paw_number_to_s (PawNumber* self) { g_return_val_if_fail(PAW_IS_NUMBER(self), NULL ); PawNumberClass* class = PAW_NUMBER_GET_CLASS(self); return class->to_s ? class->to_s(self) : NULL ; } static void paw_number_class_init (PawNumberClass* class) { class -> mul = NULL ; class -> div = NULL ; class -> uminus = NULL ; class -> to_s = NULL ; } static void paw_number_init (PawNumber* self) { }
4:定义抽象类型PawNumber
6-42:公共函数,如果子类有提供重写,则调用子类的实现;如果没有,则不进行任何操作。
44-51:类初始化函数
47-50:虚函数,它们期望被子类重写,所以在此抽象类中赋值为NULL,意味着它们在基类中的默认实现就是什么也不做。
53-55:实例初始化函数。由于抽象类型无法被实例化,因此该函数什么也不做。不过,不能省略该函数的定义。
PawInt 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #ifndef PAW_INT_H #define PAW_INT_H #include <glib-object.h> G_BEGIN_DECLS #define PAW_TYPE_INT (paw_int_get_type()) G_DECLARE_FINAL_TYPE(PawInt, paw_int, PAW, INT, PawNumber) struct _PawInt { PawNumber parent; int value; }; PawInt* paw_int_new (int value) ; G_END_DECLS #endif
6,20:宏,用于在C++环境下保持C语言链接约定,确保C函数不会被当作C++函数进行名称修饰。
9:声明最终类型PawInt
12-16:定义实例结构体
18:声明公共函数。算术函数和to_s
函数在PawNumber中声明,所以PawInt无需再次声明。
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 #include "PawNumber.h" #include "PawInt.h" #include "PawDouble.h" G_DEFINE_TYPE(PawInt, paw_int, PAW_TYPE_NUMBER) #define paw_int_binary_op(op)\ int i;\ double d;\ if (PAW_IS_INT(other))\ {\ i = PAW_INT(other)->value;\ return PAW_NUMBER(paw_int_new(PAW_INT(self)->value op i));\ }\ else \ {\ d = PAW_DOUBLE(other)->value;\ return PAW_NUMBER(paw_int_new(PAW_INT(self)->value op (int)d));\ } static PawNumber* paw_int_mul (PawNumber* self, PawNumber* other) { g_return_val_if_fail(PAW_IS_INT(self), NULL ); paw_int_binary_op(*); } static PawNumber* paw_int_div (PawNumber* self, PawNumber* other) { g_return_val_if_fail(PAW_IS_INT(self), NULL ); paw_int_binary_op(/); } static PawNumber* paw_int_uminus (PawNumber* self) { g_return_val_if_fail(PAW_IS_INT(self), NULL ); return PAW_NUMBER(paw_int_new(-PAW_INT(self)->value)); } static char * paw_int_to_s (PawNumber* self) { g_return_val_if_fail(PAW_IS_INT(self), NULL ); PawInt* int_self = PAW_INT(self); return g_strdup_printf("%d" , int_self->value); } PawInt* paw_int_new (int value) { PawInt* d; d = g_object_new(PAW_TYPE_INT, NULL ); d->value = value; return d; } static void paw_int_class_init (PawIntClass* class) { PawNumberClass* number_class = PAW_NUMBER_CLASS(class); number_class->mul = paw_int_mul; number_class->div = paw_int_div; number_class->uminus = paw_int_uminus; number_class->to_s = paw_int_to_s; } static void paw_int_init (PawInt* self) { }
6:定义类型PawInt。
10-22:定义了一个宏,用于对PawInt和PawDouble类型实例执行二元算术操作,并返回一个新的PawNumber类型实例。
24-36:使用paw_int_binary_op
进行的乘,除操作。
38-43:一元负号运算,取反操作。
45-51:to_s
函数,将整形转换为字符串。调用者负责释放创建出的字符串。
61-70:类初始化函数。将子类实现的乘,除等操作绑定到基类的函数指针上以达到重写的目的。
72-74:实例初始化函数。
PawDouble 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #ifndef PAW_DOUBLE_H #define PAW_DOUBLE_H #include <glib-object.h> G_BEGIN_DECLS #define PAW_TYPE_DOUBLE (paw_double_get_type()) G_DECLARE_FINAL_TYPE(PawDouble, paw_double, PAW, DOUBLE, PawNumber) struct _PawDouble { PawNumber parent; double value; }; PawDouble* paw_double_new (double value) ; 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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 #include "PawNumber.h" #include "PawDouble.h" #include "PawInt.h" G_DEFINE_TYPE(PawDouble, paw_double, PAW_TYPE_NUMBER) #define paw_double_binary_op(op)\ int i;\ double d;\ if (PAW_IS_INT(other))\ {\ i = PAW_INT(other)->value;\ return PAW_NUMBER(paw_double_new(PAW_DOUBLE(self)->value op (double)i));\ }\ else \ {\ d = PAW_DOUBLE(other)->value;\ return PAW_NUMBER(paw_double_new(PAW_DOUBLE(self)->value op d));\ } static PawNumber* paw_double_mul (PawNumber* self, PawNumber* other) { g_return_val_if_fail(PAW_IS_DOUBLE(self), NULL ); paw_double_binary_op(*); } static PawNumber* paw_double_div (PawNumber* self, PawNumber* other) { g_return_val_if_fail(PAW_IS_DOUBLE(self), NULL ); paw_double_binary_op(/); } static PawNumber* paw_double_uminus (PawNumber* self) { g_return_val_if_fail(PAW_IS_DOUBLE(self), NULL ); return PAW_NUMBER(paw_double_new(- PAW_DOUBLE(self)->value)); } static char * paw_double_to_s (PawNumber* self) { g_return_val_if_fail(PAW_IS_DOUBLE(self), NULL ); PawDouble* double_self = PAW_DOUBLE(self); return g_strdup_printf("%lf" , double_self->value); } PawDouble* paw_double_new (double value) { PawDouble* d; d = g_object_new(PAW_TYPE_DOUBLE, NULL ); d->value = value; return d; } static void paw_double_class_init (PawDoubleClass* class) { PawNumberClass* number_class = PAW_NUMBER_CLASS(class); number_class->mul = paw_double_mul; number_class->div = paw_double_div; number_class->uminus = paw_double_uminus; number_class->to_s = paw_double_to_s; } static void paw_double_init (PawDouble* self) { }
内容类似于PawInt,此处不赘述。
main函数 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 37 38 39 40 41 #include <glib-object.h> #include "PawNumber.h" #include "PawInt.h" #include "PawDouble.h" int main (int argc, char **argv) { PawNumber *i, *d, *num; char *si, *sd, *snum; i = PAW_NUMBER(paw_int_new(23 )); d = PAW_NUMBER(paw_double_new(10.3 )); num = paw_number_mul(i, d); si = paw_number_to_s(i); sd = paw_number_to_s(d); snum = paw_number_to_s(num); g_print("%s * %s is %s\n" , si, sd, snum); g_object_unref(num); g_free(snum); num = paw_number_mul(d, i); snum = paw_number_to_s(num); g_print("%s * %s is %s\n" , sd, si, snum); g_object_unref(num); g_free(snum); g_free(sd); g_free(si); g_object_unref(d); g_object_unref(i); return 0 ; }
主函数的功能较简单:
12-13:创建PawDouble和PawInt实例并初始化
15:给i乘上d
26:给d乘上i
总结 上述代码还存在着一些缺陷,由于还没有掌握相关知识,将在后续章节中优化,现总结如下:
对于除运算,没有对除数为0的特殊情况做处理。这将在《信号章节》 中进行优化。
对于二元运算宏paw_int_binary_op
和paw_double_binary_op
,由于其内部要使用类型实例的value属性,因此不得不把实例结构体_PawInt
和_PawDouble
定义在头文件中,将细节暴露给了外部代码,破坏了封装性。这将在《属性章节》 中进行优化。
参考文章 1.GObject Tutorial for beginners