创建非抽象可派生类型比创建抽象类型更常见。本节介绍如何创建非抽象可派生类型对象。派生类型的例子是string的对象。它是TStr。它的子对象是一个数字字符串对象。数字字符串是表示数字的字符串。例如“0”、“-100”、“123.45”。子对象(数字字符串)将在下一节中解释。
我想先写字符串,再写可派生对象。因为TStr是字符串的对象,我们需要小心使用字符串编写程序。
字符串和内存管理
TStr有一个字符串类型的值。它类似于TInt或TDouble,但string比int和double更复杂。在编写TStr程序时,需要注意内存管理,这对于TInt和TDouble来说是不必要的。
字符串和内存
String是一个字符数组,以’\0’结束。String不是C类型,如char, int, float或double。但是指向字符数组的指针的行为与其他语言的字符串类型类似。我们通常称指针为字符串。
如果指针为NULL,则它不指向任何东西。指针不是字符串。如果不小心使用空指针,带有字符串的程序将包含错误。
另一个恼人的问题是内存分配。因为字符串是一个字符数组,所以需要分配内存来创建一个新的字符串。我们没有忘记分配内存,但经常忘记释放内存。它会导致内存泄漏。
char *s;
s = g_strdup ("Hello.");
... ... ... do something with s
g_free (s);
g_strdup复制一个字符串。它:
- 计算字符串的大小。"Hello."的大小是7,因为字符串是以零结尾的。字符串是一个数组‘H’,‘e’,‘l’,‘l’,‘o’、‘。‘和零(’\0’)。
- 请求系统分配7个字节的内存。
- 将字符串复制到内存中。
- 返回指向新分配内存的指针。
- 如果参数为NULL,则g_strdup返回NULL。
如果字符串s
不再使用,则必须释放s
,这意味着必须将分配的7个字节返回给系统。g_free
释放内存。
由双引号(如“Hello.”)限定的字符串是字符串常量。它是一个字符数组,但是数组的内容不允许更改。它不能被释放。如果将字符写入字符串字面量或释放字符串字面量,则结果为未定义。可能会发生不好的事情,例如,段错误。
当你用字符串文字初始化数组和指针时,它们之间是有区别的。如果数组是用字符串字面值初始化的,则数组可以被更改。
char a[]="Hello!";
a[1]='a';
g_print ("%s\n", a); /* Hallo will appear in your display. */
第一行是数组a初始化,初始化并不简单。首先,编译器计算“Hello!”的长度。它是7,因为字符串文字的末尾有’\0’。然后在静态内存或堆栈内存中分配7个字节的内存。它取决于数组的类,是静态的还是自动的。用“Hello!”初始化内存。数组中的字符串可以被改变。该程序成功显示“Hallo!”
上面程序的第一行如下所示。
char a[] = {'H', 'e', 'l', 'l', 'o', '!', '\0'};
如果你用字符串常量定义指针,你不能改变指针指向的字符串内容。
char *a = "Hello";
*(a+1) = 'a'; /* This is illegal. */
g_print ("%s\n", a);
第一行只是将字符串字面值的地址赋给变量a。字符串常量是一个字符数组,但它是只读的。它可能在程序代码区域或其他一些不可写的区域。这取决于编译器的实现。因此,第二行尝试将字符’a’写入只读内存,结果是未定义的,例如,发生了分段错误。不管怎样,不要写这样的程序。
总之,字符串是一个字符数组,它被放置在以下类型之一。
- 只读内存。字符串常量是只读的。
- 栈。如果一个数组的类是auto,那么该数组将被放置在栈中。stack是可写的。如果数组是在函数中定义的,它的默认类是auto。当函数返回给调用者时,栈区域将消失。
- 静态区域。如果数组的类是静态的,则该数组被放置在静态区域中。它保持其值,并在程序的生命周期内保持不变。这个区域是可写的。
- 堆。您可以为字符串分配和释放内存。对于分配,使用g_new或g_new0。对于释放,使用g_free。
拷贝字符串
复制字符串有两种方法。第一种方法是复制指针。
char *s = "Hello";
char *t = s;
两个指针s
和t
指向同一个地址。因此,您不能修改t,因为t指向一个只读的字符串字面值。
第二种方法是创建内存并将字符串复制到内存中。
char *s = "Hello";
char *t = g_strdup (s);
g_strdup
分配内存并使用"Hello"初始化它,然后返回指向内存的指针。函数g_strdup几乎与下面的string_dup函数相同。
#include <glib-object.h>
#include <string.h>
char *
string_dup (char *s) {
int length;
char *t;
if (s == NULL)
return NULL;
length = strlen (s) + 1;
t = g_new (char, length);
strcpy (t, s);
return t;
}
如果使用g_strdup,则两个指针s和t指向不同的内存。您可以修改t,因为它位于从堆区域分配的内存中。
知道赋值指针和复制字符串之间的区别很重要。
常量限定符
限定符const创建的变量不会改变其值。它也可以应用于数组。这样,数组的元素就不会被改变。
const double PI = 3.1415926536;
const char a[] = "只读字符串";
函数中的数组形参可以用const限定,以指示该函数不改变数组。同样,函数的返回值(指向数组或字符串的指针)可以用const限定。调用者不能修改或释放返回的数组或字符串内容。
char *
string_dup (const char *s) {
……
}
const char *
g_value_get_string (const GValue *value);
限定符const表示在对象函数中使用该字符串时,谁是该字符串的所有者。“Owner”是一个对象或函数的调用者,它有权修改或释放字符串。
例如,g_value_get_string被赋予const GValue *value作为参数。由value指向的GValue归调用者所有,函数不会改变或释放它。该函数返回一个带有const限定的字符串。返回的字符串归对象所有,调用者不能改变或释放字符串。字符串可能稍后会被对象更改或释放。
头文件
本节的其余部分是关于TStr的。TStr是GObject的子对象,它包含一个字符串。字符串是一个指针和一个字符数组。指针指向数组。指针可以是NULL。如果为NULL,则TStr没有数组。TStr有一个字符串类型属性。
头文件tstr.h
如下所示。
1 #ifndef __T_STR_H__
2 #define __T_STR_H__
3
4 #include <glib-object.h>
5
6 #define T_TYPE_STR (t_str_get_type ())
7 G_DECLARE_DERIVABLE_TYPE (TStr, t_str, T, STR, GObject)
8
9 struct _TStrClass {
10 GObjectClass parent_class;
11 /* expect that descendants will override the setter */
12 void (*set_string) (TStr *self, const char *s);
13 };
14
15 TStr *
16 t_str_concat (TStr *self, TStr *other);
17
18 /* setter and getter */
19 void
20 t_str_set_string (TStr *self, const char *s);
21
22 char *
23 t_str_get_string (TStr *self);
24
25 /* create a new TStr instance */
26 TStr *
27 t_str_new_with_string (const char *s);
28
29 TStr *
30 t_str_new (void);
31 #endif /* __T_STR_H__ */
32
- 7:使用
G_DECLARE_DERIVABLE_TYPE
,类似于前一节中的例子。 - 9-13: TStrClass有一个类方法。它是set_string。这将被子类TNumStr覆盖。类方法由公共函数t_str_set_string调用。它有一个字符串形参,并使用string(参数)设置实例字符串。TNumStr类保存类似TStr的字符串,但它也保存字符串的类型。类方法set_string将被重写,它不仅将设置字符串,还将设置子类中的类型。详细的解释将在本节的后面部分和下一节中。
- 15-16: t_str_concat连接两个TStr实例字符串并返回一个新的TStr实例。
- 19-23: Setter和getter。
- 26-30:创建TStr对象的函数。
C file
1 #include "tstr.h"
2
3 enum {
4 PROP_0,
5 PROP_STRING,
6 N_PROPERTIES
7 };
8
9 static GParamSpec *str_properties[N_PROPERTIES] = {NULL, };
10
11 typedef struct {
12 char *string;
13 } TStrPrivate;
14
15 G_DEFINE_TYPE_WITH_PRIVATE(TStr, t_str, G_TYPE_OBJECT)
16
17 static void
18 t_str_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) {
19 TStr *self = T_STR (object);
20
21 if (property_id == PROP_STRING) {
22 t_str_set_string(self, g_value_get_string (value));
23 } else
24 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
25 }
26
27 static void
28 t_str_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) {
29 TStr *self = T_STR (object);
30
31 if (property_id == PROP_STRING)
32 g_value_set_string (value, t_str_get_string(self));
33 else
34 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
35 }
36
37 static void
38 t_str_real_set_string (TStr *self, const char *s) {
39 TStrPrivate *priv = t_str_get_instance_private (self);
40
41 if (priv->string)
42 g_free (priv->string);
43 priv->string = g_strdup (s);
44 }
45
46 static void
47 t_str_finalize (GObject *object) {
48 TStr *self = T_STR (object);
49 TStrPrivate *priv = t_str_get_instance_private (self);
50
51 if (priv->string)
52 g_free (priv->string);
53 G_OBJECT_CLASS (t_str_parent_class)->finalize (object);
54 }
55
56 static void
57 t_str_init (TStr *self) {
58 TStrPrivate *priv = t_str_get_instance_private (self);
59
60 priv->string = NULL;
61 }
62
63 static void
64 t_str_class_init (TStrClass *class) {
65 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
66
67 gobject_class->finalize = t_str_finalize;
68 gobject_class->set_property = t_str_set_property;
69 gobject_class->get_property = t_str_get_property;
70 str_properties[PROP_STRING] = g_param_spec_string ("string", "str", "string", "", G_PARAM_READWRITE);
71 g_object_class_install_properties (gobject_class, N_PROPERTIES, str_properties);
72
73 class->set_string = t_str_real_set_string;
74 }
75
76 /* setter and getter */
77 void
78 t_str_set_string (TStr *self, const char *s) {
79 g_return_if_fail (T_IS_STR (self));
80 TStrClass *class = T_STR_GET_CLASS (self);
81
82 class->set_string (self, s);
83 }
84
85 char *
86 t_str_get_string (TStr *self) {
87 g_return_val_if_fail (T_IS_STR (self), NULL);
88 TStrPrivate *priv = t_str_get_instance_private (self);
89
90 return g_strdup (priv->string);
91 }
92
93 TStr *
94 t_str_concat (TStr *self, TStr *other) {
95 g_return_val_if_fail (T_IS_STR (self), NULL);
96 g_return_val_if_fail (T_IS_STR (other), NULL);
97
98 char *s1, *s2, *s3;
99 TStr *str;
100
101 s1 = t_str_get_string (self);
102 s2 = t_str_get_string (other);
103 if (s1 && s2)
104 s3 = g_strconcat (s1, s2, NULL);
105 else if (s1)
106 s3 = g_strdup (s1);
107 else if (s2)
108 s3 = g_strdup (s2);
109 else
110 s3 = NULL;
111 str = t_str_new_with_string (s3);
112 if (s1) g_free (s1);
113 if (s2) g_free (s2);
114 if (s3) g_free (s3);
115 return str;
116 }
117
118 /* create a new TStr instance */
119 TStr *
120 t_str_new_with_string (const char *s) {
121 return T_STR (g_object_new (T_TYPE_STR, "string", s, NULL));
122 }
123
124 TStr *
125 t_str_new (void) {
126 return T_STR (g_object_new (T_TYPE_STR, NULL));
127 }
128
- 3-9: enum定义属性id。PROP_STRING是“string”属性的id。这里只定义了一个属性,所以可以在没有enum的情况下定义它。但是,enum可以用于定义两个或多个属性。所以,更推荐这种方式。以同样的方式,使用数组str_properties。它为GParamSpec存储一个静态指针。
- 11-13: TStrPrivate是一个C结构体。它是TStr的私有数据区域。如果TStr是最终类型,则不存在后代,TStr实例可以看作是私有数据区域。但是TStr是可派生的,所以你不能在对后代开放的TStr实例中存储这样的私有数据。这个结构的名称是“
<object name>Private
”,例如TStrPrivate。该结构必须在G_DEFINE_TYPE_WITH_PRIVATE之前定义。 - 15: G_DEFINE_TYPE_WITH_PRIVATE宏。这个宏展开为:
1.
声明t_str_class_init是一个类初始化函数。
2.
声明实例初始化函数t_str_init。
3.
定义t_str_parent_class静态变量。它指向TStr的父类。
4.
将私有实例数据添加到类型中。它是一个C结构,它的名字是TStrPrivate。(见上图)。
5.
定义t_str_get_type()函数。该函数在第一次调用时注册该类型。
6.
定义一个私有实例getter t_str_get_instance_private()。这个函数有一个参数,它是指向实例的指针。 - 17-25: t_str_set_property。这类似于t_int_set_property,但是属性值存储在
私有区域
中。 - 22:使用t_str_set_string函数用GValue中的字符串设置私有数据区域。这很重要,因为t_str_set_string调用类方法set_string,该方法将被子类覆盖。因此,t_str_set_property函数将在子类中设置字符串及其类型。函数g_value_get_string返回GValue拥有的字符串的指针。你不能拥有字符串。这意味着你不能改变或释放字符串。因此,有必要在存储字符串之前复制字符串。复制在函数t_str_set_string中完成。
- 27-35: t_str_get_property。它使用t_str_get_string函数。字符串复制在函数中完成。
- 37-44 t_str_real_set_string函数。这是类中的set_string所指向的。所以,它是类方法的主体。首先,它使用t_str_get_instance_private函数获取私有区域的指针。如果实例拥有一个字符串,在使用新字符串设置它之前释放它。它复制字符串并将其放入priv->字符串。
- 46-54:当TStr实例被销毁时调用t_str_finalize。这个函数释放字符串priv->。之后,它调用父类的finalize方法。这被称为“chain up to its parent”,这将在本节后面解释。
- 56-61: t_str_init初始化priv->字符串。
- 63-74: t_str_class_init函数初始化TStr对象的类。
- 67:覆盖finalize方法。在销毁过程中调用此方法。
- 68-69:覆盖set_property和get_property方法。
- 70-71:创建GParamSpec。然后安装该属性。
- 73:类方法set_string指向t_str_real_set_string。这个方法预计将在子类中被替换。
- 77-91: Setter和getter。它们被属性设置/获取函数t_str_set_property和t_str_get_property使用。setter t_str_set_string只调用类方法。所以它的行为会在子类中改变。
- 93-116: t_str_concat函数。它连接了self和other的字符串,并创建了一个新的TStr。
- 119-127: t_str_new_with_string和t_str_new创建一个新的TStr实例。
链接到它的父节点
“链到其父类”流程如下图所示。
有两个类,GObjectCLass和TStrClass。每个类都有它们的finalize方法(函数),由类结构中的指针指向。TStrClass的finalize方法会终结TStr实例中自己的部分。在函数的末尾,它会调用父函数的finalize方法。它是GObjectClass的finalize方法。它调用自己的finalize函数,完成GObject私有数据的处理。
如果GObjectClass有两个或多个子类,则finalize函数的数量可能与后代类的数量相同。它们通过“链式连接”的方式连接在一起。
如何编写可派生类型
- 在头文件中使用
G_DECLARE_DERIVABLE_TYPE
宏。你需要在G_DECLARE_DERIVABLE_TYPE
之前写一个#define T_TYPE_STR (t_str_get_type())这样的宏。 - 在头文件中声明类结构。类的内容对后代开放。大多数成员是类方法。
- 在C文件中使用G_DEFINE_TYPE_WITH_PRIVATE。需要在G_DEFINE_TYPE_WITH_PRIVATE之前定义私有区域。它是一个C结构,名称为“
<object name>Private
”,类似于“TStrPrivate”。 - 定义类和实例初始化函数。
翻译自:Gobject-tutorial