欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C++字符串分割(一) 個人總結向,基於strtok()的實作,面向少打一點代碼

程序员文章站 2022-07-08 09:42:21
《前言》 中有一函式 char * strtok ( char * str, const char * delimiters ); 常用來處理C風格陣列的分割問題。其中str是待分割字符串,delimiters 是分割符字符串,舉列說現在有 str[] = "ab,cd,efg,. ......

《前言》

<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, 其他都是自己(或憑記憶)打出來的,

沒有本人同意,不得以任何形式轉載。雖然很爛:)