IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发工具 -> 【项目学习总结】GitHub : miloyip/json-tutorial(轻量级JSON) -> 正文阅读

[开发工具]【项目学习总结】GitHub : miloyip/json-tutorial(轻量级JSON)

一、项目介绍

本工程是一个轻量版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);
}
  开发工具 最新文章
Postman接口测试之Mock快速入门
ASCII码空格替换查表_最全ASCII码对照表0-2
如何使用 ssh 建立 socks 代理
Typora配合PicGo阿里云图床配置
SoapUI、Jmeter、Postman三种接口测试工具的
github用相对路径显示图片_GitHub 中 readm
Windows编译g2o及其g2o viewer
解决jupyter notebook无法连接/ jupyter连接
Git恢复到之前版本
VScode常用快捷键
上一篇文章      下一篇文章      查看所有文章
加:2021-10-26 12:23:30  更:2021-10-26 12:25:10 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 21:24:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码