使用'for'循环遍历C++ Vector

Fly*_*ynn 119 c++ iterator for-loop coding-style

我是C++语言的新手.我已经开始使用向量,并且已经注意到在我看到的所有代码中通过索引迭代通过向量,for循环的第一个参数总是基于向量.在Java中,我可以使用ArrayList执行类似的操作:

for(int i=0; i < vector.size(); i++){
   vector[i].doSomething();
}
Run Code Online (Sandbox Code Playgroud)

有没有理由我在C++中没有看到这个?这是不好的做法吗?

iam*_*ind 92

你没有看到这种做法的原因是非常主观的,并且没有明确的答案,因为我看到许多代码使用你提到的方式而不是iterator样式代码.

以下可能是人们不考虑vector.size()循环方式的原因:

  1. 关于size()每次在循环条件下调用都是偏执狂.然而,要么它不是一个问题,要么可以轻微修复
  2. 宁愿std::for_each()for循环本身
  3. 后来从改变容器std::vector到另一个(例如 map,list)也将要求因为不是每个容器支持循环机制的变化,size()风格的循环

C++ 11提供了一个很好的工具来移动容器.这称为"基于范围的循环"(或Java中的"增强循环").

使用很少的代码,您可以遍历完整(强制!)std::vector:

vector<int> vi;
...
for(int i : vi) 
  cout << "i = " << i << endl;
Run Code Online (Sandbox Code Playgroud)

  • 只是要注意基于loop_的_range的一个小缺点:你不能将它与`#pragma omp parallel for`一起使用. (9认同)
  • 我喜欢紧凑版本,因为可以阅读的代码更少.一旦你进行了心理调整,就会更容易理解,而且错误更突出.当发生非标准迭代时,它也会更加明显,因为有更大的代码块. (2认同)
  • @liborm 请注意,这不再是真的;OpenMP 5.0 支持基于范围的迭代。(来源:/sf/answers/3597359251/) (2认同)
  • 基于范围的 for 循环语法非常简洁。然而,调试可能需要索引的显式知识。例如:通过循环写入一个文件。发现对应于第125次迭代的第125行产生了错误的值。如何在第 125 次迭代时对循环设置断点以进行调试?(如果不更改代码并显式打印,您将不知道失败迭代时循环变量的值。 (2认同)

Alo*_*ave 82

有什么理由我在C++中没有看到这个吗?这是不好的做法吗?

不.这不是一个坏习惯,但它会使您的代码具有一定的灵活性.

通常,在C++之前,用于迭代容器元素的代码使用迭代器,例如:

std::vector<int>::iterator it = vector.begin();
Run Code Online (Sandbox Code Playgroud)

这是因为它使代码更加灵活.

所有标准库容器都支持并提供迭代器,并且如果在以后的开发阶段需要切换另一个容器,则不需要更改此代码.

注意:编写适用于每个可能的标准库容器的代码并不像看起来那样容易.

  • 谁能解释一下为什么在这个特殊情况/代码片段中你建议迭代器索引?你所谈论的这种"灵活性"是什么?就个人而言,我不喜欢迭代器,他们膨胀代码 - 只需更多字符来输入相同的效果.特别是如果你不能使用`auto`. (21认同)
  • 为什么你只展示如何声明迭代器而不展示如何使用它来执行循环......? (17认同)
  • @VioletGiraffe:在使用迭代器时,很难出现某些情况,例如空范围,而且代码更冗长.Ofcourse它是一个问题或感知和选择,所以它可以无休止地辩论. (6认同)

Joh*_*hnB 75

迭代向量的最干净方法是通过迭代器:

for (auto it = begin (vector); it != end (vector); ++it) {
    it->doSomething ();
}
Run Code Online (Sandbox Code Playgroud)

或(相当于上述)

for (auto & element : vector) {
    element.doSomething ();
}
Run Code Online (Sandbox Code Playgroud)

在C++ 0x之前,您必须使用迭代器类型替换auto并使用成员函数而不是全局函数begin和end.

这可能就是你所看到的.与您提到的方法相比,优势在于您不会严重依赖于其类型vector.如果您更改vector为其他"集合类型"类,您的代码可能仍然有效.但是,您也可以在Java中执行类似的操作.概念上没有太大差异; 但是,C++使用模板来实现它(与Java中的泛型相比); 因此,该方法适用于定义beginend定义函数的所有类型,即使对于静态数组等非类类型也是如此.请参阅此处:基于范围的普通数组的工作原理如何?

  • auto,free begin/end也是C++ 11.在许多情况下,你应该使用++,而不是++. (5认同)

DiG*_*GMi 34

正确的方法是:

for(std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
    it->doSomething();
 }
Run Code Online (Sandbox Code Playgroud)

其中T是向量内类的类型.例如,如果该类是CActivity,则只需编写CActivity而不是T.

这种类型的方法适用于每个STL(不仅是矢量,它更好一点).

如果您仍想使用索引,方法是:

for(std::vector<T>::size_type i = 0; i != v.size(); i++) {
    v[i].doSomething();
}
Run Code Online (Sandbox Code Playgroud)

  • `std::vector&lt;T&gt;::size_type` 不是总是 `size_t` 吗?这就是我经常使用的类型。 (2认同)

小智 10

使用 auto 运算符确实很容易使用,因为人们不必担心数据类型和向量或任何其他数据结构的大小

使用 auto 和 for 循环迭代向量

vector<int> vec = {1,2,3,4,5}

for(auto itr : vec)
    cout << itr << " ";
Run Code Online (Sandbox Code Playgroud)

输出:

1 2 3 4 5
Run Code Online (Sandbox Code Playgroud)

您还可以使用此方法迭代集合和列表。使用auto 会自动检测模板中使用的数据类型并让您使用它。所以,即使我们有vectorofstringchar相同的语法也能正常工作


Edd*_*ker 8

使用迭代器有几个很强的理由,其中一些在这里提到:

稍后切换容器不会使代码无效.

即,如果从std :: vector转到std :: list或std :: set,则不能使用数字索引来获取包含的值.使用迭代器仍然有效.

运行时捕获无效迭代

如果在循环中间修改容器,则下次使用迭代器时会抛出无效的迭代器异常.


小智 7

迭代向量并打印其值的正确方法如下:

#include<vector>

// declare the vector of type int
vector<int> v;

// insert elements in the vector
for (unsigned int i = 0; i < 5; ++i){
    v.push_back(i);
}

// print those elements
for (auto it = v.begin(); it != v.end(); ++it){
    std::cout << *it << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

但至少在目前的情况下,使用基于范围的 for 循环更好:(
for (auto x: v) std::cout << x << "\n";
您也可以添加&afterautox引用元素而不是它们的副本。它非常类似于上述基于迭代器的方法,但更易于阅读和编写。)


For*_*veR 5

使用STL,程序员可以用来iterators遍历容器,因为迭代器是一个抽象概念,在所有标准容器中实现。例如,根本std::list没有。operator []


Dio*_*lis 5

我很惊讶没有人提到迭代一个带有整数索引的数组可以让你很容易地通过为一个带有错误索引的数组添加下标来编写错误的代码。例如,如果您使用ij作为索引的嵌套循环,您可能会错误地使用j而不是数组下标i,从而将错误引入程序。

相比之下,这里列出的其他形式,即基于范围的for循环和迭代器,更不容易出错。语言的语义和编译器的类型检查机制将防止您使用错误的索引意外访问数组。


Akr*_*med 5

这是在向量中迭代和打印值的更简单方法。

for(int x: A) // for integer x in vector A
    cout<< x <<" "; 
Run Code Online (Sandbox Code Playgroud)


小智 5

不要忘记具有 const 正确性的示例 - 循环可以修改元素吗?这里的许多例子没有,并且应该使用 cont 迭代器。这里我们假设

class T {
  public:
    T (double d) : _d { d } {}
    void doSomething () const { cout << _d << endl; return; }
    void changeSomething ()   { ++_d; return; }
  private:
    double _d;
};

vector<T> v;
// ...
for (const auto iter = v.cbegin(); iter != v.cend(); ++iter) {
    iter->doSomething();
}
Run Code Online (Sandbox Code Playgroud)

另请注意,对于 C++11 表示法,默认情况下是复制元素。使用引用来避免这种情况,和/或允许修改原始元素:

vector<T> v;
// ...
for (auto t : v) {
    t.changeSomething(); // changes local t, but not element of v
    t.doSomething();
}
for (auto& t : v) {      // reference avoids copying element
    t.changeSomething(); // changes element of v
    t.doSomething();
}
for (const auto& t : v) { // reference avoids copying element
    t.doSomething();      // element can not be changed
}
Run Code Online (Sandbox Code Playgroud)