Java有一个方便的分割方法:

String str = "The quick brown fox";
String[] results = str.split(" ");

在c++中有简单的方法来做到这一点吗?


当前回答

我认为这就是字符串流上的>>操作符的用途:

string word; sin >> word;

其他回答

使用strtok。在我看来,没有必要围绕标记化构建类,除非strtok不能提供您所需要的东西。可能不会,但在用C和c++编写各种解析代码的15年多时间里,我一直在使用strtok。这里有一个例子

char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
    printf ("Token: %s\n", p);
    p = strtok(NULL, " ");
}

一些注意事项(可能不适合您的需要)。该字符串在该过程中被“销毁”,这意味着EOS字符内联放置在分隔符点中。正确的用法可能需要创建字符串的非const版本。还可以在解析过程中更改分隔符列表。

在我看来,上面的代码比为它单独编写一个类要简单得多,也更容易使用。对我来说,这是语言提供的功能之一,而且它做得很好,很干净。这只是一个“基于C”的解决方案。它很合适,很简单,而且你不需要写很多额外的代码:-)

/// split a string into multiple sub strings, based on a separator string
/// for example, if separator="::",
///
/// s = "abc" -> "abc"
///
/// s = "abc::def xy::st:" -> "abc", "def xy" and "st:",
///
/// s = "::abc::" -> "abc"
///
/// s = "::" -> NO sub strings found
///
/// s = "" -> NO sub strings found
///
/// then append the sub-strings to the end of the vector v.
/// 
/// the idea comes from the findUrls() function of "Accelerated C++", chapt7,
/// findurls.cpp
///
void split(const string& s, const string& sep, vector<string>& v)
{
    typedef string::const_iterator iter;
    iter b = s.begin(), e = s.end(), i;
    iter sep_b = sep.begin(), sep_e = sep.end();

    // search through s
    while (b != e){
        i = search(b, e, sep_b, sep_e);

        // no more separator found
        if (i == e){
            // it's not an empty string
            if (b != e)
                v.push_back(string(b, e));
            break;
        }
        else if (i == b){
            // the separator is found and right at the beginning
            // in this case, we need to move on and search for the
            // next separator
            b = i + sep.length();
        }
        else{
            // found the separator
            v.push_back(string(b, i));
            b = i;
        }
    }
}

boost库很好,但并不总是可用的。手工做这些事情也是很好的脑力锻炼。这里我们只使用STL中的std::search()算法,参见上面的代码。

如果你正在使用c++ ranges——完整的range -v3库,而不是c++ 20所接受的有限功能——你可以这样做:

auto results = str | ranges::views::tokenize(" ",1);

... 这是惰性求值。你也可以在这个范围内设置一个向量:

auto results = str | ranges::views::tokenize(" ",1) | ranges::to<std::vector>();

如果str有n个字符组成m个单词,这将占用O(m)个空间和O(n)个时间。

参见标准库自己的标记化示例。

无意冒犯,但对于这样一个简单的问题,你把事情搞得太复杂了。使用Boost有很多理由。但对于这么简单的事情,就像用20号雪橇打苍蝇一样。

void
split( vector<string> & theStringVector,  /* Altered/returned value */
       const  string  & theString,
       const  string  & theDelimiter)
{
    UASSERT( theDelimiter.size(), >, 0); // My own ASSERT macro.

    size_t  start = 0, end = 0;

    while ( end != string::npos)
    {
        end = theString.find( theDelimiter, start);

        // If at end, use length=maxLength.  Else use length=end-start.
        theStringVector.push_back( theString.substr( start,
                       (end == string::npos) ? string::npos : end - start));

        // If at end, use start=maxSize.  Else use start=end+delimiter.
        start = (   ( end > (string::npos - theDelimiter.size()) )
                  ?  string::npos  :  end + theDelimiter.size());
    }
}

例如(以Doug为例),

#define SHOW(I,X)   cout << "[" << (I) << "]\t " # X " = \"" << (X) << "\"" << endl

int
main()
{
    vector<string> v;

    split( v, "A:PEP:909:Inventory Item", ":" );

    for (unsigned int i = 0;  i < v.size();   i++)
        SHOW( i, v[i] );
}

是的,我们可以split()返回一个新的向量,而不是传入一个。包装和重载是很简单的。但根据我所做的事情,我经常发现重用已有的对象比总是创建新对象更好。(只要我不忘记清空中间的向量!)

参考:http://www.cplusplus.com/reference/string/string/。

(我最初是在写一个回应Doug的问题:基于分隔符的c++字符串修改和提取(关闭)。但由于马丁·约克用这里的指针结束了这个问题……我将泛化我的代码。)

简单的c++代码(标准c++ 98),接受多个分隔符(在std::string中指定),只使用向量、字符串和迭代器。

#include <iostream>
#include <vector>
#include <string>
#include <stdexcept> 

std::vector<std::string> 
split(const std::string& str, const std::string& delim){
    std::vector<std::string> result;
    if (str.empty())
        throw std::runtime_error("Can not tokenize an empty string!");
    std::string::const_iterator begin, str_it;
    begin = str_it = str.begin(); 
    do {
        while (delim.find(*str_it) == std::string::npos && str_it != str.end())
            str_it++; // find the position of the first delimiter in str
        std::string token = std::string(begin, str_it); // grab the token
        if (!token.empty()) // empty token only when str starts with a delimiter
            result.push_back(token); // push the token into a vector<string>
        while (delim.find(*str_it) != std::string::npos && str_it != str.end())
            str_it++; // ignore the additional consecutive delimiters
        begin = str_it; // process the remaining tokens
        } while (str_it != str.end());
    return result;
}

int main() {
    std::string test_string = ".this is.a.../.simple;;test;;;END";
    std::string delim = "; ./"; // string containing the delimiters
    std::vector<std::string> tokens = split(test_string, delim);           
    for (std::vector<std::string>::const_iterator it = tokens.begin(); 
        it != tokens.end(); it++)
            std::cout << *it << std::endl;
}