算法解决方案:
std::generate(numbers.begin(), numbers.end(), rand);
基于范围的for循环解决方案:
for (int& x : numbers) x = rand();
为什么我要std::generate
在C ++ 11中使用比基于范围的for循环更详细的信息?
算法解决方案:
std::generate(numbers.begin(), numbers.end(), rand);
基于范围的for循环解决方案:
for (int& x : numbers) x = rand();
为什么我要std::generate
在C ++ 11中使用比基于范围的for循环更详细的信息?
begin()
和end()
?
range
在他们的工具箱中都有一个功能。(即for(auto& x : range(first, last))
)
boost::generate(numbers, rand); // ♪
Answers:
第一个版本
std::generate(numbers.begin(), numbers.end(), rand);
告诉我们您要生成一个值序列。
在第二版中,读者必须自己弄清楚这一点。
节省键入时间通常不是最佳选择,因为它通常会浪费在阅读时间上。多数代码读取的内容远远超过其键入的内容。
numbers
无缘无故地指定两次。因此:boost::range::generate(numbers, rand);
。没有理由在完善的库中不能同时拥有更短和更清晰的代码。
std::generate(number.begin(), numbers.begin()+3, rand)
,不是吗?因此,我想number
有时指定两次可能有用。
std::generate()
,则可以std::generate(slice(number.begin(), 3), rand)
使用假设的范围切片语法std::generate(number[0:3], rand)
,甚至可以做得更好,甚至可以做得更好,例如这样可以消除重复的number
同时仍然允许灵活指定范围的一部分。从三个论点开始做相反的工作std::generate()
比较乏味。
无论for循环是否基于范围,都没有任何区别,它仅简化了括号内的代码。算法更清晰,因为它们表明了意图。
我个人最初阅读以下内容:
std::generate(numbers.begin(), numbers.end(), rand);
是“我们正在分配范围内的所有内容。范围是numbers
。分配的值是随机的”。
我的初步阅读:
for (int& x : numbers) x = rand();
是“我们正在对范围内的所有内容执行操作。范围是numbers
。我们要做的是分配随机值。”
这些非常相似,但不完全相同。我可能想引起一读的一个可能的原因是,因为我认为有关此代码的最重要事实是它分配给该范围。所以有您的“我为什么要...”。我使用generate
C ++std::generate
是因为“范围分配”。就像btw一样std::copy
,两者之间的区别就是您要从中进行分配。
不过,还有一些混杂因素。与基于numbers
迭代器的算法相比,基于范围的for循环本质上更直接地表示范围为。这就是为什么人们使用基于范围的算法库的原因:boost::range::generate(numbers, rand);
看起来比std::generate
版本更好。
与此相反,int&
在基于范围的for循环中会出现皱纹。如果范围的值类型不是int
,那我们将在此做一些令人讨厌的微妙工作,这取决于它是否可以转换为int&
,而generate
代码仅取决于rand
可分配给元素的返回。即使值类型是int
,我仍然可能会停止考虑它是否是。因此auto
,推迟了对类型的思考,直到我看到要分配的内容为止-auto &x
我说“请引用range元素,无论可能具有什么类型”。早在C ++ 03,算法(因为它们是函数模板)都将隐藏确切类型的方式,现在他们一个方式。
我认为,一直以来,最简单的算法仅比等效循环具有边际优势。基于范围的for循环可改善循环(主要是通过删除大部分样板,尽管它们要多得多)。因此,利润越来越紧,在某些特定情况下,您可能会改变主意。但是那里仍然存在风格差异。
operator int&()
?:)
int&
为SomeClass&
,现在您必须担心转换运算符和未标记的单参数构造函数explicit
。
operator int&()
和operator int const &() const
可以工作,但是同样可以通过重载operator int() const
和来工作operator=(int)
。
在我看来,有效的STL项目43:“首选算法调用优先于手写循环。” 仍然是一个很好的建议。
我通常编写包装函数来摆脱begin()
/ end()
hell。如果这样做,您的示例将如下所示:
my_util::generate(numbers, rand);
我认为它在传达意图和可读性方面都超过了for循环的范围。
话虽如此,我必须承认,在C ++ 98中,某些STL算法调用产生了无法言喻的代码,并且遵循“首选对手写循环的算法调用”似乎不是一个好主意。幸运的是,lambda改变了这一点。
考虑来自Herb Sutter的以下示例:Lambdas,Lambdas Everywhere。
任务:在v中找到第一个元素是> x
和< y
。
没有lambda:
auto i = find_if( v.begin(), v.end(),
bind( logical_and<bool>(),
bind(greater<int>(), _1, x),
bind(less<int>(), _1, y) ) );
带lambda
auto i=find_if( v.begin(), v.end(), [=](int i) { return i > x && i < y; } );
在我看来,人工循环,但可能会减少冗长,缺乏readabitly:
for (int& x : numbers) x = rand();
我不会使用此循环来初始化1由数字定义的范围,因为当我看它时,似乎在一个数字范围内进行迭代,但是实际上它并没有(本质上),即不是从范围读取,它正在写入范围。
使用时意图更加清晰std::generate
。
1.在这种情况下,初始化意味着为容器的元素赋予有意义的值。
std::generate
,这可以由C ++程序员假定(如果他们不熟悉,他们会查找它,得到相同的结果)。
&
字符该怎么办?)算法的优点是它们显示了意图,而对于循环则必须进行推断。如果循环的实现中存在错误,则不清楚这是错误还是故意的。
std::generate
,仅看一眼就可以说此函数正在生成某些内容。什么的东西是由第三个参数的函数回答。我认为这要好得多。
对于基于范围的循环,有些事情是您做不到的(简单地),而将迭代器作为输入的算法则可以做到。例如std::generate
:
使用一个发行版中的变量填充容器limit
(不包括limit
上的有效迭代器numbers
),而使用另一个发行版中的变量填充容器。
std::generate(numbers.begin(), limit, rand1);
std::generate(limit, numbers.end(), rand2);
基于迭代器的算法可让您更好地控制要操作的范围。
对于的特殊情况std::generate
,我同意先前关于可读性/意图问题的答案。std :: generate在我看来似乎更清晰。但是我承认这在某种程度上是一种品味问题。
就是说,我还有另一个理由不放弃std :: algorithm-有些算法专门针对某些数据类型。
最简单的示例是std::fill
。通用版本被实现为提供范围内的for循环,并且在实例化模板时将使用该版本。但不总是。例如,如果您提供一个范围std::vector<int>
-通常它实际上会在后台调用memset
,从而产生更快,更好的代码。
所以我想在这里打一个效率卡。
您的手写循环可能和std :: algorithm版本一样快,但是几乎不可能更快。不仅如此,std :: algorithm可能专门用于特定的容器和类型,并且是在干净的STL接口下完成的。
我的答案可能是,否。如果我们在谈论C ++ 11,那么也许(更像是没有)。例如std::for_each
,即使与lambda一起使用也很烦人:
std::for_each(c.begin(), c.end(), [&](ExactTypeOfContainedValue& x)
{
// do stuff with x
});
但是使用基于范围的for会更好:
for (auto& x : c)
{
// do stuff with x
}
另一方面,如果我们谈论的是C ++ 1y,那么我认为不可以,基于范围的for不会淘汰这些算法。在C ++标准委员会中,有一个研究小组正在研究将范围添加到C ++的提案,并且还在处理多态lambda。范围将消除使用迭代器对的需要,而多态lambda将使您不必指定lambda的确切参数类型。这意味着std::for_each
可以这样使用(不要把这个当成困难的事实,这就是今天的梦想):
std::for_each(c.range(), [](x)
{
// do stuff with x
});
[]
使用lambda进行写入,您可以指定零捕获?也就是说,与仅编写循环体相比,您已经从按词法出现的变量查找上下文中分离出了一部分代码。隔离通常对读者有帮助,而在阅读时却很少考虑。
for_each
即使是在使用lambda的情况下,在这种假设的C ++ 1y中仍然没有意义。foreach + captureing lambda目前是编写基于范围的for循环的详细方法,它变得比以前的循环略显冗长,但仍然比循环更复杂。for_each
当然,这并不是说我不应该为自己辩护,但是即使在看到您的答案之前,我仍在思考,如果发问者想打败算法,他可能会被for_each
选为所有目标中最软的;-)
for_each
,但与基于范围的协议相比,它有一个微小的优势-您可以通过在其前面加上parallel_使其变得更容易使其并行化parallel_for_each
(如果使用PPL,并假定这样做是线程安全的) 。:-D
std::algorithm
。