标准C库字符串库功能可能有:
- 字符串复制追加(strcpy,strcat)
- 字符串查找比较(strstr,strcmp)
- 字符串转数字(atoi,strtol)
有两个非常有用但是可能被大家忽略的函数,介绍给大家。
任务:解析经纬度
让我们以解析GPS中的RMC消息为例,数据如下:
$GNRMC,122921.000,A,3204.862246,N,11845.911047,E,0.099,191.76,280521,,E,A*00
GPS的各字段以逗号分隔。我们需要提取经纬度信息,集中在:A,3204.862246,N,11845.911047,E
。A表示经纬度有效,3204.862246是纬度,11845.911047是纬度,具体的解释参见下图:
拆分字符串strtok_r
由于GPS中各字段以逗号分隔,大家最先想到的可能是用strstr或strchr去查找逗号的位置,再一一处理。如果有一个函数可以帮我们完成拆分,效果如下图,那将会很方便后续处理。
这个函数是有的,而且就在C标准库中,那就是strtok。
char *strtok(char *source, const char *delimiters);
其根据提供的分隔符集delimiters,对source进行拆分。
- source 待拆分的字符串。
- delimiters 分隔符集,可以包含多个字符。比如"\r\n\t "表示以换行,tab等字符进行拆分。
- return 返回指向子字符串的指针。
在拆分一个字符串时,需要多次调用该方法:
- 初次调用时,source为待拆分字符串,delimiters为分隔符。函数返回第一个子字符串地址。
- 之后的调用,source为NULL,delimiters为分隔符,分隔符的内容并不需要与之前的一致。函数返回下一个子字符串地址。
- 当某次调用后返回NULL时,整个拆分就结束了。
其实过程并不复杂,请看拆分GPS的代码:
#define GPS_RMC "$GNRMC,122921.000,A,3204.862246,N,11845.911047,E,0.099,191.76,280521,,E,A*00"
void split_string_example(void)
{
char buf[128];
int buf_len;
char *token = NULL;
char *saveptr = NULL;
const char *delim = ",*";
LOG_I("test split string");
buf_len = snprintf(buf, sizeof(buf), "%s", GPS_RMC);
token = strtok_r(buf, delim, &saveptr);
while (token)
{
LOG_D("%s", token);
token = strtok_r(NULL, delim, &saveptr);
}
LOG_HEX_V(buf, buf_len, "finally, buf:");
}
结果刚才已经放过了,这次放一个更完整的。
示例中用宏GPS_RMC来定义GPS的内容,再用snprintf把它打印到buf之中?
buf_len = snprintf(buf, sizeof(buf), "%s", GPS_RMC);
这可不是笔者多此一举,而是因为strtok在拆分字符串时会修改其内容。以下两点需要牢记:
- strtok并不是重新分配内存以存放子字符串,其返回的子字符串直接指向待拆分字符串中的相应位置。没有任何的内存分配。
- 所谓的拆分,是将字符串中的分隔符替换为’\0’,也只有这样,你才能进行后续操作。上图的结尾展示了拆分后的buf的内容,红框都是’\0’。因此,待拆分字符串必须是可被修改的,必须是变量,而不能是常量。
笔者用的不是strtok,而是strtok_r。C语言中很多函数有两种版本,一种不带_r,一种带_r,_r表示可重入。
strtok_r比strtok多了一个参数,其为char *指针,用于保存拆分的状态。其实用法很简单,定义一个指针变量并传入就行,不需要关注它的值。
优化一下
我们再看下GPS的数据,如果想提取其中的A
,3204.862246
和11845.911047
,直接使用strtok并不方便。
$GNRMC,122921.000,A,3204.862246,N,11845.911047,E,0.099,191.76,280521,,E,A*00
如果使用Java的话,如下几行代码即可完成提取。
String gps = "$GNRMC,122921.000,A,3204.862246,N,11845.911047,E,0.099,191.76,280521,,E,A*00";
String[] sub = gps.split(",");
if (sub.length < 6) {
System.out.println("parse fail");
} else {
System.out.println(String.format(
"parse succeed, valid:%s, longitude:%s, latitude:%s",
sub[2], sub[3], sub[5]));
}
输出结果:
parse succeed, valid:A, longitude:3204.862246, latitude:11845.911047
Java之所以方便,关键在于split函数返回了拆分后的字符串数组,可直接通过下标提取相关字段。
C语言没有这样的函数,那我们就自己写一个。
static int split_string(char *str, const char *delim, char *sub_ptr[], int size)
{
char *token = NULL;
char *saveptr = NULL;
int idx = 0;
token = strtok_r(str, delim, &saveptr);
while (token && idx < size)
{
sub_ptr[idx++] = token;
token = strtok_r(NULL, delim, &saveptr);
}
return idx;
}
split_string将拆分的结果写入sub_ptr之中,并返回子字符串个数。有了这个函数,提取就如Java一样方便了。
void split_string_example2(void)
{
char buf[128];
char *sub_buf[20];
int num;
LOG_I("test split string 2");
snprintf(buf, sizeof(buf), "%s", GPS_RMC);
num = split_string(buf, ",", sub_buf, ARRAY_SIZE(sub_buf));
if (num < 7)
{
LOG_E("fail");
return;
}
LOG_D("succeed, valid:%s, latitude:%s, longitude:%s", sub_buf[2], sub_buf[3], sub_buf[5]);
}
使用strtok或者是split_string仅仅是提取出目标字符串,想得到经纬度数值的话,还需要转换成浮点数,可使用atof函数。其实还有一种更为简单的方法,咱明天继续。
文中完整的示例代码,参见笔者基于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/119091142
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件