我的老师今天发表了这篇演讲,讲述了如何在C++中不再/很少使用循环,而是使用STL算法.他还说如果我确实需要使用for循环,请确保它使用迭代器或基于范围的for循环.
然后他继续讨论常规循环:
for (int i = 0; i < N; ++i) ...
Run Code Online (Sandbox Code Playgroud)
说这实际上应该永远不会被使用.
现在我正在编写一些代码,而常规for循环只是一个救生员.它适用于很多情况,它简单而简短,众所周知,每个人都立即知道发生了什么,并且它可以很快完成工作(这是C++,而不是Python!).此外,在实现数学算法时也非常清楚,因为这些算法是用索引描述的......而不是使用STL算法或迭代器.因此,使用常规for循环匹配该样式.
更重要的是,它不仅具有所有这些好处,而且我甚至认为在某些情况下,它是唯一的选择.没有其他选择.至少实际上并非如此.
例如,如果我需要迭代一大堆类似数组的容器,并且我需要一个索引来访问元素,那么常规for循环为我提供了索引.使用STL算法几乎是不可能的,因为我需要boost和zip,所以使用迭代器也很麻烦.
为什么我不应该使用这个惊人的结构?
Max*_*hof 11
当存在现有std算法时,使用它几乎总是更好.搜索,排序,删除重复项,找到最大值(或第n个最大值)等是常见操作,并归结为单行.算法标题中的这些工具很棒,绝对应该优于"原始"循环.
有许多任务根本无法用这些现有功能有效和/或优雅地表达(例如,一次循环多个容器).这可能随着ranges可用/成熟而改变,但仍然有很多方法.
但是,通常还有一些不可否认的事情,通过基于索引的循环最清晰地表达出来.值得注意的是,如果您的元素操作涉及索引(例如打印它,或交叉引用完全不同的数据结构等),那么使用范围或现有算法非常麻烦,因为您必须首先提取索引(并压缩您的范围与iota不是真的更可读).
最后,这些都是工具.一个优秀的程序员试图为给定的任务选择最合适的工具,而不是遵循教条.
Cal*_*eth 10
在猜测,你的老师不喜欢for的正是你喜欢它的原因:
特定 std::vector<int> values { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
相比:
auto isEven = [](int i) { return (i % 2) == 0; };
auto val = *std::find_if(values.begin(), values.end(), isEven);
std::cout << val << "\n";
Run Code Online (Sandbox Code Playgroud)
至
int val;
for (auto && value : values)
{
if ((value % 2) == 0)
{
val = value;
break;
}
}
std::cout << val << "\n";
Run Code Online (Sandbox Code Playgroud)
您可以立即看到正在进行的操作(std::find_if),并且突出显示的细节很突出(名称isEven是参数)
除了避免可能的错误,最小化循环变量和优化循环结构之外,使用标准算法还具有比通用循环更多地传达意图的优点.在我看来,这是他们的主要优势.因此,它坚持"为您的读者编写代码,而不是为编译器编写代码或只是为了得到正确的答案".
举例来说,如果你看到std::rotate(),std::count_if(),std::transform(),或者std::find_if(),你知道是什么,而不需要读取的代码(假设你已经知道你的算法头)立即正在进行的格局.
话虽如此,我仍然更喜欢范围换环,std::for_each()因为它们都做同样的事情 - 访问每个项目并执行一些操作 - 前者有点不那么笨重.但是,如果for循环正在通过标准算法(例如,复制或转换)执行某些操作,那么我通常 - 不是Procrustean关于它 - 更喜欢标准版本.
有关此主题的更多信息,请参阅Sean Parent的半着名的C++ Seasoning讲座.
为什么我不应该使用这个惊人的结构?
你不应该总是使用它,因为在许多用例中有一些替代方案可以说是更好的.
让我们考虑你想要一个向量(由其他人创建)初始化为[1,2,3,...,N].让我们说初学者决定使用惊人的for循环:
// let there be
std::vector seq(N);
// our code
for (int i = 1; i <= N; i++)
seq[i] = i;
Run Code Online (Sandbox Code Playgroud)
非常简洁,非常紧凑.除此之外,初学者犯了一个错误,因此程序有不确定的行为.虽然对于我们这些灰白色的C++老手来说很明显,但对于初学者而言,向量的有效索引结束时并不明显N - 1.更糟糕的是,在C++中,你不能保证得到一个关于访问坏索引的错误(除非你使用vector::at,但是这个速度慢,所以几乎没有人使用它.即使是初学者也想要速度).
现在,让我们假设初学者勤奋并使用正确的形式
for (int i = 0; i < N; i++)
seq[i] = i + 1;
Run Code Online (Sandbox Code Playgroud)
现在,这是正确的.好极了!但是,如果其他人决定向量不合适,该怎么办?他们需要一个列表:
// let there be
std::list seq(N);
Run Code Online (Sandbox Code Playgroud)
不好了!他们打破了我们的循环!std::list没有operator[].
将此与使用相比较 std::iota
std::iota(std::begin(seq), std::end(seq), 1);
Run Code Online (Sandbox Code Playgroud)
代码显然是正确的.初学者在不知不觉中将UB注入该呼叫更难.您不需要证明向量具有足够的大小,您不需要知道seq除了提供前向迭代器之外的类型.作为奖励,它更加简洁.
它是众所周知的,每个人都立即知道发生了什么
存在具有基于1的索引的编程语言,并且基于0的索引对于初学者程序员来说是反直觉的.一些初学者可能会被愚弄,认为他们知道发生了什么,什么时候不知道.
基于范围的循环也是众所周知的.自从他们被引入语言以来已有 7年了,许多其他语言也有它们,包括Java,Python,PHP等等.
例如,如果我需要迭代一大堆类似数组的容器,并且我需要一个索引来访问元素,那么常规for循环为我提供了索引.使用STL算法几乎是不可能的,因为我需要boost和zip,所以使用迭代器也很麻烦.
你的例子很稳固,虽然你不需要专门提升(但这是一个不错的选择),而且几乎不可能是夸大其词.迭代并行迭代器范围确实有点复杂,如果你可以选择使用旧式的索引循环,它可以说比使用迭代器更具可读性.
这样的情况为什么我会争辩说从不使用索引for循环不是一个明智的建议(你的老师也允许他们应该很少使用).