【项目学习总结】GitHub : miloyip/json-tutorial(轻量级JSON)

  • Post author:
  • Post category:其他

一、项目介绍

本工程是一个轻量版JSON,来自GitHub,由腾讯 T4 专家、互动娱乐事业群魔方工作室群游戏客户端技术总监叶劲峰(Milo Yip)开发,把项目地址赋于此:miloyip/json-tutorial。

项目共分为8部分,用C语言实现了一个轻量级的JSON,用来入门学习一些编程的基础知识非常好,几乎不需要任何其他知识,懂C语言即可入手。

关于作者:
叶劲峰(Milo Yip)现任腾讯 T4 专家、互动娱乐事业群魔方工作室群游戏客户端技术总监。他获得香港大学认知科学学士(BCogSc)、香港中文大学系统工程及工程管理哲学硕士(MPhil)。他是《游戏引擎架构》译者、《C++ Primer 中文版(第五版)》审校。他曾参与《天涯明月刀》、《斗战神》、《爱丽丝:疯狂回归》、《美食从天降》、《王子传奇》等游戏项目,以及多个游戏引擎及中间件的研发。他是开源项目 RapidJSON 的作者,开发 nativejson-benchmark 比较 41 个开源原生 JSON 库的标准符合程度及性能。他在 1990 年学习 C 语言,1995 年开始使用 C++ 于各种项目。

二、知识点总结

1. 项目中的命名格式:

1.1 xxx.h文件中的#ifndef (tutorial01)

C 语言有头文件的概念,需要使用 #include去引入头文件中的类型声明和函数声明。但由于头文件也可以 #include 其他头文件,为避免重复声明,通常会利用宏加入 include 防范(include guard):

#ifndef LEPTJSON_H__
#define LEPTJSON_H__

/* ... */

#endif /* LEPTJSON_H__ */

宏的名字必须是唯一的,通常习惯以 _H__ 作为后缀。由于 leptjson 只有一个头文件,可以简单命名为 LEPTJSON_H__。如果项目有多个文件或目录结构,可以用 项目名称_目录_文件名称_H__ 这种命名方式。

1.2 变量命名格式(tutorial01)

通常枚举值用全大写(如 LEPT_NULL),而类型及函数则用小写(如 lept_type

2. 善用枚举(tutorial01)

本项目中的错误码,均是通过枚举来定义的,既可清楚得表明意义,代码又简洁优雅。如本项目中的错误码所示:

enum {
    LEPT_PARSE_OK = 0,
    LEPT_PARSE_EXPECT_VALUE,
    LEPT_PARSE_INVALID_VALUE,
    LEPT_PARSE_ROOT_NOT_SINGULAR
};

3. 宏定义函数

3.1 do-while的使用(tutorial01)

有些同学可能不了解 EXPECT_EQ_BASE 宏的编写技巧,简单说明一下。反斜线代表该行未结束,会串接下一行。而如果宏里有多过一个语句(statement),就需要用 do { /*...*/ } while(0) 包裹成单个语句,否则会有如下的问题:

#define M() a(); b()
if (cond)
    M();
else
    c();

/* 预处理后 */

if (cond)
    a(); b(); /* b(); 在 if 之外     */
else          /* <- else 缺乏对应 if */
    c();

只用 { } 也不行:

#define M() { a(); b(); }

/* 预处理后 */

if (cond)
    { a(); b(); }; /* 最后的分号代表 if 语句结束 */
else               /* else 缺乏对应 if */
    c();

用 do while 就行了:

#define M() do { a(); b(); } while(0)

/* 预处理后 */

if (cond)
    do { a(); b(); } while(0);
else
    c();

3.2 哪些情况下必须用宏定义函数?(tutorial01)

如测试框架使用了 __LINE__ 这个编译器提供的宏,代表编译时该行的行号。如果用函数或内联函数,每次的行号便都会相同。

//一段在全局位置的代码:
static int main_ret = 0;
static int test_count = 0;
static int test_pass = 0;

#define EXPECT_EQ_BASE(equality, expect, actual, format) \
    do {\
        test_count++;\
        if (equality)\
            test_pass++;\
        else {\
            fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\
            main_ret = 1;\
        }\
    } while(0)

4. 优雅的代码

4.1 形参太多时用结构体指针传递(tutorial 01)

函数间通常会传递多个形参,当形参较多时既影响程序美观,又影响程序速度。因此通常把多个参数存到一个结构体对象里,在函数间只传递这个对象的地址即可。

typedef struct {
	/*********
	Something
	...
	...
	*********/
} info;

info a;

void function(info* a);

4.2 errno的使用(tutorial02)

errno在初试时常被程序员置为0,一旦程序检查出错误如越界等,就会改变errno的值。然而,errno的值在被修改后不会自动改回0,所以如果下次判断到errno不为0,不一定是程序又出错了,而可能是上一次错误后errno没被改回0。

所以,在判错时,通常用errno加上错误检查,如本项目中用errno检查越界,不能简单地if(errno == ERANGE),而是if(errno == ERANGE && (n == HUGE_VAL || v->n == -HUGE_VAL))。

4.3 union的使用(tutorial03)

本项目中,所有数据(不管是字符串,还是数字、布尔值、字符等)均用一个结构体lept_value来表示,但如何把如此多的类型融于一个结构体?可以用union这个类型。

如下所示,结构体lept_value可表示数字字符串,如果是数字,则存储在double n中,如果是字符串,则存储在char* s中,并用size_t len来表示字符串的长度。

typedef struct {
    char* s;
    size_t len;
    double n;
    lept_type type;  //类型标识,用来表示某个对象中存的是数字还是字符串
}lept_value;
~~~

但由于本项目中一个lept_value对象只会存储一个值,要么数字、要么字符串,所以上述结构体显然浪费了空间。可用union修改如下:

typedef struct {
    union {
        struct { char* s; size_t len; }s;  /* string */
        double n;                          /* number */
    }u;
    lept_type type;
}lept_value;

5. 数据结构

5.1 手写动态数组

本项目实现了堆栈的动态压入及弹出操作(以字节为操作单位)。每次可要求压入任意大小的数据,它会返回数据起始的指针。以下代码的意思,是把结构体lept_context中的const char* json作为一个字符串,即原始数据;并把char* stack作为一个动态增长的栈。然后把字符串const char* json压入到栈char* stack中(每当栈空间不够时,以1.5倍扩容)。用size记录当前这个栈的总容量(不一定全用完,可能有空余),而top记录当前已被使用的栈容量(即栈顶),所以始终有top <= size

#ifndef LEPT_PARSE_STACK_INIT_SIZE
#define LEPT_PARSE_STACK_INIT_SIZE 256
#endif

typedef struct {
    const char* json;
    char* stack;
    size_t size, top;
}lept_context;

static void* lept_context_push(lept_context* c, size_t size) {
    void* ret;
    assert(size > 0);
    if (c->top + size >= c->size) {
        if (c->size == 0)
            c->size = LEPT_PARSE_STACK_INIT_SIZE;
        while (c->top + size >= c->size)
            c->size += c->size >> 1;  /* c->size * 1.5 */
        c->stack = (char*)realloc(c->stack, c->size);
    }
    ret = c->stack + c->top;
    c->top += size;
    return ret;
}

static void* lept_context_pop(lept_context* c, size_t size) {
    assert(c->top >= size);
    return c->stack + (c->top -= size);
}

版权声明:本文为qq_39642978原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。