sec7-可派生和非抽象类型

  创建非抽象可派生类型比创建抽象类型更常见。本节介绍如何创建非抽象可派生类型对象。派生类型的例子是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;

两个指针st指向同一个地址。因此,您不能修改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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值