ChatGPT解决这个技术问题 Extra ChatGPT

使用“for”循环遍历 C++ 向量

我是 C++ 语言的新手。我已经开始使用向量,并且注意到在我看到通过索引迭代向量的所有代码中,for 循环的第一个参数始终是基于向量的。在 Java 中,我可能会用 ArrayList 做这样的事情:

for(int i=0; i < vector.size(); i++){
   vector[i].doSomething();
}

我在 C++ 中看不到这个是有原因的吗?这是不好的做法吗?

for 循环不是函数,因此它没有参数(或参数,这是您传入的)。不过,您的意思是像 std::vector<int>::size_type i = 0; 还是 std::vector<int>::iterator it = vector.begin();
在 Java 中,我更喜欢 for-each 循环或使用迭代器。与 C++ 几乎相同,但语法略有不同。
这里的大多数答案都错误地假设 Q 是:迭代 std::vector 的最佳/最短方法是什么?,这里问的实际 Q 是:我有什么理由吗?在 C++ 中看不到这个?这是不好的做法吗? 也就是 为什么我总是看到 C++ 中的代码在迭代 std::vector 时使用迭代器?
我认为这个问题给出了更好的解释stackoverflow.com/questions/409348/iteration-over-vector-in-c

i
iammilind

您没有看到这种做法的原因是非常主观的,无法给出明确的答案,因为我见过许多使用您提到的方式而不是 iterator 样式代码的代码。

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

每次在循环条件下都对调用 size() 感到偏执。但是,要么它不是问题,要么可以很容易地修复 优先使用 std::for_each() 而不是 for 循环本身 稍后将容器从 std::vector 更改为另一个容器(例如 map、list)也将要求更改循环机制,因为不是每个容器都支持 size() 样式的循环

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

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

vector<int> vi;
...
for(int i : vi) 
  cout << "i = " << i << endl;

请注意 基于范围的 for 循环 的一个小缺点:您不能将它与 #pragma omp parallel for 一起使用。
我喜欢精简版,因为要阅读的代码更少。一旦您进行了心理调整,就会更容易理解,并且错误会更加突出。当发生非标准迭代时,它也会变得更加明显,因为有更大的代码块。
@liborm 请注意,这不再是真的; OpenMP 5.0 支持基于范围的迭代。 (来源:stackoverflow.com/a/51390846/2378475
基于范围的 for 循环语法简洁明了。但是,调试可能需要索引的显式知识。例如:一个文件被循环写入。发现第 125 次迭代对应的第 125 行产生了不正确的值。如何在第 125 次迭代中对循环进行断点以进行调试? (如果不更改代码并显式打印,您不知道迭代失败时循环变量的值。
C
Community

遍历向量的最简洁方法是通过迭代器:

for (auto it = begin (vector); it != end (vector); ++it) {
    it->doSomething ();
}

或(相当于上述)

for (auto & element : vector) {
    element.doSomething ();
}

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

这可能是你所看到的。与您提到的方法相比,优点是您不会严重依赖 vector 的类型。如果您将 vector 更改为不同的“集合类型”类,您的代码可能仍然有效。但是,您也可以在 Java 中执行类似的操作。概念上没有太大区别;然而,C++ 使用模板来实现这一点(与 Java 中的泛型相比);因此该方法适用于定义了 beginend 函数的所有类型,甚至适用于静态数组等非类类型。见这里:How does the range-based for work for plain arrays?


自动、自由开始/结束也是 C++11。而且,在许多情况下,您应该使用 ++it 而不是 it++。
你是对的。然而,实现 beginend 是单行的。
@JohnB 这是一个不止一个衬里,因为它也适用于固定大小的数组。另一方面,auto 会非常棘手。
如果你只需要它作为矢量,它就是一个单线。
不过,第一个示例具有误导性,因为它不能在 C++03 中工作,而您的措辞表明它可以。
s
sifferman

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

不,这不是一个坏习惯,但以下方法使您的代码具有一定的灵活性。

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

std::vector<int>::iterator it = vector.begin();

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

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

注意:编写适用于所有可能的标准库容器的代码并不像看起来那么容易。


谁能解释一下为什么在这个特殊的案例/代码片段中你建议迭代器不要索引?你说的这种“灵活性”是什么?就个人而言,我不喜欢迭代器,它们会使代码膨胀——只需键入更多字符即可获得相同的效果。特别是如果您不能使用 auto
@VioletGiraffe:在使用迭代器时,在某些情况下(例如空范围)很难出错,而且代码更冗长。当然这是一个问题或感知和选择,因此可以无休止地争论。
为什么你只展示如何声明迭代器而不是如何使用它来执行循环......?
D
Dig

正确的做法是:

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

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

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

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

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

std::vector<T>::size_type 不总是 size_t 吗?这就是我一直使用的类型。
@VioletGiraffe 我很确定你是对的(还没有真正检查过),但使用 std::vector::size_type 是更好的做法。
H
Hrishikesh

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

使用 auto 和 for 循环迭代向量

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

for(auto itr : vec)
    cout << itr << " ";

输出:

1 2 3 4 5

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


M
Mario Petrovic

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

#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;
}

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


为什么使用 auto 而不是 int
E
Eddie Parker

使用迭代器有两个强有力的理由,这里提到了其中的一些:

稍后切换容器不会使您的代码无效。

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

无效迭代的运行时捕获

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


你能指出一些我们的文章/帖子,用示例代码解释上述几点吗?会很好!或者如果你可以添加一个:)
1. IIRC,使用(实际上是取消引用)无效的迭代器是 UB(哎哟!)。虽然某些实现可能会抛出异常 - IIRC,但它远非普遍。
2.在循环中间对某些容器进行某些修改是完全可以的(例如,在list<>/map<>/等中)
A
Akram Mohammed

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

for(int x: A) // for integer x in vector A
    cout<< x <<" "; 

P
Peter Mortensen

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


D
Diomidis Spinellis

令我惊讶的是,没有人提到遍历具有整数索引的数组使您可以通过为具有错误索引的数组下标来轻松编写错误代码。例如,如果您有使用 ij 作为索引的嵌套循环,您可能会错误地使用 j 而不是 i 为数组下标,从而在程序中引入错误。

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


b
bashar
 //different declaration type
    vector<int>v;  
    vector<int>v2(5,30); //size is 5 and fill up with 30
    vector<int>v3={10,20,30};
    
    //From C++11 and onwards
    for(auto itr:v2)
        cout<<"\n"<<itr;
     
     //(pre c++11)   
    for(auto itr=v3.begin(); itr !=v3.end(); itr++)
        cout<<"\n"<<*itr;

S
Shujaul Hind
int main()
{
    int n;
    int input;
    vector<int> p1;
    vector<int> ::iterator it;

    cout << "Enter the number of elements you want to insert" << endl;
    cin >> n;

    for (int i = 0;i < n;i++)
    {
        cin >> input;
        p1.push_back(input);
    }
    for(it=p1.begin();it!=p1.end();it++)
    {
        cout << *it << endl;
    }
      //Iterating in vector through iterator it

    return 0;
}

传统形式的迭代器


J
John O'Donnell

不要忘记具有 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();
}

另请注意,使用 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
}

r
rob

如果你使用

std::vector<std::reference_wrapper<std::string>> names{ };

不要忘记,当你在 for 循环中使用 auto 时,也要使用 get,如下所示:

for (auto element in : names)
{
    element.get()//do something
}

那不是有效的语法。