八、子类扩展父类的功能
本节的示例是 TNumStr 对象。TNumStr 是 TStr 对象的子类。TNumStr 包含一个字符串及其类型,类型为 t_int、t_double 或 t_none。
-
t_int:字符串表示一个整数
-
t_double:字符串表示一个双精度(浮点)数
-
t_none:字符串不表示上述两种数字
t_int 或 t_double 类型的字符串被称为数字字符串。这不是一个通用术语,仅在本教程中使用。简而言之,数字字符串是表示数字的字符串。例如,”0”、”-100” 和 “123.456” 都是数字字符串。它们是字符串,同时表示数字。
-
“0” 是一个字符串。它是一个字符数组,其元素是 ‘0’ 和 ‘\0’。它表示的是整数零。
-
“-100” 是由 ‘-‘, ‘1’, ‘0’, ‘0’ 和 ‘\0’ 组成的字符串。它表示整数 -100。
-
“123.456” 由 ‘1’, ‘2’, ‘3’, ‘.’, ‘4’, ‘5’, ‘6’ 和 ‘\0’ 组成。它表示实数(双精度类型)123.456。
数字字符串就是这样一种特定的字符串。
1 数字字符串的验证
在定义 TNumStr 之前,我们需要一种验证数字字符串的方法。
数字字符串包括:
-
整数。例如,”0”、”100”、”-10” 和 “+20”。
-
双精度数。例如,”0.1”、”-10.3”、”+3.14”、”.05”、”1.” 和 “0.0”。
我们需要注意 “0” 和 “0.0” 是不同的。因为它们的类型不同。”0” 的类型是整数,而 “0.0” 的类型是双精度数。同样地,”1” 是一个整数,而 “1.” 是一个双精度数。
“.5” 和 “0.5” 是相同的。它们都是双精度数,它们的值都是 0.5。
数字字符串的验证是一种词法分析的形式。通常在词法分析中会使用状态图和状态矩阵。
数字字符串是满足以下条件的字符序列:
-
’+’ 或 ‘-‘ 可以是第一个字符。它也可以省略不写。
-
紧接着是一系列数字。
-
然后是一个点。
-
最后是另一系列数字。
第二部分可以省略。例如,”.56” 或 “-.56” 是正确的。
第三和第四部分也可以省略。例如,”12” 或 “-23” 是正确的。
第四部分也可以省略。例如,”100.” 是正确的。
-
0 是起始点。
-
1 是 ‘+’ 或 ‘-‘ 之后的状态。
-
2 是第一系列数字(整数部分)的中间状态。
-
3 是小数点之后的状态。
-
4 是字符串的结尾,且字符串是整数。
-
5 是字符串的结尾,且字符串是双精度数。
-
6 是错误状态。字符串不表示一个数字。
输入字符如下:
-
0:’+’ 或 ‘-‘
-
1:数字 (‘0’ - ‘9’)
-
2:句点 ‘.’
-
3:字符串结束 ‘\0’
-
4:其他字符
状态图如下所示。
状态矩阵如下:
状态\输入 | 0(正负号) | 1(数字 0-9 ) | 2(点 .) | 3('\0') | 4(其他) |
---|---|---|---|---|---|
0 | 1 | 2 | 3 | 6 | 6 |
1 | 6 | 2 | 3 | 6 | 6 |
2 | 6 | 2 | 3 | 4 | 6 |
3 | 6 | 3 | 6 | 5 | 6 |
这个状态图不支持 “1.23e5” 风格的双精度数(十进制浮点数)。如果支持,状态图将会更加复杂。(然而,这将是锻炼编程技能的好机会。)
2 头文件 tnumstr.h
TNumStr的头文件是tnumstr.h。它在 /assets/GObjectStudy/202311/08_GObject/tstr/ 目录下。
1 #pragma once
2
3 #include <glib-object.h>
4 #include "tstr.h"
5 #include "../tnumber/tnumber.h"
6
7 #define T_TYPE_NUM_STR (t_num_str_get_type ())
8 G_DECLARE_FINAL_TYPE (TNumStr, t_num_str, T, NUM_STR, TStr)
9
10 /* type of the numeric string */
11 enum {
12 t_none,
13 t_int,
14 t_double
15 };
16
17 /* get the type of the TNumStr object */
18 int
19 t_num_str_get_string_type (TNumStr *self);
20
21 /* setter and getter */
22 void
23 t_num_str_set_from_t_number (TNumStr *self, TNumber *num);
24
25 // TNumStr can have any string, which is t_none, t_int or t_double type.
26 // If the type is t_none, t_num_str_get_t_number returns NULL.
27 // It is good idea to call t_num_str_get_string_type and check the type in advance.
28
29 TNumber *
30 t_num_str_get_t_number (TNumStr *self);
31
32 /* create a new TNumStr instance */
33 TNumStr *
34 t_num_str_new_with_tnumber (TNumber *num);
35
36 TNumStr *
37 t_num_str_new (void);
-
8:宏
G_DECLARE_FINAL_TYPE
用于 TNumStr 类。它是 TStr 的子类,也是一个最终类型类。 -
11-15:这三个枚举数据定义了 TNumStr 字符串的类型。
-
t_none:没有存储字符串或字符串不是数字字符串。
-
t_int:字符串表示一个整数。
-
t_double:字符串表示一个实数,即 C 语言中的双精度类型。
-
-
18-19:公共函数 t_num_str_get_string_type 返回 TStrNum 对象所持有的字符串类型。返回的值是 t_none、t_int 或 t_double。
-
22-30:从 TNumber 对象到 TNumber 对象的设置器和获取器。
-
33-37:创建新 TNumStr 对象的函数。
3 源文件 tnumstr.c
TNumStr 的 C 文件是 tnumstr.c。它位于 /assets/GObjectStudy/202311/08_GObject/tstr/ 目录中。
1 #include <stdlib.h>
2 #include <ctype.h>
3 #include "tnumstr.h"
4 #include "tstr.h"
5 #include "../tnumber/tnumber.h"
6 #include "../tnumber/tint.h"
7 #include "../tnumber/tdouble.h"
8
9 struct _TNumStr {
10 TStr parent;
11 int type;
12 };
13
14 G_DEFINE_TYPE(TNumStr, t_num_str, T_TYPE_STR)
15
16 static int
17 t_num_str_string_type (const char *string) {
18 const char *t;
19 int stat, input;
20 /* state matrix */
21 int m[4][5] = {
22 {1, 2, 3, 6, 6},
23 {6, 2, 3, 6, 6},
24 {6, 2, 3, 4, 6},
25 {6, 3, 6, 5, 6}
26 };
27
28 if (string == NULL)
29 return t_none;
30 stat = 0;
31 for (t = string; ; ++t) {
32 if (*t == '+' || *t == '-')
33 input = 0;
34 else if (isdigit (*t))
35 input = 1;
36 else if (*t == '.')
37 input = 2;
38 else if (*t == '\0')
39 input = 3;
40 else
41 input = 4;
42
43 stat = m[stat][input];
44
45 if (stat >= 4 || *t == '\0')
46 break;
47 }
48 if (stat == 4)
49 return t_int;
50 else if (stat == 5)
51 return t_double;
52 else
53 return t_none;
54 }
55
56 /* This function overrides t_str_set_string. */
57 /* And it also changes the behavior of setting the "string" property. */
58 /* On TStr => just set the "string" property */
59 /* On TNumStr => set the "string" property and set the type of the string. */
60 static void
61 t_num_str_real_set_string (TStr *self, const char *s) {
62 T_STR_CLASS (t_num_str_parent_class)->set_string (self, s);
63 T_NUM_STR (self)->type = t_num_str_string_type(s);
64 }
65
66 static void
67 t_num_str_init (TNumStr *self) {
68 self->type = t_none;
69 }
70
71 static void
72 t_num_str_class_init (TNumStrClass *class) {
73 TStrClass *t_str_class = T_STR_CLASS (class);
74
75 t_str_class->set_string = t_num_str_real_set_string;
76 }
77
78 int
79 t_num_str_get_string_type (TNumStr *self) {
80 g_return_val_if_fail (T_IS_NUM_STR (self), -1);
81
82 return self->type;
83 }
84
85 /* setter and getter */
86 void
87 t_num_str_set_from_t_number (TNumStr *self, TNumber *num) {
88 g_return_if_fail (T_IS_NUM_STR (self));
89 g_return_if_fail (T_IS_NUMBER (num));
90
91 char *s;
92
93 s = t_number_to_s (T_NUMBER (num));
94 t_str_set_string (T_STR (self), s);
95 g_free (s);
96 }
97
98 TNumber *
99 t_num_str_get_t_number (TNumStr *self) {
100 g_return_val_if_fail (T_IS_NUM_STR (self), NULL);
101
102 char *s = t_str_get_string(T_STR (self));
103 TNumber *tnum;
104
105 if (self->type == t_int)
106 tnum = T_NUMBER (t_int_new_with_value (atoi (s)));
107 else if (self->type == t_double)
108 tnum = T_NUMBER (t_double_new_with_value (atof (s)));
109 else
110 tnum = NULL;
111 g_free (s);
112 return tnum;
113 }
114
115 /* create a new TNumStr instance */
116
117 TNumStr *
118 t_num_str_new_with_tnumber (TNumber *num) {
119 g_return_val_if_fail (T_IS_NUMBER (num), NULL);
120
121 TNumStr *numstr;
122
123 numstr = t_num_str_new ();
124 t_num_str_set_from_t_number (numstr, num);
125 return numstr;
126 }
127
128 TNumStr *
129 t_num_str_new (void) {
130 return T_NUM_STR (g_object_new (T_TYPE_NUM_STR, NULL));
131 }
-
9-12: TNumStr 结构体有其父类 “TStr” 和整型类型 “type” 成员。因此,TNumStr 实例持有一个字符串,该字符串放置在父类的私有区域中,并有一个类型。
-
14: 宏 G_DEFINE_TYPE。
-
16-54: 函数 t_num_str_string_type 检查给定的字符串并返回 t_int、t_double 或 t_none。如果字符串是 NULL 或非数字字符串,则返回 t_none。检查算法在第一小节 “验证数字字符串” 中有解释。
-
60-64: 函数 t_num_str_real_set_string 设置 TNumStr 的字符串及其类型。这是类方法 set_string 指向的类方法体,该类方法在类初始化函数 t_num_str_class_init 中初始化。
-
66-69: 实例初始化函数 t_num_str_init 将类型设置为 t_none,因为其父类初始化函数将指针 priv->string 设置为 NULL。
-
71-76: 类初始化函数 t_num_str_class_init 将 t_num_str_real_set_string 分配给成员 set_string。因此,函数 t_str_set_string 调用 t_num_str_real_set_string,不仅设置字符串,还设置类型。函数 g_object_set 也调用它并同时设置字符串和类型。
-
78-83: 公共函数 t_num_str_get_string_type 返回字符串的类型。
-
86-113: 设置器和获取器。设置器从 TNumber 对象设置数字字符串。获取器返回一个 TNumber 对象。
-
117-131: 这两个函数创建 TNumStr 实例。
4 子类扩展父类的功能
TNumStr 是 TStr 的子类,因此它拥有 TStr 所有的公共函数。
TStr *t_str_concat (TStr *self, TStr *other)
void t_str_set_string (TStr *self, const char *s)
char *t_str_get_string (TStr *self)
当你想要为 TNumStr 实例设置一个字符串时,你可以使用 t_str_set_string
` 函数。
TNumStr *ns = t_num_str_new ();
t_str_set_string (T_STR (ns), "123.456");
TNumStr 扩展了 t_str_set_string
` 函数,它不仅设置字符串,还为 TNumStr 实例设置了类型。
int t;
t = t_num_str_get_string_type (ns);
if (t == t_none) g_print ("t_none\n");
if (t == t_int) g_print ("t_int\n");
if (t == t_double) g_print ("t_double\n");
// => t_double appears on your display
TNumStr 添加了一些公共函数。
int t_num_str_get_string_type (TNumStr *self)
void t_num_str_set_from_t_number (TNumStr *self, TNumber *num)
TNumber *t_num_str_get_t_number (TNumStr *self)
子类扩展了父类的功能,因此子类比父类更具体,功能也更丰富。
5 编译和运行
在 /assets/GObjectStudy/202311/08_GObject/tstr/ 目录中有 main.c
、test1.c
和 test2.c
文件。两个程序 test1.c
和 test2.c
分别生成 _build/test1
和 _build/test2
。它们用来测试 tstr.c
和 tnumstr.c
。如果有错误,会显示消息。否则不会显示任何内容。
程序 main.c
生成 _build/tnumstr
。它展示了 TStr
和 TNumStr
是如何工作的。
编译按照通常的方式进行。首先,将当前目录更改为 /assets/GObjectStudy/202311/08_GObject/tstr/。
$ cd /assets/GObjectStudy/202311/08_GObject/tstr/
$ meson setup _build
$ ninja -C _build
$ _build/test1
$ _build/test2
$ _build/tnumstr
String property is set to one.
"one" and "two" is "onetwo".
123 + 456 + 789 = 1368
TNumStr => TNumber => TNumStr
123 => 123 => 123
-45 => -45 => -45
+0 => 0 => 0
123.456 => 123.456000 => 123.456000
+123.456 => 123.456000 => 123.456000
-123.456 => -123.456000 => -123.456000
.456 => 0.456000 => 0.456000
123. => 123.000000 => 123.000000
0.0 => 0.000000 => 0.000000
123.4567890123456789 => 123.456789 => 123.456789
abc => (null) => abc
(null) => (null) => (null)
main.c
文件的最后一部分是 TNumStr 和 TNumber 之间的转换。它们之间存在一些差异,原因如下:
-
浮点数是四舍五入的。当值具有长数字时,它不是一个精确值。TNumStr “123.4567890123456789” 被转换为 TNumber 123.456789。
-
在浮点数中存在两个或更多的字符串表示形式。TNumStr “.456” 被转换为 TNumber x,x 又被转换为 TNumStr “0.456000”。
比较两个 TNumStr 实例是困难的。最佳方式是比较从 TNumStr 转换来的 TNumber 实例。