1. 程式人生 > >分割字串(C++)

分割字串(C++)

方案1:

利用"IO流"的概念,即C++中的stream,我們都用過C++中std::iostream中的std::istreamstd::ostream

image.png

如果你接觸過網路程式設計(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,會方便許多。