sscanf之模式匹配

30 篇文章 10 订阅

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

热身

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

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

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

  • str 待解析的字符串
  • 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:%d\n", ret, year, month, day);
}

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

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

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

问题

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

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:%d\n", ret, addr, port);
}

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

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

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

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

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

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

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

模式

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

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

  • %[abcd] 匹配由a,b,c和d组成的字符串。比如对于abcdefg这个字符串,其会匹配abcd
  • %[^abcd] 当pattern由^开头时,其匹配pattern字符集以外的字符。因此,对于gfedcba这个字符串,其会匹配gfe
  • %[0-9a-fA-F] 其组成的字符集为0123456789abcdefABCDEF,其实就是16进制数字字符。

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

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

格式串%[*] [width] [{h|l|I64|L}]  type中,中括号代表该段可由可无,我们发现,只有%和type字段是必须的,width字段、{h|l|I64|L}字段可有可无。

每一个%都要对应输出一个结果,

[*]代表该%段不向结果缓冲区输出(用正则术语来说就是:只匹配不捕获)

width字段代表从从上一个%段匹配完之后的子串开始,最多检查多少个字符。(一般可以填结果缓冲区的容量以避免越界)

type段是最常见的:

%c 一个单一的字符  

%d 一个十进制整数  

%i 一个整数  

%e, %f, %g 一个浮点数  

%o 一个八进制数  

%s 一个字符串  

%x 一个十六进制数  

%p 一个指针  

%n 一个等于读取字符数量的整数  

%u 一个无符号整数  

%[ ] 一个字符集 (正则就使用这个,而且仅支持贪婪模式,能匹配个多少就匹配多少个) 

%% 一个精度符

尤其:在sscanf的type正则段中,^代表不允许包含

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

要不再思考一下?

好了,答案如下:

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

现在可以用sscanf来解析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);
    }
}

下图标出了格式化参数中各格式说明符对应的字段。
def751578cf84100908e8fa4f8b26c1f.png
第二个说明符,%*[^,]用于匹配时间122921.000。与之前不同的是,这里多了一个*,这表示不用解析对应字段的内容,后面的地址参数中也没有相关变量。你看,&valid, &latitude, &longitude分别存放有效标志字符,纬度和纬度,并没有时间变量的地址。%*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]);
    }

}

结果如下:
d38bc460d417459ea72149d69200a68d.png

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

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


---------------------
作者:wenbodong
来源:CSDN
原文:https://blog.csdn.net/wenbodong/article/details/119279278
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值