接上一篇文章,笔者给大家介绍一个更加简单的解析工具,那就是。

热身

大家在上C语言课,做C语言课程设计或实验时,应该经常接触和scanf,前者打印字符中到标准输出,而后者从标准输入读取并解析字符串。

和scanf类似,只不过它并不从标准输入读取,而是直接解析用户传入的字符串。

int sscanf(const char *str, const char *format, ...);

格式化参数是啥意思?我们先来热身下。

static void test(void)
{
 const char *str = "Today is 2021.7.31";

 int year = 0;
 int month = 0;
 int day = 0;
 int ret;

 ret = sscanf(str, "Today is %d.%d.%d", &year, &month, &day);
 printf("ret=%d, year:%d, month:%d, day:%dn", ret, year, month, day);
}

"Today is %d.%d.%d"为格式化参数,里面有普通字符,如Today is,和格式说明符,%d表int类型。依据格式化参数来解析str。对于普通字符,检查str是否与其一致。对于格式说明符,则按其含义来提取str中的内容,并将结果存入地址参数中。上述代码演示了提取年月日信息的方法,结果如下:

ret=3, year:2021, month:7, day:31

如果还是对格式化参数没有印象的话,同学,你肯定没认真上C语言课,要不翻书复习下呗。笔者今天不是想从零开始讲,而是介绍一个鲜为人知的用法。

问题

再看一个例子,解析域名和端口号。

static void test2(void)
{
 const char *str = "www.baidu.com:80";

 char addr[64] = "";
 int port = 0;
 int ret;

 ret = sscanf(str, "%s:%d", addr, &port);
 printf("ret=%d, addr:%s, port:%dn", ret, addr, port);
}

%s用于解析字符串(域名)sscanf,%d用于解析int(端口号)。结果如下:

ret=1, addr:www.baidu.com:80, port:0

这并不是我们期望的结果,将域名和端口号都当字符串来解析了。这是因为%s对应的字符串,遇到空白字符(空格、换行)或者是''才算结束。结束之后,才会去理会:%d。

上述情况属于:想让字符串结束却没结束,从而解析了过多的内容。有时还会遇到相反的情况,请看下一个例子:

static void test3(void)
{
 const char *str = "how are you";
 char buf[64] = "";

 sscanf(str, "%s", buf);
 printf("%sn", buf);
}

本想解析出完整的how are you,而输出的结果只有how。

模式

使用%s来匹配字符串,会受到很大的限制。但这并不意味着不好用,其还有一种匹配字符串的方法,那就是模式匹配。

模式的格式为:%[],其中的用于定义一个字符集,待匹配的字符串由这个字符集组成。可以是多个字符,也可以使用-定义一个范围,还可以使用^反向定义字符集。说着有点抽象,让我们看些具体的示例吧。

当使用-定义范围时,需要注意,起始字符必须小于结束字符。%[z-a]匹配的就不是范围,而是z,-和a这3个字符。

如果你学过正则表达式的话,对上述模式应该很熟悉。只不过,提供的模式匹配的功能比正则表达式简单的多。笔者在知道的这种隐藏用法后,屡试不爽,用的最多的就是^。

现在大家知道如何解析域名和端口号了吗?

要不再思考一下?

好了,答案如下:

static void test2_fix(void)
{
 const char *str = "www.baidu.com:80";

 char addr[64] = "";
 int port = 0;
 int ret;

 ret = sscanf(str, "%[^:]:%d", addr, &port);
 printf("ret=%d, addr:%s, port:%d", ret, addr, port);
}

是不是非常简单,既然域名是:之前的内容,那就定义为%[^:]。

解析GPS

现在可以用来解析GPS了,GPS样例如下:

$GNRMC,122921.000,A,3204.862246,N,11845.911047,E,0.099,191.76,280521,,E,A*00

直接上代码:

static void parse_gps(const char *gps)
{
    char valid = ' ';
    double longitude = 0;
    double latitude = 0;
    int ret;

    ret = sscanf(gps,
             "$GNRMC,%*[^,],%c,%lf,%*c,%lf,%*c,",   /* UTC,valid,latitude,ns,longitude,ew,  */
             &valid, &latitude, &longitude);

    LOG_D("parse gps(%s)", gps);


    if (ret != 3)
    {
        LOG_E("fail");
    }
    else
    {
        LOG_D("succeed, valid:%c, latitude:%lf, longitude:%lf", valid, latitude, longitude);
    }
}

下图标出了格式化参数中各格式说明符对应的字段。

图片

第二个说明符,%*[^,]用于匹配时间.000。与之前不同的是sscanf,这里多了一个*,这表示不用解析对应字段的内容,后面的地址参数中也没有相关变量。你看,&valid, &, &分别存放有效标志字符,纬度和纬度,并没有时间变量的地址。%*c同理。

测试时,用3个用例进行测试,以测试成功和失败的场景。

void parse_string_example(void)
{
    const char *strs[] =
    {
            "$GNRMC,122921.000,A,3204.862246,N,11845.911047,E,0.099,191.76,280521,,E,A*00",
            "hello world",
            "$GNRMC,,,,,,,,,,,,*00"
    };

    LOG_I("test parse string");

    for (int i = 0; i < ARRAY_SIZE(strs); i++)
    {
        parse_gps(strs[i]);
    }

}

结果如下:

图片

文中完整的示例代码,参见笔者基于创建的demo工程:

地址:git@gitee.com:wenbodong/mcu_demo.git
示例:examples/05_string/example.c
使用时需要打开examples/examples.h中的EXAMPLE_SHOW_STRING。

你可以添加微信为好友,注明:公司+姓名,拉进 RT- 官方微信交流群!

图片


限时特惠:
本站持续每日更新海量各大内部创业课程,一年会员仅需要98元,全站资源免费下载
点击查看详情

站长微信:Jiucxh

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注