循环整数范围的最简洁方法

63
自从C++11引入了基于范围的for循环(range-based for in c++11),如何以最简洁的方式表达循环整数范围?
不要使用
for (int i=0; i<n; ++i)

我想写类似于

for (int i : range(0,n))

新标准是否支持这种类型的内容?
更新:本文介绍如何在C++11中实现一个范围生成器:C++中的生成器

3
@Xeo,你是认真的吗?循环遍历整数范围(我非常确定)在除了简单的“HelloWorld”程序之外的每个C源代码中都有。或者你的意思是:为什么要使用基于范围的“for”来做那件事呢? - paxdiablo
1
Boost.Range有irange。 - Cat Plus Plus
2
@paxdiablo:C源代码是无关紧要的。问题明确地涉及到C++11。 - Puppy
3
@Xeo,我现在理解你了。 你的观点很好,除了一个小用例(我在许多for/foreach问题中发现)。 有时您确实需要知道索引。 尽管这可能是一个小用例,但我可以想象出一些可能需要它的事情-就在前几天,我让我的孩子做了一个12倍表程序,尽管不是在C++上,因为我认为对于一个9岁的孩子来说,那会是残忍的惩罚 :-) - paxdiablo
3
@Xeo:有很多时候我需要循环一定范围内的整数,即使只是为了同时循环容器并访问索引。 - Nicol Bolas
显示剩余5条评论
6个回答

39
虽然C++11标准没有提供这个功能,但是你可以编写自己的视图或使用boost提供的视图库:
#include <boost/range/irange.hpp>
#include <iostream>

int main(int argc, char **argv)
{
    for (auto i : boost::irange(1, 10))
        std::cout << i << "\n";
}

此外,Boost.Range 还包含一些更有趣的范围,结合新的 for 循环可能会非常有用。例如,您可以获得一个reversed view

1
基本上,一个人可以自己实现一个满足范围for要求的类。 - Euri Pinhollow
1
@EuriPinhollow,我选择了不好的措辞。现在已经更新。 - ixSci
你是否知道一个C++14/C++17的最小自包含irange实现? - einpoklum
1
@ixSci:来吧,使用堆?这对编译器来说将是非常困难的(https://godbolt.org/g/vcPKXc)。 - einpoklum
1
@ixSci:好的,说得也有道理,但我的观点是实现需要(轻松地)转换为与普通for循环相同的指令,并且在堆上创建std::vector并不是这样做到的。 - einpoklum
显示剩余3条评论

37

最简洁的方法仍然是这样的:

for (int i=0; i<n; ++i)

我猜你是可以做到这个,但我不认为它很简洁:

#include <iostream>

int main()
{
  for ( auto i : { 1,2,3,4,5 } )
  {
    std::cout<<i<<std::endl;
  }
}

11
我会为那第一句话点一个赞。有时候老方法仍是最好的 :-) - paxdiablo
除非n是一个昂贵的表达式,否则irange是简洁且高效的。 - Macke
6
这不是很简洁的代码。它有三个(或如果你想要严谨一些就是两个)不同的命令语句,你必须将它们组合起来才能意识到这是一个循环。一点也不简洁。此外,它没有表明变量“i”是否重要,或者作者只是想运行一段代码“n”次。 - einpoklum
@einpoklum:“neatest”并不等同于“整洁”,这是根据某些隐含的黄金标准所判断的。它只是意味着“在纯粹的、裸露的C++中还没有更好的替代方案,令人遗憾”。 - Sz.
1
我不小心打了“for (int j=0; j<n; ++i)”好几次,让我觉得这样做并不好。 - Don Hatch
我正在从另一个角度寻找“更整洁”的解决方案:由于 i 的递增,i 不能是 const,尽管有人希望表达 i 不应该被修改(除了递增)。第二种解决方案虽然不是“那么整洁”,但至少允许将 i 设为 const - Roland Sarrazin

37
使用C++20,我们将拥有ranges。如果您无法访问C++20,可以通过从其作者Eric Niebler的github上下载最新的稳定版本,或者访问Wandbox来尝试它们。您感兴趣的是ranges::views::iota,它使得以下代码合法:
#include <range/v3/view/iota.hpp>
#include <iostream>

int main() {
    using namespace ranges;
    
    for (const int i : views::iota(1, 10)) {
        std::cout << i << ' ';
    }
}

这种方法的优点在于视图是“惰性”的。这意味着即使`views::iota`表示从`1`到`10`(不包括`10`)的范围,该范围内的整数在任何给定时间内都不会超过一个。元素是“按需生成”的。
如果您有C++20的访问权限,这个版本可以直接使用。
#include <ranges>
#include <iostream>

int main() {  
    for (const int i : std::views::iota(1, 10)) {
        std::cout << i << ' ';
    }
}

1
挑剔一点:据我所知,ranges-v3仅适用于C++14,而不适用于C++11;而这个问题是关于C++11的 - 它被标记为这样,并且OP在正文中也这么说了。 - einpoklum
@einpoklum 我相信它们可以与C++11一起使用。虽然我没有测试过,但我相信Eric Niebler在他的演讲中说过它们与C++11兼容。 - Fureeish
该库文档表示支持C++14及以上版本。另外,如果不麻烦的话,您能告诉我您回答这个问题的确切时间吗?(这与一些关于元数据的争论有关)我只看到小时分辨率的时间。 - einpoklum
也许你应该只包含range/v3的部分内容? - einpoklum
1
这个的好处是你可以将迭代变量i标记为const - bers
显示剩余2条评论

3
如果您不介意反向循环,您可以将
```for (int i=0; i
简化为
```for (int i=n; i--; )```

0

根据你需要对整数执行的操作,也要考虑<numeric>头文件,特别是结合std::transformstd::fill使用std::iota函数来处理不同情况。


我怎么能相信编译器会优化掉所有这些,使它再次成为一个简单的循环呢?特别是因为你可能需要一个辅助向量来存储0...n-1的值。 - einpoklum
5
如果你不信任编译器的优化,使用C语言而不是C++。 - Emilio Garavaglia
2
我不是在谈论那种编译器优化。我指的是更加前沿的优化,例如当编译器可以确定malloc和free调用实际上并不必要时,将其删除,这正是你在这里所需要的。 - einpoklum

-4

嗯,我真的很喜欢这个解决方案,在这里提供(抱歉,它没有被翻译成英语):

#define FOR(I,UPPERBND) for(int I = 0; I<int(UPPERBND); ++I)

主要思想是这样描述的:当我们谈论简单的迭代索引循环时,我们不需要去考虑它。然而,当我们使用for(;;)结构时-总是有三个步骤:初始化、结束条件检查、迭代。而对于这样一个简单的循环来说,这是一种过度设计,只需i:[0,n)即可。 我喜欢这个想法,我们希望用简单的方式写简单的东西。当你看到FOR(i,N)时-你只需知道没有什么特别的。当您看到for(;;)结构时-您必须更加小心,并看到它的所有三个部分。 这是那篇文章中的一个例子:
for (int iter=0; iter<nb_iter; iter++) {          // some iterative computation
    for (int c=0; c<mesh.cells.nb(); c++)         // loop through all tetrahedra
        for (int lv0=0; lv0<4; lv0++)             // for every pair of
            for (int lv1 = lv0+1; lv1<4; lv1++)   // vertices in the tet
                for (int d=0; d<3; d++) {         // do stuff for each of 3 dimensions
                    nlRowScaling(weight);
                    nlBegin(NL_ROW);
                    nlCoefficient(mesh.cells.vertex(c, lv0)*3 + d,  1);
                    nlCoefficient(mesh.cells.vertex(c, lv1)*3 + d, -1);
                    nlEnd(NL_ROW);
                }
    [...]
}

成为一个:

FOR(iter, nb_iter) {
    FOR(c, mesh.cells.nb())
        FOR(lv0, 4)
            for (int lv1 = lv0+1; lv1<4; lv1++)
                FOR(d, 3) {
                    nlRowScaling(weight);
                    nlBegin(NL_ROW);
                    nlCoefficient(mesh.cells.vertex(c, lv0)*3 + d,  1);
                    nlCoefficient(mesh.cells.vertex(c, lv1)*3 + d, -1);
                    nlEnd(NL_ROW);
                }
    [...]
}

你看,你应该把注意力集中在哪里。


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