C++中的foreach算法

3

有没有一种方法可以从我传递给foreach的函数中返回值?

例如: 我有一个:

void myfunction (int i) 
{
        cout << " " << i;
}

vector<int> myvector;
myvector.push_back(10);
for_each (myvector.begin(), myvector.end(), myfunction);

假设我想按照某种规则计算向量中元素的数量,我希望从myFunction获得一个返回值,这是可能的吗?

不要使用for_each来做那件事。有更好的算法可用于此目的。 - jalf
10个回答

13

有一个专门用来计算值出现次数的std::count和计算谓词函数返回true次数的std::count_if。不要滥用std::for_each,它并不是为此而设计的。


1
我认为在这种特定情况下,OP试图举一个例子(“假设”)来说明他想要实现的目标,虽然std::count对于这种情况很有用,但你的答案并没有展示如何解决更通用的问题。 - Matthieu M.

9

for_each将返回您传递给它的函数对象的副本。这意味着您可以执行以下操作:

template <typename T>
class has_value
{
    has_value(const T& pValue) : mValue(pValue), mFlag(false) {}

    void operator()(const T& pX)
    {
        if (pX == mValue)
            mFlag = true;
    }

    operator bool(void) const { return mFlag; }
private:
    T mValue;
    bool mFlag;
};

bool has_seven = std::for_each(myvector.begin(), myvector.end(), has_value<int>(7));

例如,如果要进行计数等操作,请查看algorithm,看看您的函数是否已经存在。 (例如count

1
当你的回答出现时,我已经完成了一半的代码,很高兴看到我不是唯一一个这样做的人。 - tzenes
1
+1 给这个好玩的强制转换技巧,我想我还有很多关于函数对象要学习。 - int3
3
这种做法不具备可移植性,因为无法保证 for_each 函数在将传入的复制品应用于输入范围之前不会复制输入函数的返回值,也无法保证它不会制作多个副本并将不同的副本应用于范围中的每个成员。 - CB Bailey
3
标准规定:“对于范围 [first, last) 中的每个迭代器解引用后的结果,从 first 开始依次应用函数 f,直到 last - 1。返回值为 f。”SGI 网站也指出:“for_each 对每个元素应用函数对象后返回该对象。” 我认为如果返回已应用的函数对象的副本,任何实现都不符合标准,对吗?返回值的整个目的是获取已应用的函数对象。 - GManNickG
1
这根本行不通。因为实现可以自由地复制您的谓词,所以最终可能会得到一份从未在其生命周期中看到单个 7 的副本... - Matthieu M.
标准并没有提到可以自由复制,只规定 f 应该应用于元素,并返回 f。任何未能做到这一点的 for_each 实现都会变得不太有用,并且根据我的理解是不符合标准的。 - GManNickG

5

不行,但是你可以将myfunction转换成一个函数对象,传递一个指向内存的指针,并通过该指针存储你的返回值。

struct MyFunctor {
    int *count;
    MyFunctor(int *count_) : count(count_) { }
    void operator()(int n) {
        if (n > 5) (*count)++;
    }
};

int main() {
    vector<int> vec;
    for (int i=0; i<10; i++) vec.push_back(i);
    int count = 0;
    for_each(vec.begin(), vec.end(), Myfunctor(&count));
    printf("%d\n", count);
    return 0;
}
编辑:正如评论所指出的那样,我的第一个例子会失败,因为for_each会复制MyFunctor,所以我们不能从原始对象中检索返回值。我已经修正了原始方法的方向;但你真的应该看看GMan的解决方案,它更加优雅。我不确定可移植性,但它在我的gcc(4.4.2)上确实有效。正如其他人提到的,尽可能使用<algorithm>提供的功能。

2
这不应该起作用。函数对象被复制到 for_each 中,而那个副本才是计数的那个。你应该使用 for_each 的返回值。 - GManNickG
实际的替代方案是让MyFunctor持有对外部对象的引用,这样所有的拷贝都共享同一引用,因此指向的对象将被正确地更新。 - Matthieu M.
而且还要减1,因为“for_each”算法本来就不是正确的选择。 - jalf
他提出了一个更一般化的问题,但随后给出了一个更具体的用例。回答更一般化的问题有什么不对吗?其他人提出了更适合OP示例的STL算法,并且我在我的答案中已经退让了;但如果OP没有接受那些算法,那么也许他更关心一般情况。 - int3

3
如果你想要强大的 foreach,可以使用 BOOST_FOREACH 宏。此外,boost 大多是头文件库,所以你只需要将 boost_foreach.hpp(如果我没记错的话)包含到你的项目中即可。示例代码如下:
BOOST_FOREACH( int & i , my_vector )
{
     i = 0;
}

My_vector可以是vector<int>,也可以是int[]或任何其他类型的迭代器。


不错的替代方案,虽然我不喜欢宏,但它确实非常方便。 - Matthieu M.

2

1

std::for_each 不是为此而设计的。使用 std::count 计算等于某个值的元素数量,或者使用 std::count_if 计算满足某个谓词的元素数量:

std::vector<SomeType> vec;
std::count(vec.begin(), vec.end(), SomeType(9));
/*or */
bool myfunc(const SomeType& v)
{
    return v == 9;
}
std::count_if(vec.begin(), vec.end(), f);

如果您只想将容器的内容复制到像 std::cout 这样的 ostream 对象中,请使用 std::copy:

std::vector<SomeType> vec;
...
std::copy(vec.begin(), vec.end(), \
    std::ostream_iterator<SomeType>(std::cout," "));

如果您需要从每次调用函数中返回值,请使用 std::transform:
std::vector<SomeType> src;
std::vector<SomeType> result;
int myfunc(int val)
{
    ...
}
std::transform(src.begin(), src.end() \
    result.begin(), myfunc);

std::transform也被重载了,因此它适用于二元函数和一元函数。


1

好的,我担心你在选择计数问题时选择了一个不太合适的例子...

问题在于,for_each非常通用,而特定实现存在更具体的算法(如countaccumulatetransform等)。

所以让我们选择另一个例子:通常使用for_each对其处理的对象应用变异操作。这并不妨碍您在执行此操作的同时收集统计信息。

但是我们必须小心,虽然for_each确实返回一个Predicate对象,但不能保证该对象已用于范围内的每个项目。实现可以自由地复制谓词并在范围的一部分上使用副本...因此最终返回的副本可能是错误的。

class Predicate
{
public:
  Predicate(size_t& errors) : m_errors(errors) {}
  void operator()(MyObject& o)
  {
    try { /* complicated */ } catch(unfit&) { ++m_errors; }
  }
private:
  size_t& m_errors;
};

std::vector<MyObject> myVec;
// fill myVec

size_t errors = 0;
std::for_each(myVec.begin(), myVec.end(), Predicate(errors));

这里的诀窍在于原始谓词的所有副本都指向同一个size_t变量,因此该变量已被正确更新。

1

你可以像GMan展示的那样,适应std::for_each来实现这个功能。

但更好的解决方案是使用正确的算法。

你应该能够使用std::countstd::count_if,或者可能是std::accumulate。 这些函数允许你返回对整个序列进行处理的一个结果。

另外,std::transform允许你为序列中的每个元素返回一个结果,创建一个包含结果的新输出序列。


0

这是可行的:

int main()
{
  std::vector<int> v;
  for (int i = 1; i <= 10; i++)
    v.push_back(i);

  int hits = 0;
  CountEven evens(&hits);
  std::for_each(v.begin(), v.end(), evens);
  std::cout << "hits = " << hits << std::endl;

  return 0;
}

但是看一下 CountEvens 的糟糕实现:

class CountEven {
  public:
  CountEven(int *hits) : hits(hits) {}
  CountEven(const CountEven &rhs) : hits(rhs.hits) {}
  void operator() (int n) { if (n % 2 == 0) ++*hits; }

  private:
  int *hits;
};

请注意,复制构造函数会导致多个实例共享同一个指针。
使用 std::countstd::count_if

0

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