像这样的 for 循环被认为是不好的做法吗?

Is a for loop like this considered bad practice?

本文关键字:认为是 for 循环 像这样      更新时间:2023-10-16

所以在这个例子中,假设我有一个名为 original 的 std::vector,我想把它分成两半,分成两个不同的向量。假设原件具有偶数数量的元素。

std::vector<int> firstHalf;
std::vector<int> secondHalf;
for (int i = 0, j = original.size()/2; i < original.size() / 2; i++, j++)
{
firstHalf.push_back(original[i]);
secondHalf.push_back(original[j]);
}

更明显的方法是有两个单独的 for 循环,一个用于填充上半部分,一个用于填充后半部分。

像我一样编写 for 循环被认为是不好的做法吗?根据我的测试,此解决方案比使用两个单独的 for 循环略高效。

实际上,您可以将代码减少到两行:

std::vector<int> firstHalf(original.begin(), original.begin() + original.size() / 2);
std::vector<int> secondHalf(original.begin() + original.size() / 2, original.end());

原因:

push_back可能会在元素数量增加的同时重新分配内存。 STL 会在开始时分配一次足够的内存。

我会说这不是不好的做法,但也不是很好的练习。

正如杰特的回答所指出的,可以将其简化为

std::vector<int> firstHalf(original.begin(), original.begin() + original.size() / 2);
std::vector<int> secondHalf(original.begin() + original.size() / 2, original.end());

不过,我可能会设法避免重新计算original.size()/2

std::size_t halfsize = original.size()/2;
std::vector<int> firstHalf(original.begin(), original.begin() + halfsize);
std::vector<int> secondHalf(original.begin() + halfsize, original.end());

或者,甚至,

std::vector<int>::const_iterator halfway = original.begin() + original.size()/2;
std::vector<int> firstHalf(original.begin(), halfway);
std::vector<int> secondHalf(halfway, original.end());

(在 C++11 及更高版本中,halfsizehalfway的声明可以使用auto来确定类型)。

这些是否更好(例如可读性)是非常主观的。

基本信息是,使用标准算法是一个好主意,其中结果是更干净的代码,并且效果明显等效。 添加附加变量以避免重复表达式有助于提高可读性。

如果您出于某种原因确实必须使用循环(例如,您所做的不仅仅是将向量的一部分复制到其他向量中),请考虑;

  • 在多次调用push_back()之前使用reserve()
  • 在向量上使用迭代器而不是数组下标
  • 预先计算重复使用的值(例如std::size_t halfsize = original.size()/2循环之前,而不是在循环内重复计算original.size()/2)。 如果original不是const,则尤其相关,因为 - 取决于你的循环的作用 - 编译器确定它的大小没有变化的机会较小。
  • 在循环中使用标准算法,而不是实现嵌套循环。

我会在两个单独的循环中执行此操作。这样,如果元素数量不均匀并且循环特别简单,它也可以工作。

std::vector<int> firstHalf;
std::vector<int> secondHalf;
size_t middle = original.size()/2;
for (size_t i = 0; i < middle; i++)
{
firstHalf.push_back(original[i]);
}
for (size_t i = middle; i < original.size(); i++)
{
secondHalf.push_back(original[i]);
}

但我不会把你的原始代码称为不好的做法。

为了通过空间局部性实现缓存友好性,我会使用两个循环。在原始代码中,在原始数组的各个部分之间来回跳转,索引相距数组大小的一半。 以 stride-1 模式访问数组的元素要好得多。 此外,为您的子数组保留空间并保存其他变量(如大小和计数)可能是值得的。

size_t size = original.size();
size_t mid_size = size / 2;
std::vector<int> firstHalf(mid_size);
std::vector<int> secondHalf((size - mid_size == mid_size) ? mid_size : mid_size + 1);
size_t i = 0;
for (; i < mid_size; i++) {
firstHalf[i] = original[i];
}
for (; i < size; i++) {
secondHalf[i - mid_size] = original[i];
}

不过,杰特的回答非常好。

对于像这样的简单代码,很容易理解发生了什么。但是,对于更高级的代码(1000 行),我相信大多数人宁愿将其分成 2 个for循环。

"更高效"是什么意思?你看过汇编代码吗?