strtok拆分字符串
大家好,我是驚覺,今天聊聊字符串。字符串的使用場景非常之多,人機交互和雙機通信都會用到。比如:
- 通過串口向單片機發送指令,以執行操作或配置參數。
- 單片機讀取傳感器數據,數據格式是字符串。一般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*00GPS的各字段以逗號分隔。我們需要提取經緯度信息,集中在:A,3204.862246,N,11845.911047,E。A表示經緯度有效,3204.862246是緯度,11845.911047是緯度,具體的解釋參見下圖:
拆分字符串strtok_r
由于GPS中各字段以逗號分隔,大家最先想到的可能是用strstr或strchr去查找逗號的位置,再一一處理。如果有一個函數可以幫我們完成拆分,效果如下圖,那將會很方便后續處理。
這個函數是有的,而且就在C標準庫中,那就是strtok。
其根據提供的分隔符集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之中?
這可不是筆者多此一舉,而是因為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.911047Java之所以方便,關鍵在于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拆分字符串的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Mikrotik RouterOS安全性
- 下一篇: day14、4 - ARP攻击防御