栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

简单理解C++范围for循环

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

简单理解C++范围for循环

范围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,如果有不对的地方还请各位大佬批评指正

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/948106.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号