使用字符串分隔符(标准C++)在C++中解析(拆分)一个字符串

657

我正在使用以下代码在C++中解析字符串:

using namespace std;

string parsed,input="text to be parsed";
stringstream input_stringstream(input);

if (getline(input_stringstream,parsed,' '))
{
     // do some processing.
}

使用单个字符作为分隔符是可以的。但如果我想用一个字符串作为分隔符怎么办。

例如:我想要分割:

scott>=tiger

使用>=作为分隔符,以便我可以获取scott和tiger。


7
请跳转到链接 https://stackoverflow.blog/2019/10/11/c-creator-bjarne-stroustrup-answers-our-top-five-c-questions 并滚动至第五个问题。 - Wais Kamal
请参考此问题,使用C++20实现读取文件和分割字符串的功能。 - AmirSalar
1
@WaisKamal:你本可以直接链接到 https://dev59.com/k3VC5IYBdhLWcg3wnCj6。 - Thomas Weller
35个回答

5
我会使用boost::tokenizer。以下是解释如何创建一个适当的分词器函数的文档:http://www.boost.org/doc/libs/1_52_0/libs/tokenizer/tokenizerfunction.htm 以下是适用于您情况的示例代码。
struct my_tokenizer_func
{
    template<typename It>
    bool operator()(It& next, It end, std::string & tok)
    {
        if (next == end)
            return false;
        char const * del = ">=";
        auto pos = std::search(next, end, del, del + 2);
        tok.assign(next, pos);
        next = pos;
        if (next != end)
            std::advance(next, 2);
        return true;
    }

    void reset() {}
};

int main()
{
    std::string to_be_parsed = "1) one>=2) two>=3) three>=4) four";
    for (auto i : boost::tokenizer<my_tokenizer_func>(to_be_parsed))
        std::cout << i << '\n';
}

7
谢谢。但我只想要标准C++,不需要第三方库。 - TheCrazyProgrammer
@TheCrazyProgrammer:好的,当我看到“标准C ++”时,我认为这意味着没有非标准扩展,而不是不能使用符合标准的第三方库。 - Benjamin Lindley

5
一个非常简单/天真的方法:
vector<string> words_seperate(string s){
    vector<string> ans;
    string w="";
    for(auto i:s){
        if(i==' '){
           ans.push_back(w);
           w="";
        }
        else{
           w+=i;
        }
    }
    ans.push_back(w);
    return ans;
}

或者你可以使用boost库的split函数:

vector<string> result; 
boost::split(result, input, boost::is_any_of("\t"));

或者您可以尝试使用TOKEN或strtok函数:

char str[] = "DELIMIT-ME-C++"; 
char *token = strtok(str, "-"); 
while (token) 
{ 
    cout<<token; 
    token = strtok(NULL, "-"); 
} 

或者你可以这样做:

char split_with=' ';
vector<string> words;
string token; 
stringstream ss(our_string);
while(getline(ss , token , split_with)) words.push_back(token);

5
这应该完美适用于字符串(或单个字符)分隔符。不要忘记包含#include <sstream>
std::string input = "Alfa=,+Bravo=,+Charlie=,+Delta";
std::string delimiter = "=,+"; 
std::istringstream ss(input);
std::string token;
std::string::iterator it;

while(std::getline(ss, token, *(it = delimiter.begin()))) {
    std::cout << token << std::endl; // Token is extracted using '='
    it++;
    // Skip the rest of delimiter if exists ",+"
    while(it != delimiter.end() and ss.peek() == *(it)) { 
        it++; ss.get(); 
    }
}

第一个while循环使用字符串定界符的第一个字符提取标记。第二个while循环跳过余下的定界符,并停在下一个标记的开头。


这是不正确的。如果输入被修改如下,它会使用第一个=分割,但实际上不应该这样做:std::string input = "Alfa=,+Bravo=,+Charlie=,+Delta=Echo"; - Amitoj
@Amitoj 很好的发现。我修改了我的答案,甚至能够处理带有错误分隔符的输入。 - hmofrad

5

以下是我的见解。它处理了边缘情况并带有可选参数,以从结果中删除空条目。

bool endsWith(const std::string& s, const std::string& suffix)
{
    return s.size() >= suffix.size() &&
           s.substr(s.size() - suffix.size()) == suffix;
}

std::vector<std::string> split(const std::string& s, const std::string& delimiter, const bool removeEmptyEntries = false)
{
    std::vector<std::string> tokens;

    for (size_t start = 0, end; start < s.length(); start = end + delimiter.length())
    {
         size_t position = s.find(delimiter, start);
         end = position != std::string::npos ? position : s.length();

         std::string token = s.substr(start, end - start);
         if (!removeEmptyEntries || !token.empty())
         {
             tokens.push_back(token);
         }
    }

    if (!removeEmptyEntries &&
        (s.empty() || endsWith(s, delimiter)))
    {
        tokens.push_back("");
    }

    return tokens;
}

示例

split("a-b-c", "-"); // [3]("a","b","c")

split("a--c", "-"); // [3]("a","","c")

split("-b-", "-"); // [3]("","b","")

split("--c--", "-"); // [5]("","","c","","")

split("--c--", "-", true); // [1]("c")

split("a", "-"); // [1]("a")

split("", "-"); // [1]("")

split("", "-", true); // [0]()

C++20 在 std::string 中新增了 ends_with 成员函数。https://en.cppreference.com/w/cpp/string/basic_string/ends_with - Steve Ward

3

这与其他答案类似,但使用的是 string_view。因此,这些只是原始字符串的视图。类似于C++20示例。虽然这将是一个C++17的示例。(编辑以跳过空匹配)

#include <algorithm>
#include <iostream>
#include <string_view>
#include <vector>
std::vector<std::string_view> split(std::string_view buffer,
                                    const std::string_view delimeter = " ") {
  std::vector<std::string_view> ret{};
  std::decay_t<decltype(std::string_view::npos)> pos{};
  while ((pos = buffer.find(delimeter)) != std::string_view::npos) {
    const auto match = buffer.substr(0, pos);
    if (!match.empty()) ret.push_back(match);
    buffer = buffer.substr(pos + delimeter.size());
  }
  if (!buffer.empty()) ret.push_back(buffer);
  return ret;
}
int main() {
  const auto split_values = split("1 2 3 4 5 6 7 8 9     10 ");
  std::for_each(split_values.begin(), split_values.end(),
                [](const auto& str) { std::cout << str << '\n'; });
  return split_values.size();
}

你通过值传递了 buffer,所以向量中的 string_views 指向临时对象。 - Steve Ward
是的,这就是string_views的意义所在。如果你让原始值消失了,那么它指向的就是垃圾。在我的例子中,我使用了一个字符串字面量。因此,它将始终存在于程序的生命周期中。如果您想要制作永久副本,则应改用std::string。我只在这里使用std::vector,因为我不知道我们会得到多少结果。也许在c++23中有一种std视图可以使用,这样我们就可以以更懒惰的方式获得结果。 - Robert Russell
@SteveWard。我认为这是可以的,但只适用于字符串字面量,就像这个例子一样。因为这样的字面量实际上不是临时的,而是相当静态的常量。我同意对于string_view来说,临时字符串可能会有悬空的问题。 - Кое Кто

3
我制作了这个解决方案。它非常简单,所有的打印/值都在循环中(不需要在循环后检查)。
#include <iostream>
#include <string>

using std::cout;
using std::string;

int main() {
    string s = "it-+is-+working!";
    string d = "-+";

    int firstFindI = 0;
    int secendFindI = 0;
    while (secendFindI != string::npos)
    {
        secendFindI = s.find(d, firstFindI);
        cout << s.substr(firstFindI, secendFindI - firstFindI) << "\n"; // print sliced part
        firstFindI = secendFindI + d.size(); // add to the search index
    }
}

感谢 @SteveWard 改进了这个答案。


如果您使用do/while循环,就不需要两次调用s.find - Steve Ward

3
这是一个完整的方法,它可以根据任何分隔符将字符串拆分,并返回一个切割后的字符串向量。
这是对ryanbwork答案的改编。然而,他对if(token != mystring)的检查在字符串中有重复元素时会得到错误的结果。这是我对这个问题的解决方案。
vector<string> Split(string mystring, string delimiter)
{
    vector<string> subStringList;
    string token;
    while (true)
    {
        size_t findfirst = mystring.find(delimiter);
        if (findfirst == string::npos) //find returns npos if it couldn't find the delimiter anymore
        {
            subStringList.push_back(mystring); //push back the final piece of mystring
            return subStringList;
        }
        token = mystring.substr(0, mystring.find(delimiter));
        mystring = mystring.substr(mystring.find(delimiter) + delimiter.size());
        subStringList.push_back(token);
    }
    return subStringList;
}

2
在代码中看到像 while (true) 这样的东西通常是令人害怕的。个人建议重写代码,使得与 std::string::npos 的比较(或者相应地检查 mystring.size())可以使 while (true) 变得不必要。 - Joel Bodenmann
1
重复赋值 mystring 是低效的。你可以将起始索引传递给 find_first_of。此外,每次迭代都在调用 find_first_of 3 次。 - Steve Ward
我不确定它是否能正确处理多字符分隔符。因为 find_first_of :"查找与给定字符序列中的任意一个字符相等的第一个字符。" - Кое Кто
1
我认为你是对的 @КоеКто。我调整并测试了代码,使用一些基本示例来处理多个分隔符。 - Amber Elferink

2
另一种答案:在这里,我正在使用find_first_not_of字符串函数,它返回第一个不匹配定界符中任何字符的位置。
size_t find_first_not_of(const string& delim, size_t pos = 0) const noexcept;

例子:

int main()
{
    size_t start = 0, end = 0;
    std::string str = "scott>=tiger>=cat";
    std::string delim = ">=";
    while ((start = str.find_first_not_of(delim, end)) != std::string::npos)
    {
        end = str.find(delim, start); // finds the 'first' occurance from the 'start'
        std::cout << str.substr(start, end - start)<<std::endl; // extract substring
    }
    return 0;
}

输出:

    scott
    tiger
    cat

2

由于这是 Stack Overflow 中最受欢迎的谷歌搜索结果之一,与 C++ split string 相关,因此我会发布一份完整的、可复制粘贴的示例,展示两种方法。

splitString 使用 stringstream(在大多数情况下更好、更容易),

splitString2 使用 findsubstr(一种更为手动的方法)。

// SplitString.cpp

#include <iostream>
#include <vector>
#include <string>
#include <sstream>

// function prototypes
std::vector<std::string> splitString(const std::string& str, char delim);
std::vector<std::string> splitString2(const std::string& str, char delim);
std::string getSubstring(const std::string& str, int leftIdx, int rightIdx);


int main(void)
{
  // Test cases - all will pass
  
  std::string str = "ab,cd,ef";
  //std::string str = "abcdef";
  //std::string str = "";
  //std::string str = ",cd,ef";
  //std::string str = "ab,cd,";   // behavior of splitString and splitString2 is different for this final case only, if this case matters to you choose which one you need as applicable
  
  
  std::vector<std::string> tokens = splitString(str, ',');
  
  std::cout << "tokens: " << "\n";
  
  if (tokens.empty())
  {
    std::cout << "(tokens is empty)" << "\n";
  }
  else
  {
    for (auto& token : tokens)
    {
      if (token == "") std::cout << "(empty string)" << "\n";
      else std::cout << token << "\n";
    }
  }
    
  return 0;
}

std::vector<std::string> splitString(const std::string& str, char delim)
{
  std::vector<std::string> tokens;
  
  if (str == "") return tokens;
  
  std::string currentToken;
  
  std::stringstream ss(str);
  
  while (std::getline(ss, currentToken, delim))
  {
    tokens.push_back(currentToken);
  }
  
  return tokens;
}

std::vector<std::string> splitString2(const std::string& str, char delim)
{
  std::vector<std::string> tokens;
  
  if (str == "") return tokens;
  
  int leftIdx = 0;
  
  int delimIdx = str.find(delim);
  
  int rightIdx;
  
  while (delimIdx != std::string::npos)
  {
    rightIdx = delimIdx - 1;
    
    std::string token = getSubstring(str, leftIdx, rightIdx);
    tokens.push_back(token);
    
    // prep for next time around
    leftIdx = delimIdx + 1;
    
    delimIdx = str.find(delim, delimIdx + 1);
  }
  
  rightIdx = str.size() - 1;
  
  std::string token = getSubstring(str, leftIdx, rightIdx);
  tokens.push_back(token);
  
  return tokens;
}

std::string getSubstring(const std::string& str, int leftIdx, int rightIdx)
{
  return str.substr(leftIdx, rightIdx - leftIdx + 1);
}

1
这是一个简洁的分割函数。我决定让相邻的分隔符返回为空字符串,但如果子字符串为空,你也可以轻松地检查并将其添加到向量中。请注意保留HTML标记。
#include <vector>
#include <string>
using namespace std;



vector<string> split(string to_split, string delimiter) {
    size_t pos = 0;
    vector<string> matches{};
    do {
        pos = to_split.find(delimiter);
        int change_end;
        if (pos == string::npos) {
            pos = to_split.length() - 1;
            change_end = 1;
        }
        else {
            change_end = 0;
        }
        matches.push_back(to_split.substr(0, pos+change_end));
        
        to_split.erase(0, pos+1);

    }
    while (!to_split.empty());
    return matches;

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接