内容来自 深入应用C++11:代码优化与工程级应用

在 C++03/98 中,不同的容器和数组,遍历的方法不尽相同,写法不统一,也不够简洁,而 C++11 基于范围的 for 循环以统一、简洁的方式来遍历容器和数组,用起来更方便了。

C++11 for 循环的新用法

我们知道,在 C++ 中遍历一个容器的方法一般是这样的:

#include <iostream>
#include <vector>
int main(void)
{std::vector<int> arr;// ...for(auto it = arr.begin(); it != arr.end(); ++it){std::cout << *it << std::endl;}return 0;
}

上面借助前面介绍过的 C++ auto 关键字,省略了迭代器的声明。

当然,熟悉stl的读者肯定还知道在 中有一个 for_each 算法可以用来完成和上述同样的功能:

#include <algorithm>
#include <iostream>
#include <vector>
void do_cout(int n)
{std::cout << n << std::endl;
}
int main(void)
{std::vector<int> arr;// ...std::for_each(arr.begin(), arr.end(), do_cout);return 0;
}

std::for_each 比起前面的 for 循环,最大的好处是不再需要关注迭代器(Iterator)的概念,只需要关心容器中的元素类型即可。

但不管是上述哪一种遍历方法,都必须显式给出容器的开头(Begin)和结尾(End)。这是因为上面的两种方法都不是基于“范围(Range)”来设计的。

我们先来看一段简单的C#代码:

int[] fibarray = new int[] { 0, 1, 1, 2, 3, 5, 8, 13 };
foreach (int element in fibarray)
{System.Console.WriteLine(element);
}

上面这段代码通过“foreach”关键字使用了基于范围的 for 循环。可以看到,在这种 for 循环中,不再需要传递容器的两端,循环会自动以容器为范围展开,并且循环中也屏蔽掉了迭代器的遍历细节,直接抽取出容器中的元素进行运算。

与普通的for循环相比,基于范围的循环方式是“自明”的。这种语法构成的循环不需要额外的注释或语言基础,很容易就可以看清楚它想表达的意义。在实际项目中经常会遇到需要针对容器做遍历的情况,使用这种循环方式无疑会让编码和维护变得更加简便。

现在,在 C++11 中终于有了基于范围的 for 循环(The range-based for statement)。再来看一开始的 vector 遍历使用基于范围的 for 循环应该如何书写:

#include <iostream>
#include <vector>
int main(void)
{std::vector<int> arr = { 1, 2, 3 };// ...for(auto n : arr)  //使用基于范围的for循环{std::cout << n << std::endl;}return 0;
}

在上面的基于范围的 for 循环中,n 表示 arr 中的一个元素,auto 则是让编译器自动推导出 n 的类型。在这里,n 的类型将被自动推导为 vector 中的元素类型 int。

在 n 的定义之后,紧跟一个冒号:,之后直接写上需要遍历的表达式,for 循环将自动以表达式返回的容器为范围进行迭代。

在上面的例子中,我们使用 auto 自动推导了 n 的类型。当然在使用时也可以直接写上我们需要的类型:

std::vector<int> arr;
for(int n : arr) ;

基于范围的 for 循环,对于冒号前面的局部变量声明(for-range-declaration)只要求能够支持容器类型的隐式转换。因此,在使用时需要注意,像下面这样写也是可以通过编译的:

std::vector<int> arr;
for(char n : arr) ; // int会被隐式转换为char

在上面的例子中,我们都是在使用只读方式遍历容器。如果需要在遍历时修改容器中的值,则需要使用引用,代码如下:

for(auto& n : arr)
{std::cout << n++ << std::endl;
}

在完成上面的遍历后,arr 中的每个元素都会被自加 1。

当然,若只是希望遍历,而不希望修改,可以使用 const auto& 来定义 n 的类型。这样对于复制负担比较大的容器元素(比如一个 std::vectorstd::string 数组)也可以无损耗地进行遍历。

基于范围的 for 循环的使用细节

从前面的示例中可以看出,range-based for 的使用是比较简单的。但是再简单的使用方法也有一些需要注意的细节。

首先,看一下使用 range-based for 对 map 的遍历方法:

#include <iostream>
#include <map>
int main(void)
{std::map<std::string, int> mm ={{ "1", 1 }, { "2", 2 }, { "3", 3 }};for(auto& val : mm){std::cout << val.first << " -> " << val.second << std::endl;}return 0;
}

这里需要注意两点:
for 循环中 val 的类型是 std::pair。因此,对于 map 这种关联性容器而言,需要使用 val.first 或 val.second 来提取键值。
auto 自动推导出的类型是容器中的 value_type,而不是迭代器。

关于上述第二点,我们再来看一个对比的例子:

std::map<std::string, int> mm =
{{ "1", 1 }, { "2", 2 }, { "3", 3 }
};
for(auto ite = mm.begin(); ite != mm.end(); ++ite)
{std::cout << ite->first << " -> " << ite->second << std::endl;
}
for(auto& val : mm) // 使用基于范围的for循环
{std::cout << val.first << " -> " << val.second << std::endl;
}

从这里就可以很清晰地看出,在基于范围的 for 循环中每次迭代时使用的类型和普通 for 循环有何不同。

在使用基于范围的 for 循环时,还需要注意容器本身的一些约束。比如下面这个例子:

#include <iostream>
#include <set>
int main(void)
{std::set<int> ss = { 1, 2, 3 };for(auto& val : ss){// error: increment of read-only reference 'val'std::cout << val++ << std::endl;}return 0;
}

例子中使用 auto& 定义了 std::set 中元素的引用,希望能够在循环中对 set 的值进行修改,但 std::set 的内部元素是只读的——这是由 std::set 的特征决定的,因此,for 循环中的 auto& 会被推导为 const int&。

同样的细节也会出现在 std::map 的遍历中。基于范围的 for 循环中的 std::pair 引用,是不能够修改 first 的。

接下来,看看基于范围的 for 循环对容器的访问频率。看下面这段代码:

#include <iostream>
#include <vector>
std::vector<int> arr = { 1, 2, 3, 4, 5 };
std::vector<int>& get_range(void)
{std::cout << "get_range ->: " << std::endl;return arr;
}
int main(void)
{for(auto val : get_range()){std::cout << val << std::endl;}return 0;
}

输出结果:

get_range ->:
1
2
3
4
5

从上面的结果中可以看到,不论基于范围的 for 循环迭代了多少次,get_range() 只在第一次迭代之前被调用。

因此,对于基于范围的 for 循环而言,冒号后面的表达式只会被执行一次。

最后,让我们看看在基于范围的 for 循环迭代时修改容器会出现什么情况。比如,下面这段代码:

#include <iostream>
#include <vector>
int main(void)
{std::vector<int>arr = { 1, 2, 3, 4, 5 };for(auto val : arr){std::cout << val << std::endl;arr.push_back(0); // 扩大容器}return 0;
}

执行结果(32位mingw4.8):

1
5189584
-17891602
-17891602
-17891602

若把上面的 vector 换成 list,结果又将发生变化。

这是因为基于范围的 for 循环其实是普通 for 循环的语法糖,因此,同普通的 for 循环一样,在迭代时修改容器很可能会引起迭代器失效,导致一些意料之外的结果。由于在这里我们是看不到迭代器的,因此,直接分析对基于范围的 for 循环中的容器修改会造成什么样的影响是比较困难的。

其实对于上面的基于范围的 for 循环而言,等价的普通 for 循环如下:

#include <iostream>
#include <vector>
int main(void)
{std::vector<int> arr = { 1, 2, 3, 4, 5 };auto && __range = (arr);for (auto __begin = __range.begin(), __end = __range.end(); __begin != __end; ++__begin){auto val = *__begin;std::cout << val << std::endl;arr.push_back(0); // 扩大容器}return 0;
}

从这里可以很清晰地看到,和我们平时写的容器遍历不同,基于范围的 for 循环倾向于在循环开始之前确定好迭代的范围,而不是在每次迭代之前都去调用一次 arr.end()。

当然,良好的编程习惯是尽量不要在迭代过程中修改迭代的容器。但是实际情况要求我们不得不这样做的时候,通过理解基于范围的 for 循环的这个特点,就可以方便地分析每次迭代的结果,提前避免算法的错误。

c++ 11 for循环相关推荐

  1. C++ 之 for 循环 | C++11 for 循环 | 内存 Destory 示例

    C++ 之 传统 for 循环 和 C++11 for 循环 #include <iostream> int main() {int arr[4] = {100, 200, 300, 40 ...

  2. C++11 for循环新用法、for_each 用法

    转载:https://blog.csdn.net/weixin_38500110/article/details/78766592 c++11 for循环多了新的语法 #include<iost ...

  3. C++ 笔记(11)— 循环(while/for/do while/嵌套循环、break/continue/goto 循环控制语句)

    1. while 循环 C++ 中 while 循环的语法: while(condition) {statement(s); } 2. for 循环 2.1 典型 for 循环 C++ 中 for 循 ...

  4. birt脚本for循环语句_Python初级教程(11): for循环语句

    在本文中,您将学习使用for循环的不同变体对元素序列进行迭代. 1. for循环 Python中的for循环用于迭代序列(list,tuple,string)或其他可迭代对象.在序列上进行迭代称为遍历 ...

  5. 11.for循环的4个练习题

    1.计算0到100之间的奇数和偶数的和 public static void main(String[] args) {int oddsum=0;//保存奇数和int evensum=0;//保存偶数 ...

  6. TensorFlow 2.0深度学习算法实战教材---第11章 循环神经网络

    人工智能的强力崛起,可能是人类历史上最好的事情,也可能是最糟糕的事情.−史蒂芬•霍金 卷积神经网络利用数据的局部相关性和权值共享的思想大大减少了网络的参数量,非常适合于图片这种具有空间(Spatial ...

  7. 1.11 双向循环神经网络-深度学习第五课《序列模型》-Stanford吴恩达教授

    ←上一篇 ↓↑ 下一篇→ 1.10 长短期机器 (LSTM) 回到目录 1.12 深层循环神经网络 双向循环神经网络 (Bidirectional RNN) 现在,你已经了解了大部分RNN模型的关键的 ...

  8. 【学习笔记】11、循环语句—while

    一.while循环 [语法] while <条件表达式>: 条件满足时会执行的代码 -- 当条件表达式为真的时候,执行循环内部的代码:直到条件表达式为假的时候,才继续执行该循环后面的代码. ...

  9. 2.11 while循环的嵌套以及应用(难)

    while循环嵌套 前面学习过if的嵌套了,想一想if嵌套是什么样子的? 类似if的嵌套,while嵌套就是:while里面还有while <1>while嵌套的格式 while 条件1: ...

  10. TensorFlow学习笔记——(11)循环神经网络

    文章目录 一.循环核 二.循环核时间步展开 三.循环计算层 四.TF描述循环计算层 五.循环计算过程 1.RNN实现单个字母预测 (1)过程 (2)完整代码 2.RNN实现输入多个字母,预测一个字母 ...

最新文章

  1. Sigma Function LightOJ - 1336[约数和定理]
  2. The EF Core tools version '2.1.1-rtm-30846' is older than that of the runtime '2.1.3-rtm-32065'. ...
  3. 39[dropbox etc]
  4. 基于jQuery 2.0的源代码分析
  5. Echarts开源可视化库学习(三)主题的使用
  6. 剑指offer 栈的压入、弹出序列
  7. usb 1-5.2: device not accepting address 4, error -110问题解决方案
  8. Java私有构造函数不能阻止继承
  9. L1-056 猜数字 (20 分)
  10. 为什么要学python语言、学完有什么好处_学编程为什么首选Python?学完Python的优势有哪些?...
  11. html ng-app,为什么AngularJs写了2个及以上的ng-app=会导致页面渲染不成功
  12. OpenShift 4 之AMQ Streams(1) - 多个Consumer从Partition接收数据
  13. lucene的Query类型
  14. 零基础学python实战-Python3.6零基础入门与实战 PDF 带源码视频版
  15. 如何让一个用户拥有root权限
  16. 一张书籍清单(软件工程师学习参考资料)
  17. [转]nodejs Error: request entity too large解决方案
  18. 数据挖掘导论课后习题答案 第二章(一)
  19. 全屋Wi-Fi:一个谁也解决不好的痛点?
  20. GIT部署(三)GIT 远程仓库:添加远程库、从远程库克隆

热门文章

  1. jQuery 遍历 - closest() 方法 is()方法
  2. RC电路 波形分析
  3. mm游戏大全HTML5小游戏,HTML5小游戏——看你有多色(示例代码)
  4. 编程常用英语单词(一)
  5. 单体架构与微服务架构区别
  6. 修复图片音频全新升级带特效喝酒神器小游戏微信小程序源码下载-多种游戏支持流量主
  7. 搜集ipv6 DNS服务器信息,IPV6 DNS服务器地址列表
  8. 获取当前格林威治时间
  9. Sublime不支持GDK导致中文乱码(两种方法)
  10. 【推荐】文婧@若凡上传的经典视频(陆续更新)