IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 嵌入式基础--解析字符串之模式匹配 -> 正文阅读

[嵌入式]嵌入式基础--解析字符串之模式匹配

接上一篇文章,笔者给大家介绍一个更加简单的解析工具,那就是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的这种隐藏用法后,屡试不爽,用的最多的就是^

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

要不再思考一下?

好了,答案如下:

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);
    }
}

下图标出了格式化参数中各格式说明符对应的字段。
在这里插入图片描述
第二个说明符,%*[^,]用于匹配时间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]);
    }

}

结果如下:
在这里插入图片描述

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

地址:git@gitee.com:wenbodong/mcu_demo.git
示例:examples/05_string/example.c
使用时需要打开examples/examples.h中的EXAMPLE_SHOW_STRING。
  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-08-02 10:57:40  更:2021-08-02 10:58:59 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 18:25:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码