int 取值范围_C++ 基于范围的 for 循环

(给CPP开发者加星标,提升C/C++技能)

来源:Rayen0715 https://blog.csdn.net/hailong0715/article/details/54172848

【导读】:C++11提供了一个特殊版本的 for 循环,在很多情况下,它都可以简化数组的处理,这就是基于范围的 for 循环。在使用基于范围的 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操作并没有生效,修改后的输出仍然和原来的元素值一样,因为这种遍历方法做的是值拷贝。那么如果要遍历容器内的元素的同时又要修改元素的值该怎么做呢,方法就是将遍历的变量声明为引用类型,看下面例子:

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;}

输出为:

GetSet123456请按任意键继续. . .

可以看出, 如果冒号后面的表达式是一个函数调用时,函数仅会被调用一次。
最后来看看用基于范围的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,运行上述代码程序崩溃了

0027bb3c2efb106212278edda06f3914.png

究其原因还是由于在遍历容器的时候,在容器中插入一个元素导致迭代器失效了,因此,基于范围的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& 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& begin()    {        return Myiterator(__begin);    }    Myiterator& end()    {        return Myiterator(__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、自定义迭代器* 解引用操作,显然解引用操作必须容器对应元素的引用,否则引用遍历时将会出错

- EOF -

推荐阅读   点击标题可跳转

1、华为开始对 C 语言开发者下手了!

2、C++ rapidjson 基础入门

3、C++ 列表初始化详解

关于C++ 基于范围的For循环,欢迎在评论中和我探讨。觉得文章不错,请点赞和在看支持我继续分享好文。谢谢!

关注『CPP开发者』

看精选C++技术文章 . 加C++开发者专属圈子

↓↓↓

0d46c9edf3d6bacc64081ec681338d6b.png

点赞和在看就是最大的支持❤️

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值