目錄
使用strtok()完成分割
使用strsep()完成分割
使用strtok_r()完成分割
實作字元串分割
前言
最近遇到了一個字元串分割的問題,在C++的标準庫裡面沒有字元分割函數split()。我想這個問題今後可能還會再遇見,是以使用C/C++中的字元串分割方法完成相應的功能。
需求:對一個字元串進行分割,然後将分割後的資料存起來,想用的時候像用數組那樣拿就行了。是以在完成分割後,需要使用vector将相應的資料儲存起來。
使用strtok()完成分割
char * strtok ( char * str,const char * delim );
其中str是需要分割的字元串。delim是分隔符辨別。
在<cstring> <string.h>中定義
該函數被設計為多次調用,以從同一字元串中擷取連續的token。
如果 str != NULL,該調用被視為 strtok 對此特定字元串的第一個調用。該函數搜尋中不包含的第一個字元 delim。
如果找不到這樣的字元,則根本沒有标記str,并且該函數傳回空指針。
如果找到了這樣的字元,則這是token的開頭。然後,函數從該點開始搜尋包含delim的第一個字元。
如果未找到這樣的字元,str則隻有一個标記,以後調用strtok将傳回一個空指針
如果找到了這樣的字元,則将其替換為空字元'\0',并将指向下一個字元的指針存儲在靜态位置中,以供後續調用。
然後,該函數将指針傳回到token的開頭
如果str == NULL,則該調用将被視為對後續調用strtok:該函數從上次調用中保留的位置繼續。行為與先前存儲的指針作為str傳遞的行為相同。
#include <cstring>
#include <iostream>
int main()
{
char input[100] = "A, bird, came, down, the, walk";
char *token = std::strtok(input, ", ");
while (token != NULL) {
std::cout << token << '\n';
token = std::strtok(NULL, ", ");
}
}
筆記:
(1)此函數具有破壞性:它将 '\0' 字元寫入 str 字元串的元素中。
(2)每次對該函數的調用都會修改一個靜态變量:不是線程安全的。
(3)與大多數其他strtok不同,每個後續标記中的定界符可以不同,甚至可以取決于先前标記的内容。
(4)其中delim可以包含多個分隔符,strtok函數會将str内容按分隔符分割。如果對資料格式有要求,需要注意這一點。
源自百度百科:
下面的說明摘自于Linux核心2.6.29,說明了這個函數已經不再使用,由速度更快的strsep()代替。
使用strsep()完成分割
strsep函數用于分解字元串為一組字元串。其原型為:
char *strsep(char **s, const char *delim);
存在的問題
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//char *strsep(char **str, const char *delim);
int main(void) {
char source[] = "hello, world! welcome to china!";
char delim[] = " ,!";
char *input = strdup(source);
char *token = strsep(&input, delim);
while (token != NULL) {
printf(token);
printf("-");
token = strsep(&input, delim);
printf("\n");
}
free(input);
return 0;
}
為什麼使用strsep()分割字元串後會多出幾個" - " ?
原因是:如果輸入的串的有連續的多個字元屬于delim,strtok會傳回NULL,而strsep會傳回空串 ""。如果想用strsep函數分割字元串必須進行傳回值是否是空串的判斷。
使用strtok_r()完成分割
strtok是一個線程不安全的函數,因為它使用了靜态配置設定的空間來存儲被分割的字元串位置,線程安全的函數是strtok_r()。
char *strtok_r(char *str, const char *delim, char **saveptr);
strtok_r函數是strtok函數的可重入版本。str為要分解的字元串,delim為分隔符字元串。char **saveptr參數是一個指向char *的指針變量,用來在strtok_r内部儲存切分時的上下文,以應對連續調用分解相同源字元串。
第一次調用strtok_r時,str參數必須指向待提取的字元串,saveptr參數的值可以忽略。連續調用時,str指派為NULL,saveptr為上次調用後傳回的值,不要修改。一系列不同的字元串可能會同時連續調用strtok_r進行提取,要為不同的調用傳遞不同的saveptr參數。
strtok_r實際上就是将strtok内部隐式儲存的this指針,以參數的形式與函數外部進行互動。由調用者進行傳遞、儲存甚至是修改。需要調用者在連續切分相同源字元串時,除了将str參數指派為NULL,還要傳遞上次切分時儲存下的saveptr。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//char *strtok_r(char *str, const char *delim, char **saveptr);
int main()
{
char *Src = "Can I help you";
char Buffer[100];
char *delim = " ";
char *saveptr = NULL;
char *input = NULL;
strncpy(Buffer, Src, sizeof(Buffer) - 1);
input = Buffer;
while(NULL != ( input = strtok_r( input, delim, &saveptr)))
{
printf("input[%s] saveptr[%s]\n", input,saveptr);
input = NULL;
}
return 0;
}
實作字元串分割
以上函數都會改變源字元串,是以在完成split函數功能時,要先複制一個副本,對副本進行分割後傳回相應的值才是正确的思路。
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
namespace tyler
{
//strtok版本
//char *strtok(char s[], const char *delim);
vector <string> stringsplit(const string &str, const char *delim)
{
vector <std::string> strlist;
int size = str.size();
char *input = new char[size+1];
strcpy(input, str.c_str());
char *token = std::strtok(input, delim);
while (token != NULL) {
strlist.push_back(token);
token = std::strtok(NULL, delim);
}
delete []input;
return strlist;
}
//另一種寫法
//for(token = strtok(input, delim); token != NULL; token = strtok(NULL, delim))
//{
//}
//strsep版本
//char *strsep(char **stringp, const char *delim);
vector <string> stringsplit1(const string &str, const char *delim)
{
vector <std::string> strlist;
char *p = const_cast<char*>(str.c_str());
char *input = strdup(p); //strdup()在内部調用了malloc()為變量配置設定記憶體,不需要使用傳回的字元串時,需要用free()釋放相應的記憶體空間,否則會造成記憶體洩漏。
char *token = strsep(&input, delim);
while (token != NULL) {
if(strcmp(token, "") == 0)
token = strsep(&input, delim);
else
{
strlist.push_back(token);
token = strsep(&input, delim);
}
}
free(input);
return strlist;
}
//strtok_r版本
//char *strtok_r(char *str, const char *delim, char **saveptr);
vector <string> stringsplit2(const string &str, const char *delim)
{
vector <std::string> strlist;
char *saveptr = NULL;
char *p = const_cast<char*>(str.c_str());
char *input = strdup(p); //strdup()在内部調用了malloc()為變量配置設定記憶體,不需要使用傳回的字元串時,需要用free()釋放相應的記憶體空間,否則會造成記憶體洩漏。
while(NULL != ( input = strtok_r( input, delim, &saveptr) ))
{
//printf("input[%s] saveptr[%s]\n",input,saveptr);
strlist.push_back(input);
input = NULL;
}
free(input);
return strlist;
}
}
int main()
{
string str = "hello, world! welcome to china!";
cout << str <<endl;
vector<string> asd = tyler::stringsplit2(str, " ,!");
cout << "StringList:";
for(int i = 0; i < asd.size(); ++i)
{
cout << "[" << asd[i] << "]";
}
cout <<endl;
cout << "String:" << str <<endl;
return 0;
}
參考:
https://baike.baidu.com/item/strtok_r
https://blog.csdn.net/yafeng_jiang/article/details/7109285
https://www.cnblogs.com/carsonzhu/p/5859552.html
https://blog.csdn.net/zhouzhenhe2008/article/details/74011399