【问题标题】:How do I iterate over the words of a string?如何迭代字符串的单词?
【发布时间】:2010-09-19 03:57:46
【问题描述】:

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

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

请注意,我对 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);
}

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

【问题讨论】:

  • 伙计...优雅只是在我的书中说“效率-看起来-漂亮”的一种奇特方式。不要仅仅因为它不包含在模板中而回避使用 C 函数和快速方法来完成任何事情;)
  • while (iss) { string subs; iss &gt;&gt; subs; cout &lt;&lt; "Substring: " &lt;&lt; sub &lt;&lt; endl; }
  • @Eduardo:这也是错误的......您需要在尝试流式传输另一个值和使用该值之间进行测试,即string sub; while (iss &gt;&gt; sub) cout &lt;&lt; "Substring: " &lt;&lt; sub &lt;&lt; '\n';
  • C++ 中默认执行此操作的各种选项:cplusplus.com/faq/sequences/strings/split
  • 优雅不仅仅是高效。优雅的属性包括低行数和高易读性。恕我直言,优雅不是效率的代表,而是可维护性的代表。

标签: c++ string split


【解决方案1】:

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

但是,您可以通过 std::string::c_str() 成员来使用 C 的 strtok() 函数,也可以编写自己的函数。这是我在 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 或重载&lt;&lt; 运算符并不意味着它是糟糕的代码。我经常使用 C 函数。例如,printfscanf 都比 std::cinstd::cout 快(显着),fopen 语法对二进制类型更友好,而且它们也倾向于生成更小的 EXE。

不要被这种“优雅胜过性能”交易所吸引。

【讨论】:

  • 我知道 C 字符串函数并且我也知道性能问题(我在我的问题中提到了这两个问题)。但是,对于这个特定的问题,我正在寻找一个优雅的 C++ 解决方案。
  • @Nelson LaQuet:让我猜猜:因为 strtok 不是可重入的?
  • @Nelson 不要永远将 string.c_str() 传递给 strtok! strtok 丢弃输入字符串(插入 '\0' 字符以替换每个 foudn 分隔符)并且 c_str() 返回一个不可修改的字符串。
  • @Nelson:在您的最后一条评论中,该数组的大小必须为 str.size() + 1。但我同意你的论点,即出于“审美”原因而避免使用 C 函数是愚蠢的。
  • @paulm:不,C++ 流的缓慢是由方面造成的。即使禁用了同步(以及在无法同步的字符串流上),它们仍然比 stdio.h 函数慢。
【解决方案2】:

这是我最喜欢的遍历字符串的方式。每个字都可以随心所欲。

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

istringstream iss(line, istringstream::in);

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

【讨论】:

  • 是否可以将word 声明为char
  • 对不起,abatishchev,C++ 不是我的强项。但我想添加一个内部循环来遍历每个单词中的每个字符并不难。但是现在我相信当前的循环取决于单词分隔的空格。除非您知道每个空格之间只有一个字符,在这种情况下,您可以将“单词”转换为字符......对不起,我无法提供更多帮助,我一直想复习我的 C++
  • 如果您将 word 声明为 char,它将遍历每个非空白字符。很简单,可以试试:stringstream ss("Hello World, this is*@#&amp;$(@ a string"); char c; while(ss &gt;&gt; c) cout &lt;&lt; c;
【解决方案3】:

使用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();
【解决方案4】:

对于一个大得离谱且可能冗余的版本,请尝试大量的 for 循环。

string stringlist[10];
int count = 0;

for (int i = 0; i < sequence.length(); i++)
{
    if (sequence[i] == ' ')
    {
        stringlist[count] = sequence.substr(0, i);
        sequence.erase(0, i+1);
        i = 0;
        count++;
    }
    else if (i == sequence.length()-1)  // Last word
    {
        stringlist[count] = sequence.substr(0, i+1);
    }
}

它并不漂亮,但总的来说(除非标点符号和许多其他错误)它有效!

【讨论】:

  • 我很想 +1 这个答案,因为它简单易读的代码(我认为它以错误的方式摩擦了一个优雅的人,因此是 -1),但后来我看到你分配了一个固定大小保存标记的字符串数组。来吧,你知道它会在最糟糕的时刻崩溃! :)
【解决方案5】:

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

#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 与 is_any_of() 的拆分效果很好。

【讨论】:

  • 终于找到了在字符串两边正确处理空标记的解决方案
【解决方案6】:

这类似于 Stack 溢出问题 How do I tokenize a string in C++?需要 Boost 外部库

#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;
    }
}

【讨论】:

  • 这是实现所有令牌的副本,还是只保留当前令牌的开始和结束位置?
【解决方案7】:

我用它来通过分隔符分割字符串。第一个将结果放入预先构建的向量中,第二个返回一个新向量。

#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;
}

注意这个方案没有跳过空tokens,所以下面会找到4个item,其中一个是空的:

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

【讨论】:

  • 为了避免它跳过空令牌,请检查empty()if (!item.empty()) elems.push_back(item)
  • delim 包含两个字符为-&gt; 怎么样?
  • @herohuyongtao,此解决方案仅适用于单个字符分隔符。
  • @JeshwanthKumarNK,这不是必需的,但它可以让你做一些事情,比如将结果直接传递给这样的函数:f(split(s, d, v)) 同时仍然可以享受预先分配的vector 的好处,如果你喜欢。
  • 警告: split("one:two::three", ':') 和 split("one:two::three:", ':') 返回相同的值。
【解决方案8】:

使用 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)。

详情请参阅documentation

【讨论】:

  • 速度在这里无关紧要,因为这两种情况都比类似 strtok 的函数慢得多。
  • 对于那些还没有 boost 的人... bcp 为此复制了 1,000 多个文件 :)
  • 警告,当给定一个空字符串(“”)时,此方法返回一个包含“”字符串的向量。所以在拆分之前添加一个“if (!string_to_split.empty())”。
  • @Ian Embedded 开发人员并非都在使用 boost。
  • 作为附录:我只在必要时使用 boost,通常我更喜欢添加到我自己的独立且可移植的代码库中,这样我就可以实现小而精确的特定代码,从而完成给定的目的。这样,代码是非公开的、高性能的、简单的和可移植的。 Boost 有它的位置,但我建议它对标记字符串有点矫枉过正:你不会把你的整个房子运到一家工程公司去把一个新钉子钉在墙上挂一张照片……他们可能会这样做非常好,但缺点远远超过了缺点。
【解决方案9】:

对于它的价值,这是从输入字符串中提取标记的另一种方法,仅依赖于标准库设施。这是 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>{}};

【讨论】:

  • 是否可以为此指定分隔符?比如用逗号分割?
  • @Jonathan: \n 在这种情况下不是分隔符,它是输出到 cout 的分隔符。
  • 这是一个糟糕的解决方案,因为它不需要任何其他分隔符,因此不可扩展且不可维护。
  • 实际上,这个 can 与其他分隔符一起工作得很好(虽然做一些有点难看)。您创建一个 ctype facet,将所需的分隔符分类为空格,创建一个包含该 facet 的语言环境,然后在提取字符串之前用该语言环境填充字符串流。
  • @Kinderchocolate “可以假定该字符串由空格分隔的单词组成” - 嗯,这听起来不像是问题问题的糟糕解决方案。 “不可扩展且不可维护” - 哈,不错。
【解决方案10】:

对于那些不喜欢牺牲所有效率来换取代码大小并将“高效”视为一种优雅的人来说,以下内容应该是一个最佳选择(我认为模板容器类是一个非常优雅的补充.):

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&lt;std::string&gt; 类型作为我的第二个参数(ContainerT)...但list&lt;&gt; 在不需要直接访问时比vector&lt;&gt; 快得多,您甚至可以创建自己的字符串类并使用 std::list&lt;subString&gt; 之类的东西,其中 subString 不会做任何复制以实现令人难以置信的速度提升。

它比此页面上最快的标记化速度快了一倍多,比其他一些快了近 5 倍。此外,借助完美的参数类型,您可以消除所有字符串和列表副本,从而进一步提高速度。

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

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

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

【讨论】:

  • 我非常喜欢这个,但对于 g++(可能是好的做法),任何使用它的人都需要 typedef 和 typenames:typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType; 然后相应地替换 value_type 和 size_types。跨度>
  • 对于我们这些模板内容和第一条评论完全陌生的人来说,一个包含必需包含的用法示例会很可爱。
  • 嗯,我想通了。我将 aws 注释中的 C++ 行放在 tokenize() 的函数体内,然后编辑 tokens.push_back() 行以将 ContainerT::value_type 更改为 ValueType 并将 (ContainerT::value_type::size_type) 更改为 (尺码类型)。修复了 g++ 一直抱怨的问题。只需将其调用为 tokenize( some_string, some_vector );
  • 除了对样本数据进行一些性能测试外,我主要将其减少到尽可能少的指令,并通过使用仅引用的子字符串类启用尽可能少的内存副本其他字符串中的偏移量/长度。 (我推出了自己的,但还有一些其他的实现)。不幸的是,没有太多其他方法可以改善这一点,但可以逐步增加。
  • 这是trimEmpty = true 时的正确输出。请记住,"abo" 不是此答案中的分隔符,而是分隔符列表。将其修改为采用单个分隔符字符串会很简单(我认为str.find_first_of 应该更改为str.find_first,但我可能错了......无法测试)
【解决方案11】:

这是另一种方法..

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);
  }
}

【讨论】:

  • 我相信这可以通过使用word.clear() 而不是word = "" 来优化一点。调用 clear 方法将清空字符串,但保留已分配的缓冲区,该缓冲区将在进一步连接时重用。现在为每个单词创建一个新的缓冲区,导致额外的分配。
【解决方案12】:

另一种灵活快速的方式

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 );

受想象力限制;)

【讨论】:

【解决方案13】:

有一个名为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;
}

【讨论】:

  • strtok 来自 C 标准库,而不是 C++。在多线程程序中使用是不安全的。它修改输入字符串。
  • 因为它将第一次调用的 char 指针存储在一个静态变量中,因此在传递 NULL 时的后续调用时,它会记住应该使用哪个指针。如果第二个线程在另一个线程仍在处理时调用strtok,则此字符指针将被覆盖,两个线程都会得到不正确的结果。 mkssoftware.com/docs/man3/strtok.3.asp
  • 如前所述 strtok 是不安全的,甚至在 C 中也推荐使用 strtok_r
  • strtok_r 如果您处于可访问的代码段中,则可以使用。这是上述所有不是“线路噪音”的唯一解决方案,并且证明了 c++ 究竟有什么问题
  • strtok 是邪恶的。如果两个分隔符之间没有任何内容,它将把它们视为一个分隔符。
【解决方案14】:

我使用这个 simpleton 是因为我们的 String 类是“特殊的”(即不是标准的):

void splitString(const String &s, const String &delim, std::vector<String> &result) {
    const int l = delim.length();
    int f = 0;
    int i = s.indexOf(delim,f);
    while (i>=0) {
        String token( i-f > 0 ? s.substring(f,i-f) : "");
        result.push_back(token);
        f=i+l;
        i = s.indexOf(delim,f);
    }
    String token = s.substring(f);
    result.push_back(token);
}

【讨论】:

    【解决方案15】:

    循环getline 并使用“ ”作为标记。

    【讨论】:

      【解决方案16】:

      到目前为止,我使用了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 中,您可以传递多个字符。

      【讨论】:

        【解决方案17】:
        #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;
        }
        

        【讨论】:

        • 如果您在while 条件中使用getline,也可以在其他分隔符上进行拆分,例如要以逗号分隔,请使用while(getline(ss, buff, ','))
        【解决方案18】:

        如果您喜欢使用 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;
        }
        

        【讨论】:

          【解决方案19】:

          以下是执行此操作的更好方法。它可以采用任何字符,并且除非您愿意,否则不会拆分行。不需要特殊的库(好吧,除了std,但谁真的认为这是一个额外的库),没有指针,没有引用,而且它是静态的。只是简单的普通 C++。

          #pragma once
          #include <vector>
          #include <sstream>
          using namespace std;
          class Helpers
          {
              public:
                  static vector<string> split(string s, char delim)
                  {
                      stringstream temp (stringstream::in | stringstream::out);
                      vector<string> elems(0);
                      if (s.size() == 0 || delim == 0)
                          return elems;
                      for(char c : s)
                      {
                          if(c == delim)
                          {
                              elems.push_back(temp.str());
                              temp = stringstream(stringstream::in | stringstream::out);
                          }
                          else
                              temp << c;
                      }
                      if (temp.str().size() > 0)
                          elems.push_back(temp.str());
                          return elems;
                      }
          
                  //Splits string s with a list of delimiters in delims (it's just a list, like if we wanted to
                  //split at the following letters, a, b, c we would make delims="abc".
                  static vector<string> split(string s, string delims)
                  {
                      stringstream temp (stringstream::in | stringstream::out);
                      vector<string> elems(0);
                      bool found;
                      if(s.size() == 0 || delims.size() == 0)
                          return elems;
                      for(char c : s)
                      {
                          found = false;
                          for(char d : delims)
                          {
                              if (c == d)
                              {
                                  elems.push_back(temp.str());
                                  temp = stringstream(stringstream::in | stringstream::out);
                                  found = true;
                                  break;
                              }
                          }
                          if(!found)
                              temp << c;
                      }
                      if(temp.str().size() > 0)
                          elems.push_back(temp.str());
                      return elems;
                  }
          };
          

          【讨论】:

            【解决方案20】:

            我喜欢在这个任务中使用 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;
            }
            

            【讨论】:

              【解决方案21】:

              这个呢:

              #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
              【解决方案22】:

              这是我的版本,取自 Kev 的源代码:

              #include <string>
              #include <vector>
              void split(vector<string> &result, string str, char delim ) {
                string tmp;
                string::iterator i;
                result.clear();
              
                for(i = str.begin(); i <= str.end(); ++i) {
                  if((const char)*i != delim  && i != str.end()) {
                    tmp += *i;
                  } else {
                    result.push_back(tmp);
                    tmp = "";
                  }
                }
              }
              

              之后,调用该函数并对其进行处理:

              vector<string> hosts;
              split(hosts, "192.168.1.2,192.168.1.3", ',');
              for( size_t i = 0; i < hosts.size(); i++){
                cout <<  "Connecting host : " << hosts.at(i) << "..." << endl;
              }
              

              【讨论】:

                【解决方案23】:

                stringstream如果需要用非空格符号解析字符串会很方便:

                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, ';')
                

                【讨论】:

                  【解决方案24】:

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

                  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;
                  }
                  

                  【讨论】:

                  • 第一个版本很简单,可以完美地完成工作。我要做的唯一更改是直接返回结果,而不是将其作为参数传递。
                  • 输出作为参数传递以提高效率。如果返回结果,则需要一个向量的副本,或者需要释放的堆分配。
                  • @AlecThomas:即使在 C++11 之前,大多数编译器不会通过 NRVO 优化返回副本吗? (无论如何+1;非常简洁)
                  • 在所有答案中,这似乎是最有吸引力和最灵活的答案之一。与带有分隔符的 getline 一起,尽管它是一个不太明显的解决方案。 c++11标准对此没有任何帮助吗? c++11 现在支持打孔卡了吗?
                  • 建议使用 std::string::size_type 而不是 int,否则某些编译器可能会发出带符号/无符号警告。
                  【解决方案25】:

                  最近我不得不将一个驼峰式单词拆分成子词。没有分隔符,只有大写字符。

                  #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 应该作为函数模板参数真正传递。然后这个函数的更广义的 from 也可以在 ","";"" " 等分隔符处拆分。

                  【讨论】:

                  • 有 2 转。那很好。好像我的英语有很多“德语”。然而,修正主义者没有修复两个小错误,可能是因为它们很明显:std::isupper 可以作为参数传递,而不是std::upper。其次在String::const_iterator 之前放置一个typename
                  【解决方案26】:

                  以下代码使用strtok() 将字符串拆分为标记并将标记存储在向量中。

                  #include <iostream>
                  #include <algorithm>
                  #include <vector>
                  #include <string>
                  
                  using namespace std;
                  
                  
                  char one_line_string[] = "hello hi how are you nice weather we are having ok then bye";
                  char seps[]   = " ,\t\n";
                  char *token;
                  
                  
                  
                  int main()
                  {
                     vector<string> vec_String_Lines;
                     token = strtok( one_line_string, seps );
                  
                     cout << "Extracting and storing data in a vector..\n\n\n";
                  
                     while( token != NULL )
                     {
                        vec_String_Lines.push_back(token);
                        token = strtok( NULL, seps );
                     }
                       cout << "Displaying end result in vector line storage..\n\n";
                  
                      for ( int i = 0; i < vec_String_Lines.size(); ++i)
                      cout << vec_String_Lines[i] << "\n";
                      cout << "\n\n\n";
                  
                  
                  return 0;
                  }
                  

                  【讨论】:

                    【解决方案27】:

                    使用vector 作为基类的快速版本,提供对其所有运算符的完全访问权限:

                        // Split string into parts.
                        class Split : public std::vector<std::string>
                        {
                            public:
                                Split(const std::string& str, char* delimList)
                                {
                                   size_t lastPos = 0;
                                   size_t pos = str.find_first_of(delimList);
                    
                                   while (pos != std::string::npos)
                                   {
                                        if (pos != lastPos)
                                            push_back(str.substr(lastPos, pos-lastPos));
                                        lastPos = pos + 1;
                                        pos = str.find_first_of(delimList, lastPos);
                                   }
                                   if (lastPos < str.length())
                                       push_back(str.substr(lastPos, pos-lastPos));
                                }
                        };
                    

                    用于填充 STL 集的示例:

                    std::set<std::string> words;
                    Split split("Hello,World", ",");
                    words.insert(split.begin(), split.end());
                    

                    【讨论】:

                    • 效率低下,而且您从 STL 容器派生 - 可能是您能做的最糟糕的事情之一。
                    【解决方案28】:

                    我使用以下

                    void split(string in, vector<string>& parts, char separator) {
                        string::iterator  ts, curr;
                        ts = curr = in.begin();
                        for(; curr <= in.end(); curr++ ) {
                            if( (curr == in.end() || *curr == separator) && curr > ts )
                                   parts.push_back( string( ts, curr ));
                            if( curr == in.end() )
                                   break;
                            if( *curr == separator ) ts = curr + 1; 
                        }
                    }
                    

                    PlasmaHH,我忘记包含用于删除带有空格的标记的额外检查(curr > ts)。

                    【讨论】:

                    • 不会被任意空格分割,并为后续空格生成空字符串。
                    【解决方案29】:

                    这是一个拆分函数:

                    • 是通用的
                    • 使用标准 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";,");
                    

                    【讨论】:

                    • 您忘记添加使用列表:“效率极低”
                    • @XanderTulip,你能更​​有建设性地解释一下如何或为什么?
                    • @XanderTulip:我假设您指的是它按值返回向量。 Return-Value-Optimization (RVO, google it) 应该解决这个问题。同样在 C++11 中,您可以通过移动引用返回。
                    • 这实际上可以进一步优化:可以使用 .emplace_back(str, start, pos - start) 代替 .push_back(str.substr(...))。通过这种方式,字符串对象在容器中构建,因此我们避免了移动操作 + .substr 函数完成的其他恶作剧。
                    • @zoopp 是的。好主意。当我写这篇文章时,VS10 没有 emplace_back 支持。我会更新我的答案。谢谢
                    【解决方案30】:

                    我的代码是:

                    #include <list>
                    #include <string>
                    template<class StringType = std::string, class ContainerType = std::list<StringType> >
                    class DSplitString:public ContainerType
                    {
                    public:
                        explicit DSplitString(const StringType& strString, char cChar, bool bSkipEmptyParts = true)
                        {
                            size_t iPos = 0;
                            size_t iPos_char = 0;
                            while(StringType::npos != (iPos_char = strString.find(cChar, iPos)))
                            {
                                StringType strTemp = strString.substr(iPos, iPos_char - iPos);
                                if((bSkipEmptyParts && !strTemp.empty()) || (!bSkipEmptyParts))
                                    push_back(strTemp);
                                iPos = iPos_char + 1;
                            }
                        }
                        explicit DSplitString(const StringType& strString, const StringType& strSub, bool bSkipEmptyParts = true)
                        {
                            size_t iPos = 0;
                            size_t iPos_char = 0;
                            while(StringType::npos != (iPos_char = strString.find(strSub, iPos)))
                            {
                                StringType strTemp = strString.substr(iPos, iPos_char - iPos);
                                if((bSkipEmptyParts && !strTemp.empty()) || (!bSkipEmptyParts))
                                    push_back(strTemp);
                                iPos = iPos_char + strSub.length();
                            }
                        }
                    };
                    

                    示例:

                    #include <iostream>
                    #include <string>
                    int _tmain(int argc, _TCHAR* argv[])
                    {
                        DSplitString<> aa("doicanhden1;doicanhden2;doicanhden3;", ';');
                        for each (std::string var in aa)
                        {
                            std::cout << var << std::endl;
                        }
                        std::cin.get();
                        return 0;
                    }
                    

                    【讨论】:

                    • 这只是一堵又大又丑的代码墙。你应该解释一下它背后的逻辑。
                    猜你喜欢
                    • 1970-01-01
                    • 2022-09-23
                    • 1970-01-01
                    • 2010-10-24
                    • 2020-01-04
                    • 2019-07-15
                    • 1970-01-01
                    • 2020-03-12
                    相关资源
                    最近更新 更多