在 C++ STL 中,沒有線程的分割字符串的函數,但是該函數的又是很常用的。所以,本文介紹幾種字符串分割方法。
使用 C 中的strtok
函數
C 中的 strtok
函數可以對 C 風格的字符串進行分割,strtok_r
是strtok
的線程安全版本。
參考https://linux.die.net/man/3/strtok_r 和 關於函數strtok和strtok_r的使用要點和實現原理。
#include <string.h>
// str: 要分割的字符串
// delim: 作爲分割點的分隔符
// saveptr: 線程安全版本中,用於保存分割點位置的指針
char* strtok(char* str, const char* delim);
char* strtok_r(char* str, const char* delim, char** saveptr);
當str
不爲NULL
,則從頭開始搜索第一個合法的分隔符,然後使用'\0
'替換分隔符,並使用靜態變量(strtok
版本)或者傳入的變量(strtok_t
版本)保存分隔符的位置,最後返回str
。因爲 C 風格的字符串是以'\0
'結尾的,所以調用函數後就能得到分割的第一個字符串。
往後,如果需要繼續對str
進行分割,不需要再傳入str
,只需要傳入NULL
即可,其他參數和第一次調用一樣。
split
函數的實現:
// str: 要分割的字符串
// result: 保存分割結果的字符串數組
// delim: 分隔字符串
void split(const std::string& str,
std::vector<std::string>& tokens,
const std::string delim = " ") {
tokens.clear();
char* buffer = new char[str.size() + 1];
std::strcpy(buffer, str.c_str());
char* tmp;
char* p = strtok_r(buffer, delim.c_str(), &tmp); // 第一次分割
do {
tokens.push_back(p); // 如果 p 爲 nullptr,則將整個字符串作爲結果
} while ((p = strtok_r(nullptr, delim.c_str(), &tmp)) != nullptr);
// strtok_r 爲 strtok 的線程安全版本。
}
注意:strtok
和strtok_r
會修改傳進去的字符串str
,所以調用它們之前,需要做好備份。
C++ 中的流
使用istringstream
配合getline
,可以分割字符串。不過有一個限制:getline
接受的分隔符只能是char
。
split
函數的實現:
void split(const std::string& str,
std::vector<std::string>& tokens,
const char delim = ' ') {
tokens.clear();
std::istringstream iss(str);
std::string tmp;
while (std::getline(iss, tmp, delim)) {
if (tmp != "") {
// 如果兩個分隔符相鄰,則 tmp == "",忽略。
tokens.emplace_back(std::move(tmp));
}
}
}
C++ 流迭代器
如果分隔符是流所定義的空白符,則有更簡潔的實現。流迭代器istream_iterator
每次迭代都分割出一個字符串(以空白符爲分隔符)。
split
函數的實現:
// str: 要分割的字符串
// tokens: 保存分割結果的字符串數組
void split(const std::string& str,
std::vector<std::string>& tokens) {
tokens.clear();
std::istringstream iss(str);
std::copy(std::istream_iterator<std::string>(iss),
std::istream_iterator<std::string>(),
std::back_inserter(tokens));
}
find_first_not_of
、find_first_of
使用find_first_not_of
查找到分割字符串的起點start
,使用find_first_of
查找到分隔符的位置position
,則[start, position)
就是分割到一個字符串。
重複上述過程,知道position == std::string::npos && start == std::string::npos
,則分割結束。
split
函數的實現:
void split(const std::string& str,
std::vector<std::string>& tokens,
const std::string delim = " ") {
tokens.clear();
auto start = str.find_first_not_of(delim, 0); // 分割到的字符串的第一個字符
auto position = str.find_first_of(delim, start); // 分隔符的位置
while (position != std::string::npos || start != std::string::npos) {
// [start, position) 爲分割下來的字符串
tokens.emplace_back(std::move(str.substr(start, position - start)));
start = str.find_first_not_of(delim, position);
position = str.find_first_of(delim, start);
}
}
更多
還可以使用 C++ 11 正則表達式庫或者 C++20 提供了ranges
,有專門的split view
,只要寫str | split(delim)
就可以分割字符串。詳細見C++ 的 string 爲什麼不提供 split 函數? - 林暗草驚風的回答 - 知乎。