算法解决方案:
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 循环是否基于范围根本没有区别,它只是简化了括号内的代码。算法更清晰,因为它们显示了意图。
就个人而言,我最初的阅读:
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
我说“引用范围元素,无论可能有什么类型”。回到 C++03,算法(因为它们是函数模板)是隐藏确切类型的方法,现在它们是一种方法。
我认为,最简单的算法与等效循环相比只有一点点优势。基于范围的 for 循环改进了循环(主要是通过删除大部分样板,尽管它们还有更多的东西)。因此,利润越来越紧,也许您在某些特定情况下会改变主意。但那里仍然存在风格差异。
在我看来,Effective STL Item 43:“Prefer algorithm calls to hand-written loops”。仍然是一个很好的建议。
我通常编写包装函数来摆脱begin()
/end()
地狱。如果这样做,您的示例将如下所示:
my_util::generate(numbers, rand);
我相信它在传达意图和可读性方面都优于基于范围的 for 循环。
话虽如此,我必须承认,在 C++98 中,一些 STL 算法调用产生了无法发音的代码,并且遵循“首选算法调用而不是手写循环”似乎不是一个好主意。幸运的是,lambdas 改变了这一点。
考虑以下来自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; } );
在我看来,手动循环虽然可能会减少冗长,但缺乏可读性:
for (int& x : numbers) x = rand();
我不会使用这个循环来初始化1由numbers定义的范围,因为当我查看它时,在我看来它正在迭代一个数字范围,但实际上它没有(本质上),即而不是从范围读取,它正在写入范围。
当您使用std::generate
.
1.在这个上下文中初始化意味着给容器的元素赋予有意义的值。
有些事情你不能(简单地)用基于范围的循环来做,而将迭代器作为输入的算法可以。例如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
,即使使用 lambdas 也很烦人:
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
});
应该注意的一件事是算法表达了做了什么,而不是如何做。
基于范围的循环包括事情的完成方式:从第一个开始,应用并进入下一个元素,直到结束。即使是一个简单的算法也可以做不同的事情(至少特定容器的一些重载,甚至不考虑可怕的向量),并且至少它的完成方式与编写器无关。
对我来说,差异很大,尽可能多地封装,并且尽可能使用算法来证明句子的合理性。
基于范围的 for 循环就是这样。直到当然标准被改变。
算法是一个函数。一个对其参数提出一些要求的函数。这些要求在标准中进行了表述,以允许例如利用所有可用执行线程并自动加快速度的实现。