diff --git a/tutorial01/tutorial01.md b/tutorial01/tutorial01.md index 68942fe8..50902693 100644 --- a/tutorial01/tutorial01.md +++ b/tutorial01/tutorial01.md @@ -20,7 +20,7 @@ ## JSON 是什么 -JSON(JavaScript Object Notation)是一个用于数据交换的文本格式,现时的标准为[ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf)。 +JSON(JavaScript Object Notation)是一个用于数据交换的文本格式,现时的标准为[ECMA-404](https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf)。 虽然 JSON 源至于 JavaScript 语言,但它只是一种数据格式,可用于任何编程语言。现时具类似功能的格式有 XML、YAML,当中以 JSON 的语法最为简单。 @@ -90,7 +90,7 @@ JSON(JavaScript Object Notation)是一个用于数据交换的文本格式 按 Configure,选择编译器,然后按 Generate 便会生成 Visual Studio 的 .sln 和 .vcproj 等文件。注意这个 build 目录都是生成的文件,可以随时删除,也不用上传至仓库。 -在 OS X 下,建议安装 [Homebrew](http://brew.sh/),然后在命令行键入: +在 OS X 下,建议安装 [Homebrew](https://brew.sh/),然后在命令行键入: ~~~ $ brew install cmake @@ -226,7 +226,7 @@ true = "true" 许多同学在做练习题时,都是以 `printf`/`cout` 打印结果,再用肉眼对比结果是否乎合预期。但当软件项目越来越复杂,这个做法会越来越低效。一般我们会采用自动的测试方式,例如单元测试(unit testing)。单元测试也能确保其他人修改代码后,原来的功能维持正确(这称为回归测试/regression testing)。 -常用的单元测试框架有 xUnit 系列,如 C++ 的 [Google Test](https://github.com/google/googletest)、C# 的 [NUnit](http://www.nunit.org/)。我们为了简单起见,会编写一个极简单的单元测试方式。 +常用的单元测试框架有 xUnit 系列,如 C++ 的 [Google Test](https://github.com/google/googletest)、C# 的 [NUnit](https://www.nunit.org/)。我们为了简单起见,会编写一个极简单的单元测试方式。 一般来说,软件开发是以周期进行的。例如,加入一个功能,再写关于该功能的单元测试。但也有另一种软件开发方法论,称为测试驱动开发(test-driven development, TDD),它的主要循环步骤是: @@ -423,7 +423,7 @@ static int lept_parse_value(lept_context* c, lept_value* v) { 断言(assertion)是 C 语言中常用的防御式编程方式,减少编程错误。最常用的是在函数开始的地方,检测所有参数。有时候也可以在调用函数后,检查上下文是否正确。 -C 语言的标准库含有 [`assert()`](http://en.cppreference.com/w/c/error/assert) 这个宏(需 `#include `),提供断言功能。当程序以 release 配置编译时(定义了 `NDEBUG` 宏),`assert()` 不会做检测;而当在 debug 配置时(没定义 `NDEBUG` 宏),则会在运行时检测 `assert(cond)` 中的条件是否为真(非 0),断言失败会直接令程序崩溃。 +C 语言的标准库含有 [`assert()`](https://en.cppreference.com/w/c/error/assert) 这个宏(需 `#include `),提供断言功能。当程序以 release 配置编译时(定义了 `NDEBUG` 宏),`assert()` 不会做检测;而当在 debug 配置时(没定义 `NDEBUG` 宏),则会在运行时检测 `assert(cond)` 中的条件是否为真(非 0),断言失败会直接令程序崩溃。 例如上面的 `lept_parse_null()` 开始时,当前字符应该是 `'n'`,所以我们使用一个宏 `EXPECT(c, ch)` 进行断言,并跳到下一字符。 diff --git a/tutorial02/tutorial02.md b/tutorial02/tutorial02.md index 9ab7ea17..6eb7d8d7 100644 --- a/tutorial02/tutorial02.md +++ b/tutorial02/tutorial02.md @@ -64,7 +64,7 @@ number 是以十进制表示,它主要由 4 部分顺序组成:负号、整 JSON 可使用科学记数法,指数部分由大写 E 或小写 e 开始,然后可有正负号,之后是一或多个数字(0-9)。 -JSON 标准 [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf) 采用图的形式表示语法,也可以更直观地看到解析时可能经过的路径: +JSON 标准 [ECMA-404](https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf) 采用图的形式表示语法,也可以更直观地看到解析时可能经过的路径: ![number](images/number.png) @@ -151,7 +151,7 @@ static void test_parse_invalid_value() { # 5. 十进制转换至二进制 -我们需要把十进制的数字转换成二进制的 `double`。这并不是容易的事情 [2]。为了简单起见,leptjson 将使用标准库的 [`strtod()`](http://en.cppreference.com/w/c/string/byte/strtof) 来进行转换。`strtod()` 可转换 JSON 所要求的格式,但问题是,一些 JSON 不容许的格式,`strtod()` 也可转换,所以我们需要自行做格式校验。 +我们需要把十进制的数字转换成二进制的 `double`。这并不是容易的事情 [2]。为了简单起见,leptjson 将使用标准库的 [`strtod()`](https://en.cppreference.com/w/c/string/byte/strtof) 来进行转换。`strtod()` 可转换 JSON 所要求的格式,但问题是,一些 JSON 不容许的格式,`strtod()` 也可转换,所以我们需要自行做格式校验。 ~~~c #include /* NULL, strtod() */ @@ -201,7 +201,7 @@ static int lept_parse_value(lept_context* c, lept_value* v) { 1. 重构合并 `lept_parse_null()`、`lept_parse_false()`、`lept_parse_true` 为 `lept_parse_literal()`。 2. 加入 [维基百科双精度浮点数](https://en.wikipedia.org/wiki/Double-precision_floating-point_format#Double-precision_examples) 的一些边界值至单元测试,如 min subnormal positive double、max double 等。 3. 去掉 `test_parse_invalid_value()` 和 `test_parse_root_not_singular` 中的 `#if 0 ... #endif`,执行测试,证实测试失败。按 JSON number 的语法在 lept_parse_number() 校验,不符合标准的程况返回 `LEPT_PARSE_INVALID_VALUE` 错误码。 -4. 去掉 `test_parse_number_too_big` 中的 `#if 0 ... #endif`,执行测试,证实测试失败。仔细阅读 [`strtod()`](http://en.cppreference.com/w/c/string/byte/strtof),看看怎样从返回值得知数值是否过大,以返回 `LEPT_PARSE_NUMBER_TOO_BIG` 错误码。(提示:这里需要 `#include` 额外两个标准库头文件。) +4. 去掉 `test_parse_number_too_big` 中的 `#if 0 ... #endif`,执行测试,证实测试失败。仔细阅读 [`strtod()`](https://en.cppreference.com/w/c/string/byte/strtof),看看怎样从返回值得知数值是否过大,以返回 `LEPT_PARSE_NUMBER_TOO_BIG` 错误码。(提示:这里需要 `#include` 额外两个标准库头文件。) 以上最重要的是第 3 条题目,就是要校验 JSON 的数字语法。建议可使用以下两个宏去简化一下代码: @@ -228,6 +228,6 @@ static int lept_parse_value(lept_context* c, lept_value* v) { 2. 科学计数法的指数部分没有对前导零作限制吗?`1E012` 也是合法的吗? - 是的,这是合法的。JSON 源自于 JavaScript([ECMA-262, 3rd edition](http://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262,%203rd%20edition,%20December%201999.pdf)),数字语法取自 JavaScript 的十进位数字的语法(§7.8.3 Numeric Literals)。整数不容许前导零(leading zero),是因为更久的 JavaScript 版本容许以前导零来表示八进位数字,如 `052 == 42`,这种八进位常数表示方式来自于 [C 语言](http://en.cppreference.com/w/c/language/integer_constant)。禁止前导零避免了可能出现的歧义。但是在指数里就不会出现这个问题。多谢 @Smallay 提出及协助解答这个问题。 + 是的,这是合法的。JSON 源自于 JavaScript([ECMA-262, 3rd edition](https://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262,%203rd%20edition,%20December%201999.pdf)),数字语法取自 JavaScript 的十进位数字的语法(§7.8.3 Numeric Literals)。整数不容许前导零(leading zero),是因为更久的 JavaScript 版本容许以前导零来表示八进位数字,如 `052 == 42`,这种八进位常数表示方式来自于 [C 语言](https://en.cppreference.com/w/c/language/integer_constant)。禁止前导零避免了可能出现的歧义。但是在指数里就不会出现这个问题。多谢 @Smallay 提出及协助解答这个问题。 其他常见问答将会从评论中整理。 diff --git a/tutorial02_answer/tutorial02_answer.md b/tutorial02_answer/tutorial02_answer.md index 524b27e2..4c83eebd 100644 --- a/tutorial02_answer/tutorial02_answer.md +++ b/tutorial02_answer/tutorial02_answer.md @@ -157,7 +157,7 @@ static int lept_parse_number(lept_context* c, lept_value* v) { } ~~~ -许多时候课本/书籍也不会把每个标准库功能说得很仔细,我想藉此提醒同学要好好看参考文档,学会读文档编程就简单得多![cppreference.com](http://cppreference.com) 是 C/C++ 程序员的宝库。 +许多时候课本/书籍也不会把每个标准库功能说得很仔细,我想藉此提醒同学要好好看参考文档,学会读文档编程就简单得多![cppreference.com](https://cppreference.com) 是 C/C++ 程序员的宝库。 ## 5. 总结 diff --git a/tutorial03/tutorial03.md b/tutorial03/tutorial03.md index 113eea78..162f2dc6 100644 --- a/tutorial03/tutorial03.md +++ b/tutorial03/tutorial03.md @@ -225,7 +225,7 @@ static void* lept_context_pop(lept_context* c, size_t size) { 压入时若空间不足,便回以 1.5 倍大小扩展。为什么是 1.5 倍而不是两倍?可参考我在 [STL 的 vector 有哪些封装上的技巧?](https://www.zhihu.com/question/25079705/answer/30030883) 的答案。 -注意到这里使用了 [`realloc()`](http://en.cppreference.com/w/c/memory/realloc) 来重新分配内存,`c->stack` 在初始化时为 `NULL`,`realloc(NULL, size)` 的行为是等价于 `malloc(size)` 的,所以我们不需要为第一次分配内存作特别处理。 +注意到这里使用了 [`realloc()`](https://en.cppreference.com/w/c/memory/realloc) 来重新分配内存,`c->stack` 在初始化时为 `NULL`,`realloc(NULL, size)` 的行为是等价于 `malloc(size)` 的,所以我们不需要为第一次分配内存作特别处理。 另外,我们把初始大小以宏 `LEPT_PARSE_STACK_INIT_SIZE` 的形式定义,使用 `#ifndef X #define X ... #endif` 方式的好处是,使用者可在编译选项中自行设置宏,没设置的话就用缺省值。 diff --git a/tutorial03_answer/tutorial03_answer.md b/tutorial03_answer/tutorial03_answer.md index 77d56084..8747fccc 100644 --- a/tutorial03_answer/tutorial03_answer.md +++ b/tutorial03_answer/tutorial03_answer.md @@ -105,7 +105,7 @@ Object dump complete. ## 1B. Linux/OSX 下的内存泄漏检测方法 -在 Linux、OS X 下,我们可以使用 [valgrind](http://valgrind.org/) 工具(用 `apt-get install valgrind`、 `brew install valgrind`)。我们完全不用修改代码,只要在命令行执行: +在 Linux、OS X 下,我们可以使用 [valgrind](https://valgrind.org/) 工具(用 `apt-get install valgrind`、 `brew install valgrind`)。我们完全不用修改代码,只要在命令行执行: ~~~ $ valgrind --leak-check=full ./leptjson_test diff --git a/tutorial04_answer/tutorial04_answer.md b/tutorial04_answer/tutorial04_answer.md index e634a069..6c75e783 100644 --- a/tutorial04_answer/tutorial04_answer.md +++ b/tutorial04_answer/tutorial04_answer.md @@ -25,7 +25,7 @@ static const char* lept_parse_hex4(const char* p, unsigned* u) { } ~~~ -可能有同学想到用标准库的 [`strtol()`](http://en.cppreference.com/w/c/string/byte/strtol),因为它也能解析 16 进制数字,那么可以简短的写成: +可能有同学想到用标准库的 [`strtol()`](https://en.cppreference.com/w/c/string/byte/strtol),因为它也能解析 16 进制数字,那么可以简短的写成: ~~~c static const char* lept_parse_hex4(const char* p, unsigned* u) { diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index 749270ae..e83ed259 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -26,10 +26,10 @@ object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D 要表示键值对的集合,有很多数据结构可供选择,例如: -* 动态数组(dynamic array):可扩展容量的数组,如 C++ 的 [`std::vector`](http://en.cppreference.com/w/cpp/container/vector)。 +* 动态数组(dynamic array):可扩展容量的数组,如 C++ 的 [`std::vector`](https://en.cppreference.com/w/cpp/container/vector)。 * 有序动态数组(sorted dynamic array):和动态数组相同,但保证元素已排序,可用二分搜寻查询成员。 -* 平衡树(balanced tree):平衡二叉树可有序地遍历成员,如红黑树和 C++ 的 [`std::map`](http://en.cppreference.com/w/cpp/container/map)([`std::multi_map`](http://en.cppreference.com/w/cpp/container/multimap) 支持重复键)。 -* 哈希表(hash table):通过哈希函数能实现平均 O(1) 查询,如 C++11 的 [`std::unordered_map`](http://en.cppreference.com/w/cpp/container/unordered_map)([`unordered_multimap`](http://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 +* 平衡树(balanced tree):平衡二叉树可有序地遍历成员,如红黑树和 C++ 的 [`std::map`](https://en.cppreference.com/w/cpp/container/map)([`std::multi_map`](https://en.cppreference.com/w/cpp/container/multimap) 支持重复键)。 +* 哈希表(hash table):通过哈希函数能实现平均 O(1) 查询,如 C++11 的 [`std::unordered_map`](https://en.cppreference.com/w/cpp/container/unordered_map)([`unordered_multimap`](https://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 设一个对象有 n 个成员,数据结构的容量是 m,n ⩽ m,那么一些常用操作的时间/空间复杂度如下: