首页 > 编程语言 >C++11新特性之基本范围的For循环(range-based-for)

C++11新特性之基本范围的For循环(range-based-for)

时间:2023-10-12 11:11:56浏览次数:36  
标签:11 容器 begin 遍历 based cout value range end

C++11新特性之基本范围的For循环(range-based-for)

最新推荐文章于 2023-07-22 19:30:58 发布 于 2017-01-07 13:49:35 发布 49588 收藏 174 版权

Range-Based-For

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

  1. int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  2. for (int i = 0; i < 10; i++)
  3. cout << arr[i];
而遍历容器类的For如下:

  1. std::vector<int> vec {1,2,3,4,5,6,7,8,9,10};
  2. for (std::vector<int>::iterator itr = vec.begin(); itr != vec.end(); itr++)
  3. std::cout << *itr;
不管上面哪一种方法,都必须明确的确定for循环开头以及结尾条件,而熟悉C#或者python的人都知道在C#和python中存在一种for的使用方法不需要明确给出容器的开始和结束条件,就可以遍历整个容器,幸运的是C++11中引入了这种方法也就是基于范围的For(Range-Based-For),用基于范围的For 改写上面两个例子:

  1. std::vector<int> vec {1,2,3,4,5,6,7,8,9,10};
  2. for (auto n :vec)
  3. std::cout << n;
  4. int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  5. for (auto n : arr)
  6. std::cout << n;
可以看到改写后的使用方法简单了很多,代码的可读性提升了一个档次,但是需要注意的在上述对容器的遍历是只读的,也就是说遍历的值是不可修改的,看下面例子:

  1. std::vector<int> vec {1,2,3,4,5,6,7,8,9,10};
  2. cout << "修改前" << endl;
  3. for (auto n :vec)
  4. std::cout << n++;
  5. cout << endl;
  6. cout << "修改后" << endl;
  7. for (auto j : vec)
  8. std::cout << j;
在上述例子中,我们首先遍历容器的内容,然后给容器内的元素每个都加1,然后再遍历一次容器的内容,示例的输出结果如下:

  1. 修改前
  2. 12345678910
  3. 修改后
  4. 12345678910
可以看到,我们遍历后对容器内元素的加1操作并没有生效,修改后的输出仍然和原来的元素值一样,因此可以看出,这种遍历方法是可读的。(不过个人觉得C++11在这个方面做的不够好,首先如果这种遍历方法是只读的,那么编译器就应该拒绝对遍历的元素进行修改的操作,需要编程的人来注意的话就太不明智了,不知道C++11标准的制定人员是处于什么目的让这种只读的遍历却允许伴随了着修改的语法存在,如果不注意的话很容易有疑惑为什么命名对容器内的值修改了但未生效。括号内的内容仅为吐槽,哈哈),那么如果要遍历容器内的元素的同时又要修改元素的值该怎么做呢,方法就是将遍历的变量声明为引用类型,看下面例子:
  1. std::vector<int> vec {1,2,3,4,5,6,7,8,9,10};
  2. cout << "修改前" << endl;
  3. for (auto& n :vec)
  4. std::cout << n++;
  5. cout << endl;
  6. cout << "修改后" << endl;
  7. for (auto j : vec)
  8. std::cout << j;
代码的输出结果为:

  1. 修改前
  2. 12345678910
  3. 修改后
  4. 234567891011
可以看到,容器内的元素每个都加了1,两者的区别仅为在修改的时候,将声明的遍历遍历n从auto 声明为auto &,使用基于范围的For循环一定要注意这一点, 基于范围的FOR循环的遍历是只读的遍历,除非将变量变量的类型声明为引用类型。

基于范围的For使用需要注意的细节

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

  1. std::map<string, int> map = { { "a", 1 }, { "b", 2 }, { "c", 3 } };
  2. for (auto &val : map)
  3. 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元素,也是不能修改器元素的,看下面例子:

  1. set<int> ss = { 1, 2, 3, 4, 5, 6 };
  2. for (auto& n : ss)
  3. cout << n++ << endl;
上述代码定义了一个set,使用引用类型遍历set中的元素,然后对元素的值进行修改,该段代码编译失败:error C3892: 'n' : you cannot assign to a variable that is const。同样对于map中的first元素也是不能进行修改的。
再来看看假如我们给基于范围的for循环的:冒号都免的表达式不是一个容器而是一个函数,看看函数会被调用多少次?
  1. set<int> ss = { 1, 2, 3, 4, 5, 6 };
  2. const set<int>& getSet()
  3. {
  4. cout << "GetSet" << endl;
  5. return ss;
  6. }
  7. int main()
  8. {
  9. for (auto &n : getSet())
  10. cout << n << endl;
  11. }
输出为:

  1. GetSet
  2. 1
  3. 2
  4. 3
  5. 4
  6. 5
  7. 6
  8. 请按任意键继续. . .
可以看出, 如果冒号后面的表达式是一个函数调用时,函数仅会被调用一次。

最后来看看用基于范围的for循环迭代时修改容器会出现什么情况?

  1. vector<int> vec = { 1, 2, 3, 4, 5, 6 };
  2. int main()
  3. {
  4. for (auto &n : vec)
  5. {
  6. cout << n << endl;
  7. vec.push_back(7);
  8. }
  9. }
上述代码在遍历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方法,此外还需要一个自定义的迭代器对象来负责范围的取值。看下面的例子:
  1. class Myiterator
  2. {
  3. public:
  4. Myiterator(int val) :_value(val){}
  5. bool operator!=(const Myiterator& other) const
  6. {
  7. return this->_value != other._value;
  8. }
  9. const int & operator*()
  10. {
  11. return _value;
  12. }
  13. int& operator++()
  14. {
  15. return ++_value;
  16. }
  17. private:
  18. int _value;
  19. };
  20. class Range
  21. {
  22. public:
  23. Range(int begin, int end) :__begin(begin), __end(end){}
  24. Myiterator& begin()
  25. {
  26. return Myiterator(__begin);
  27. }
  28. Myiterator& end()
  29. {
  30. return Myiterator(__end);
  31. }
  32. private:
  33. int __begin;
  34. int __end;
  35. };
  36. int main()
  37. {
  38. for (auto i : Range(1, 10))
  39. cout << i << " ";
  40. }
输出为:1 2 3 4 5 6 7 8 9 请按任意键继续. . .,可见,对于自定义的Range类我们可以用估计与范围的For循环来遍历,如果要实现Range(1,10,2)也就是带步长的Range的话只需要将其迭代器的++操作符改写,_value+=2即可。 也可将上述代码改写成模板类型,但是这部分模板值适用于数值类型,如果要适合其他自定义的类,需要各位自己去改写迭代器的相关代码,是的其能够支持自定义类的++,*,!操作。各位大牛执行实现吧。
  1. template <typename T>
  2. class Myiterator
  3. {
  4. public:
  5. Myiterator(T val) :_value(val){}
  6. bool operator!=(const Myiterator<T>& other) const
  7. {
  8. return this->_value != other._value;
  9. }
  10. const T & operator*()
  11. {
  12. return _value;
  13. }
  14. T operator++()
  15. {
  16. return ++_value;
  17. }
  18. private:
  19. T _value;
  20. };
  21. template<typename T>
  22. class Range
  23. {
  24. public:
  25. Range(T begin, T end) :__begin(begin), __end(end){}
  26. Myiterator<T>& begin()
  27. {
  28. return Myiterator<T>(__begin);
  29. }
  30. Myiterator<T>& end()
  31. {
  32. return Myiterator<T>(__end);
  33. }
  34. private:
  35. T __begin;
  36. T __end;
  37. };
  38. int main()
  39. {
  40. for (auto i : Range<int>(1, 10))
  41. cout << i << " ";
  42. }


因此归纳总结,为了给自定义的类实现基于范围的For的步骤如下:

1、定义自定义类的迭代器  //这里的迭代器是广义的迭代器,指针也属于该范畴。

2、该类型拥有begin() 和 end() 成员方法,返回值为迭代器(或者重载全局的begin() 和 end() 函数也可以) 。

3、自定义迭代器的 != 比较操作 。

4、自定义迭代器的++ 前置自增操作,显然该操作要是迭代器对象指向该容器的下一个元素 。

5、自定义迭代器* 解引用操作,显然解引用操作必须容器对应元素的引用,否则引用遍历时将会出错


<script> $(function() { setTimeout(function () { var mathcodeList = document.querySelectorAll('.htmledit_views img.mathcode'); if (mathcodeList.length > 0) { for (let i = 0; i < mathcodeList.length; i++) { if (mathcodeList[i].naturalWidth === 0 || mathcodeList[i].naturalHeight === 0) { var alt = mathcodeList[i].alt; alt = '\\(' + alt + '\\)'; var curSpan = $(''); curSpan.text(alt); $(mathcodeList[i]).before(curSpan); $(mathcodeList[i]).remove(); } } MathJax.Hub.Queue(["Typeset",MathJax.Hub]); } }, 1000) }); </script>

标签:11,容器,begin,遍历,based,cout,value,range,end
From: https://www.cnblogs.com/tomato-haha/p/17759053.html

相关文章

  • 2023ICCV_Retinexformer: One-stage Retinex-based Transformer for Low-light Image
    一.Motivation(1)Retinex理论没有考虑到噪声,并且基于Retinex分解的网络通常需要很多阶段训练。(2)直接使用从CNN从低光图像到正常光图像的映射忽略了人类的颜色感知,CNN更适合捕获局部信息,对于捕获远程依赖和非局部自相似性方面存在局限。二.Contribution(1)设计了一个阶段......
  • 10.11
    又是运气爆表的一天!8.10切A,线段树板子看了一眼发现B有点像贪心,但是不太会,开C9.40切C换根板子,并且考过回去看B,想了想会了10.50切B打了个T4的暴力+剪枝检查跑路应得分数400=100+100+100+80实得分数372=100+92+100+80最高分400=100+100+100+100......
  • 11. 用Rust手把手编写一个wmproxy(代理,内网穿透等), 实现健康检查
    11.用Rust手把手编写一个wmproxy(代理,内网穿透等),实现健康检查项目++wmproxy++gite:https://gitee.com/tickbh/wmproxygithub:https://github.com/tickbh/wmproxy健康检查的意义健康检查维持着系统的稳定运行,极大的加速着服务的响应时间,并保证服务器不会把消息包转......
  • 10.11总结
    1.解决了好几个报错①数据库表设置自增,在application.properties中mybatiesplus中要写id_type=auto,实现不写入id数据,数据库进行自增操作②没写注释@Autowired导致的一系列错误③mapper映射找不到,包对不上忽略一个问题就是创建包的时候要用com/example/mapper而不是com.example.......
  • 1011打卡
    1.串联所有单词的子串(30) 思想:哈希表+滑动窗口classSolution{publicList<Integer>findSubstring(Strings,String[]words){intlen=words.length;intwordlen=words[0].length();HashMap<String,Integer>map=newHashMap<>......
  • 每日总结20231011
    代码时间(包括上课)3h代码量(行):100行博客数量(篇):1篇相关事项:1、今天是周三,今天上午上的是软件构造,软件构造讲的是程序规范化。2、今天下午我们进行了献血的演讲的观看,明白了献血的意义。3、今天还打算看看软件设计师相关的题目,我要过,我要通过,我要高分通过!......
  • 20231011
    //eclectic,extreme,halfway,medium,request,tradeoff,giveground,meanmethod,middleway,midwaypoint,strikeahappymediumeclectic-折中的,多元的Eclecticreferstoastyleorapproachthatcombineselementsorideasfromvarioussourcesorstyles.......
  • 144-11
    给定二叉树,删除结点值为x的左右子树利用层次遍历找到结点值为x的左右子树,分别删除;删除算法voidDelete(Tree&T){if(T){Delete(T->lchild);Delete(T->rchild);free(T);}}完整算法#include<stdio.h>#include<stdlib.h>#d......
  • Attribute Based Group Signature with Revocation
    AttributeBasedGroupSignatureswerefirstintroducedin[12].Itwasproposedtoservethepurposeofincludingattributesinagroupsignaturescheme.GroupSignaturesallowamemberofagrouptosignonbehalfoftheotherswhileinABGSschemesthe......
  • 2023.10.11 一些好题
    A你有\(m\)个相同的球,球有性能\(c\),你可以测试\(x\),若\(x\gec\),那么球会碎掉,若\(x<c\),那么球不碎。性能的范围\(n\le1e5\)。求最多要测试多少次。首先答案有一个上限是\(\logn\)。所以令\(m\to\min(m,\logn)\)所以我们记状态可以记\(dp_{l,r,k}\)表示当前确......