大家好,我是惊觉,今天聊聊字符串。字符串的使用场景非常之多,人机交互和双机通信都会用到。比如:

  • 通过串口向单片机发送指令,以执行操作或配置参数。
  • 单片机读取传感器数据,数据格式是字符串。一般GPS数据就是字符格式。
  • 有些场景需要使用多个处理器协同工作,比如单片机+openmv,它们之间需要通信,可以采用字符格式的编码方式。

操作字符串,无非是两件事儿:生成字符串与解析字符串,后者往往更复杂一些。Java,Python之类的高级编程语言自带了强大的字符串处理库,提供非常丰富的操作。下图是Java的String类函数,密密麻麻有木有,这还只是一部分。

相对而言,标准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的数据,如果想提取其中的A3204.86224611845.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。

strtok拆分字符串相关推荐

  1. C语言常用字符串函数之 strtok 拆分字符串

    strtok 常用字符串分割方法实例汇总 C语言 strtok 字符串分割

  2. swift和OC - 拆分数组 和 拆分字符串

    1. 拆分数组 /// 根据 数组 截取 指定个数返回 多个数组的集合func splitArray( array: [Date], withSubSize subSize: Int) -> [ ...

  3. Java如何拆分字符串

    假如现在有这样一串字符序列"沉默王二,一枚有趣的程序员",需要按照中文逗号","进行拆分,这意味着第一串字符序列为逗号前面的"沉默王二",第 ...

  4. [SqlServer]数据库中自定义拆分字符串函数Split()

     经常我们要用到批量操作时都会用到字符串的拆分,郁闷的是SQL Server中却没有自带Split函数,所以我们只能自己动手来解决一下.为了减少和数据库的通讯次数,我们都会利用这种方法来实现批量操作. ...

  5. 算法应用 ---拆分字符串为n节字符

    package com.tw.str.util; import java.util.ArrayList; import java.util.Iterator; import java.util.Lis ...

  6. 如何在Bash中的分隔符上拆分字符串?

    我将此字符串存储在变量中: IN="bla@some.com;john@home.com" 现在我想用拆分字符串; 分隔符,以便我有: ADDR1="bla@some.c ...

  7. SQLSERVER拆分字符串的函数(表值函数)

    -- ============================================= -- Author:        <over> -- Create date: < ...

  8. oracle sql字符拆分字符串函数,oracle-是否有在PL / SQL中拆分字符串的功能?

    oracle-是否有在PL / SQL中拆分字符串的功能? 我需要编写一个过程来规范具有由一个字符连接的多个令牌的记录. 我需要获得这些令牌来分割字符串,并将每个令牌作为新记录插入表中. Oracle ...

  9. 处理字符串_6_拆分字符串里的字符和数

    拆分字符串里的字符和数字 需求描述 需求:过滤tmp_v视图里data字段拆分会原来的ename和deptno两个字段. 解决方法:这里通过translate.replace.repeate(repl ...

最新文章

  1. SQLite 源码仓库(Repository)
  2. 图解数据中心水系统标准和架构(大全)
  3. 六台机器搭建RedisCluster分布式集群
  4. HDU6956-Pass!(2021杭电多校一)(BSGS)
  5. 移动端Rem之讲解总结
  6. EWSN 2019 (待续)
  7. VC6.0 中的__asm语句
  8. php找不到intl,php_intl.dll找不到指定模块怎么办
  9. linux系统在物流公司的z作用,【项目案例】基于RFID的智能物流仓储系统
  10. C++中使用初始化列表比在构造函数中对成员变量赋值更高效
  11. BUCK电路中,输入电压增加后,电感电流曲线变化的推导 // 《精通开关电源设计》P44 图2-3
  12. 三步解决NLP数据标注难题,百度大脑EasyDL专业版上线文本智能标注功能
  13. 口碑营销:如何让传统行业的电商引发口碑效应并营销
  14. 计算机视觉-论文阅读笔记-基于高性能检测器与表观特征的多目标跟踪
  15. 书小宅之概念汇总——胜读十年书
  16. MIT6.824 Lab1 MapReduce
  17. 最小生成树 算法思想及模板代码
  18. 基于STM32物联网WiFi智能家居控制系统设计(原理图+源代码+系统资料)
  19. c++函数模板,有默认参数的函数
  20. 传奇手游单职业服务器外网搭建架设一键端-2023

热门文章

  1. python数据分析与挖掘实战 之笔记2
  2. 桂林电子科技大学计算机专科可以换专业吗,桂林电子科技大学计算机专业怎样...
  3. Python去掉空格的常用方法
  4. 《底层逻辑:看清这个世界的底牌》读书笔记
  5. 一个小巧的PDF阅读器
  6. Visual Studio Community 2015 下载链接
  7. 2023年福建农林大学程序设计校赛个人题解(无D解析)
  8. 4月14日~15日,2021慕尼黑上海电子展,我等你来!
  9. java:匹配网址的正则表达式
  10. ElasticSearch(三)springboot整合ES