引言
这篇博文和前一篇 C json实战引擎一,实现解析部分设计是相同的,都是采用递归下降分析.
这里扯一点 假如你是学生 推荐一本书 给 大家
当然学了上面内容,以后对编译链接设计方面会有很大提高. 但是对于 其它 也没有什么鸟用.
再扯一点 如果想流畅的看完并成功写完上面书中三个案例. 你还需要 看完 市面上关于 C 讲解的所有出名的 有意义的书籍.
编程很简单,勤能补拙, 想提高就会提高. 但成长很难......
风暴 http://music.163.com/#/song?id=211222 陈洁仪
前言
同样,一开始将最有益,最简单的部分代码拿出来供大家分享.
1.从简单 有益代码来
开始分析一段 代码, 先展示一下使用的数据结构
struct cjson {
struct cjson *next, *prev;
struct cjson *child; // type == _CJSON_ARRAY or type == _CJSON_OBJECT 那么 child 就不为空
int type;
char *key; // json内容那块的 key名称
char *vs; // type == _CJSON_STRING, 是一个字符串
double vd; // type == _CJSON_NUMBER, 是一个num值, ((int)c->vd) 转成int 或 bool
};
//定义cjson_t json类型
typedef struct cjson* cjson_t;
再展示用的 tstring 结构
#ifndef _STRUCT_TSTRING #define _STRUCT_TSTRING //简单字符串结构,并定义文本字符串类型tstring struct tstring { char* str; //字符串实际保存的内容 int len; //当前字符串大小 int size; //字符池大小 }; typedef struct tstring* tstring; #endif // !_STRUCT_TSTRING
这些数据结构前面 博文已经对其进行过详细 设计利用分析.
先看一个 double 变成 cjson_t 的算法, 很多细节真的适合 学习尝试用于底层库封装设计中.
// 将item 中值转换成字符串 保存到p中
static char* __print_number(cjson_t item, tstring p)
{
char* str = NULL;
double d = item->vd;
int i = (int)d;
if (d == 0) { //普通0
str = __ensure(p, 2);
if (str)
str[0] = '0', str[1] = '\0';
}
else if ((fabs(d - i)) <= DBL_EPSILON && d <= INT_MAX && d >= INT_MIN) {
str = __ensure(p, 21); //int 值
if (str)
sprintf(str, "%d", i);
}
else {
str = __ensure(p, 64); //double值
if (str) {
double nd = fabs(d); //得到正值开始比较
if(fabs(floor(d) - d) <= DBL_EPSILON && nd < 1.0e60)
sprintf(str, "%.0f", d);
else if(nd < 1.0e-6 || nd > 1.0e9) //科学计数法
sprintf(str, "%e", d);
else
sprintf(str, "%f", d);
}
}
return str;
}
是不是感觉 很巧妙. 这里 把 int 和 double 都算作 number类型, 出现了 上面算法. 需要导入 #include <float.h> 引用了 DBL_EPSILON 判断是否相等宏阀值.
其中 __ensure 函数 是一个 协助 tstring 分配内存的一个函数
/* * 这里使用 tstring 结构 size 这里表示 字符串总大小,没有变化 * len 表示当前字符串的字符串起始偏移量 即 tstring->str + tstring->len 起始的 */ static char* __ensure(tstring p, int need) { char* nbuf; int nsize; if (!p || !p->str) { SL_FATAL("p:%p need:%p is error!", p, need); return NULL; } need += p->len; if (need <= p->size) //内存够用直接返回结果 return p->str + p->len; nsize = __pow2gt(need); if ((nbuf = malloc(nsize*sizeof(char))) == NULL) { free(p->str); p->size = p->len = 0; p->str = NULL; SL_FATAL("malloc nsize = %d error!", nsize); return NULL; } //这里复制内容 memcpy(nbuf, p->str, p->size); free(p->str); p->size = nsize; p->str = nbuf; return nbuf + p->len; }
这里采用的 SL_FATAL 日志库, 看我前面博文 如何写一个 高效多用户的 日志库, 特别有用,基本上是开发中标配.
还有一个 __pow2gt(x) 函数技巧, 返回 一个比x 的 n 其中n是2的幂,并且是最小的幂.是一种技巧记住就可以了.估计都是那些写汇编的老代码遗留下来的潜规则吧.
性能没的说. 不明白就当有个印象.
1 // 2^n>=x , n是最小的整数 2 static int __pow2gt(int x) 3 { 4 --x; 5 x |= x >> 1; 6 x |= x >> 2; 7 x |= x >> 4; 8 x |= x >> 8; 9 x |= x >> 16; 10 return x + 1; 11 }
到这里 这几个函数 就可以代表这整篇文章了. 后面就可以省略了.
正文
1.开始说 cjson 的 构造
cjson 解析 先认为 所有的都是 一个 value => null or bool or number or string or array or object
其中 array or object 需要再特殊处理,因为其中可能包含 value 即 array or object => value
这样的递归顺序进行的. 这就是传说中的低估下降分析 !!!! 爽不爽 , 当我还是学生的时候,NB任务告诉我学会了 递归了下降分析就可以找个
不错的工作, 找个不错的对象. 现在只想说 呵呵!!.
大概像下面调用关系图
递归嵌套. 好像 Linux 之父 也 说过 去它码的递归.
2.展示 cjson 构造用的接口
这里比较简单,今天只分析 构造部分 接口就一个
// --------------------------------- 下面是 cjson 输出部分的处理代码 ----------------------------------------- /* * 这里是将 cjson_t item 转换成字符串内容,需要自己free * item : cjson的具体结点 * : 返回生成的item的json串内容 */ extern char* cjson_print(cjson_t item);
值得注意的是 上面接口能够将 item变成 char*, 这个char*是堆上分配的. 需要自己 用完后 free.
3. 展示 cjson 部分代码
上面接口构造的函数为
#define _INT_CJONSTR (256) /* * 这里是将 cjson_t item 转换成字符串内容,需要自己free * item : cjson的具体结点 * : 返回生成的item的json串内容 */ char* cjson_print(cjson_t item) { struct tstring p; char* out; if ((!item) || !(p.str = malloc(sizeof(char)*_INT_CJONSTR))) { SL_FATAL("item:%p, p.str = malloc is error!", item); return NULL; } p.size = _INT_CJONSTR; p.len = 0; out = __print_value(item, &p); //从值处理开始, 返回最终结果 if (out == NULL) { free(p.str); SL_FATAL("__print_value item:%p, p:%p is error!", item, &p); return NULL; } return realloc(out,strlen(out) + 1); // 体积变小 realloc返回一定成功 }
核心 是 __print_value 当然设计方面也参照了一点 cJSON内容. 那我们 继续细说 它
//这里是 递归下降 的函数声明处, 分别是处理值, 数组, object static char* __print_value(cjson_t item, tstring p); static char* __print_array(cjson_t item, tstring p); static char* __print_object(cjson_t item, tstring p); // 定义实现部分, 内部私有函数 认为 item 和 p都是存在的 static char* __print_value(cjson_t item, tstring p) { char* out = NULL; switch ((item->type) & UCHAR_MAX) { // 0xff case _CJSON_FALSE: if ((out = __ensure(p, 6))) strcpy(out, "false"); break; case _CJSON_TRUE: if ((out = __ensure(p, 5))) strcpy(out, "true"); break; case _CJSON_NULL: if ((out = __ensure(p, 5))) strcpy(out, "null"); break; case _CJSON_NUMBER: out = __print_number(item, p); break; case _CJSON_STRING: out = __print_string(item->vs, p); break; case _CJSON_ARRAY: out = __print_array(item, p); break; case _CJSON_OBJECT: out = __print_object(item, p); break; } return out; }
有没有感觉 很自然就是这样的. 上面先声明的 __print_* 系列函数,是为了告诉编译器这个函数地址是什么,方便它能找到 并进入处理.
再展示 其中 __print_object 处理函数, 也很直白
1 // 同样 假定 item 和 p都是存在且不为NULL, 相信这些代码是安全的 2 static char* __print_object(cjson_t item, tstring p) 3 { 4 char* ptr; 5 int i, ncut, len; 6 cjson_t child = item->child; 7 8 // 得到孩子结点的深度 9 for (ncut = 0; child; child = child->child) 10 ++ncut; 11 if (!ncut) { 12 char* out = NULL; 13 if (!(out = __ensure(p, 3))) 14 strcpy(out, "{}"); 15 return out; 16 } 17 18 i = p->len; 19 if (!(ptr = __ensure(p, 2))) 20 return NULL; 21 *ptr++ = '{'; 22 *ptr -= '\0'; 23 p->len += 1; 24 // 根据子结点 处理 25 for (child = item->child; (child); child = child->next) { 26 __print_string(child->key, p); 27 p->len = __update(p); 28 29 //加入一个冒号 30 if (!(ptr = __ensure(p, 1))) 31 return NULL; 32 *ptr++ = ':'; 33 p->len += 1; 34 35 //继续打印一个值 36 __print_value(child, p); 37 p->len = __update(p); 38 39 //结算最后内容 40 len = child->next ? 1 : 0; 41 if ((ptr = __ensure(p, len + 1)) == NULL) 42 return NULL; 43 if (child->next) 44 *ptr++ = ','; 45 *ptr = '\0'; 46 p->len += len; 47 } 48 if (!(ptr = __ensure(p, 2))) 49 return NULL; 50 *ptr++ = '}'; 51 *ptr = '\0'; 52 return p->str + i; 53 }
先处理key ,后面value 用 __print_value 处理. 到这里 基本思路都有了,其它是靠你自己努力临摹 把键盘敲烂!
完整部分代码如下
cjson.h / 有些辅助接口没有实现,下一个博文中全部实现
1 #ifndef _H_CJSON 2 #define _H_CJSON 3 4 // json 中几种数据类型定义 , 对于C而言 最难的是看不见源码,而不是api复杂, 更不是业务复杂 5 #define _CJSON_FALSE (0) 6 #define _CJSON_TRUE (1) 7 #define _CJSON_NULL (2) 8 #define _CJSON_NUMBER (3) 9 #define _CJSON_STRING (4) 10 #define _CJSON_ARRAY (5) 11 #define _CJSON_OBJECT (6) 12 13 #define _CJSON_ISREF (256) //set 时候用如果是引用就不释放了 14 #define _CJSON_ISCONST (512) //set时候用, 如果是const char* 就不释放了 15 16 struct cjson { 17 struct cjson *next, *prev; 18 struct cjson *child; // type == _CJSON_ARRAY or type == _CJSON_OBJECT 那么 child 就不为空 19 20 int type; 21 char *key; // json内容那块的 key名称 22 char *vs; // type == _CJSON_STRING, 是一个字符串 23 double vd; // type == _CJSON_NUMBER, 是一个num值, ((int)c->vd) 转成int 或 bool 24 }; 25 26 //定义cjson_t json类型 27 typedef struct cjson* cjson_t; 28 29 /* 30 * 这个宏,协助我们得到 int 值 或 bool 值 31 * 32 * item : 待处理的目标cjson_t结点 33 */ 34 #define cjson_getint(item) \ 35 ((int)((item)->vd)) 36 37 /* 38 * 删除json串内容 39 * c : 待释放json_t串内容 40 */ 41 extern void cjson_delete(cjson_t* pc); 42 43 /* 44 * 对json字符串解析返回解析后的结果 45 * jstr : 待解析的字符串 46 */ 47 extern cjson_t cjson_parse(const char* jstr); 48 49 /* 50 * 根据 item当前结点的 next 一直寻找到 NULL, 返回个数 51 *推荐是数组使用 52 * array : 待处理的cjson_t数组对象 53 * : 返回这个数组中长度 54 */ 55 extern int cjson_getlen(cjson_t array); 56 57 /* 58 * 根据索引得到这个数组中对象 59 * array : 数组对象 60 * idx : 查找的索引 必须 [0,cjson_getlen(array)) 范围内 61 * : 返回查找到的当前对象 62 */ 63 extern cjson_t cjson_getarray(cjson_t array, int idx); 64 65 /* 66 * 根据key得到这个对象 相应位置的值 67 * object : 待处理对象中值 68 * key : 寻找的key 69 * : 返回 查找 cjson_t 对象 70 */ 71 extern cjson_t cjson_getobject(cjson_t object, const char* key); 72 73 74 // --------------------------------- 下面是 cjson 输出部分的处理代码 ----------------------------------------- 75 76 /* 77 * 这里是将 cjson_t item 转换成字符串内容,需要自己free 78 * item : cjson的具体结点 79 * : 返回生成的item的json串内容 80 */ 81 extern char* cjson_print(cjson_t item); 82 83 // --------------------------------- 下面是 cjson 输出部分的辅助代码 ----------------------------------------- 84 85 /* 86 * 创建一个bool的对象 b==0表示false,否则都是true 87 * b : bool 值 最好是 _Bool 88 * : 返回 创建好的json 内容 89 */ 90 extern cjson_t cjson_newbool(int b); 91 extern cjson_t cjson_newnumber(double vd); 92 extern cjson_t cjson_newstring(const char* vs); 93 extern cjson_t cjson_newarray(void); 94 extern cjson_t cjson_newobject(void); 95 96 /* 97 * 按照类型,创建 对映类型的数组 cjson对象 98 *目前支持 _CJSON_NULL _CJSON_BOOL/FALSE or TRUE , _CJSON_NUMBER, _CJSON_STRING 99 * type : 类型目前支持 上面几种类型 100 * array : 数组原始数据 101 * len : 数组中元素长度 102 * : 返回创建的数组对象 103 */ 104 extern cjson_t cjson_newtypearray(int type, const void* array, int len); 105 106 /* 107 * 将 jstr中 不需要解析的字符串都去掉 108 * jstr : 待处理的json串 109 * : 返回压缩后的json串内容 110 */ 111 extern char* cjson_mini(char* jstr); 112 113 /* 114 * 将json文件解析成json内容返回 115 * jpath : json串路径 116 * : 返回处理好的cjson_t 内容,失败返回NULL 117 */ 118 extern cjson_t cjson_dofile(char* jpath); 119 120 #endif // !_H_CJSON