范围for循环是C++11新特性,在C++各种容器如vector,map中都可以使用,对于遍历容器全部元素非常的方便,我自己在通过手动实现array类的过程中发现了范围for循环较底层机制。写一篇blog仅仅作为自己的学习记录,如果凑巧能够帮助某位初学者理解其中的机制,那是本菜菜的荣幸。
手动实现array类参考手动模拟C++ stl中的array类_Harris_ayaka的博客-CSDN博客
语法直接上代码:
#include#include int main() { std::vector nums{ 1,2,3,4,5,6,7,8 }; for (int num : nums) { std::cout << num << std::endl; } }
输出结果:
可以看到这种写法在需要完整遍历容器,并且不需要用到下标的时候非常的方便。
使用机制如果想修改变量的值,那么要使用引用类型:
for (int& num : nums) {//声明为引用类型
num += 114514;
std::cout << num << std::endl;
}
结果:
这里建议如果不修改容器内变量的值,也声明为const的引用类型:
for (const int& num : nums) {
std::cout << num << std::endl;
}
下面简单解释一下这样做的原因,首先自定义一个类,并且重载一下输出流运算符(为了输出省点事~)
#include#include template class complex { private: Type x; Type y; public: complex(Type x = Type(), Type y = Type()) :x(x), y(y) { std::cout << "构造函数调用" << std::endl; } complex(const complex& right) { x = right.x; y = right.y; std::cout << "复制构造函数调用" << std::endl; } template friend std::ostream& operator<<(std::ostream &c, const complex & out); }; template std::ostream& operator<<(std::ostream &c, const complex & out){ //重载了输出流,方便输出 c << out.x << "," << out.y; return c; }
然后用下面的主函数测试
int main() {
std::vector> nums;
nums.reserve(10);
nums.emplace_back(1, 2);
nums.emplace_back(3, 4);
nums.emplace_back(5, 6);
nums.emplace_back(7, 8);
nums.emplace_back(9, 10);
for (complex num : nums) {
std::cout << num << std::endl;
}
}
看一下输出结果:
调用了五次复制构造函数,也就是每次输出之前,都复制了一次。为了避免不必要的复制,改用引用类型:
for (complex& num : nums) { std::cout << num << std::endl; }
简单的原理,省去了复制环节,因此不论是否修改容器内变量的值,都推荐使用引用,如果不需要修改,那就使用const关键字修饰一下
内部机制问题:如果我自定义了一个容器,(可以参考上文提到的手动模拟的array容器),如果要使用这样的for循环,自己需要怎样实现?
在我自己实现的过程中,得到一个结论,就是迭代器。自定义容器类型中,只要定义了和标准库接口相同的迭代器,就能够使用范围for循环,(需要定义iterator类型,begin(),以及end() )
为了证明这个机制,先来手动快速实现一个阉割版顺序容器:
class array {
private:
int* list;
unsigned int size;
public:
array(unsigned int n = 0) {
list = new int[n];
size = n;
for (int i = 0; i < n; ++i) {
list[i] = 0;
}//简单的将每个值初始化为0;
}
int& operator[](const unsigned int &idx) {
return *(list + idx);
}//重载取下标运算符
};
如果这时候我对这个容器 使用范围for循环:
int main() {
int n = 10;
array nums(n);
for (int &num : nums) {
num = 5;
std::cout << num << std::endl;
}
}
他报了一个这样的错误:
于是我们自己定义一个迭代器:
class array {
private:
int* list;
unsigned int size;
public:
array(unsigned int n = 0) {
list = new int[n];
size = n;
_begin = list;
_end = list + n;
for (int i = 0; i < n; ++i) {
list[i] = 0;
}//简单的将每个值初始化为0;
}
~array() {
delete[]list;
}//加上了析构函数,刚刚没加是因为疏忽 qwq
int& operator[](const unsigned int &idx) {
return *(list + idx);
}//重载取下标运算符
typedef int* iterator;//定义array容器类型的迭代器
iterator begin() {
return _begin;
}
iterator end() {
return _end;
}
private:
iterator _begin;
iterator _end;
};
再使用相同的测试函数,就没有报错了
int main() {
int n = 10;
array nums(n);
for (int &num : nums) {
num = 5;
std::cout << num << std::endl;
}
}
得到如下输出结果
上文的报错提到,只需要begin()接口,那么试一试删掉迭代器,只保留begin()和end()能否完成范围for循环:
内部形式如下
int* begin() {
return list;
}
int* end() {
return list + size;
}
依然得到了正确的结果
提示:如果只保留begin的话会报错
在定义里,迭代器是泛化的指针,也就是说其实指针就是特殊的迭代器。因此如果自定义容器要使用范围for循环,就必须定义迭代器,并且提供begin()和end()接口
对于一个范围for循环也可以等效为下面这种代码
for (int& num : nums) {
std::cout << num << std::endl;
}
//两种写法等价
for (std::vector::iterator it = nums.begin(); it != nums.end(); ++it) {
int& num = *it;
std::cout << num << std::endl;
}
总结
范围for循环在有些地方非常难用,尤其是在循环中需要对下标进行操作,或是不需要遍历容器全部元素的时候,但特定情况下书写起来又特别整洁,尤其在代替迭代器的时候,非常的简洁。这一次我斗胆简单挖掘了一下其中的底层机制,并写了一篇blog,如果有不对的地方还请各位大佬批评指正



