目录
C++11新特性之基本范围的For循环(range-based-for)
范围for循环
1.基于范围的for循环
for(元素类型 元素对象:容器对象)
{
循环体
}
(1.1)如果循环体由单条语句或者单个结构块组成,可以省略花括号
(1.2)用元素对象依次结合容器对象中的每一个元素,每结合一个元素,执行依次循环体,直至容器内的所有元素都被结合完为止.
(1.3)不依赖于下标元素,通用
(1.4)不需要访问迭代器,透明
(1.5)不需要定义处理函数,简洁
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void print(int i)
{
cout << i << " ";
}
int main()
{
int ai[]{ 65, 66, 67, 68, 69 };
//计算数组元素个数
size_t size = sizeof(ai) / sizeof(ai[0]);
vector<int> vi(ai, ai + size);
//基于下标运算的for循环,不是所有的容器都支持下标运算,不通用
for (size_t i = 0; i < size; ++i)
cout << ai[i] << " ";
cout << endl;
for (size_t i = 0; i < vi.size(); ++i)
cout << vi[i] << " ";
cout << endl;
//基于迭代器的for循环,需要指明容器两端,且对迭代器做自增,必须了解迭代器的运算规则,不够透明
for (int *it = ai; it != ai + size; ++it)
cout << *it << " ";
cout << endl;
for (auto it = vi.begin(); it != vi.end(); ++it)
cout << *it << " ";
cout << endl;
//基于泛型函数的for循环,需要提供针对元素的处理函数,对于一般性遍历而言比较繁琐
for_each(ai, ai + size, print);
cout << endl;
for_each(vi.begin(), vi.end(), print);
cout << endl;
for (auto a : ai)
cout << a << " ";
cout << endl;
for (auto a : vi)
cout << a << " ";
cout << endl;
for (auto &a : ai)
++a;
for (auto &a : vi)
--a;
for (auto const &a : ai)
cout << a << " ";
cout << endl;
for (auto const &a : vi)
cout << a << " ";
cout << endl;
for (char a : ai)
cout << a << " ";
cout << endl;
for (char a : vi)
cout << a << " ";
cout << endl;
return 0;
}
2.范围循环的注意事项
(2.1)对map和multimap容器使用范围循环,每次拿到的元素既不是键也不是值,而是由键和值组成的pair
(2.2)在使用基于范围的for循环时,不能违背容器本身的约束
(2.3)基于范围的for循环,无论循环体执行多少次,冒号后面的表达式永远只执行一次
(2.4)基于范围的for循环,其底层实现依然要借助于容器的迭代器,
因此任何可能导致迭代器失效的结构性改变,都可能引发未定义的后果
#include <iostream>
#include <string>
#include <map>
#include <list>
using namespace std;
list<int> getScores(void)
{
cout << __FUNCTION__ << endl;
return{ 70, 75, 80, 85, 90, 95 };
}
int main()
{
//对map和multimap容器使用范围循环,每次拿到的元素既不是键也不是值,而是由键和值组成的pair
multimap<string, int> msi;
msi.insert(make_pair("张飞", 100));
msi.insert(make_pair("赵云", 90));
msi.insert(make_pair("关羽", 80));
for (auto c : msi)
cout << c.first << ":" << c.second << endl;
cout << endl;
for (auto it = msi.begin(); it != msi.end(); ++it)
cout << it->first << ":" << it->second << endl;
cout << endl;
//在使用基于范围的for循环时,不能违背容器本身的约束
/*for (pair<string,int> &c:msi)
if (c.first == "张飞")
c.first = "张菲菲";*/
for (auto c : msi)
cout << c.first << ":" << c.second << endl;
cout << endl;
//基于范围的for循环,无论循环体执行多少次,冒号后面的表达式永远只执行一次
for (auto score : getScores())
cout << score << " ";
cout << endl;
auto scores = getScores();
for (auto score : scores)
{
cout << score << " ";
//基于范围的for循环,其底层实现依然要借助于容器的迭代器,
//因此任何可能导致迭代器失效的结构性改变,都可能引发未定义的后果
//scores.pop_front();
}
cout << endl;
return 0;
}
3.使自己定义的容器类型支持范围循环
一个类只要提供了分别获取起始和终止迭代器的begin和end函数,就可以支持基于范围的for循环
#include "stdafx.h"
#include <iostream>
using namespace std;
template <typename T, size_t S>
class Array
{
public:
T &operator[](size_t i)
{
return m_array[i];
}
T const &operator[](size_t i)const
{
return const_cast<Array&>(*this)[i];
}
//获取起始迭代器
T *begin()
{
return m_array;
}
T const *begin()const
{
return const_cast<Array *>(this)->begin();
}
//获取终止迭代器
T *end()
{
return m_array + S;
}
T const *end()const
{
return const_cast<Array *>(this)->end();
}
private:
T m_array[S];
};
int main()
{
int i = 0;
Array<int, 5> ai;
for (auto &a : ai)
a = ++i * 10;
auto const &cai = ai;
for (auto &a : cai)
cout << /*++*/a << " ";
cout << endl;
return 0;
}
C++11新特性之基本范围的For循环(range-based-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 itr = vec.begin(); itr != vec.end(); itr++)
std::cout << *itr;
不管上面哪一种方法,都必须明确的确定for循环开头以及结尾条件,而熟悉C#或者python的人都知道在C#和python中存在一种for的使用方法不需要明确给出容器的开始和结束条件,就可以遍历整个容器,幸运的是C++11中引入了这种方法也就是基于范围的For(Range-Based-For),用基于范围的For 改写上面两个例子:
std::vector<int> vec {1,2,3,4,5,6,7,8,9,10};
for (auto n :vec)
std::cout << n;
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
for (auto n : arr)
std::cout << n;
可以看到改写后的使用方法简单了很多,代码的可读性提升了一个档次,但是需要注意的在上述对容器的遍历是只读的,也就是说遍历的值是不可修改的,看下面例子:
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;
在上述例子中,我们首先遍历容器的内容,然后给容器内的元素每个都加1,然后再遍历一次容器的内容,示例的输出结果如下:
修改前
12345678910
修改后
12345678910
可以看到,我们遍历后对容器内元素的加1操作并没有生效,修改后的输出仍然和原来的元素值一样,因此可以看出,这种遍历方法是可读的。(不过个人觉得C++11在这个方面做的不够好,首先如果这种遍历方法是只读的,那么编译器就应该拒绝对遍历的元素进行修改的操作,需要编程的人来注意的话就太不明智了,不知道C++11标准的制定人员是处于什么目的让这种只读的遍历却允许伴随了着修改的语法存在,如果不注意的话很容易有疑惑为什么命名对容器内的值修改了但未生效。括号内的内容仅为吐槽,哈哈),那么如果要遍历容器内的元素的同时又要修改元素的值该怎么做呢,方法就是将遍历的变量声明为引用类型,看下面例子:
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;
修改前
12345678910
修改后
234567891011
可以看到,容器内的元素每个都加了1,两者的区别仅为在修改的时候,将声明的遍历遍历n从auto 声明为auto &,使用基于范围的For循环一定要注意这一点,基于范围的FOR循环的遍历是只读的遍历,除非将变量变量的类型声明为引用类型。
基于范围的For使用需要注意的细节
虽然基于范围的For循环使用起来非常的方便,我们不用再去关注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来访问数据。
此外,使用基于范围的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元素也是不能进行修改的。
再来看看假如我们给基于范围的for循环的:冒号都免的表达式不是一个容器而是一个函数,看看函数会被调用多少次?
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;
}
GetSet
1
2
3
4
5
6
请按任意键继续. . .
可以看出,如果冒号后面的表达式是一个函数调用时,函数仅会被调用一次。
最后来看看用基于范围的for循环迭代时修改容器会出现什么情况?
vector<int> vec = { 1, 2, 3, 4, 5, 6 };
int main()
{
for (auto &n : vec)
{
cout << n << endl;
vec.push_back(7);
}
}
上述代码在遍历vector时,在容器内插入一个元素7,运行上述代码程序崩溃了
究其原因还是由于在遍历容器的时候,在容器中插入一个元素导致迭代器失效了,因此,基于范围的for循环和普通的for循环一样,在遍历的过程中如果修改容器,会造成迭代器失效,(有关迭代器失效的问题请参阅C++ primer这本书,写的很详细)也就是说基于范围的For循环的内部实现机制还是依赖于迭代器的相关实现。
自定义的类实现基于范围的for
上面说了这么多的基于范围的For的用法和使用细节,但是这些用法都用来遍历C++提供的一些数组,容器类,是否可以遍历自定义的类呢?比如在python中for可以有这种用法:for(i in range(1,10)),遍历1到10的多有元素,我们是否可以自定义一个Range类来实现相关的操作呢?答案是肯定的,下面来通过这个Range类的实现看下如果为自定义的类实现基于范围的For。
由于基本范围的For不需要明确指定遍历的开始和结束范围,但是在内部实现上依赖于自定义类提供的begin和end方法,此外还需要一个自定义的迭代器对象来负责范围的取值。看下面的例子:
class Myiterator
{
public:
Myiterator(int val) :_value(val){}
bool operator!=(const Myiterator& other) const
{
return this->_value != other._value;
}
const int & operator*()
{
return _value;
}
int& operator++()
{
return ++_value;
}
private:
int _value;
};
class Range
{
public:
Range(int begin, int end) :__begin(begin), __end(end){}
Myiterator& begin()
{
return Myiterator(__begin);
}
Myiterator& end()
{
return Myiterator(__end);
}
private:
int __begin;
int __end;
};
int main()
{
for (auto i : Range(1, 10))
cout << i << " ";
}
输出为:1 2 3 4 5 6 7 8 9 请按任意键继续. . .,可见,对于自定义的Range类我们可以用估计与范围的For循环来遍历,如果要实现Range(1,10,2)也就是带步长的Range的话只需要将其迭代器的++操作符改写,_value+=2即可。
也可将上述代码改写成模板类型,但是这部分模板值适用于数值类型,如果要适合其他自定义的类,需要各位自己去改写迭代器的相关代码,是的其能够支持自定义类的++,*,!操作。各位大牛执行实现吧。
template <typename T>
class Myiterator
{
public:
Myiterator(T val) :_value(val){}
bool operator!=(const Myiterator<T>& other) const
{
return this->_value != other._value;
}
const T & operator*()
{
return _value;
}
T operator++()
{
return ++_value;
}
private:
T _value;
};
template<typename T>
class Range
{
public:
Range(T begin, T end) :__begin(begin), __end(end){}
Myiterator<T>& begin()
{
return Myiterator<T>(__begin);
}
Myiterator<T>& end()
{
return Myiterator<T>(__end);
}
private:
T __begin;
T __end;
};
int main()
{
for (auto i : Range<int>(1, 10))
cout << i << " ";
}
因此归纳总结,为了给自定义的类实现基于范围的For的步骤如下:
1、定义自定义类的迭代器 //这里的迭代器是广义的迭代器,指针也属于该范畴。
2、该类型拥有begin() 和 end() 成员方法,返回值为迭代器(或者重载全局的begin() 和 end() 函数也可以) 。
3、自定义迭代器的 != 比较操作 。
4、自定义迭代器的++ 前置自增操作,显然该操作要是迭代器对象指向该容器的下一个元素 。
5、自定义迭代器* 解引用操作,显然解引用操作必须容器对应元素的引用,否则引用遍历时将会出错
还可以参考:https://www.cnblogs.com/h46incon/archive/2013/06/02/3113737.html