C语言序列化与反序列化--TCL中支持的数据结构(二)

API概念

要使用tpl,您需要知道调用API函数的顺序,以及格式字符串、数组和索引号的背景概念。

方法的顺序

创建tpl始终是第一步,释放它是最后一步。在此期间,您可以打包并转储tpl(如果您正在序列化数据),或者加载tpl映像并解压缩它(如果您正在反序列化数据)。
序列化和反序列化的顺序

格式字符串

当使用tpl_map()创建tpl时,其数据类型表示为格式字符串。格式字符串中的每个字符都有一个特定类型的关联参数。例如,格式字符串和它的参数是这样传递给tpl_map的:

tpl_node *tn;
char c;
int i[10];
tn = tpl_map("ci#", &c, i, 10);  /* ci# is our format string */
明确的尺寸

数据类型(如long和double)的大小因平台而异。必须记住这一点,因为大多数tpl格式字符都需要一个指向上面列出的特定大小类型的指针参数。您可以在程序中使用显式大小的类型,如int32_t(在inttypes.h中定义),如果您觉得这很有帮助的话。
双重的麻烦
不幸的是,没有标准的显式大小的浮点类型——例如,没有float64_t。如果您计划在您的平台上使用tpl的f格式字符序列化double,首先要确保您的double是64位的。其次,如果您计划在不同类型的CPU上反序列化它,请确保两个CPU使用相同的浮点表示,例如IEEE 754。

数组

数组有两种类型:定长数组和变长数组。直观地说,它们可以被看作是传统的C数组和链表。一般来说,尽可能使用固定长度的数组,必要时使用可变长度的数组。变长数组支持更复杂的数据类型,并逐个向程序提供或接收元素。
定长数组与变长数组

符号

固定长度数组表示为i#(简单类型后面跟着一个或多个#符号),但可变长度数组表示为a (i)
元素处理

固定长度数组的所有元素都被一次打包或拆包。但是变长数组的元素是一个接一个地打包或拆包的。
数组长度

固定长度数组中的元素数量是在使用之前指定的——在打包任何数据之前。但是变长数组没有固定的元素计数。它们可以包含任意数量的元素。在解包可变长度数组时,将逐个解包,直到用尽为止。
元素类型

固定长度数组的元素可以是整型、字节型、双精度型、字符串型或结构型。(不包括格式字符BA)。固定长度的数组也可以像i##一样是多维的。变长数组可以包含简单或复杂的元素——例如,整型数组A(i),整型/双精度对数组A(if),甚至是嵌套数组A(A(if))。

在解释所有概念之前,先来看看这两种数组是如何使用的。我们把0到9的整数用两种方式打包。

将0-9打包为固定长度的数组

打包固定长度的数组,固定长度数组的长度(10)作为参数传递给tpl_map()

将0-9打包成一个变长数组

注意我们是如何在循环中调用tpl_pack的,对于0到9的每个元素调用一次。同样,本指南后面会给出一个相应的解包示例。您可能还注意到,这一次,我们将1作为最后一个参数传递给tpl_pack。这是一个索引号,指定我们要打包的可变长度数组。在这种情况下,只有一个。

索引号

索引号在格式字符串中标识一个特定的变长数组。格式字符串中的每个A(…)都有自己的索引号。索引号从左到右从1开始分配。例子:
在这里插入图片描述

特殊索引号0

特殊索引号0指定不在A(…)内的所有格式字符。索引0指定(或不指定)的示例:
在这里插入图片描述将索引号传递给tpl_pack和tpl_unpack,以指定要处理哪个变长数组(或者索引号为0的情况下的非数组)。

整数

上面的数组示例演示了如何打包整数。我们将在这里展示一些进一步的解包整数和处理多维数组的示例。同样的程序可以用来演示处理字节、16位短格式、32位或64位带符号和无符号整数,只需要更改数据类型和格式字符。

从固定长度的数组中拆包0-9

在这里插入图片描述

从可变长度数组中解包0-9

在这里插入图片描述

多维数组

多维整数矩阵可以像固定长度的数组一样打包和解包。

int xy[XDIM][YDIM];
...
tn = tpl_map("i##", xy, XDIM, YDIM);
tpl_pack(tn, 0);

这个对tpl_pack的调用打包了整个矩阵。

字符串

Tpl可以序列化C字符串。char和char[]使用了不同的格式,如下所述。让我们先看看char:

打包字符串
#include "tpl.h"

    int main() {
        tpl_node *tn;
        char *s = "hello, world!";
        tn = tpl_map("s", &s);
        tpl_pack(tn,0);  /* copies "hello, world!" into the tpl */
        tpl_dump(tn,TPL_FILE,"string.tpl");
        tpl_free(tn);
    }

char*必须指向以空结束的字符串,或者是NULL指针。
当反序列化(解包)一个C字符串时,它的空间将被自动分配,但你负责释放它(除非它是NULL):

反序列化字符串
   #include "tpl.h"

    int main() {
        tpl_node *tn;
        char *s;
        tn = tpl_map("s", &s);
        tpl_load(tn,TPL_FILE,"string.tpl");
        tpl_unpack(tn,0);   /* allocates space, points s to "hello, world!" */
        printf("unpacked %s\n", s);
        free(s);            /* our responsibility to free s */
        tpl_free(tn);
    }
Char * vs Char []

s格式字符仅用于char*类型。在上面的例子中,s是一个char *。如果它是一个字符 s[14],我们将使用c#格式字符来打包或解包它,作为一个固定长度的字符数组。(这将“就地”解包字符,而不是将其解包到动态分配的缓冲区中)。同样,c#描述的固定长度缓冲区不需要以空结束。

字符串数组

您可以在tpl中使用固定长度或可变长度的字符串数组。下面展示了一个包装固定长度的二维字符串数组的示例。

char *labels[2][3] = { {"one", "two", "three"},
                       {"eins", "zwei", "drei" } };
tpl_node *tn;
tn = tpl_map("s##", labels, 2, 3);
tpl_pack(tn,0);
tpl_dump(tn,TPL_FILE,filename);
tpl_free(tn);

之后,当解包这些字符串时,程序员必须记住在不再需要它们之后,逐个释放它们。

char *olabels[2][3];
int i,j;

tn = tpl_map("s##", olabels, 2, 3);
tpl_load(tn,TPL_FILE,filename);
tpl_unpack(tn,0);
tpl_free(tn);

for(i=0;i<2;i++) {
  for(j=0;j<3;j++) {
    printf("%s\n", olabels[i][j]);
    free(olabels[i][j]);
  }
}

二进制缓冲区

打包一个任意长度的二进制缓冲区(tpl格式字符B)利用了tpl_bin结构。您必须声明这个结构,并用要打包的二进制缓冲区的地址和长度填充它。

//序列化二进制缓冲区
#include "tpl.h"
    #include <sys/time.h>

    int main() {
        tpl_node *tn;
        tpl_bin tb;

        /* we'll use a timeval as our guinea pig */
        struct timeval tv;
        gettimeofday(&tv,NULL);

        tn = tpl_map( "B", &tb );
        tb.sz = sizeof(struct timeval);  /* size of buffer to pack */
        tb.addr = &tv;                   /* address of buffer to pack */
        tpl_pack( tn, 0 );
        tpl_dump(tn, TPL_FILE, "bin.tpl");
        tpl_free(tn);
    }

当您解包二进制缓冲区时,tpl将自动分配它,并将用它的地址和长度填充tpl_bin结构。您负责最终释放缓冲区。

//反序列化二进制缓冲区
 #include "tpl.h"

    int main() {
        tpl_node *tn;
        tpl_bin tb;

        tn = tpl_map( "B", &tb );
        tpl_load( tn, TPL_FILE, "bin.tpl" );
        tpl_unpack( tn, 0 );
        tpl_free(tn);

        printf("binary buffer of length %d at address %p\n", tb.sz, tb.addr);
        free(tb.addr);  /* our responsibility to free it */
    }

结构

可以使用tpl打包和解包结构和结构数组。

//序列化结构数组
struct ci {
    char c;
    int i;
};
struct ci s = {'a', 1};

tn = tpl_map("S(ci)", &s);  /* pass structure address */
tpl_pack(tn, 0);
tpl_dump(tn, TPL_FILE, "struct.tpl");
tpl_free(tn);

如图所示,省略括号内格式字符的单个参数。固定长度数组例外;当S(…)包含#字符时,需要其长度参数:tpl_map(“S(f#i)”, &s, 10);

当使用S(…)格式时,括号内允许的唯一字符是iujvcsfiu# $()。

结构数组

结构数组与简单数组相同。支持固定长度或可变长度的数组。

struct ci sa[100], one;
tn = tpl_map("S(ci)#", sa, 100);  /* fixed-length array of 100 structures */
tn = tpl_map("A(S(ci))", &one);   /* variable-length array (one at a time) */

固定长度数组和可变长度数组之间的区别将在数组一节中解释。

嵌套结构

当处理嵌套结构时,最外层的结构使用S格式字符,而内部的嵌套结构使用$ format。tpl_map只给最外层结构的地址。

struct inner_t {
  char a;
}

struct outer_t {
  char b;
  struct inner_t i;
}
tpl_node *tn;
struct outer_t outer = {'b', {'a'}};
tn = tpl_map("S(c$(c))", &outer);

结构可以嵌套到任何级别。目前tpl不支持内部结构上的固定长度数组后缀。然而,最外层的结构可以有一个长度后缀,即使它包含一些嵌套结构。

链表

虽然tpl没有针对链表的特定数据类型,但这里将说明打包链表的技术。首先将列表元素描述为格式字符串,然后用a(…)包围它,将其描述为可变长度数组。然后,使用临时变量遍历每个列表元素,将其复制到临时变量并打包。



struct element {
  char c;
  int i;
  struct element *next;
}

struct element *list, *i, tmp;
tpl_node *tn;

/* add some elements to list.. (not shown) */

tn = tpl_map("A(S(ci))", &tmp);
for(i = list; i != NULL; i=i->next) {
  tmp = *i;
  tpl_pack(tn, 1);
}
tpl_dump(tn,TPL_FILE,"list.tpl");
tpl_free(tn);

拆包也是类似的。for循环被替换为:

while( tpl_unpack(tn,1) > 0) {
  struct element *newelt = malloc(sizeof(struct element));
  *newelt = tmp;
  add_to_list(list, newelt);
}

正如您所看到的,tpl不会立即恢复整个列表——一次只恢复一个元素。您需要手动链接这些元素。tpl的未来版本可能会支持指针切换,以使这更容易。

  • 10
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值