1. 语法概述
基于范围的 for 循环(Range-based for loop)是C++11引入的新特性。这种循环形式使得遍历容器(如数组、向量、列表等)中的元素变得更加简洁和直观。在C++11之前,我们通常使用传统的 for 循环或迭代器来遍历容器中的元素。
基于范围的 for 循环的语法结构如下:
for (const auto& element : container) {
// 使用 element 进行操作
}
其中,container 是一个容器(如数组、向量、列表等),element 是容器中的每个元素。auto 关键字用于自动推导容器中的元素类型。这种循环会遍历容器中的每个元素,将元素赋值给 element,然后执行循环体内的操作。
基于范围的 for 循环的引入使得代码更加简洁,减少了迭代器的使用,提高了代码的可读性。同时,它还可以避免越界和迭代器失效等问题,使得代码更加安全。
2. 示例代码
#include <iostream>
using namespace std;
#include <vector>
// c++11之前的for循环遍历容器
void printVector1(vector<int>& v) {
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
cout << *it << " ";
}
cout << endl;
}
// 基于范围的for循环
void printVector2(vector<int>& v) {
for (const int& x : v) {
cout << x << " ";
}
cout << endl;
}
void test01() {
vector<int> v = { 1,3,5,7,9 };
printVector1(v);
printVector2(v);
}
int main() {
test01();
return 0;
}
3. 注意事项
3.1 关系型容器
使用基于范围的for循环有一些需要注意的细节,先来看一下对关系型容器map的遍历:
#include <iostream>
#include <string>
#include <map>
using namespace std;
int main(void)
{
map<int, string> m{
{1, "lucy"},{2, "lily"},{3, "tom"}
};
// 基于范围的for循环方式
for (auto& it : m)
{
cout << "id: " << it.first << ", name: " << it.second << endl;
}
// 普通的for循环方式
for (auto it = m.begin(); it != m.end(); ++it)
{
cout << "id: " << it->first << ", name: " << it->second << endl;
}
return 0;
}
在上面的例子中使用两种方式对map进行了遍历,通过对比有两点需要注意的事项:
- 使用普通的for循环方式(基于迭代器)遍历关联性容器, auto自动推导出的是一个迭代器类型,需要使用迭代器的方式取出元素中的键值对(和指针的操作方法相同):
it->first
it->second
- 使用基于范围的for循环遍历关联性容器,auto自动推导出的类型是容器中的value_type,相当于一个对组(std::pair)对象,提取键值对的方式如下:
it.first
it.second
3.2 元素只读
通过对基于范围的for循环语法的介绍可以得知,在for循环内部声明一个变量的引用就可以修改遍历的表达式中的元素的值,但是这并不适用于所有的情况,对应set容器来说,内部元素都是只读的,这是由容器的特性决定的,因此在for循环中auto&会被视为const auto & 。
#include <iostream>
#include <set>
using namespace std;
int main(void)
{
set<int> st{ 1,2,3,4,5,6 };
for (auto &item : st)
{
cout << item++ << endl; // error, 不能给常量赋值
}
return 0;
}
除此之外,在遍历关联型容器时也会出现同样的问题,基于范围的for循环中,虽然可以得到一个std::pair引用,但是我们是不能修改里边的first值的,也就是key值。
#include <iostream>
#include <string>
#include <map>
using namespace std;
int main(void)
{
map<int, string> m{
{1, "lucy"},{2, "lily"},{3, "tom"}
};
for (auto& item : m)
{
// item.first 是一个常量
cout << "id: " << item.first++ << ", name: " << item.second << endl; // error
}
return 0;
}
3.3 访问次数
基于范围的for循环遍历的对象可以是一个表达式或者容器/数组等。假设我们对一个容器进行遍历,在遍历过程中for循环对这个容器的访问频率是一次还是多次呢?我们通过下面的例子验证一下:
#include <iostream>
#include <vector>
using namespace std;
vector<int> v{ 1,2,3,4,5,6 };
vector<int>& getRange()
{
cout << "get vector range..." << endl;
return v;
}
int main(void)
{
for (auto val : getRange())
{
cout << val << " ";
}
cout << endl;
return 0;
}
输出的结果如下:
get vector range...
1 2 3 4 5 6
从上面的结果中可以看到,不论基于范围的for循环迭代了多少次,函数getRange()只在第一次迭代之前被调用,得到这个容器对象之后就不会再去重新获取这个对象了。
结论:
对应基于范围的for循环来说,冒号后边的表达式只会被执行一次。在得到遍历对象之后会先确定好迭代的范围,基于这个范围直接进行遍历。如果是普通的for循环,在每次迭代的时候都需要判断是否已经到了结束边界。
注意事项部分参考:基于范围的for循环 | 爱编程的大丙