在用C/C++實現字符串切割中,strtok函數經常用到,其主要作用是按照給定的字符集分隔字符串,並返回各子字符串。
但是實際上,可不止有strtok(),還有strtok、strtok_s、strtok_r 函數,我們本篇文章作為基礎篇,來一些簡單的介紹。因為濫用了這個函數,我可是被老工程師嘲笑的無地自容了。
strtok()函數詳解描述該函數用來將字符串分割成一個個片段,並返回各子字符串。
函數原型char *strtok(char *str, const char *delim)
參數返回值該函數返回被分解的第一個子字符串,如果沒有可檢索的字符串,則返回一個空指針。
實例//https://tool.lu/coderunner/
//來源:技術讓夢想更偉大
//作者:李肖遙
#include <string.h>
#include <stdio.h>
#define INFO_MAX_SZ 80
int main () {
char str[INFO_MAX_SZ] = "dream - coder - lixiaoyao";
const char delim[2] = "-";
char *token;
//獲取第一個子字符串
token = strtok(str,delim);
//繼續獲取其他的子字符串
while( token != NULL )
{
printf( "%s\n", token );
token = strtok(NULL, delim);
}
return(0);
}運行的結果如下:
注意事項使用該函數進行字符串分割時,會破壞被分解字符串的完整,調用前和調用後的s已經不一樣了。第一次分割之後,原字符串str是分割完成之後的第一個字符串,剩餘的字符串存儲在一個靜態變量中。
strtok函數在提取字符串時使用了靜態緩衝區,因此,它是線程不安全的,多線程同時訪問該靜態變量時,則會出現錯誤。本篇為基礎篇,在後續中將進一步剖析
拓展一個應用實例網絡上一個比較經典的例子是將字符串切分,存入結構體中,我整理了一下,看代碼
//https://tool.lu/coderunner/
//來源:技術讓夢想更偉大
//作者:李肖遙
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define INFO_MAX_SZ 80
typedef struct person{
char name[25];
char sex[10];
char age[4];
}Person;
int main()
{
int in=0;
int j;
char buffer[INFO_MAX_SZ]="Aob male 18,Bob male 19,Cob female 20";
char *p[20];
char *buf = buffer;
while((p[in]=strtok(buf,","))!=NULL)//先以,為分界符,將三個人的信息分開
{
buf=p[in];//調用strtok,先將子串先一一保存到字符串指針數組中,
while((p[in]=strtok(buf," "))!=NULL)//以空格為分界符
{
in++;
buf=NULL;
}
buf=NULL;
}
printf("Here we have %d strings\n", in);
for (j=0; j<in; j++)
{
//列印指針數組中保存的所有子串
printf(">%s<\n",p[j]);
}
return 0;
}運行結果如下
按照這個結果並沒有得到我們想要的結果,僅僅提取出了第一個人的信息。
那麼出現了什麼問題呢?
我們分析得到,其實在第一次循環中,strtok函數將第一個人信息後的這個逗號,改為了'\0,這時strtok內部的this指針指向的是逗號的後一個字符。
而在第一個循環結束後,函數第一個參數被設定為NULL,strtok將以this指針指向的位置作為分解起始位置,此時this指針指向的是'\0』,strtok對一個空串無法切分,返回NULL,所以得到上面的結果。
那麼我們怎麼解決這個問題呢?
我們看一下代碼來實現這個想要的結果
//https://tool.lu/coderunner/
//來源:技術讓夢想更偉大
//作者:李肖遙
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define INFO_MAX_SZ 80
typedef struct person{
char name[25];
char sex[10];
char age[4];
}Person;
int main()
{
int in=0;
int j;
char buffer[INFO_MAX_SZ]="Aob male 18,Bob male 19,Cob female 20";
char *p[20];
char *buf = buffer;
while ((p[in] = strtok(buf, " ,")) != NULL)//同時以逗號和空格為分界符
{
switch (in % 3)
{
case 0:
printf("第%d個人:Name!\n", in/3+1);
break;
case 1:
printf("第%d個人:Sex!\n", in/3+1);
break;
case 2:
printf("第%d個人:Age!\n", in/3+1);
break;
}
in++;
buf = NULL;
}
printf("Here we have %d strings\n", in);
for (j=0; j<in; j++)
{
printf(">%s<\n",p[j]);
}
return 0;
}最終運行的結果如下
額,這樣的代碼我看不下去了,要實現我們必須提前知道一個結構體中究竟包含了幾個數據成員,那麼有沒有合適的函數能夠代替strtok呢?
有的,它就是strtok_r。
Linux下的strtok_r函數描述strtok_r是linux平臺下的strtok函數的線程安全版。windows的string.h中並不包含它。要想使用這個函數,找到linux下的實現源碼,複製到你的程序中即,或者使用GNU C Library。
strtok_r函數是strtok函數的可重入版本。char **saveptr參數是一個指向char *的指針變量,用來在strtok_r內部保存切分時的上下文,以應對連續調用分解相同源字符串。
第一次調用strtok_r時,str參數必須指向待提取的字符串,saveptr參數的值可以忽略。連續調用時,str賦值為NULL,saveptr為上次調用後返回的值,不要修改。
一系列不同的字符串可能會同時連續調用strtok_r進行提取,要為不同的調用傳遞不同的saveptr參數。
strtok_r實際上就是將strtok內部隱式保存的this指針,以參數的形式與函數外部進行交互。由調用者進行傳遞、保存甚至是修改。需要調用者在連續切分相同源字符串時,除了將str參數賦值為NULL,還要傳遞上次切分時保存下的saveptr。
函數原型如下char *strtok_r(char *str, const char *delim, char **saveptr);
源碼/* Parse S into tokens separated by characters in DELIM.
If S is NULL, the saved pointer in SAVE_PTR is used as
the next starting point. For example:
char s[] = "-abc-=-def";
char *sp;
x = strtok_r(s, "-", &sp); // x = "abc", sp = "=-def"
x = strtok_r(NULL, "-=", &sp); // x = "def", sp = NULL
x = strtok_r(NULL, "=", &sp); // x = NULL
// s = "abc\0-def\0"
*/
char *strtok_r(char *s, const char *delim, char **save_ptr) {
char *token;
/*判斷參數s是否為NULL,如果是NULL就以傳遞進來的save_ptr作為起始分解位置;若不是NULL,則以s開始切分*/
if (s == NULL) s = *save_ptr;
/* Scan leading delimiters. */
s += strspn(s, delim);
/*判斷當前待分解的位置是否為'\0',若是則返回NULL(聯繫到(一)中所說對返回值為NULL的解釋);不是則繼續。*/
if (*s == '\0')
return NULL;
/* Find the end of the token. */
token = s;
s = strpbrk(token, delim);
if (s == NULL)
/* This token finishes the string. */
*save_ptr = strchr(token, '\0');
else {
/* Terminate the token and make *SAVE_PTR point past it. */
*s = '\0';
*save_ptr = s + 1;
}
return token;
}
實現以上實例調用strtok_r的代碼比調用strtok的代碼多了兩個指針,outer_ptr和inner_ptr。outer_ptr用於標記每個人的提取位置,即外循環;inner_ptr用於標記每個人內部每項信息的提取位置,即內循環。
strtok_r將原內部指針顯示化,提供了saveptr這個參數。增加了函數的靈活性和安全性。
//https://tool.lu/coderunner/
//來源:技術讓夢想更偉大
//作者:李肖遙
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define INFO_MAX_SZ 80
typedef struct person{
char name[25];
char sex[10];
char age[4];
}Person;
int main()
{
int in=0;
int j;
char buffer[INFO_MAX_SZ]="Aob male 18,Bob male 19,Cob female 20";
char *p[20];
char *buf=buffer;
char *outer_ptr=NULL;
char *inner_ptr=NULL;
while((p[in] = strtok_r(buf, ",", &outer_ptr))!=NULL)
{
buf=p[in];
while((p[in]=strtok_r(buf, " ", &inner_ptr))!=NULL)
{
in++;
buf=NULL;
}
buf=NULL;
}
printf("Here we have %d strings\n",in);
for (j=0; j<in; j++)
{
printf(">%s<\n",p[j]);
}
return 0;
}編譯結果如下
注意事項該函數也會破壞帶分解字符串的完整性,但是其將剩餘的字符串保存在saveptr變量中,保證了安全性。
Windows下的strtok_s函數描述strtok_s是windows下的一個分割字符串安全函數,
原型char *strtok_s( char *strToken, const char *strDelimit, char **buf);char * strtok_s(char * restrict str,rsize_t * restrict strmax,const char * restrict delim,char ** restrict ptr);在由str指向的以空字符結尾的字節字符串中查找下一個標記。分隔符字符由delim指向的以空字符結尾的字節字符串標識。
該函數被設計為被稱為倍數時間以從相同的字符串獲得連續的令牌。
這裡大家可以參考,我在這裡不多講了。
https://cloud.tencent.com/developer/section/1009645
巨人的肩膀https://blog.csdn.net/bobyangsmile/article/details/38420985
https://www.runoob.com/cprogramming/c-function-strtok.html
最後這裡先簡單介紹下這幾個函數的基本使用以及一些優缺點等等,後續會根據自己踩的坑來解讀strtok()的隱含特性,下一期,我們再見!