《从零开始的JSON库教程》笔记|《教程二:解析数字》知识点

  • 写在前面:该系列内容重点用于复习C的知识点,将整合叶劲峰老师的《从零开始的JSON库教程》等内容作为个人学习的笔记。
  • 学习地址:从零开始的 JSON 库教程 - 知乎

目录

一、JSON数字语法

二、标准库的strtof() 

三、输出语句

四、教程一的补充:断言assert()

五、教程一的补充:测试驱动开发(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),它的主要循环步骤是:

  1. 加入一个测试。
  2. 运行所有测试,新的测试应该会失败。
  3. 编写实现代码。
  4. 运行所有测试,若有测试失败回到3。
  5. 重构代码。
  6. 回到 1。

        TDD 是先写测试,再实现功能。好处是实现只会刚好满足测试,而不会写了一些不需要的代码,或是没有被测试的代码。但无论我们是采用 TDD,或是先实现后测试,都应尽量加入足够覆盖率的单元测试。

六、总结

        本单元最重要的收获就是学会开始看c/c++的参考文档cppreference.com,这绝对是我们小白的宝库!

        第二复习了断言、strtof()函数和C语言常见的输出方法(一年没用都还给老师了 可恶)

        第三学习了JSON数字语法及其检验方式,该方法类似于《编译原理》中LR(1)分析法,通过DNF就可以直观地写出数字校验方法,但实现起来又较之简单得多。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值