C++ STL - 遍历序列中的所有内容

5
我有一个序列,例如:
std::vector< Foo > someVariable;

"我想要一个循环,可以遍历其中的所有内容。
我可以这样做:"
for (int i=0;i<someVariable.size();i++) {
    blah(someVariable[i].x,someVariable[i].y);
    woop(someVariable[i].z);
}

或者我可以这样做:
for (std::vector< Foo >::iterator i=someVariable.begin(); i!=someVariable.end(); i++) {
    blah(i->x,i->y);
    woop(i->z);
}

这两种方法似乎都需要很多重复/过度输入。在理想的语言中,我希望能够像这样做一些事情:
for (i in someVariable) {
    blah(i->x,i->y);
    woop(i->z);
}

似乎在一个序列中迭代所有东西是一种非常常见的操作。有没有一种方法可以使代码不必比应有的长度多一倍?

第二个比第一个更好,因为并非所有类型的序列都可以被索引。 - newacct
C++0x将使第二个更容易阅读,因为您可以使用auto声明i:for(auto i = x.begin(); i!= x.end(); ++i){...} - jmucchiello
6个回答

12
您可以使用标准库中的for_each。您可以向它传递一个函数对象或一个函数。我喜欢的解决方案是BOOST_FOREACH,它就像其他语言中的foreach一样。C+0x 也将有这个功能。
例如:
#include <iostream>
#include <vector>
#include <algorithm>
#include <boost/foreach.hpp>

#define foreach BOOST_FOREACH 

void print(int v)
{
    std::cout << v << std::endl;
}

int main()
{
    std::vector<int> array;

    for(int i = 0; i < 100; ++i)
    {
        array.push_back(i);
    }

    std::for_each(array.begin(), array.end(), print); // using STL

    foreach(int v, array) // using Boost
    {
        std::cout << v << std::endl;
    }
}

1
+1:我也正要写同样的内容。另外,如果创建向量元素的引用,第一个for循环的长度就不会那么糟糕了。 - sellibitze
1
值得知道的是,BOOST_FOREACH接受引用类型(int&而不是int),在这种情况下,您可以在迭代过程中修改容器。 - Steve Jessop

5

除了AraK已经建议的BOOST_FOREACH之外,在C++中今天您有以下两个选项:

void function(Foo& arg){
  blah(arg.x, arg.y);
  woop(arg.z);
}

std::for_each(someVariable.begin(), someVariable.end(), function); 

struct functor {
  void operator()(Foo& arg){
    blah(arg.x, arg.y);
    woop(arg.z);
  }
};

std::for_each(someVariable.begin(), someVariable.end(), functor());

两种方法都要求您在其他地方指定循环的“body”,可以是函数或者是函数对象(重载了operator()的类)。这可能是一件好事(如果您需要在多个循环中执行相同的操作,您只需要定义一次函数),但也可能有点繁琐。函数版本可能会稍微低效,因为编译器通常无法内联函数调用。(一个函数指针作为第三个参数传递,并且编译器必须进行更详细的分析以确定它指向哪个函数)
函数对象版本基本上没有额外开销。因为将类型为functor的对象传递给for_each,编译器知道要调用哪个函数:functor::operator(),因此可以轻松地进行内联,并且与原始循环一样有效。
C++0x将引入lambda表达式,使第三种形式成为可能。
std::for_each(someVariable.begin(), someVariable.end(), [](Foo& arg){
  blah(arg.x, arg.y);
  woop(arg.z);
});

最后,它还将介绍一种基于范围的for循环:
for(Foo& arg : my_someVariable)
{
  blah(arg.x, arg.y);
  woop(arg.z);
}

如果您可以使用支持C++0x子集的编译器,那么您可能可以使用最后两种形式中的一种或两种。否则,惯用的解决方案(不使用Boost)是像前两个示例中的一个一样使用for_each


一些编译器可以看到函数指针并进行内联。例如,GCC 4.4有一个-findirect-inlining选项。 - Zan Lynx
就像我说的那样,编译器必须进行更详细的分析才能弄清楚它。这并非不可能,我认为大多数编译器在简单情况下都会这样做,但您不能指望每种情况都能内联。为什么要让编译器比必要的更难呢? - jalf

1
顺便提一下,MSVS 2008有一个“for each”C++关键字。请查看如何使用for each迭代STL集合
int main() {
   int retval = 0;

   vector<int> col(3);
   col[0] = 10;
   col[1] = 20;
   col[2] = 30;

   for each( const int& c in col )
      retval += c;

   cout << "retval: " << retval << endl;
}

1

优先使用算法调用而不是手写循环

有三个原因:

1)效率:算法通常比程序员编写的循环更高效。

2)正确性:编写循环比调用算法更容易出现错误。

3)可维护性:算法调用通常可以产生比相应的显式循环更清晰和直接的代码。


算法调用经常会产生代码?难道你不觉得缺少一个“更好”或者“更简单”之类的词吗? - jalf

0

除了for_each(),几乎所有其他算法都更好

原因有两个:

  1. for_each非常通用,无法告诉您实际正在执行什么操作,只是告诉您正在对序列中的所有项目执行某些操作。
  2. 更专业的算法通常会更简单、更直接

考虑一下之前回复中的一个例子:

void print(int v)
{
    std::cout << v << std::endl;
}
// ...
std::for_each(array.begin(), array.end(), print); // using STL

使用std::copy代替,整个过程变成了: std::copy(array.begin(), array.end(), std::ostream_iterator(std::cout, "\n"));


0
"struct functor {
  void operator()(Foo& arg){
    blah(arg.x, arg.y);
    woop(arg.z);
  }
};

std::for_each(someVariable.begin(), someVariable.end(), functor());"

我认为像这样的方法对于一个简单的问题来说常常过于复杂。

do i=1,N
 call blah( X(i),Y(i) )
 call woop( Z(i) )
end do

即使它已经40年了(显然不是C++),也很清晰。

如果容器始终是一个向量(STL名称),我认为使用索引没有问题,将该索引称为整数也没有问题。

在实践中,通常需要同时迭代多个相同大小的容器,并从每个容器中剥离出一个数据,然后对它们进行处理。在这种情况下,为什么不使用索引呢?

至于SSS上面提到的第2点和第3点,我认为对于复杂的情况可能是这样,但通常迭代1...N与其他任何东西一样简单明了。

如果你必须在白板上解释算法,你能否在使用“i”或不使用“i”的情况下更快地做到这一点?我认为如果你的口头解释使用索引更清晰,请在代码空间中使用它。

将重型C++火力留给难度较大的目标。


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