分割字串(C++)
方案1:
利用"IO流"的概念,即C++中的stream,我們都用過C++中std::iostream
中的std::istream
與std::ostream
如果你接觸過網路程式設計(Socket程式設計),可能會對這個流的概念更加清楚。在C++中,我們常用的cin其實是一個istream物件,從標準輸入讀取資料,cout是一個ostream物件,用於向標準輸出寫入資料。IO物件無拷貝或賦值。
相應的,我們可以使用std::istream_iterator
來作為關聯輸入流的迭代器:
std::string text = "Let me split this into words"; std::istringstream iss(text); std::vector<std::string> results(std::istream_iterator<std::string>{iss}, std::istream_iterator<std::string>());
利用stream_iterator的方法,由於是stream,我們甚至可以利用fstream
對除了字串之外的輸入進行分割,雖然可以分割,但是他只能識別出空格。
針對這個,我們希望可以過載>>
,使得滿足原有功能的基礎上還能滿足我們需要的一些操作:
std::istream& operator>>(std::istream& is, std::string& output)
{
// ...do operations we need...
}
最後的形式需要變為:
std::istream& operator>>(std::istream& is, SELF_STRING(public std:string)& output)
其中的SELF_STRING是我們可以把除了空格之外的字元引入,從而可以分割的型別。在這裡提出一個可能受爭議的解決方式:
構造一個新的類wordDelimitedBy
去繼承std:string,然後對於這個新的類我們可以模板化使其適應於多種分隔符:
template<char delimiter> class WordDelimitedBy : public std::string {}; template<char delimiter> std::istream& operator>>(std::istream& is, WordDelimitedBy<delimiter>& output) { std::getline(is, output, delimiter); return is; } int main() { std::string text = "Let,me,split,this,into,words"; std::istringstream iss(text); std::vector<std::string> results(std::istream_iterator<WordDelimitedBy<','>>{iss}, std::istream_iterator<WordDelimitedBy<','>>()); for (int i = 0; i < results.size(); ++i) std::cout << results[i] << std::endl; system("pause"); }
然後為什麼說會受爭議的方式,因為std::string
並沒有virtual destructor
,即出現了:當一個派生類物件通過使用一個基類指標刪除,而這個基類有一個非虛的解構函式,則結果是未定義的。執行時比較有代表性的後果是物件的派生 部分不會被銷燬。然而,基類部分很可能已被銷燬,這就導致了一個古怪的“部分析構”物件,這是一個洩漏資源。在C++中並沒有Java的GC機制。但是銅鼓哦程式碼我們也會發現,我們並沒有例項化WordDelimitedBy
,而是一直使用著他的型別和模板化,但是我們也並沒有足夠的手段去阻止這種例項化的發生;所以嚴格來說,這種方式雖然比stream的iterator方式快並支援多種分隔符,但是存在漏洞的。
此外,我們還以可以利用std::getline
的一個特性:
std::vector<std::string> split(const std::string& s, char delimiter)
{
std::vector<std::string> tokens;
std::string token;
std::istringstream tokenStream(s);
while (std::getline(tokenStream, token, delimiter))
{
tokens.push_back(token);
}
return tokens;
}
int main()
{
std::string text = "Let,me,split,this,into,words";
std::vector<std::string> results=split(text, ',');
for (int i = 0; i < results.size(); ++i)
std::cout << results[i] << std::endl;
system("pause");
}
方案2:
我們可以利用boost庫中的split函式,安裝boost庫可以參照這一篇教程.
#include <boost/algorithm/string.hpp>
std::string text = "Let me split this into words";
std::vector<std::string> results;
boost::split(results, text, [](char c){return c == ' ';});
注意到這裡的split函式第三個引數實際上是一個lambda表示式,用來判斷分隔符是不是一個空格。原理實際上也非常簡單,就是執行多次find_if
直到到string的結尾。
方案3:
第三種方案實際上涉及到Ranges,這是作者Eric Niebler 的庫地址,這個應該會在C++20的標準中被納入。
用法是這樣的:
std::string text = "Let me split this into words";
auto splitText = text | view::split(' ');
同樣我們在庫的test中可以看見相應的程式碼 rangeV3
其中我們方案1是最中規中矩的,當然如果ranges被納入了C++20,會方便許多。