本节的示例是 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。

数字字符串的验证是一种词法分析的形式。通常在词法分析中会使用状态图和状态矩阵。

数字字符串是满足以下条件的字符序列:

  1. ’+’ 或 ‘-‘ 可以是第一个字符。它也可以省略不写。

  2. 紧接着是一系列数字。

  3. 然后是一个点。

  4. 最后是另一系列数字。

第二部分可以省略。例如,”.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.ctest1.ctest2.c 文件。两个程序 test1.ctest2.c 分别生成 _build/test1_build/test2。它们用来测试 tstr.ctnumstr.c。如果有错误,会显示消息。否则不会显示任何内容。

程序 main.c 生成 _build/tnumstr。它展示了 TStrTNumStr 是如何工作的。

编译按照通常的方式进行。首先,将当前目录更改为 /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 实例。

参考

参考1:Child class extends parent’s function