Chromium base庫分割字串SplitString
前一段時間在工作過程中遇到一個場景需要將http response中的request header中的cookie欄位取出並進行解析,但是手頭沒有解析cookie的工具類,同時cookie的表現就是個字串,於是想到手動分割
但是在C++的標準庫中,並沒有提供類似split的函式,在有些時候可能會很不方便,今天就看看google大佬是如何實現字串的分割的。
在chromium的base庫中有提供和字串相關的函式,在base/strings/string_split.h和base/strings/string_split.cc中定義了SplitString函式用於分割std::string型別的字串,下圖是大體流程圖
std::vector<std::string> SplitString(StringPiece input, StringPiece separators, WhitespaceHandling whitespace, SplitResult result_type) { if (separators.size() == 1) { return SplitStringT<std::string, std::string, char>( input, separators[0], whitespace, result_type); } return SplitStringT<std::string, std::string, StringPiece>( input, separators, whitespace, result_type); }
其中StringPiece
是google定義的一種字串型別,是對std::string
的一種封裝,這裡就不再多說,可以直接看成std::string
函式這裡傳入的四個引數分別是輸入字串,分割符,遇到空格處理(保留,跳過),結果型別(保留空值,不保留)
可以看到google根據傳入的分割符的長度做了兩種處理方式,說是做了兩種處理方式,但其實就是講分割符一個看成單個字元char
,一個看成std::string
字串而已,這與SplitStringT
模板的具體實現有關。下面重頭戲來了,看下SplitStringT
是如何實現的
template<typename Str, typename OutputStringType, typename DelimiterType> static std::vector<OutputStringType> SplitStringT( BasicStringPiece<Str> str, DelimiterType delimiter, WhitespaceHandling whitespace, SplitResult result_type) { std::vector<OutputStringType> result; if (str.empty()) return result; size_t start = 0; while (start != Str::npos) { size_t end = FindFirstOf(str, delimiter, start); BasicStringPiece<Str> piece; if (end == Str::npos) { piece = str.substr(start); start = Str::npos; } else { piece = str.substr(start, end - start); start = end + 1; } if (whitespace == TRIM_WHITESPACE) piece = TrimString(piece, WhitespaceForType<Str>(), TRIM_ALL); if (result_type == SPLIT_WANT_ALL || !piece.empty()) result.push_back(PieceToOutputType<Str, OutputStringType>(piece)); } return result; }
模板引數的三個定義分別是<傳入被分割字串的型別,輸出vector的模板型別,分界符型別>,函式的四個引數和上面說的相同,其中第三個第四個引數主要是對空值和空格的取捨。
Str::npos
指的是size_t的最大值,也就是說在這個函式中為了避免字串過長導致函式內部使用的start
和end
發生溢位。
最基本就是迴圈遍歷原始字串,主要使用到了FindFirstOf
函式,函式FindFirstOf
的實現如下(只看分界符是單個字元char
的情況)
size_t FindFirstOf(StringPiece piece, char c, size_t pos) {
return piece.find(c, pos);
}
這裡的find
函式和std::string
的find
函式功能一致,從當前pos
位置開始想後查詢c
字元,找到第一個並返回其所在位置。如果找到了就更新end
的值,然後取start
和end
之間的子字串,更新start的值。如果找到下一個分界符了,這個函式返回result
就結束了。
剩下最後的部分就是對空格和空值的取捨,下面是取捨部分的程式碼,取自SplitStringT
函式
if (whitespace == TRIM_WHITESPACE)
piece = TrimString(piece, WhitespaceForType<Str>(), TRIM_ALL);
if (result_type == SPLIT_WANT_ALL || !piece.empty())
result.push_back(PieceToOutputType<Str, OutputStringType>(piece));
空值取捨就是一個判斷,這裡不再描述,就只看空格取捨,WhitespaceForType<Str>()
主要是提供一個模板空格,根據傳入的Str型別不同空格也有可能不同,而TRIM_ALL
的主要作用如下
enum TrimPositions {
TRIM_NONE = 0,
TRIM_LEADING = 1 << 0,
TRIM_TRAILING = 1 << 1,
TRIM_ALL = TRIM_LEADING | TRIM_TRAILING,
};
用於區分空格型別,頭部空格和尾部空格。預設是TRIM_ALL
全部。下面是TrimString
函式的實現
StringPiece TrimString(StringPiece input,
StringPiece trim_chars,
TrimPositions positions) {
return TrimStringPieceT(input, trim_chars, positions);
}
template<typename Str>
BasicStringPiece<Str> TrimStringPieceT(BasicStringPiece<Str> input,
BasicStringPiece<Str> trim_chars,
TrimPositions positions) {
size_t begin = (positions & TRIM_LEADING) ?
input.find_first_not_of(trim_chars) : 0;
size_t end = (positions & TRIM_TRAILING) ?
input.find_last_not_of(trim_chars) + 1 : input.size();
return input.substr(begin, end - begin);
}
用的函式也和std::string
的成員函式功能一致,是很簡單的去除空格的方式。
附錄
我略微整理了一下一個VS可直接編譯執行的版本(幾乎沒啥改動就是了)
#include <iostream>
#include <vector>
#include <string>
enum TrimPositions {
TRIM_NONE = 0,
TRIM_LEADING = 1 << 0,
TRIM_TRAILING = 1 << 1,
TRIM_ALL = TRIM_LEADING | TRIM_TRAILING,
};
size_t FindFirstOf(std::string piece, std::string c, size_t pos) {
return piece.find(c, pos);
}
std::string TrimString(std::string input,
TrimPositions positions) {
size_t begin = (positions & TRIM_LEADING) ?
input.find_first_not_of(" ") : 0;
size_t end = (positions & TRIM_TRAILING) ?
input.find_last_not_of(" ") + 1 : input.size();
return input.substr(begin, end - begin);
}
std::vector<std::string> SplitString(std::string str,
std::string c,
bool skip_whitespace,
bool skip_empty)
{
std::vector<std::string> result;
if (str.empty())
return result;
size_t start = 0;
while (start != std::string::npos) {
size_t end = FindFirstOf(str, c, start);
std::string piece;
if (end == std::string::npos) {
piece = str.substr(start);
start = std::string::npos;
}
else {
piece = str.substr(start, end - start);
start = end + 1;
}
if (skip_whitespace)
piece = TrimString(piece, TRIM_NONE);
if (!skip_empty || !piece.empty())
result.push_back(piece);
}
return result;
}
int main()
{
std::vector<std::string> result = SplitString("url=https://www.baidu.com", ".", true, false);
return 0;
}