C++编程法则365天一天一条(199) 数组、容器遍历的高级写法--基于范围的for循环

C++11引入的基于范围的for循环简化了数组和容器的遍历,无需明确开始和结束条件。它等价于传统的迭代器遍历,但更易读。对于数组,begin-expr为__range,end-expr为__range+__bound。对于类,begin-expr为__range.begin(),end-expr为__range.end()。使用时注意auto推导的类型、容器约束、不可在循环中修改容器以及函数调用次数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考:https://en.cppreference.com/w/cpp/language/range-for

前言

熟悉C++98/03的对于for循环就再了解不过了,如果我们要遍历一个数组,那么在C++98/03中的实现方式:

int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 
for (int i = 0; i < 10; i++) 
    cout << arr[i];  

而遍历容器类的for如下:

std::vector<int> vec {1,2,3,4,5,6,7,8,9,10};  
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it)  
    cout << *it;  

C++11引入auto关键字后,可以自动推导迭代器的类型,所以上面简化为:

std::vector<int> vec {1,2,3,4,5,6,7,8,9,10};  
for (auto it = vec.begin(); it != vec.end(); ++it)  
    cout << *it;  

基于范围的for循环写法

不管上面哪一种方法,都必须明确的确定for循环开头以及结尾条件,而熟悉C#或者python的人都知道在C#和python中存在一种for的使用方法不需要明确给出容器的开始和结束条件,就可以遍历整个容器,幸运的是C++11中引入了这种方法也就是基于范围的for循环:

基于范围的for循环的定义为:

attr (optional) for ( init-statement (optional) range-declaration : range-expression )
loop-statement

等价于:

{
init-statement
auto && __range = range-expression ;
auto __begin = begin-expr ;
auto __end = end-expr ;
for ( ; __begin != __end; ++__begin)
{
	range-declaration = *__begin;
	loop-statement
}
}

对于数组:
begin-expr = __range;
end-expr = __range + __bound;

对于类:
begin-expr = __range.begin();
end-expr = __range.end();

用基于范围的for循环改写上面两个例子:

//数组
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };  
for (auto n : arr)  
    cout << n;  

//vector容器
std::vector<int> vec {1,2,3,4,5,6,7,8,9,10};  
for (auto n :vec)  
    std::cout << n;  

可以看到改写后的使用方法简单了很多,代码的可读性提升了一个档次,但是需要注意的n是对容器元素的值拷贝,所以修改n无法改变容器元素的值,如果需要修改其中元素,可以声明为auto &:

#include <iostream>
#include <vector>
using namespace std;

int main()
{
	std::vector<int> vec{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	cout << "修改前" << endl;
	for (auto &n : vec)
		std::cout << ++n;
	cout << endl;

	cout << "修改后" << endl;
	for (auto j : vec)
		std::cout << j;
	cout << endl;

	system("pause");
	return 0;
}

img

基于范围的for循环使用的要求及依赖条件

有没有思考过,什么东西可以支持基于范围的for循环,上面的例子我们使用了普通数组和vector容器。

(1)for循环迭代的范围是可以确定的;如数组的第一个元素和最后一个元素便构成了for循环的迭代范围。
(2)对于用户自定义的类,能对此自定义数据结构类型调用begin和end方法,无论是成员函数或者独立函数都可以,要能返回迭代器类型。
(3)返回的迭代器类型必须支持operator*方法,operator!=方法和前缀形式的operator++方法,同样无论是成员函数或独立函数都可以。
(4)对于STL标准模板库中(如:vector,set,list,map,queue,deque,string等)的各种容器使用“基于范围的for循环”是不会有任何问题的,因为这些容器中都定义了相关操作。

事实上,我们基本没有机会去实现一个类来支持for循环,因为STL提供的各种容器已经足以满足我们99.9%的需求了。

**目前来看,支持数组、STL提供的容器、值列表、表达式。**当然,表达式其实就是返回数组、容器、值列表。

值列表:

// Example program
#include <iostream>
#include <string>

int main()
{
    for(auto i : {1,2,3,4}) {
        std::cout << i << " ";
    }
}

使用时需要注意的地方

1.注意auto自动推导的类型

虽然基于范围的for循环使用起来非常的方便,我们不用再去关注for的开始条件和结束条件等问题了,但是还是有一些细节问题在使用的时候需要注意,来看下对于容器map的遍历:

std::map<string, int>  map = { { "a", 1 }, { "b", 2 }, { "c", 3 } };  

for (auto &val : map)  
    cout << val.first << "->" << val.second << endl;  

为什么是使用val.first,val.second而不是直接输出value呢?在遍历容器的时候,auto自动推导的类型是容器的value_type类型,而不是迭代器,而map中的value_type是std::pair,也就是说val的类型是std::pair类型的,因此需要使用val.first,val.second来访问数据。

2.注意容器本身的约束

使用基于范围的for循环还要注意一些容器类本身的约束,比如set的容器内的元素本身有容器的特性就决定了其元素是只读的,哪怕的使用了引用类型来遍历set元素,也是不能修改器元素的,看下面例子:

set<int> ss = { 1, 2, 3, 4, 5, 6 };  
for (auto& n : ss)  
    cout << ++n << endl;  

上述代码定义了一个set,使用引用类型遍历set中的元素,然后对元素的值进行修改,该段代码编译失败:error C3892: ‘n’ : you cannot assign to a variable that is const。同样对于map中的first元素也是不能进行修改的。

3.当冒号后不是容器而是一个函数

再来看看假如我们给基于范围的for循环的:冒号后面的表达式不是一个容器而是一个函数,看看函数会被调用多少次?

#include <iostream>
#include <set>

using namespace std;
set<int> ss = { 1, 2, 3, 4, 5, 6 };
const set<int> getSet()
{
	cout << "GetSet" << endl;
	return ss;
}

int main()
{
	for (auto n : getSet())
		cout << n << endl;
	system("pause");
	return 0;
}

img

可以看出,如果冒号后面的表达式是一个函数调用时,函数仅会被调用一次,本质是将函数的返回值作为容器进行遍历。

4.不要在for循环中修改容器

#include <iostream>
#include <vector>

using namespace std;
vector<int> vec = { 1, 2, 3, 4, 5, 6 };

int main()
{
	for (auto n : vec)
	{
		cout << n << endl;
		vec.push_back(7);
	}
	system("pause");
	return 0;
}

上述代码在遍历vector时,在容器内插入一个元素7,运行上述代码程序崩溃了。

img

究其原因还是由于在遍历容器的时候,在容器中插入一个元素导致迭代器失效了,因此,基于范围的for循环和普通的for循环一样,在遍历的过程中如果修改容器,会造成迭代器失效,(有关迭代器失效的问题请参阅C++ primer这本书,写的很详细)也就是说基于范围的for循环的内部实现机制还是依赖于迭代器的相关实现。

需要明白的一点是,C++11 提供的这种遍历方法,本质还是借助了容器提供的迭代器来实现,所以我们也可以定义自己的容器,来实现这样的遍历方法,当然,没有这个必要,现有的容器足够支撑我们使用了。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值