目录
1.传统for循环
2.新for循环
3.实现自定义对象支持Range-based循环语法
4.总结
1.传统for循环
使用for循环的对象都是容器,如std::vector、std::list、数组、std::initializer_list、std::array等,一般遍历容器的方式有两种,一种是使用直接下标访问元素的方式,如:
int a[5] = {10, 44, 55, 2, 8};
for (int i = 0; i < sizeof(a)/sizeof(a[0]); i++){a[1] = 0;//省略其它操作
}
二是使用迭代器的方式间接访问元素,如:
int a[5] = {10, 44, 55, 2, 8};
for (auto p = std::begin(a); p != std::end(a); p++){*p = 100;//省略其它操作
}
使用auto推导出p为int*。
2.新for循环
C++11引入了基于range-based for循环语法糖,它提供了一种简洁而直观的方式来遍历一个序列或容器中的元素。它的基本语法如下:
for(decl : coll){//statement}
①decl用于声明元素及类型,如int elem或auto elem(让编译器自动推导集合中元素的类型),但应注意auto& elem和auto elem的区别,前者是元素的引用,后者是元素的副本。
②coll为元素的集合
下面是一个简单的示例代码,用于展示如何使用range-based for循环:
#include <iostream>
#include <vector>int main() {int v[] = {1, 2, 3, 4, 5};for (int i : v) {std::cout << i << " ";}std::cout << std::endl;return 0;
}
在这个示例中,我们定义了一个包含整数的数组,然后使用range-based for循环遍历其中的元素。for循环中的int i是一个迭代变量,每次循环时都会被设置为数组中的下一个元素,直到遍历完整个数组。
与传统的for循环相比,range-based for循环具有以下优点:
1)更加简洁和直观,代码可读性更高。
2)遍历序列时无需手动计数或指定迭代器,减少了出错的可能性。
3)可以用于所有支持迭代器的容器和序列,包括数组、vector、list、set、map等等。
使用注意:
(1) auto会引用元素的拷贝,有时为了效率可以考虑使用auto&或const auto&。
(2) decl中要求元素的类型支持隐式类型转换。如:
const MyString& elem : vecString;(其中的vecString(类型为vector<string>)中元素的类型为string,而elem被声明为MyString,两者的类型是不同的,这会进行隐式转换,这就要求MyString类不能像explicit MyString(string);这样声明构造函数。
(3) auto自动推导出的元素类型就是容器中的value_type,而不是迭代器的类型。
(4) 不论基于范围的for循环迭代了多少次,冒号后面的表达式只会被执行一次。
(5) 基于范围的for循环和普通for循环一样,在迭代时修改容器(增加或删除元素)可能会引起迭代器失效。
(6) for循环的使用还受容器本身的一些约束,如std::set<int>中的内部元素是只读的。但使用auto&时,会被推导为const auto&。
3.实现自定义对象支持Range-based循环语法
自定义类支持range-base for需要满足的条件:
1)类中需要定义容器相关的迭代器(这里的迭代器是广义的,指针也属于该范畴)
2)类中要有begin()和end()的成员方法,返回值为迭代器(或重载全局的begin()和end()也可以)
//返回第一个迭代子的位置
Iterator begin()
//返回最后一个迭代子的下一个位置
Iterator end()
3)迭代器必须支持!=、*解引用、前置++等操作
- operator++(自增),可以在自增之后返回下一个迭代子的位置
- operator!= (判不等)
- operator* (解引用)
下面就来举例说明怎么实现自定义对象支持Range-based循环语法
示例1:
#include <iostream>
using namespace std;
template<typename T,size_t N>
class A
{
public:A(){for (size_t i =0 ;i<N;++i){m_elements[i] = i;}}~A(){}T* begin(){return m_elements + 0;}T* end(){return m_elements + N;}
private:T m_elements[N];
};
int main()
{A<int, 10> a;for (auto iter: a){std::cout << iter << endl;}
}
输出: 0 1 2 3 4 5 6 7 8 9
注意:在以上代码中,迭代子Iterator是T*, 是指针类型,本身就支持operator++和operator!=操作,所以这里并没有提供这两个方法的实现。
示例2:
下面是一个简单的示例,展示了如何为一个自定义的整数范围类实现基于范围的for循环:
#include <iostream> // 自定义迭代器类
class IntegerIterator {
public: using iterator_category = std::forward_iterator_tag; using difference_type = std::ptrdiff_t; using value_type = int; using pointer = int*; using reference = int&; IntegerIterator(int value) : current(value) {} // 前缀递增 IntegerIterator& operator++() { ++current; return *this; } // 后缀递增(不常用,但为了满足迭代器的概念) IntegerIterator operator++(int) { IntegerIterator tmp = *this; ++(*this); return tmp; } // 解引用 int operator*() const { return current; } // 相等比较 bool operator==(const IntegerIterator& other) const { return current == other.current; } // 不相等比较 bool operator!=(const IntegerIterator& other) const { return !(*this == other); } private: int current;
}; // 自定义整数范围类
class IntegerRange {
public: IntegerRange(int start, int end) : start_(start), end_(end) {} // 提供begin迭代器 IntegerIterator begin() const { return IntegerIterator(start_); } // 提供end迭代器 IntegerIterator end() const { return IntegerIterator(end_); } private: int start_; int end_;
}; int main() { IntegerRange range(1, 5); // 使用range-based for循环遍历range for (int num : range) { std::cout << num << ' '; } std::cout << '\n'; return 0;
}
注意,这个示例中的IntegerIterator是一个简单的迭代器实现,只支持前向迭代(即std::forward_iterator_tag)。在实际应用中,你可能需要根据你的具体需求实现不同类型的迭代器,比如双向迭代器(std::bidirectional_iterator_tag)或随机访问迭代器(std::random_access_iterator_tag)。
另外,请注意,end()返回的迭代器应该指向范围“之后”的位置,而不是最后一个元素本身。这意味着在上面的例子中,end()返回的迭代器对应的current值是5,但实际上我们不会解引用这个迭代器来获取值。
4.总结
C++ 中为自定义对象实现基于范围的 for 循环(range-based for loop)具有多个优点,这些优点不仅使代码更加简洁和易读,还提高了代码的可维护性和灵活性。以下是一些主要的优点:
代码简洁易读:使用基于范围的 for 循环可以显著减少编写迭代循环所需的代码量。与传统的迭代器或指针循环相比,基于范围的 for 循环语法更加简洁,易于理解和编写。基于范围的 for 循环提供了更直观的方式来遍历容器或集合中的元素。它隐藏了迭代器的细节,使得循环的意图更加清晰,易于其他开发人员理解。基于范围的 for 循环是现代 C++ 编程风格的一部分,它与其他现代 C++ 特性(如 lambda 表达式、智能指针和算法库)一起使用,可以使代码更加简洁、高效和易于维护
减少错误:由于基于范围的 for 循环隐藏了迭代器的管理(如初始化、递增和检查结束条件),因此减少了与迭代器相关的常见错误,如迭代器越界或未正确初始化。
适用于多种容器:一旦为自定义对象实现了 begin() 和 end() 成员函数(或全局函数),就可以使用基于范围的 for 循环来遍历该对象,而无需修改循环的语法。这使得代码更加灵活,可以轻松地与不同的容器或集合类型一起使用。
可维护性:当需要更改遍历容器或集合的方式时(例如,从正向遍历更改为反向遍历),只需修改 begin() 和 end() 函数的实现即可,而无需更改基于范围的 for 循环的语法。这有助于提高代码的可维护性。
支持泛型编程:基于范围的 for 循环可以与模板和泛型编程一起使用,使得代码更加灵活和可重用。通过为不同类型的容器或集合提供统一的迭代接口,可以编写更加通用的代码。
需要注意的是,range-based for循环只适用于遍历序列和容器中的元素,不能用于迭代器本身。此外,如果需要对容器中的元素进行修改,应该使用引用或指针来避免复制。
总之,为自定义对象实现基于范围的 for 循环可以提高代码的质量、可读性和可维护性,同时减少错误并提高开发效率。这是现代 C++ 编程中值得推荐的一种做法。
参考:基于范围的 for 循环 (C++11 起) - cppreference.com