题 迭代字符串单词的最优雅方式[关闭]


迭代字符串的最优雅的方法是什么?可以假设该字符串由用空格分隔的单词组成。

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

2638


起源


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


答案:


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

1189



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


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

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

template<typename Out>
void split(const std::string &s, char delim, Out result) {
    std::stringstream ss(s);
    std::string item;
    while (std::getline(ss, 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", ':');

2308



为了避免它跳过空令牌,做一个 empty() 检查: if (!item.empty()) elems.push_back(item) - 0x499602D2
如果delim包含两个字符 ->? - herohuyongtao
@herohuyongtao,此解决方案仅适用于单个字符分隔符。 - Evan Teran
@JeshwanthKumarNK,这没有必要,但是它允许你做一些事情,比如将结果直接传递给像这样的函数: f(split(s, d, v)) 同时仍然享有预先分配的好处 vector 如果你喜欢。 - Evan Teran
警告:split(“one:two :: three”,“:”)和split(“one:two :: three:”,“:”)返回相同的值。 - dshin


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

文件 详情。


794



速度与此无关,因为这两种情况都比类似strtok的函数慢得多。 - Tom
对于那些还没有提升的人... bcp复制超过1,000个文件:) - Roman Starkov
strtok是一个陷阱。它的线程不安全。 - tuxSlayer
@Ian嵌入式开发人员并非都使用boost。 - ACK_stoverflow
作为附录:我只在必须使用boost时,通常我更喜欢添加到我自己的独立和可移植的代码库中,这样我就可以实现小的精确特定代码,从而实现给定的目标。这样代码就是非公开的,高效的,简单的和可移植的。 Boost有它的位置,但我会建议它有点过分用于标记字符串:你不会把你的整个房子运到一个工程公司,用一个新的钉子钉在墙上挂一张照片....他们可能会这样做非常好,但远远超过了利弊。 - GMasucci


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

321



太糟糕了它只能在空间上分裂 ' '... - Offirmo


对于那些不能很好地牺牲代码大小的所有效率并将“高效”视为一种优雅的人来说,下面的内容应该是一个最佳点(我认为模板容器类是一个非常优雅的添加。):

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> 哪里 subString 因为速度提升令人难以置信,所以没有任何副本。

它比本页面上最快的标记化速度快一倍以上,几乎是其他标记的5倍。此外,使用完美的参数类型,您可以消除所有字符串和列表副本,以提高速度。

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

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

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


168



我非常喜欢这个,但是对于g ++(可能是很好的练习),任何使用它的人都需要typedef和typenames: typedef ContainerT Base; typedef typename Base::value_type ValueType; typedef typename ValueType::size_type SizeType;  然后相应地替换value_type和size_types。 - aws
对于我们这些模板材料和第一个注释完全是外来的人来说,一个包含必需包含的用例示例很可爱。 - Wes Miller
好吧,我明白了。我将来自aws'注释的C ++行放在tokenize()的函数体内,然后编辑tokens.push_back()行以将ContainerT :: value_type更改为ValueType并将(ContainerT :: value_type :: size_type)更改为(尺码类型)。修复了g ++一直在抱怨的位。只需将其作为tokenize(some_string,some_vector)调用; - Wes Miller
除了对示例数据运行一些性能测试之外,主要是我将它减少到尽可能少的指令,并且尽可能少地使用仅引用其他字符串中的偏移量/长度的子字符串类来启用内存副本。 (我自己动手,但还有其他一些实现)。不幸的是,没有太多其他人可以做些改进,但增量增加是可能的。 - Marius
这是什么时候的正确输出 trimEmpty = true。请记住 "abo" 在这个答案中不是分隔符,而是分隔符字符列表。修改它以获取单个分隔符字符串是很简单的(我想 str.find_first_of 应该改为 str.find_first,但我可能是错的......无法测试) - Marius


这是另一种解决方案。它结构紧凑,效率高:

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

150



第一个版本很简单,完美地完成了工作。我要做的唯一改变是直接返回结果,而不是将其作为参数传递。 - gregschlom
输出作为效率参数传递。如果返回结果,则需要向量的副本或者必须释放的堆分配。 - Alec Thomas
我上面评论的一个小小的附录:如果使用C ++ 11移动语义,这个函数可以返回向量而不会受到惩罚。 - Alec Thomas
@AlecThomas:即使在C ++ 11之前,大多数编译器都不会通过NRVO优化返回副本吗? (+1反正;非常简洁) - Marcelo Cantos
在所有答案中,这似乎是最具吸引力和灵活性的答案之一。与带有分隔符的getline一起,虽然它是一个不太明显的解决方案。 c ++ 11标准没有任何内容吗?这些天c ++ 11是否支持穿孔卡? - Spacen Jasset


这是我最喜欢的迭代字符串的方法。你可以随心所欲地做任何事。

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

istringstream iss(line, istringstream::in);

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

106



是否可以申报 word 作为一个 char? - abatishchev
抱歉abatishchev,C ++不是我的强项。但我想,添加一个内循环来循环遍历每个单词中的每个字符并不困难。但是现在我相信当前的循环依赖于单词分离的空间。除非你知道每个空格之间只有一个字符,在这种情况下你可以将“单词”转换成一个字符...对不起,我不能提供更多的帮助,我已经意味着要刷新我的C ++ - gnomed
如果你将word声明为char,它将迭代每个非空白字符。这很简单,可以尝试: stringstream ss("Hello World, this is*@#&$(@ a string"); char c; while(ss >> c) cout << c; - Wayne Werner