77
88本单元内容:
99
10- 1 . [ JSON 是什么] ( #json-是什么 )
11- 2 . [ 搭建编译环境] ( #搭建编译环境 )
12- 3 . [ 头文件与 API 设计] ( #头文件与-api-设计 )
13- 4 . [ JSON 语法子集] ( #json-语法子集 )
14- 5 . [ 单元测试] ( #单元测试 )
15- 6 . [ 宏的编写技巧] ( #宏的编写技巧 )
16- 7 . [ 实现解析器] ( #实现解析器 )
17- 8 . [ 关于断言] ( #关于断言 )
18- 9 . [ 总结与练习] ( #总结与练习 )
19- 10 . [ 常见问答] ( #常见问答 )
10+ 1 . [ JSON 是什么] ( #1- json-是什么 )
11+ 2 . [ 搭建编译环境] ( #2- 搭建编译环境 )
12+ 3 . [ 头文件与 API 设计] ( #3- 头文件与-api-设计 )
13+ 4 . [ JSON 语法子集] ( #4- json-语法子集 )
14+ 5 . [ 单元测试] ( #5- 单元测试 )
15+ 6 . [ 宏的编写技巧] ( #6- 宏的编写技巧 )
16+ 7 . [ 实现解析器] ( #7- 实现解析器 )
17+ 8 . [ 关于断言] ( #8- 关于断言 )
18+ 9 . [ 总结与练习] ( #9- 总结与练习 )
19+ 10 . [ 常见问答] ( #10- 常见问答 )
2020
21- ## JSON 是什么
21+ ## 1. JSON 是什么
2222
23- JSON(JavaScript Object Notation)是一个用于数据交换的文本格式,现时的标准为[ ECMA-404] ( http ://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf) 。
23+ JSON(JavaScript Object Notation)是一个用于数据交换的文本格式,现时的标准为[ ECMA-404] ( https ://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf) 。
2424
2525虽然 JSON 源至于 JavaScript 语言,但它只是一种数据格式,可用于任何编程语言。现时具类似功能的格式有 XML、YAML,当中以 JSON 的语法最为简单。
2626
@@ -68,7 +68,7 @@ JSON(JavaScript Object Notation)是一个用于数据交换的文本格式
6868
6969我们会逐步实现这些需求。在本单元中,我们只实现最简单的 null 和 boolean 解析。
7070
71- ## 搭建编译环境
71+ ## 2. 搭建编译环境
7272
7373我们要做的库是跨平台、跨编译器的,同学可使用任意平台进行练习。
7474
@@ -90,7 +90,7 @@ JSON(JavaScript Object Notation)是一个用于数据交换的文本格式
9090
9191按 Configure,选择编译器,然后按 Generate 便会生成 Visual Studio 的 .sln 和 .vcproj 等文件。注意这个 build 目录都是生成的文件,可以随时删除,也不用上传至仓库。
9292
93- 在 OS X 下,建议安装 [ Homebrew] ( http ://brew.sh/) ,然后在命令行键入:
93+ 在 OS X 下,建议安装 [ Homebrew] ( https ://brew.sh/) ,然后在命令行键入:
9494
9595~~~
9696$ brew install cmake
@@ -126,7 +126,7 @@ $ ./leptjson_test
126126
127127若看到类似以上的结果,说明已成功搭建编译环境,我们可以去看看那几个代码文件的内容了。
128128
129- ## 头文件与 API 设计
129+ ## 3. 头文件与 API 设计
130130
131131C 语言有头文件的概念,需要使用 ` #include ` 去引入头文件中的类型声明和函数声明。但由于头文件也可以 ` #include ` 其他头文件,为避免重复声明,通常会利用宏加入 include 防范(include guard):
132132
@@ -193,9 +193,9 @@ enum {
193193lept_type lept_get_type (const lept_value* v);
194194~~~
195195
196- ## JSON 语法子集
196+ ## 4. JSON 语法子集
197197
198- 下面是此单元的 JSON 语法子集,使用 [RFC7159](http ://rfc7159.net /rfc7159) 中的 [ABNF](https://tools.ietf.org/html/rfc5234) 表示:
198+ 下面是此单元的 JSON 语法子集,使用 [RFC7159](https ://tools.ietf.org/html /rfc7159) 中的 [ABNF](https://tools.ietf.org/html/rfc5234) 表示:
199199
200200~~~
201201JSON-text = ws value ws
@@ -222,11 +222,11 @@ true = "true"
222222* 若一个值之后,在空白之后还有其他字符,传回 `LEPT_PARSE_ROOT_NOT_SINGULAR`。
223223* 若值不是那三种字面值,传回 `LEPT_PARSE_INVALID_VALUE`。
224224
225- ## 单元测试
225+ ## 5. 单元测试
226226
227227许多同学在做练习题时,都是以 `printf`/`cout` 打印结果,再用肉眼对比结果是否乎合预期。但当软件项目越来越复杂,这个做法会越来越低效。一般我们会采用自动的测试方式,例如单元测试(unit testing)。单元测试也能确保其他人修改代码后,原来的功能维持正确(这称为回归测试/regression testing)。
228228
229- 常用的单元测试框架有 xUnit 系列,如 C++ 的 [Google Test](https://github.com/google/googletest)、C# 的 [NUnit](http ://www.nunit.org/)。我们为了简单起见,会编写一个极简单的单元测试方式。
229+ 常用的单元测试框架有 xUnit 系列,如 C++ 的 [Google Test](https://github.com/google/googletest)、C# 的 [NUnit](https ://www.nunit.org/)。我们为了简单起见,会编写一个极简单的单元测试方式。
230230
231231一般来说,软件开发是以周期进行的。例如,加入一个功能,再写关于该功能的单元测试。但也有另一种软件开发方法论,称为测试驱动开发(test-driven development, TDD),它的主要循环步骤是:
232232
@@ -243,7 +243,7 @@ TDD 是先写测试,再实现功能。好处是实现只会刚好满足测试
243243
244244回到 leptjson 项目,`test.c` 包含了一个极简的单元测试框架:
245245
246- ~~~
246+ ~~~c
247247#include <stdio.h>
248248#include <stdlib.h>
249249#include <string.h>
@@ -299,7 +299,7 @@ int main() {
299299
300300然而,完全按照 TDD 的步骤来开发,是会减慢开发进程。所以我个人会在这两种极端的工作方式取平衡。通常会在设计 API 后,先写部分测试代码,再写满足那些测试的实现。
301301
302- ## 宏的编写技巧
302+ ## 6. 宏的编写技巧
303303
304304有些同学可能不了解 ` EXPECT_EQ_BASE ` 宏的编写技巧,简单说明一下。反斜线代表该行未结束,会串接下一行。而如果宏里有多过一个语句(statement),就需要用 ` do { /*...*/ } while(0) ` 包裹成单个语句,否则会有如下的问题:
305305
344344 c();
345345~~~
346346
347- ## 实现解析器
347+ ## 7. 实现解析器
348348
349349有了 API 的设计、单元测试,终于要实现解析器了。
350350
@@ -419,15 +419,15 @@ static int lept_parse_value(lept_context* c, lept_value* v) {
419419
420420由于 ` lept_parse_whitespace() ` 是不会出现错误的,返回类型为 ` void ` 。其它的解析函数会返回错误码,传递至顶层。
421421
422- ## 关于断言
422+ ## 8. 关于断言
423423
424424断言(assertion)是 C 语言中常用的防御式编程方式,减少编程错误。最常用的是在函数开始的地方,检测所有参数。有时候也可以在调用函数后,检查上下文是否正确。
425425
426- C 语言的标准库含有 [ ` assert() ` ] ( http ://en.cppreference.com/w/c/error/assert) 这个宏(需 ` #include <assert.h> ` ),提供断言功能。当程序以 release 配置编译时(定义了 ` NDEBUG ` 宏),` assert() ` 不会做检测;而当在 debug 配置时(没定义 ` NDEBUG ` 宏),则会在运行时检测 ` assert(cond) ` 中的条件是否为真(非 0),断言失败会直接令程序崩溃。
426+ C 语言的标准库含有 [ ` assert() ` ] ( https ://en.cppreference.com/w/c/error/assert) 这个宏(需 ` #include <assert.h> ` ),提供断言功能。当程序以 release 配置编译时(定义了 ` NDEBUG ` 宏),` assert() ` 不会做检测;而当在 debug 配置时(没定义 ` NDEBUG ` 宏),则会在运行时检测 ` assert(cond) ` 中的条件是否为真(非 0),断言失败会直接令程序崩溃。
427427
428428例如上面的 ` lept_parse_null() ` 开始时,当前字符应该是 ` 'n' ` ,所以我们使用一个宏 ` EXPECT(c, ch) ` 进行断言,并跳到下一字符。
429429
430- 初使用断言的同学,可能会错误地把含副作用的代码放在 ` assert() ` 中:
430+ 初使用断言的同学,可能会错误地把含 [ 副作用 ] ( https://en.wikipedia.org/wiki/Side_effect_(computer_science) ) 的代码放在 ` assert() ` 中:
431431
432432~~~ c
433433assert (x++ == 0 ); /* 这是错误的! */
@@ -437,15 +437,15 @@ assert(x++ == 0); /* 这是错误的! */
437437
438438另一个问题是,初学者可能会难于分辨何时使用断言,何时处理运行时错误(如返回错误值或在 C++ 中抛出异常)。简单的答案是,如果那个错误是由于程序员错误编码所造成的(例如传入不合法的参数),那么应用断言;如果那个错误是程序员无法避免,而是由运行时的环境所造成的,就要处理运行时错误(例如开启文件失败)。
439439
440- ## 总结与练习
440+ ## 9. 总结与练习
441441
442442本文介绍了如何配置一个编程环境,单元测试的重要性,以至于一个 JSON 解析器的子集实现。如果你读到这里,还未动手,建议你快点试一下。以下是本单元的练习,很容易的,但我也会在稍后发出解答篇。
443443
4444441 . 修正关于 ` LEPT_PARSE_ROOT_NOT_SINGULAR ` 的单元测试,若 json 在一个值之后,空白之后还有其它字符,则要返回 ` LEPT_PARSE_ROOT_NOT_SINGULAR ` 。
4454452 . 参考 ` test_parse_null() ` ,加入 ` test_parse_true() ` 、` test_parse_false() ` 单元测试。
4464463 . 参考 ` lept_parse_null() ` 的实现和调用方,解析 true 和 false 值。
447447
448- ## 常见问答
448+ ## 10. 常见问答
449449
4504501 . 为什么把例子命名为 leptjson?
451451
0 commit comments