如何遍历字符串中的单词?


2985

我正在尝试遍历字符串中的单词。

可以假定字符串由空格分隔的单词组成。

请注意,我对C字符串函数或那种字符操作/访问不感兴趣。另外,在回答问题时,请优先考虑优雅而不是效率。

我目前拥有的最佳解决方案是:

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

using namespace std;

int main()
{
    string s = "Somewhere down the road";
    istringstream iss(s);

    do
    {
        string subs;
        iss >> subs;
        cout << "Substring: " << subs << endl;
    } while (iss);
}

有没有更优雅的方法可以做到这一点?


617
杜德(Dude)...优雅只是我书中“看起来高效”的一种奇特方式。不要回避使用C函数和快速方法来完成任何事情,仅仅是因为它不包含在模板中;)

14
while (iss) { string subs; iss >> subs; cout << "Substring: " << sub << endl; }
pyon

21
@Eduardo:也是错的...您需要在尝试流式传输另一个值和使用该值之间进行测试,例如string sub; while (iss >> sub) cout << "Substring: " << sub << '\n';
Tony Delroy 2012年

9
默认情况下,C ++中的各种选项可以执行此操作:cplusplus.com/faq/sequences/strings/split
hB0 2013年

14
优雅不仅仅是效率。优雅的属性包括行数少和可读性高。恕我直言,优雅不是效率的代名词,而是可维护性。
马特

Answers:


1368

值得的是,这是另一种仅依靠标准库工具从输入字符串中提取令牌的方法。这是STL设计背后的力量和优雅的典范。

#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>

int main() {
    using namespace std;
    string sentence = "And I feel fine...";
    istringstream iss(sentence);
    copy(istream_iterator<string>(iss),
         istream_iterator<string>(),
         ostream_iterator<string>(cout, "\n"));
}

与其将提取的令牌复制到输出流,还可以使用相同的通用copy算法将其插入容器。

vector<string> tokens;
copy(istream_iterator<string>(iss),
     istream_iterator<string>(),
     back_inserter(tokens));

...或vector直接创建:

vector<string> tokens{istream_iterator<string>{iss},
                      istream_iterator<string>{}};

164
是否可以为此指定分隔符?例如在逗号上分割?
2009年

15
@Jonathan:\ n在这种情况下不是定界符,它是输出到cout的常量。

772
这是一个糟糕的解决方案,因为它没有任何其他定界符,因此无法扩展且不能维护。
HelloWorld '01

37
实际上,这可以与其他定界符一起很好地工作(尽管做一些难看)。您创建一个ctype构面,将所需的分隔符分类为空白,创建一个包含该构面的语言环境,然后在提取字符串之前使字符串流具有该语言环境。
杰里·科芬

53
@Kinderchocolate “可以假定字符串是由空格分隔的单词组成的” -嗯,听起来不是解决问题的好方法。“不可扩展且不可维护” - ,不错。
Christian Rau

2425

我用它用定界符分割字符串。第一个将结果放入预先构造的向量中,第二个返回一个新向量。

#include <string>
#include <sstream>
#include <vector>
#include <iterator>

template <typename Out>
void split(const std::string &s, char delim, Out result) {
    std::istringstream iss(s);
    std::string item;
    while (std::getline(iss, item, delim)) {
        *result++ = item;
    }
}

std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    split(s, delim, std::back_inserter(elems));
    return elems;
}

请注意,此解决方案不会跳过空令牌,因此以下内容将找到4个项目,其中之一为空:

std::vector<std::string> x = split("one:two::three", ':');

86
为了避免跳过空令牌,请执行以下empty()检查:if (!item.empty()) elems.push_back(item)
0x499602D2 2013年

11
delim包含两个字符如何->
herohuyongtao

7
@herohuyongtao,此解决方案仅适用于单个字符定界符。
埃文·特兰

4
@JeshwanthKumarNK,不是必须的,但是它允许您执行将结果直接传递给这样的函数的操作:如果您愿意f(split(s, d, v)),还可以受益于预分配vector
埃文·特兰

8
警告:split(“ one:two :: three”,':')和split(“ one:two :: three:”,':')返回相同的值。
dshin 2015年

834

使用Boost的可能解决方案可能是:

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));

此方法可能比该stringstream方法更快。由于这是一个通用的模板函数,因此可以使用各种分隔符将其拆分为其他类型的字符串(wchar等或UTF-8)。

有关详细信息,请参见文档


35
这里的速度无关紧要,因为这两种情况都比类似strtok的函数慢得多。
汤姆(Tom)2009年

45
对于那些还没有增强功能的人……bcp为此复制了1,000多个文件:)
Roman Starkov 2010年

12
警告,给定一个空字符串(“”)时,此方法返回一个包含“”字符串的向量。因此,在拆分之前添加“ if(!string_to_split.empty())”。
Offirmo 2011年

29
@Ian嵌入式开发人员并没有全都使用boost。
ACK_stoverflow,2012年

31
作为附录:仅在必要时才使用boost,通常情况下,我更喜欢将其添加到我自己的代码库中,该代码库是独立且可移植的,以便我可以实现精确的小型特定代码,从而实现既定目标。这样,代码是非公开的,高性能的,琐碎的和可移植的。Boost占有一席之地,但我建议对标记字符串进行一些过大的矫饰:您不会将整个房子搬到一家工程公司,以将新钉子钉在墙上来挂画....他们可能会这样做非常好,但缺点远不及优点。
GMasucci

362
#include <vector>
#include <string>
#include <sstream>

int main()
{
    std::string str("Split me by whitespaces");
    std::string buf;                 // Have a buffer string
    std::stringstream ss(str);       // Insert the string into a stream

    std::vector<std::string> tokens; // Create vector to hold our words

    while (ss >> buf)
        tokens.push_back(buf);

    return 0;
}

12
如果getlinewhile条件中使用,也可以分割其他定界符,例如用逗号分割,请使用while(getline(ss, buff, ','))
阿里

181

对于那些不愿意为了代码大小而牺牲所有效率并且将“效率”视为一种优雅的人来说,以下内容应该是一个不错的选择(我认为模板容器类是一个非常优雅的补充):

template < class ContainerT >
void tokenize(const std::string& str, ContainerT& tokens,
              const std::string& delimiters = " ", bool trimEmpty = false)
{
   std::string::size_type pos, lastPos = 0, length = str.length();

   using value_type = typename ContainerT::value_type;
   using size_type  = typename ContainerT::size_type;

   while(lastPos < length + 1)
   {
      pos = str.find_first_of(delimiters, lastPos);
      if(pos == std::string::npos)
      {
         pos = length;
      }

      if(pos != lastPos || !trimEmpty)
         tokens.push_back(value_type(str.data()+lastPos,
               (size_type)pos-lastPos ));

      lastPos = pos + 1;
   }
}

我通常选择使用std::vector<std::string>类型作为我的第二个参数(ContainerT)...但是list<>vector<>不需要直接访问时要快得多,您甚至可以创建自己的字符串类,并使用诸如std::list<subString>where subString不做任何复制等操作来获得惊人的速度增加。

它是此页面上最快的标记化速度的两倍以上,几乎是其他一些标记速度的5倍。同样,使用理想的参数类型,您可以消除所有字符串和列表副本,从而进一步提高速度。

此外,它不会返回结果(效率极低),而是将令牌作为参考传递,因此,如果您愿意,还可以使用多个调用来构建令牌。

最后,它允许您指定是否通过最后一个可选参数从结果中修剪空标记。

它所需要的只是std::string...其余都是可选的。它不使用流或boost库,但足够灵活以能够自然地接受其中一些外部类型。


5
我对此很感兴趣,但是对于g ++(可能是好的做法),使用此方法的任何人都需要typedef和typename: typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; 然后相应地替换value_type和size_types。
aws

11
对于我们这些完全不熟悉模板内容和第一条评论的人,使用带cmd并带有required的用法示例将是不错的选择。
Wes Miller

3
嗯,我知道了。我将aws注释中的C ++行放在tokenize()函数体内,然后编辑了tokens.push_back()行,将ContainerT :: value_type更改为ValueType并将(ContainerT :: value_type :: size_type)更改为(尺码类型)。修复了g ++一直在抱怨的地方。只需将其作为tokenize(some_string,some_vector)调用即可;
Wes Miller

2
除了对样本数据进行一些性能测试之外,主要我将其减少为尽可能少的指令,并且通过使用仅引用其他字符串中的偏移量/长度的子字符串类启用了尽可能少的内存副本。(我自己滚动了,但是还有其他一些实现)。不幸的是,在此方面没有太多其他可以改进的地方,但是可能会逐步增加。
Marius 2012年

3
那是when的正确输出trimEmpty = true。请记住,这"abo"不是此答案中的定界符,而是定界符字符列表。修改它以使用单个定界符字符串是很简单的(我认为str.find_first_of应该改为str.find_first,但是我可能错了……无法测试)
Marius

158

这是另一个解决方案。它紧凑且相当有效:

std::vector<std::string> split(const std::string &text, char sep) {
  std::vector<std::string> tokens;
  std::size_t start = 0, end = 0;
  while ((end = text.find(sep, start)) != std::string::npos) {
    tokens.push_back(text.substr(start, end - start));
    start = end + 1;
  }
  tokens.push_back(text.substr(start));
  return tokens;
}

可以很容易地将其模板化以处理字符串分隔符,宽字符串等。

请注意,分割将""导致一个空字符串,而分割","(即sep)将导致两个空字符串。

也可以轻松地扩展它以跳过空令牌:

std::vector<std::string> split(const std::string &text, char sep) {
    std::vector<std::string> tokens;
    std::size_t start = 0, end = 0;
    while ((end = text.find(sep, start)) != std::string::npos) {
        if (end != start) {
          tokens.push_back(text.substr(start, end - start));
        }
        start = end + 1;
    }
    if (end != start) {
       tokens.push_back(text.substr(start));
    }
    return tokens;
}

如果需要在多个定界符处拆分字符串同时跳过空标记,则可以使用以下版本:

std::vector<std::string> split(const std::string& text, const std::string& delims)
{
    std::vector<std::string> tokens;
    std::size_t start = text.find_first_not_of(delims), end = 0;

    while((end = text.find_first_of(delims, start)) != std::string::npos)
    {
        tokens.push_back(text.substr(start, end - start));
        start = text.find_first_not_of(delims, end);
    }
    if(start != std::string::npos)
        tokens.push_back(text.substr(start));

    return tokens;
}

10
第一个版本很简单,可以完美完成工作。我唯一要做的更改是直接返回结果,而不是将其作为参数传递。
gregschlom 2012年

2
输出作为效率参数传递。如果返回结果,则将需要向量的副本或堆分配,然后将其释放。
亚历克·托马斯

2
我在上面的评论中有一个小附录:如果使用C ++ 11 move语义,此函数可以返回向量而不会带来任何损失。
亚历克·托马斯

7
@AlecThomas:即使在C ++ 11之前,大多数编译器也会通过NRVO来优化返回副本吗?(无论如何还是+1;非常简洁)
Marcelo Cantos

11
在所有答案中,这似乎是最有吸引力且最灵活的方法之一。与带有分隔符的getline一起使用,尽管解决方案不太明显。c ++ 11标准没有为此吗?如今,c ++ 11是否支持打孔卡?
Spacen Jasset

123

这是我最喜欢的遍历字符串的方式。您可以按单词做任何您想做的事情。

string line = "a line of text to iterate through";
string word;

istringstream iss(line, istringstream::in);

while( iss >> word )     
{
    // Do something on `word` here...
}

可以声明wordchar吗?
abatishchev

抱歉abatishchev,C ++不是我的强项。但是我想添加一个内部循环来遍历每个单词中的每个字符并不困难。但是现在我相信当前循环取决于单词分隔的空间。除非你知道,只有每一个空间之间的单个字符,在这种情况下,你可以只投“字”为char ...对不起,我不能有更多的帮助,香港专业教育学院一直想刷了我的C ++
gnomed

11
如果您将word声明为char,它将遍历每个非空白字符。尝试操作非常简单:stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c;
韦恩·沃纳

79

这类似于堆栈溢出问题,如何在C ++中标记字符串?

#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int argc, char** argv)
{
    string text = "token  test\tstring";

    char_separator<char> sep(" \t");
    tokenizer<char_separator<char>> tokens(text, sep);
    for (const string& t : tokens)
    {
        cout << t << "." << endl;
    }
}

这会实现所有令牌的副本,还是仅保留当前令牌的开始和结束位置?
einpoklum

66

我喜欢以下内容,因为它将结果放入向量中,支持字符串作为delim并控制保留空值。但是,那时看起来还不怎么样。

#include <ostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

vector<string> split(const string& s, const string& delim, const bool keep_empty = true) {
    vector<string> result;
    if (delim.empty()) {
        result.push_back(s);
        return result;
    }
    string::const_iterator substart = s.begin(), subend;
    while (true) {
        subend = search(substart, s.end(), delim.begin(), delim.end());
        string temp(substart, subend);
        if (keep_empty || !temp.empty()) {
            result.push_back(temp);
        }
        if (subend == s.end()) {
            break;
        }
        substart = subend + delim.size();
    }
    return result;
}

int main() {
    const vector<string> words = split("So close no matter how far", " ");
    copy(words.begin(), words.end(), ostream_iterator<string>(cout, "\n"));
}

当然,Boost具有split()这样的部分功能。而且,如果使用“空白”,则表示使用Boost的split is_any_of()效果很好,实际上意味着任何类型的空白。


最终,一个解决方案可以在字符串的两侧正确处理空令牌
fmuecke

53

STL还没有可用的这种方法。

但是,您可以strtok()通过使用std::string::c_str()成员来使用C的函数,也可以编写自己的函数。这是经过快速Google搜索(“ STL字符串拆分”)后发现的代码示例:

void Tokenize(const string& str,
              vector<string>& tokens,
              const string& delimiters = " ")
{
    // Skip delimiters at beginning.
    string::size_type lastPos = str.find_first_not_of(delimiters, 0);
    // Find first "non-delimiter".
    string::size_type pos     = str.find_first_of(delimiters, lastPos);

    while (string::npos != pos || string::npos != lastPos)
    {
        // Found a token, add it to the vector.
        tokens.push_back(str.substr(lastPos, pos - lastPos));
        // Skip delimiters.  Note the "not_of"
        lastPos = str.find_first_not_of(delimiters, pos);
        // Find next "non-delimiter"
        pos = str.find_first_of(delimiters, lastPos);
    }
}

摘自:http : //oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html

如果您对代码示例有疑问,请发表评论,我会解释。

仅仅因为它没有实现typedef调用的迭代器或重载<<操作符,并不意味着它是错误的代码。我经常使用C函数。例如,printf并且scanf两者都比std::cinstd::cout(显着)快,并且fopen语法对二进制类型友好得多,并且它们也倾向于生成更小的EXE。

不要因为这项“绩效上的优雅”交易而被出售。


我知道C字符串函数,也知道性能问题(我在问题中都提到了这两个问题)。但是,对于这个特定问题,我正在寻找一种优雅的C ++解决方案。
Ashwin Nanjappa,

11
@Nelson LaQuet:让我猜:因为strtok不重入?
paercebal

40
@Nelson不会永远传递string.c_str()函数strtok!strtok丢弃输入字符串(插入'\ 0'字符以替换每个foudn分隔符),而c_str()返回不可修改的字符串。
埃文·特兰

3
@Nelson:该数组的大小必须为str.size()+ 1。但是,我同意您的观点,即出于“美学”原因而避免使用C函数是很愚蠢的。
j_random_hacker

2
@ paulm:不,C ++流的缓慢是由方面引起的。即使禁用同步(以及在无法同步的字符串流上),它们仍然比stdio.h函数慢。
Ben Voigt,2015年

42

这是一个拆分函数:

  • 是通用的
  • 使用标准C ++(无提升)
  • 接受多个定界符
  • 忽略空令牌(可以轻松更改)

    template<typename T>
    vector<T> 
    split(const T & str, const T & delimiters) {
        vector<T> v;
        typename T::size_type start = 0;
        auto pos = str.find_first_of(delimiters, start);
        while(pos != T::npos) {
            if(pos != start) // ignore empty tokens
                v.emplace_back(str, start, pos - start);
            start = pos + 1;
            pos = str.find_first_of(delimiters, start);
        }
        if(start < str.length()) // ignore trailing delimiter
            v.emplace_back(str, start, str.length() - start); // add what's left of the string
        return v;
    }

用法示例:

    vector<string> v = split<string>("Hello, there; World", ";,");
    vector<wstring> v = split<wstring>(L"Hello, there; World", L";,");

您忘了添加使用列表:“效率极低”
Xander Tulip

1
@XanderTulip,您能否更具建设性并解释其方式或原因?
Marco M.

3
@XanderTulip:我假设您是指它按值返回向量。Return-Value-Optimization(RVO,google it)应该注意这一点。同样在C ++ 11中,您可以通过移动引用返回。
约瑟夫·加文

3
实际上,这可以进一步优化:可以使用.emplace_back(str,start,pos-start)代替.push_back(str.substr(...))。这样,在容器中构造了字符串对象,因此避免了.substr函数完成的移动操作和其他恶作剧。
MihaiBişog2012年

@ zoopp是的。好主意。我写这篇文章时,VS10没有emplace_back支持。我将更新我的答案。谢谢
Marco M.

36

我有2行解决此问题的方法:

char sep = ' ';
std::string s="1 This is an example";

for(size_t p=0, q=0; p!=s.npos; p=q)
  std::cout << s.substr(p+(p!=0), (q=s.find(sep, p+1))-p-(p!=0)) << std::endl;

然后,您可以将其放入向量中,而不是进行打印。


35

另一种灵活而快速的方法

template<typename Operator>
void tokenize(Operator& op, const char* input, const char* delimiters) {
  const char* s = input;
  const char* e = s;
  while (*e != 0) {
    e = s;
    while (*e != 0 && strchr(delimiters, *e) == 0) ++e;
    if (e - s > 0) {
      op(s, e - s);
    }
    s = e + 1;
  }
}

将其与字符串向量一起使用(编辑:由于有人指出不要继承STL类... hrmf;)):

template<class ContainerType>
class Appender {
public:
  Appender(ContainerType& container) : container_(container) {;}
  void operator() (const char* s, unsigned length) { 
    container_.push_back(std::string(s,length));
  }
private:
  ContainerType& container_;
};

std::vector<std::string> strVector;
Appender v(strVector);
tokenize(v, "A number of words to be tokenized", " \t");

而已!这只是使用标记器的一种方式,例如如何对单词进行计数:

class WordCounter {
public:
  WordCounter() : noOfWords(0) {}
  void operator() (const char*, unsigned) {
    ++noOfWords;
  }
  unsigned noOfWords;
};

WordCounter wc;
tokenize(wc, "A number of words to be counted", " \t"); 
ASSERT( wc.noOfWords == 7 );

受想象力限制;)



32

这是仅使用标准正则表达式库的简单解决方案

#include <regex>
#include <string>
#include <vector>

std::vector<string> Tokenize( const string str, const std::regex regex )
{
    using namespace std;

    std::vector<string> result;

    sregex_token_iterator it( str.begin(), str.end(), regex, -1 );
    sregex_token_iterator reg_end;

    for ( ; it != reg_end; ++it ) {
        if ( !it->str().empty() ) //token could be empty:check
            result.emplace_back( it->str() );
    }

    return result;
}

regex参数允许检查多个参数(空格,逗号等)。

我通常只检查空格和逗号,所以我也有这个默认功能:

std::vector<string> TokenizeDefault( const string str )
{
    using namespace std;

    regex re( "[\\s,]+" );

    return Tokenize( str, re );
}

"[\\s,]+"对空间(检查\\s)和逗号(,)。

请注意,如果您想分割wstring而不是string

  • 全部更改std::regexstd::wregex
  • 全部更改sregex_token_iteratorwsregex_token_iterator

注意,根据您的编译器,您可能还想通过引用获取字符串参数。


这本来是我最喜欢的答案,但是std :: regex在GCC 4.8中已损坏。他们说他们在GCC 4.9中正确实现了它。我仍在给我+1
mchiasson 2014年

1
这是我最喜欢的改动,它像您所说的那样返回vector作为引用,并且引用也传递了参数“ str”和“ regex”。谢谢。
QuantumKarl 2015年

1
原始字符串在处理正则表达式模式时非常有用。这样,您就不必使用转义序列了……您只需使用即可R"([\s,]+)"
山姆

26

按需使用std::stringstream就可以很好地工作,并且完全可以完成您想要的操作。如果您只是在寻找不同的处理方式,则可以使用std::find()/ std::find_first_of()std::string::substr()

这是一个例子:

#include <iostream>
#include <string>

int main()
{
    std::string s("Somewhere down the road");
    std::string::size_type prev_pos = 0, pos = 0;

    while( (pos = s.find(' ', pos)) != std::string::npos )
    {
        std::string substring( s.substr(prev_pos, pos-prev_pos) );

        std::cout << substring << '\n';

        prev_pos = ++pos;
    }

    std::string substring( s.substr(prev_pos, pos-prev_pos) ); // Last word
    std::cout << substring << '\n';

    return 0;
}

这仅适用于单字符定界符。一个简单的更改使其可以与多prev_pos = pos += delimiter.length();
字符

25

如果您想使用boost,但想使用整个字符串作为定界符(而不是以前建议的大多数解决方案中的单个字符),则可以使用boost_split_iterator

包含方便模板的示例代码:

#include <iostream>
#include <vector>
#include <boost/algorithm/string.hpp>

template<typename _OutputIterator>
inline void split(
    const std::string& str, 
    const std::string& delim, 
    _OutputIterator result)
{
    using namespace boost::algorithm;
    typedef split_iterator<std::string::const_iterator> It;

    for(It iter=make_split_iterator(str, first_finder(delim, is_equal()));
            iter!=It();
            ++iter)
    {
        *(result++) = boost::copy_range<std::string>(*iter);
    }
}

int main(int argc, char* argv[])
{
    using namespace std;

    vector<string> splitted;
    split("HelloFOOworldFOO!", "FOO", back_inserter(splitted));

    // or directly to console, for example
    split("HelloFOOworldFOO!", "FOO", ostream_iterator<string>(cout, "\n"));
    return 0;
}

20

这是仅使用标准正则表达式库的正则表达式解决方案。(我有点生疏,所以可能会有一些语法错误,但这至少是一般性想法)

#include <regex.h>
#include <string.h>
#include <vector.h>

using namespace std;

vector<string> split(string s){
    regex r ("\\w+"); //regex matches whole words, (greedy, so no fragment words)
    regex_iterator<string::iterator> rit ( s.begin(), s.end(), r );
    regex_iterator<string::iterator> rend; //iterators to iterate thru words
    vector<string> result<regex_iterator>(rit, rend);
    return result;  //iterates through the matches to fill the vector
}

使用更好的正则表达式方法的类似响应:在此处此处
nobar 2014年

20

有一个名为的函数strtok

#include<string>
using namespace std;

vector<string> split(char* str,const char* delim)
{
    char* saveptr;
    char* token = strtok_r(str,delim,&saveptr);

    vector<string> result;

    while(token != NULL)
    {
        result.push_back(token);
        token = strtok_r(NULL,delim,&saveptr);
    }
    return result;
}

3
strtok来自C标准库,而不是C ++。在多线程程序中使用是不安全的。修改输入字符串。
Kevin Panko 2010年

13
因为它将第一次调用中的char指针存储在静态变量中,所以在传递NULL的后续调用中,它会记住应使用哪种指针。如果strtok在另一个线程仍在处理时调用第二个线程,则此char指针将被覆盖,并且两个线程将得到不正确的结果。mkssoftware.com/docs/man3/strtok.3.asp
Kevin Panko 2010年

1
如前所述,strtok不安全,甚至在C语言中也建议使用
strtok_r

4
如果您在可能被访问的代码段中,则可以使用strtok_r。这是上述所有方法中唯一的不是“线路噪声”的解决方案,并且证明了c ++到底有什么问题
Erik Aronesty 2011年

已更新,因此不会有C ++线程对线程安全性的反对。
Erik Aronesty

17

字符串流,如果你需要分析非空间符号串可以方便:

string s = "Name:JAck; Spouse:Susan; ...";
string dummy, name, spouse;

istringstream iss(s);
getline(iss, dummy, ':');
getline(iss, name, ';');
getline(iss, dummy, ':');
getline(iss, spouse, ';')

14

到目前为止,我在Boost中使用了那个,但是我需要一些不依赖它的东西,所以我来了:

static void Split(std::vector<std::string>& lst, const std::string& input, const std::string& separators, bool remove_empty = true)
{
    std::ostringstream word;
    for (size_t n = 0; n < input.size(); ++n)
    {
        if (std::string::npos == separators.find(input[n]))
            word << input[n];
        else
        {
            if (!word.str().empty() || !remove_empty)
                lst.push_back(word.str());
            word.str("");
        }
    }
    if (!word.str().empty() || !remove_empty)
        lst.push_back(word.str());
}

优点是separators您可以传递多个字符。


13

我使用strtok滚动了自己的代码,并使用boost来拆分字符串。我发现的最佳方法是C ++ String Toolkit库。它非常灵活和快速。

#include <iostream>
#include <vector>
#include <string>
#include <strtk.hpp>

const char *whitespace  = " \t\r\n\f";
const char *whitespace_and_punctuation  = " \t\r\n\f;,=";

int main()
{
    {   // normal parsing of a string into a vector of strings
        std::string s("Somewhere down the road");
        std::vector<std::string> result;
        if( strtk::parse( s, whitespace, result ) )
        {
            for(size_t i = 0; i < result.size(); ++i )
                std::cout << result[i] << std::endl;
        }
    }

    {  // parsing a string into a vector of floats with other separators
        // besides spaces

        std::string s("3.0, 3.14; 4.0");
        std::vector<float> values;
        if( strtk::parse( s, whitespace_and_punctuation, values ) )
        {
            for(size_t i = 0; i < values.size(); ++i )
                std::cout << values[i] << std::endl;
        }
    }

    {  // parsing a string into specific variables

        std::string s("angle = 45; radius = 9.9");
        std::string w1, w2;
        float v1, v2;
        if( strtk::parse( s, whitespace_and_punctuation, w1, v1, w2, v2) )
        {
            std::cout << "word " << w1 << ", value " << v1 << std::endl;
            std::cout << "word " << w2 << ", value " << v2 << std::endl;
        }
    }

    return 0;
}

该工具包比此简单示例显示的灵活性要大得多,但是它在将字符串解析为有用元素方面的实用性令人难以置信。


13

简短而优雅

#include <vector>
#include <string>
using namespace std;

vector<string> split(string data, string token)
{
    vector<string> output;
    size_t pos = string::npos; // size_t to avoid improbable overflow
    do
    {
        pos = data.find(token);
        output.push_back(data.substr(0, pos));
        if (string::npos != pos)
            data = data.substr(pos + token.size());
    } while (string::npos != pos);
    return output;
}

可以使用任何字符串作为分隔符,也可以用于二进制数据(std :: string支持二进制数据,包括null)

使用:

auto a = split("this!!is!!!example!string", "!!");

输出:

this
is
!example!string

1
我喜欢这种解决方案,因为它允许分隔符是字符串而不是char,但是,它是在原地修改字符串,因此它迫使创建原始字符串的副本。
Alessandro Teruzzi '16年

11

之所以这样做,是因为我需要一种简单的方法来分割字符串和基于C的字符串……希望其他人也能找到它的用处。而且它不依赖标记,您可以将字段用作定界符,这是我需要的另一个关键。

我敢肯定,可以做些改进,以进一步提高其优雅度,请务必采取一切措施

StringSplitter.hpp:

#include <vector>
#include <iostream>
#include <string.h>

using namespace std;

class StringSplit
{
private:
    void copy_fragment(char*, char*, char*);
    void copy_fragment(char*, char*, char);
    bool match_fragment(char*, char*, int);
    int untilnextdelim(char*, char);
    int untilnextdelim(char*, char*);
    void assimilate(char*, char);
    void assimilate(char*, char*);
    bool string_contains(char*, char*);
    long calc_string_size(char*);
    void copy_string(char*, char*);

public:
    vector<char*> split_cstr(char);
    vector<char*> split_cstr(char*);
    vector<string> split_string(char);
    vector<string> split_string(char*);
    char* String;
    bool do_string;
    bool keep_empty;
    vector<char*> Container;
    vector<string> ContainerS;

    StringSplit(char * in)
    {
        String = in;
    }

    StringSplit(string in)
    {
        size_t len = calc_string_size((char*)in.c_str());
        String = new char[len + 1];
        memset(String, 0, len + 1);
        copy_string(String, (char*)in.c_str());
        do_string = true;
    }

    ~StringSplit()
    {
        for (int i = 0; i < Container.size(); i++)
        {
            if (Container[i] != NULL)
            {
                delete[] Container[i];
            }
        }
        if (do_string)
        {
            delete[] String;
        }
    }
};

StringSplitter.cpp:

#include <string.h>
#include <iostream>
#include <vector>
#include "StringSplit.hpp"

using namespace std;

void StringSplit::assimilate(char*src, char delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }

        }
        else
        {
            delete[] temp;
        }
    }
}

void StringSplit::assimilate(char*src, char* delim)
{
    int until = untilnextdelim(src, delim);
    if (until > 0)
    {
        char * temp = new char[until + 1];
        memset(temp, 0, until + 1);
        copy_fragment(temp, src, delim);
        if (keep_empty || *temp != 0)
        {
            if (!do_string)
            {
                Container.push_back(temp);
            }
            else
            {
                string x = temp;
                ContainerS.push_back(x);
            }
        }
        else
        {
            delete[] temp;
        }
    }
}

long StringSplit::calc_string_size(char* _in)
{
    long i = 0;
    while (*_in++)
    {
        i++;
    }
    return i;
}

bool StringSplit::string_contains(char* haystack, char* needle)
{
    size_t len = calc_string_size(needle);
    size_t lenh = calc_string_size(haystack);
    while (lenh--)
    {
        if (match_fragment(haystack + lenh, needle, len))
        {
            return true;
        }
    }
    return false;
}

bool StringSplit::match_fragment(char* _src, char* cmp, int len)
{
    while (len--)
    {
        if (*(_src + len) != *(cmp + len))
        {
            return false;
        }
    }
    return true;
}

int StringSplit::untilnextdelim(char* _in, char delim)
{
    size_t len = calc_string_size(_in);
    if (*_in == delim)
    {
        _in += 1;
        return len - 1;
    }

    int c = 0;
    while (*(_in + c) != delim && c < len)
    {
        c++;
    }

    return c;
}

int StringSplit::untilnextdelim(char* _in, char* delim)
{
    int s = calc_string_size(delim);
    int c = 1 + s;

    if (!string_contains(_in, delim))
    {
        return calc_string_size(_in);
    }
    else if (match_fragment(_in, delim, s))
    {
        _in += s;
        return calc_string_size(_in);
    }

    while (!match_fragment(_in + c, delim, s))
    {
        c++;
    }

    return c;
}

void StringSplit::copy_fragment(char* dest, char* src, char delim)
{
    if (*src == delim)
    {
        src++;
    }

    int c = 0;
    while (*(src + c) != delim && *(src + c))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

void StringSplit::copy_string(char* dest, char* src)
{
    int i = 0;
    while (*(src + i))
    {
        *(dest + i) = *(src + i);
        i++;
    }
}

void StringSplit::copy_fragment(char* dest, char* src, char* delim)
{
    size_t len = calc_string_size(delim);
    size_t lens = calc_string_size(src);

    if (match_fragment(src, delim, len))
    {
        src += len;
        lens -= len;
    }

    int c = 0;
    while (!match_fragment(src + c, delim, len) && (c < lens))
    {
        *(dest + c) = *(src + c);
        c++;
    }
    *(dest + c) = 0;
}

vector<char*> StringSplit::split_cstr(char Delimiter)
{
    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char Delimiter)
{
    do_string = true;

    int i = 0;
    while (*String)
    {
        if (*String != Delimiter && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (*String == Delimiter)
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

vector<char*> StringSplit::split_cstr(char* Delimiter)
{
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while(*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String,Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return Container;
}

vector<string> StringSplit::split_string(char* Delimiter)
{
    do_string = true;
    int i = 0;
    size_t LenDelim = calc_string_size(Delimiter);

    while (*String)
    {
        if (!match_fragment(String, Delimiter, LenDelim) && i == 0)
        {
            assimilate(String, Delimiter);
        }
        if (match_fragment(String, Delimiter, LenDelim))
        {
            assimilate(String, Delimiter);
        }
        i++;
        String++;
    }

    String -= i;
    delete[] String;

    return ContainerS;
}

例子:

int main(int argc, char*argv[])
{
    StringSplit ss = "This:CUT:is:CUT:an:CUT:example:CUT:cstring";
    vector<char*> Split = ss.split_cstr(":CUT:");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

将输出:



一个
示例
cstring

int main(int argc, char*argv[])
{
    StringSplit ss = "This:is:an:example:cstring";
    vector<char*> Split = ss.split_cstr(':');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This[SPLIT]is[SPLIT]an[SPLIT]example[SPLIT]string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string("[SPLIT]");

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

int main(int argc, char*argv[])
{
    string mystring = "This|is|an|example|string";
    StringSplit ss = mystring;
    vector<string> Split = ss.split_string('|');

    for (int i = 0; i < Split.size(); i++)
    {
        cout << Split[i] << endl;
    }

    return 0;
}

要保留空条目(默认情况下,将不包括空):

StringSplit ss = mystring;
ss.keep_empty = true;
vector<string> Split = ss.split_string(":DELIM:");

目的是使其类似于C#的Split()方法,在该方法中,拆分字符串非常简单:

String[] Split = 
    "Hey:cut:what's:cut:your:cut:name?".Split(new[]{":cut:"}, StringSplitOptions.None);

foreach(String X in Split)
{
    Console.Write(X);
}

我希望其他人能像我一样有用。


10

那这个呢:

#include <string>
#include <vector>

using namespace std;

vector<string> split(string str, const char delim) {
    vector<string> v;
    string tmp;

    for(string::const_iterator i; i = str.begin(); i <= str.end(); ++i) {
        if(*i != delim && i != str.end()) {
            tmp += *i; 
        } else {
            v.push_back(tmp);
            tmp = ""; 
        }   
    }   

    return v;
}

如果您只想分割单个分隔符,这是最佳答案。最初的问题想在空格上分割,这意味着一个或多个连续空格或制表符的任意组合。您实际上已经回答了stackoverflow.com/questions/53849
Oktalist 2012年

10

这个答案接受字符串并将其放入字符串向量中。它使用boost库。

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));

9

这是另一种方法。

void split_string(string text,vector<string>& words)
{
  int i=0;
  char ch;
  string word;

  while(ch=text[i++])
  {
    if (isspace(ch))
    {
      if (!word.empty())
      {
        words.push_back(word);
      }
      word = "";
    }
    else
    {
      word += ch;
    }
  }
  if (!word.empty())
  {
    words.push_back(word);
  }
}

9

我喜欢将boost / regex方法用于此任务,因为它们为指定拆分标准提供了最大的灵活性。

#include <iostream>
#include <string>
#include <boost/regex.hpp>

int main() {
    std::string line("A:::line::to:split");
    const boost::regex re(":+"); // one or more colons

    // -1 means find inverse matches aka split
    boost::sregex_token_iterator tokens(line.begin(),line.end(),re,-1);
    boost::sregex_token_iterator end;

    for (; tokens != end; ++tokens)
        std::cout << *tokens << std::endl;
}

9

最近,我不得不将驼峰式单词拆分为子单词。没有定界符,只有大写字符。

#include <string>
#include <list>
#include <locale> // std::isupper

template<class String>
const std::list<String> split_camel_case_string(const String &s)
{
    std::list<String> R;
    String w;

    for (String::const_iterator i = s.begin(); i < s.end(); ++i) {  {
        if (std::isupper(*i)) {
            if (w.length()) {
                R.push_back(w);
                w.clear();
            }
        }
        w += *i;
    }

    if (w.length())
        R.push_back(w);
    return R;
}

例如,这将“ AQueryTrades”拆分为“ A”,“ Query”和“ Trades”。该函数适用于窄和宽字符串。因为它尊重当前语言环境,所以将“RaumfahrtÜberwachungsVerordnung”分为“ Raumfahrt”,“Überwachungs”和“ Verordnung”。

注意std::upper应真正作为函数模板参数传递。然后,从这个功能的更广义的可以分裂的分隔符喜欢","";"或者" "太。


2
已经有2转。真好。好像我的英语太像“德语”了。但是,修正主义者并没有修复两个小错误,也许是因为它们无论如何都是显而易见的:std::isupper可以作为参数传递,而不是std::upper。第二个放在typename前面String::const_iterator
安德里亚斯·斯平德勒

9
#include<iostream>
#include<string>
#include<sstream>
#include<vector>
using namespace std;

    vector<string> split(const string &s, char delim) {
        vector<string> elems;
        stringstream ss(s);
        string item;
        while (getline(ss, item, delim)) {
            elems.push_back(item);
        }
        return elems;
    }

int main() {

        vector<string> x = split("thi is an sample test",' ');
        unsigned int i;
        for(i=0;i<x.size();i++)
            cout<<i<<":"<<x[i]<<endl;
        return 0;
}

9

使用std::string_view和Eric Niebler的range-v3库:

https://wandbox.org/permlink/kW5lwRCL1pxjp2pW

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"
#include "range/v3/algorithm.hpp"

int main() {
    std::string s = "Somewhere down the range v3 library";
    ranges::for_each(s  
        |   ranges::view::split(' ')
        |   ranges::view::transform([](auto &&sub) {
                return std::string_view(&*sub.begin(), ranges::distance(sub));
            }),
        [](auto s) {std::cout << "Substring: " << s << "\n";}
    );
}

通过使用范围for循环而不是ranges::for_each算法:

#include <iostream>
#include <string>
#include <string_view>
#include "range/v3/view.hpp"

int main()
{
    std::string str = "Somewhere down the range v3 library";
    for (auto s : str | ranges::view::split(' ')
                      | ranges::view::transform([](auto&& sub) { return std::string_view(&*sub.begin(), ranges::distance(sub)); }
                      ))
    {
        std::cout << "Substring: " << s << "\n";
    }
}

是的,适用范围看起来更好-我同意
Porsche9II,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.