C++ 分割字符串函數 split

在 C++ STL 中,沒有線程的分割字符串的函數,但是該函數的又是很常用的。所以,本文介紹幾種字符串分割方法。

使用 C 中的strtok函數

C 中的 strtok函數可以對 C 風格的字符串進行分割,strtok_rstrtok的線程安全版本。

參考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 的線程安全版本。
}

注意strtokstrtok_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_offind_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 函數? - 林暗草驚風的回答 - 知乎

源碼

Github

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章