• 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 
    
        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的未来版本可能会支持指针切换,以使这更容易。

  • 相关阅读:
    手把手教你uniapp接入聊天IM即时通讯功能-源码分享
    何为整型提升(实例)
    设计模式——策略模式
    一站式智慧校园解决方案 SaaS云平台智慧校园管理系统源码
    Java的函数式接口是什么?
    剑指 Offer 04. 二维数组中的查找
    阿里云通义千问向全社会开放,近期将开源更大参数规模大模型
    JS-树:深度优先搜索与广度优先搜索
    云原生爱好者周刊:延迟加载任意 OCI 镜像 | 2022-09-13
    Linux安装MySQL服务8.0.11版本
  • 原文地址:https://blog.csdn.net/weixin_44939146/article/details/139301408