赞
踩
基于范围的for循环(Range-based for loop)是C++11引入的一种新的循环结构,基于范围的for循环可以遍历支持迭代器的集合,如std::vector、std::list等,也可以遍历支持下标操作的集合,如std::array和普通数组。掌握基于范围的for循环,它可以方便地操作集合中的元素,而无需手动管理索引或迭代器,将有助于你编写出更高效、更优雅的C++代码。
基于范围的for循环的语法形式如下:
- for (for-range-declaration : for-range-initializer) {
- // 循环体
- }
在这里,for-range-declaration是一个迭代变量,用于表示每一次迭代中从for-range-initializer中获取的元素的值或引用,for-range-initializer是一个表示容器或数组的表达式。迭代变量可以直接声明成for-range-initializer中元素的类型,或者声明成auto类型,使代码更加简洁。
一般情况下,范围for循环内的迭代变量的类型应该声明成被遍历的数据、容器集合中的数据元素的类型。如下:
- int arr[] = {1, 2, 3, 4, 5};
-
- for (int i : arr) {
- std::cout << i << " ";
- }
-
- std::vector<float> vec = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f};
-
- for (float i : vec) {
- std::cout << i << " ";
- }
范围fo循环可以和C++11的别一个特性:自动类型推断(auto)一起使用,使代码更加简洁。一般情况下,迭代变量可声明成auto、 auto &(左值引用类型)、auto &&(右值引用类型)、const auto &(常量引用类型)。
2.2.2.1 auto 类型
使用auto类型的迭代变量进行遍历集合时,实际上迭代变量只是集合中数据元素的拷贝。在这种情况下,循环体中不论怎么修改迭代变量的数据,都不会影响到原有集合中的数据元素。示例如下:
- vector<int> v{1,2,3,4,5};
-
- for(auto i : v) {
- i++;
- cout << i << " "; // 输出2 3 4 5 6
- }
-
- for(vector<int>::iterator it = v.begin(); it != v.end(); it++){
- cout << *it << " "; // 输出1 2 3 4 5
- }
2.2.2.2 auto &类型
使用auto &类型的迭代变量进行遍历时,迭代变量是一个非常量左值引用,它是集合中数据元素的引用,如果对迭代变量进行修改,会直接影响到集合数据元素本身。示例如下:
- vector<int> v{1,2,3,4,5};
-
- for(auto& i : v) {
- i++;
- cout << i << " "; // 输出2 3 4 5 6
- }
-
- for(vector<int>::iterator it = v.begin(); it != v.end(); it++){
- cout << *it << " "; // 输出2 3 4 5 6
- }
2.2.2.3 auto &&类型
若for-range-initializer集合返回一个临时对象(将死值),由于临时对象不能绑定在非常量左值引用,因此使用auto &声明迭代变量时会发生编译错误。这个时就需要使用 auto && 非常量右值引用声明迭代变量。示例如下:
- vector<bool> vec {true, false, true, false};
-
- // for (auto &a : vec) { // 编译报错
- for (auto &&a : vec) { // 编译通过
- cout << a << " ";
- }
输出:
1 0 1 0
这个时候,我们会提出疑问为什么vetor<bool> 类型的集合,只能使用auto &&类型的迭代变量访问而不能使用auto &类型的迭代变量访问呢?这是因为vector<bool>集合中的元素vector<bool>::reference类型,它不是一个左值,这也是使用auto &时会编译出错的原因。
- vector<bool> vec {true, false, true, false};
-
- bool &v = vec[0]; // 编译报错, vec[0]不是一个左值
- bool *w = &vec; // 编译报错,无法将一个临时变量地址绑定给指针
-
- auto &&u = vec[0]; // 编译通过,非常量右值引用
关于右值引用的详细说明,请参考【C++ 11】(八)右值引用
2.2.2.4 const auto&类型
在auto &的前面加上了const修饰,意味着迭代变量是集合中数据元素的引用,但是它是只读的不能被修改。示例如下:
- vector<int> v{1,2,3,4,5};
-
- for(const auto& i : v) {
- i++; // 编译出错
- cout << i << " ";
- }
范围for循环内进行遍历操作时,如果修改了集合中元素的个数,那么迭代器将会无效。
如下例子中,在集合追加一个元素后,迭代器会无效,输出结果自然就会是错误的。
- #include <vector>
- #include <iostream>
-
- int main(void) {
- std::vector<int> v{ 5, 5, 0, 5, 1 };
-
- for(auto&& i : v) {
- std::cout << ' ' << i;
- if (5 == i) {
- v.emplace_back(123); // 追加元素后、迭代器无效
- }
- }
- }
结果:
5 5 -1978514432 -946396368 1
迭代器(对象)的生命周期结束后,此时遍历这个对象,也会输出不正确结果甚至发生崩溃现象。
例子如下:
- #include <initializer_list>
- #include <iostream>
- #include <vector>
-
- struct something
- {
- std::vector<int> v;
-
- something(const std::initializer_list<int>& l ) : v(l) {}
- std::vector<int>& get_vector() { return v; }
- ~something() noexcept { std::cout << "destructor" << std::endl; }
- };
-
- int main()
- {
- for( auto e : something { 1,2,3,4,5,6,7,8,9,0 }.get_vector() )
- {
- std::cout << e; // something是临时对象、此时它的的生命周期已结束
- }
- std::cout << std::endl;
- }

范围for循环是C++11引入的一个强大的新特性,它大大简化了遍历数组和容器的代码,使代码更加简洁,更具可读性。掌握范围for循环,将有助于你编写出更高效、更优雅的C++代码。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。