C++字符串分割(一) 個人總結向,基於strtok()的實作,面向少打一點代碼
《前言》
<cstring>中有一函式
char * strtok ( char * str, const char * delimiters );
常用來處理C風格陣列的分割問題。其中str是待分割字符串,delimiters 是分割符字符串,舉列說現在有
str[] = "ab,cd,efg,.hi,zyx";
delimiters[] = ",.!?";
strtok 可以把它分割成
"ab", "cd", "efg", "zyx"。
其實這個算是剛入門C語言必學的一個函式,為了上實驗課可以快速解決簡單的分割問題,用迴圈手寫太耗時間,複雜一點的可以用sscanf, sprintf (這篇不討論這個)。但學了C++知道了一堆更強大的工具,很少去用原來C的函式了(變成用string::find()或string::find_first_of()來分割字符串),直到最近寫代碼時被同學提醒了一下,結果發現strtok在c++也許更好用(其實就是可以少打一點代碼)(逃)。
現在我們來討論它的用法。
《正文》
一. 作者本人平時分割字符串的方法
直接用istream& getline (istream& is, string& str, char delim);
1 stringstream ss("abc,de,fgh,ifkl,.ef,f"); // #include <sstream> 2 char delim = ','; 3 vector<string> typeZero; 4 string temp; 5 while (getline(ss, temp, delim)) { 6 typeZero.push_back(temp); // 分割不完全,第五個子串是".ef" 7 } 8 for (auto &it: typeZero) { 9 cout << it << endl; //輸出 10 }
其實std::getline() 是有第三個參數的,指定分隔符,但要注意的是,它是char 不是char* ,只能應付單個分隔符的情況。這時可以考慮用C風格的strtok
1 char *p = strtok(str, delim); 2 while (p != NULL) { 3 printf("%s\n", p); //cout << p << endl; 用cout輸出也不成問題 4 p = strtok(NULL, delim); 5 }
/*參考了cppreference*/
每一次迴圈都會把第一個分割符變成"\0",所以%s輸出不成問題,若
str[] = "ab,cd,efg,.hi,zyx";
輸出結果就是
"ab\n", "cd\n", "efg\n", "zyx\n"
不過我們通常都希望把它存下來,我們可以用一個char的二維陣列,如
char strArr[100][10];
把它存起來,但缺點也很明顯,你必須保證分割出來的子串一定少於10。或者用char指針的一維陣列直接指到原字串中各子串的開頭,如
char *strArr[100];
感覺不錯,但畢竟是淺拷貝,原字串一定要保留不能改動,想要深拷貝的話要new / malloc 一個空間,感覺更麻煩了,要先判斷子串長度,這種時候我都直接放棄用strtok, 轉用 sscanf 和 sprintf 的混合雙打 (結果我還是拿出來講了)
1 #include <iostream> 2 #include <cstdlib> 3 using namespace std; 4 int main() { 5 char context[] = "abc,de,????fgh,ifkl,.ef,f"; //如果開頭或最後出現分割字符,會出BUG,可能有辦法解決,歡迎提出 6 char delim[] = "%[^,.!?]%*[,.!?]%n"; //%[^,.!?] 碰到分割符就停, %*[,.!?] 之後把分割符吃掉, %n 目前經過的字符數 7 char temp[sizeof(context)]; 8 char *strArr[100]; 9 char *p = context; 10 int cnt = 0; 11 for (int bit = 0; ~sscanf(p += bit, delim, temp, &bit); cnt++){ //相當於sscanf(p += bit, "%[^,.!?]%*[,.!?]%n", temp, &bit); 12 strArr[cnt] = (char*) malloc(bit + 1); //bit的值是子串長加分割符數,所以size會比子串多一點,有辦法解決,這邊交給讀者 13 sprintf(strArr[cnt], "%s", temp); 14 } 15 for (int i = 0; i < cnt; i++) { 16 printf("%s\n", strArr[i]); 17 }
18 }
核心代碼只有11到14三行,但這代碼段比較難理解,而且用起來非常不安全,一不小心就會出bug。現在也不想討論 %[] %*[] 的用法,若覺得不太優的請跳過。
二. 用C++ string 類直接save
1 int main() { 2 char context[] = "abc,de,fgh,ifkl,.ef,f"; 3 char delim[] = ",.!?"; 4 5 char *typeOne[100]; 6 vector<string> typeTwo; 7 8 int cnt = 0; 9 char *p = strtok(context, delim); 10 while (p != NULL) { 11 typeOne[cnt++] = p; //typeOne 會被下面的p[0] = toupper(p[0])改掉。 12 typeTwo.push_back(p); //typeTwo 是深拷貝,不會被改掉,而且很簡單。 13 p[0] = toupper(p[0]); 14 p = strtok(NULL, delim); 15 } 16 }
typeTwo 並不會因p[0] = toupper(p[0]); 而被改掉,實現了看着更舒爽的深拷貝。(逃)
其實這篇文章的意義就是這個vector<string>::push_back(char*); 一直都沒有發現還有這種操作。把上面的所有問題都解決了(我猜其實速度會很慢)。c++ stl *~~
三. 利用模版,分割不同的型別
1 template <class T> 2 vector<T> split(char *str, char *delim) { 3 vector<T> data; 4 T temp; 5 stringstream ss; //#include <sstream> 6 char *p = strtok(str, delim); 7 while (p != NULL) { 8 ss << p; 9 ss >> temp; 10 ss.clear(); 11 data.push_back(temp); 12 p = strtok(NULL, delim); 13 } 14 return data; 15 }
實測過int 和 string 都能用。
《後記,總結》
c++ string 可以直接用c風格字串賦值可能大家都知道,雖然是比較簡單的東西,但還真沒想過把strtok混在一起用,感覺蠻神奇的。神奇到直接讓我萌生出打Blog的念頭。
第一次打博客,重新看一遍感覺新手會難理解(因為沒圖又沒有過多解釋),一般人會嫌太簡單(因為重點就只有vector<string>::push_back(char*))。這篇文章純粹拿來試水用, 歡迎大家來吐嘈。。。
代碼都是用Dev C++ (TDM-GCC 4.9.2)測的。 畢竟在visual studio 上用 <cstring> 感覺就是在給自己找麻煩一樣。
全文只參考了cppreference, 其他都是自己(或憑記憶)打出來的,
沒有本人同意,不得以任何形式轉載。雖然很爛:)
上一篇: 落枕是怎么回事 落枕的原因及预防方法
下一篇: php评论使用websocket