- 写在前面:该系列内容重点用于复习C的知识点,将整合叶劲峰老师的《从零开始的JSON库教程》等内容作为个人学习的笔记。
- 学习地址:从零开始的 JSON 库教程 - 知乎
目录
五、教程一的补充:测试驱动开发(test-driven development, TDD)
一、JSON数字语法
本单元的重点在于解析 JSON number 类型。我们先看看它的语法:
number = [ "-" ] int [ frac ] [ exp ]
int = "0" / digit1-9 *digit
frac = "." 1*digit
exp = ("e" / "E") ["-" / "+"] 1*digit
number 是以十进制表示,它主要由 4 部分顺序组成:负号、整数、小数、指数。只有整数是必需部分。注意和直觉可能不同的是,正号是不合法的。
①整数部分如果是 0 开始,只能是单个 0;而由 1-9 开始的话,可以加任意数量的数字(0-9)。也就是说,0123 不是一个合法的 JSON 数字。
②小数部分比较直观,就是小数点后是一或多个数字(0-9)。
③JSON 可使用科学记数法,指数部分由大写 E 或小写 e 开始,然后可有正负号,之后是一或多个数字(0-9)。
JSON 标准 ECMA-404 采用图的形式表示语法,也可以更直观地看到解析时可能经过的路径:
对于数字,我们要考虑怎么存储解析后的结果。从 JSON 数字的语法,我们可能直观地会认为它应该表示为一个浮点数(floating point number),因为它带有小数和指数部分。然而,标准中并没有限制数字的范围或精度。为简单起见,leptjson 选择以双精度浮点数(C 中的 double 类型)来存储 JSON 数字。
我们为 lept_value 添加成员:
typedef struct {
double n;
lept_type type;}lept_value;
仅当 type == LEPT_NUMBER 时,n 才表示 JSON 数字的数值。所以获取该值的 API 是这么实现的:
double lept_get_number(const lept_value* v) {
assert(v != NULL && v->type == LEPT_NUMBER);
return v->n;}
使用者应确保类型正确,才调用此 API。我们继续使用断言来保证。
二、标准库的strtof()
该部分可参考strtof (Strings) - C 中文开发手册 - 开发者手册 - 云+社区 - 腾讯云或cppreference.com
①使用形式如下:
②范例代码如下:
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
int main(void)
{
// parsing with error handling
const char *p = "111.11 -2.22 Nan nan(2) inF 0X1.BC70A3D70A3D7P+6 1.18973e+4932zzz";
printf("Parsing '%s':\n", p);
char *end;
for (double f = strtod(p, &end); p != end; f = strtod(p, &end))
{
printf("'%.*s' -> ", (int)(end-p), p);
p = end;
if (errno == ERANGE){
printf("range error, got ");
errno = 0;
}
printf("%f\n", f);
}
// parsing without error handling
printf("\" -0.0000000123junk\" --> %g\n", strtod(" -0.0000000123junk", NULL));
printf("\"junk\" --> %g\n", strtod("junk", NULL));
}
③输出结果如下:
Parsing '111.11 -2.22 Nan nan(2) inF 0X1.BC70A3D70A3D7P+6 1.18973e+4932zzz':
'111.11' -> 111.110000
' -2.22' -> -2.220000
' Nan' -> nan
' nan(2)' -> nan
' inF' -> inf
' 0X1.BC70A3D70A3D7P+6' -> 111.110000
' 1.18973e+4932' -> range error, got inf
" -0.0000000123junk" --> -1.23e-08
"junk" --> 0
三、输出语句
对于该段内容
#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect)==(actual), expect, actual, "%.17g")
对输出语句中的格式符%g有些陌生,发现%g可以根据值的不同自动选择%f或%e,如下:
四、教程一的补充:断言assert()
在大部分编译器下,assert() 是一个宏;在少数的编译器下,assert() 就是一个函数。我们无需关心这些差异,只管把 assert() 当做函数使用即可。
assert() 的用法很简单,我们只要传入一个表达式,它会计算这个表达式的结果:如果表达式的结果为“假”,assert() 会打印出断言失败的信息,并调用 abort() 函数终止程序的执行;如果表达式的结果为“真”,assert() 就什么也不做,程序继续往后执行。
要注意的是,我们应该遵循使用 assert() 的一个原则:每次断言只能检验一个表达式。且不要用会改变环境的语句作为断言的表达式(例如自加自减语句)。
五、教程一的补充:测试驱动开发(test-driven development, TDD)
一般来说,软件开发是以周期进行的。例如,加入一个功能,再写关于该功能的单元测试。但也有另一种软件开发方法论,称为测试驱动开发(test-driven development, TDD),它的主要循环步骤是:
- 加入一个测试。
- 运行所有测试,新的测试应该会失败。
- 编写实现代码。
- 运行所有测试,若有测试失败回到3。
- 重构代码。
- 回到 1。
TDD 是先写测试,再实现功能。好处是实现只会刚好满足测试,而不会写了一些不需要的代码,或是没有被测试的代码。但无论我们是采用 TDD,或是先实现后测试,都应尽量加入足够覆盖率的单元测试。
六、总结
本单元最重要的收获就是学会开始看c/c++的参考文档cppreference.com,这绝对是我们小白的宝库!
第二复习了断言、strtof()函数和C语言常见的输出方法(一年没用都还给老师了 可恶)
第三学习了JSON数字语法及其检验方式,该方法类似于《编译原理》中LR(1)分析法,通过DNF就可以直观地写出数字校验方法,但实现起来又较之简单得多。