我是 C++ 语言的新手。我已经开始使用向量,并且注意到在我看到的所有通过索引迭代向量的代码中,循环的第一个参数for
总是基于向量的。在 Java 中,我可能会用 ArrayList 做这样的事情:
for(int i=0; i < vector.size(); i++){
vector[i].doSomething();
}
我在 C++ 中看不到这个是有原因的吗?这是不好的做法吗?
我是 C++ 语言的新手。我已经开始使用向量,并且注意到在我看到的所有通过索引迭代向量的代码中,循环的第一个参数for
总是基于向量的。在 Java 中,我可能会用 ArrayList 做这样的事情:
for(int i=0; i < vector.size(); i++){
vector[i].doSomething();
}
我在 C++ 中看不到这个是有原因的吗?这是不好的做法吗?
您没有看到这种做法的原因是非常主观的,无法给出明确的答案,因为我已经看到了许多使用您提到的方式而不是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 (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 中的泛型相比);因此,该方法适用于定义了函数begin
和end
函数的所有类型,甚至适用于静态数组等非类类型。请参阅此处:基于范围的 for 如何适用于普通数组?
有什么理由我在 C++ 中看不到这个吗?这是不好的做法吗?
不,这不是一个坏习惯,但以下方法使您的代码具有一定的灵活性。
通常,在 C++11 之前,用于迭代容器元素的代码使用迭代器,例如:
std::vector<int>::iterator it = vector.begin();
这是因为它使代码更加灵活。
所有标准库容器都支持并提供迭代器。如果在以后的开发阶段您需要切换到另一个容器,则无需更改此代码。
注意:编写适用于所有可能的标准库容器的代码并不像看起来那么容易。
正确的做法是:
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();
}
使用自动运算符真的很容易使用,因为不必担心数据类型和向量或任何其他数据结构的大小
使用 auto 和 for 循环迭代向量
vector<int> vec = {1,2,3,4,5}
for(auto itr : vec)
cout << itr << " ";
输出:
1 2 3 4 5
您还可以使用此方法来迭代集合和列表。使用auto会自动检测模板中使用的数据类型并让您使用它。因此,即使我们有vector
ofstring
或char
相同的语法也可以正常工作
使用迭代器有两个强有力的理由,这里提到了其中的一些:
即,如果您从 std::vector 转到 std::list 或 std::set,则不能使用数字索引来获取包含的值。使用迭代器仍然有效。
如果在循环中间修改容器,下次使用迭代器时,它会抛出一个无效的迭代器异常。
迭代向量并打印其值的正确方法如下:
#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";
您也可以添加&
afterauto
以x
引用元素而不是它们的副本。它与上述基于迭代器的非常相似方法,但更易于阅读和编写。)
这是在向量中迭代和打印值的更简单方法。
for(int x: A) // for integer x in vector A
cout<< x <<" ";
令我惊讶的是,没有人提到遍历具有整数索引的数组使您可以通过为具有错误索引的数组下标来轻松编写错误代码。例如,如果您有使用i
and作为索引的嵌套循环,您可能会错误地使用而不是j
数组下标,从而在程序中引入错误。j
i
相比之下,这里列出的其他形式,即基于范围的for
循环和迭代器,更不容易出错。该语言的语义和编译器的类型检查机制将防止您使用错误的索引意外访问数组。
使用 STL,程序员iterators
用于遍历容器,因为迭代器是一个抽象概念,在所有标准容器中实现。例如,std::list
根本没有operator []
。
//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;
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;
}
传统形式的迭代器
如果你使用
std::vector<std::reference_wrapper<std::string>> names{ };
不要忘记,当你在 for 循环中使用 auto 时,也要使用 get,如下所示:
for (auto element in : names)
{
element.get()//do something
}