参考文献
1、http://c.biancheng.net/view/7759.html
2、http://c.biancheng.net/view/vip_7760.html
C++11 for循环(基于范围的循环)
C++ 11标准之前(C++ 98/03 标准),如果要用 for 循环语句遍历一个数组或者容器,只能套用如下结构:
for(表达式 1; 表达式 2; 表达式 3){
//循环体
}
而 C++ 11 标准中,除了可以沿用前面介绍的用法外,还为 for 循环添加了一种全新的语法格式,如下所示:
for (declaration : expression){
//循环体
}
其中,两个参数各自的含义如下:
declaration:表示此处要定义一个变量,该变量的类型为要遍历序列中存储元素的类型。需要注意的是,C++ 11 标准中,declaration参数处定义的变量类型可以用 auto 关键字表示,该关键字可以使编译器自行推导该变量的数据类型。
expression:表示要遍历的序列,常见的可以为事先定义好的普通数组或者容器,还可以是用 {} 大括号初始化的序列。
可以看到,同 C++ 98/03 中 for 循环的语法格式相比较,此格式并没有明确限定 for 循环的遍历范围,这是它们最大的区别,即旧格式的 for 循环可以指定循环的范围,而 C++11 标准增加的 for 循环,只会逐个遍历 expression 参数处指定序列中的每个元素。
下面程序演示了如何用 C++ 11 标准中的 for 循环遍历实例一定义的 arc 数组和 myvector 容器:
#include <iostream>
#include <vector>
using namespace std;
int main() {
char arc[] = "http://c.biancheng.net/cplus/11/";
//for循环遍历普通数组
for (char ch : arc) {
cout << ch;
}
cout << '!' << endl;
vector<char>myvector(arc, arc + 23);
//for循环遍历 vector 容器
for (auto ch : myvector) {
cout << ch;
}
cout << '!';
// for 循环遍历用{ }大括号初始化的列表
for (int num : {1, 2, 3, 4, 5}) {
cout << num << " ";
}
return 0;
}
程序执行结果为:
http://c.biancheng.net/cplus/11/ !
http://c.biancheng.net/!
1 2 3 4 5
- 仔细观察程序的输出结果,其中第一行输出的字符串和 “!” 之间还输出有一个空格,这是因为新格式的 for 循环在遍历字符串序列时,不只是遍历到最后一个字符,还会遍历位于该字符串末尾的 ‘\0’(字符串的结束标志)。之所以第二行输出的字符串和 “!” 之间没有空格,是因为 myvector 容器中没有存储 ‘\0’。
在使用新语法格式的 for 循环遍历某个序列时,如果需要遍历的同时修改序列中元素的值,实现方案是在 declaration 参数处定义引用形式的变量。举个例子:
#include <iostream>
#include <vector>
using namespace std;
int main() {
char arc[] = "abcde";
vector<char>myvector(arc, arc + 5);
//for循环遍历并修改容器中各个字符的值
for (auto &ch : myvector) {
ch++;
}
//for循环遍历输出容器中各个字符
for (auto ch : myvector) {
cout << ch;
}
return 0;
}
程序执行结果为:
bcdef
此程序中先后使用了 2 个新语法格式的 for 循环,其中前者用于修改 myvector 容器中各个元素的值,后者用于输出修改后的 myvector 容器中的各个元素。
有读者可能会问,declaration 参数既可以定义普通形式的变量,也可以定义引用形式的变量,应该如何选择呢?其实很简单,如果需要在遍历序列的过程中修改器内部元素的值,就必须定义引用形式的变量;反之,建议定义const &(常引用)形式的变量(避免了底层复制变量的过程,效率更高),也可以定义普通变量。
C++11 for循环使用注意事项
- 首先需要明确的一点是,当使用 for 循环遍历某个序列时,无论该序列是普通数组、容器还是用{ }大括号包裹的初始化列表,遍历序列的变量都表示的是当前序列中的各个元素。
#include <iostream>
#include <vector>
using namespace std;
int main() {
//for循环遍历初始化列表
for (int ch : {1,2,3,4,5}) {
cout << ch;
}
cout << endl;
//for循环遍历普通数组
char arc[] = "http://c.biancheng.net/cplus/11/";
for (char ch : arc) {
cout << ch;
}
cout << endl;
//for循环遍历 vector 容器
vector<char>myvector(arc, arc + 23);
for (auto ch : myvector) {
cout << ch;
}
return 0;
}
程序执行结果为
12345
http://c.biancheng.net/cplus/11/
http://c.biancheng.net/
上面程序演示了用 for 循环遍历 3 种序列的过程,其中前两种情况很容易理解,但对于用基于范围的 for 循环遍历容器中的元素,很多读者会将 ch 误认为指向各个元素的迭代器,其实不然,它表示的仍是容器中的各个元素。
为了加深读者对遍历容器的理解,下面程序以 map 容器为例,再举一个例子:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
map<string, string>mymap{ {"C++11","http://c.biancheng.net/cplus/11/"},
{"Python","http://c.biancheng.net/python/"},
{"Java","http://c.biancheng.net/java/"} };
for (pair<string,string> ch : mymap) {
cout << ch.first << " " << ch.second << endl;
}
return 0;
}
程序执行结果为:
C++11 http://c.biancheng.net/cplus/11/
Java http://c.biancheng.net/java/
Python http://c.biancheng.net/python/
要知道,map 容器中存储的不再是普通数据类型的数据,而是 pair 类型的数据,因此程序中在使用基于范围的 for 循环遍历 map 容器时,定义的是 pair 类型的变量。
值得初学者注意的一点是,基于范围的 for 循环也可以直接遍历某个字符串,比如:
for (char ch : "http://c.biancheng.net/cplus/11/") {
cout << ch;
}
前面提到,普通数组可以作为被遍历的序列。拿此程序中的字符串来说,其数据类型为const char[33],即在编译器看来字符串就是一个普通数组,因此完全可以直接作为被遍历的序列。
当然,基于范围的 for 循环也可以遍历 string 类型的字符串,这种情况下冒号前定义 char 类型的变量即可。
- 总的来说,基于范围的 for 循环可以遍历普通数组、string字符串、容器以及初始化列表。除此之外,for 循环冒号后还可以放置返回 string 字符串以及容器对象的函数,比如:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
string str = "http://c.biancheng.net/cplus/11/";
vector<int> myvector = { 1,2,3,4,5 };
string retStr() {
return str;
}
vector<int> retVector() {
return myvector;
}
int main() {
//遍历函数返回的 string 字符串
for (char ch : retStr()) {
cout << ch;
}
cout << endl;
//遍历函数返回的 vector 容器
for (int num : retVector()) {
cout << num << " ";
}
return 0;
}
程序执行结果为:
http://c.biancheng.net/cplus/11/
1 2 3 4 5
注意,基于范围的 for 循环不支持遍历函数返回的以指针形式表示的数组,比如:
//错误示例
#include <iostream>
using namespace std;
char str[] = "http://c.biancheng.net/cplus/11/";
char* retStr() {
return str;
}
int main() {
for (char ch : retStr()) //直接报错
{
cout << ch;
}
return 0;
}
原因很简单,此格式的 for 循环只能遍历有明确范围的一组数据,上面程序中 retStr() 函数返回的是指针变量,遍历范围并未明确指明,所以编译失败。
- 值得一提的是,当基于范围的 for 循环遍历的是某函数返回的 string 对象或者容器时,整个遍历过程中,函数只会执行一次。
#include <iostream>
#include <string>
using namespace std;
string str= "http://c.biancheng.net/cplus/11/";
string retStr() {
cout << "retStr:" << endl;
return str;
}
int main() {
//遍历函数返回的 string 字符串
for (char ch : retStr()) {
cout << ch;
}
return 0;
}
程序执行结果为:
retStr:
http://c.biancheng.net/cplus/11/
借助执行结果不难分析出,整个 for 循环遍历 str 字符串对象的过程中,retStr() 函数仅在遍历开始前执行了 1 次。
- 系统学过 STL 标准库的读者应该知道,基于关联式容器(包括哈希容器)底层存储机制的限制:
不允许修改 map、unordered_map、multimap 以及 unordered_multimap 容器存储的键的值;
不允许修改 set、unordered_set、multiset 以及 unordered_multiset 容器中存储的元素的值。
因此,当使用基于范围的 for 循环遍历此类型容器时,切勿修改容器中不允许被修改的数据部分,否则会导致程序的执行出现各种 Bug
另外,基于范围的 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(10); //向容器中添加元素
}
return 0;
}
程序执行结果可能为(输出结果不唯一):
1
-572662307
-572662307
4
5
可以看到,程序的执行结果并不是我们想要的。就是因为在 for 循环遍历 arr 容器的同时向该容器尾部添加了新的元素(对 arr 容器进行了扩增),致使遍历容器所使用的迭代器失效,整个遍历过程出现错误。
因此,在使用基于范围的 for 循环遍历容器时,应避免在循环体中修改容器存储元素的个数。