![C++服务器开发精髓](https://wfqqreader-1252317822.image.myqcloud.com/cover/623/39479623/b_39479623.jpg)
1.8 Range-based循环语法
大多数语言都支持for-each语法遍历一个数组或集合中的元素。在C++98/03规范中,对于一个数组int arr[10],如果我们想要遍历这个数组,则只能使用递增的计数去引用数组中的每个元素:
![](https://epubservercos.yuewen.com/EE4394/20637464301305906/epubprivate/OEBPS/Images/41263_52_2.jpg?sign=1739348562-c8bXOqFGarEzoSdRMe8aMMZ5ptdQU4dg-0-f62207b7e37f06eadb60e8436bb65460)
在C++11规范中有了for-each语法,可以这么写:
![](https://epubservercos.yuewen.com/EE4394/20637464301305906/epubprivate/OEBPS/Images/41263_52_3.jpg?sign=1739348562-CvtAofv1jm1KCZSeLbxXdO6770xJoEMp-0-bf47261004197b79f8312c2c0964b5da)
对于1.7节中遍历std::map的内容,我们也可以使用这种语法:
![](https://epubservercos.yuewen.com/EE4394/20637464301305906/epubprivate/OEBPS/Images/41263_52_4.jpg?sign=1739348562-CdxzdS99sWwQVFr7R9OIYE7YxJe5hT4d-0-ec384a70afb9130c3d827170bde0ac6c)
for-each语法虽然很强大,但是有两个需要注意的地方。
◎ for-each中的迭代器类型与数组或集合中元素的类型完全一致,而原来使用老式语法迭代 stl容器(如 std::map)时,迭代器 iter的类型是 stl容器中元素的指针类型。因此,在上面例子的老式语法中,iter是一个指针类型(std::pair<std::string,std::string>*),使用 iter->second 去引用键值;而在 for-each 语法中,iter 与容器中元素的数据类型(std::pair<std::string,std::string>)相同,因此使用iter.second可直接引用键值。
◎ 在 for-each 语法中,对于复杂的数据类型,迭代器是原始数据的拷贝,而不是原始数据的引用。什么意思呢?来看一个例子:
![](https://epubservercos.yuewen.com/EE4394/20637464301305906/epubprivate/OEBPS/Images/41263_53_1.jpg?sign=1739348562-rAyQepQspX0cRDibnHKetd4H2zVe2jOZ-0-55138cc36ae82b7a422d8a72a5c7cf3c)
我们遍历容器 v,试图将 v 中元素的值都修改成“hello”,在实际执行时却达不到我们想要的效果。这就是上文说的for-each中的迭代器是元素的拷贝,所以这里只是将每次的拷贝都修改成“hello”,原始数据并不会被修改。我们可以将迭代器修改成原始数据的引用:
![](https://epubservercos.yuewen.com/EE4394/20637464301305906/epubprivate/OEBPS/Images/41263_53_2.jpg?sign=1739348562-lWFEjnsK5kw8cx6Q6alO2feMluTsvnHR-0-4eec38ba1349525533118c4725bb3e4e)
这样就达到修改原始数据的目的了。这是使用for-each比较容易出错的地方。对于容器中的复杂数据类型,我们应该尽量使用这种引用原始数据的方式,减少不必要的拷贝构造函数调用开销:
![](https://epubservercos.yuewen.com/EE4394/20637464301305906/epubprivate/OEBPS/Images/41263_53_3.jpg?sign=1739348562-zToRPjq3KxfgkcEzxWvdbLdnKjhXiU4R-0-0fc020f90ea0a8b58f52c8394d2461f8)
![](https://epubservercos.yuewen.com/EE4394/20637464301305906/epubprivate/OEBPS/Images/41263_54_1.jpg?sign=1739348562-YnxEqu1T57A4R2L2Lv4IMDJ8AszCi41o-0-b5c988979bb4830715dd57dc4a189979)
1.8.1 自定义对象如何支持Range-based循环语法
介绍了这么多,如何让自定义对象支持Range-based循环语法呢?为了支持这种语法,这个对象至少需要实现如下两个方法:
![](https://epubservercos.yuewen.com/EE4394/20637464301305906/epubprivate/OEBPS/Images/41263_54_2.jpg?sign=1739348562-CmzNlX1bEKqafedVdhm8GOd95AbqYJDG-0-a49dadd225e11c3d9bf3ecab1a881a13)
上面的Iterator是自定义数据类型的迭代子类型,这里的Iterator类型必须支持如下三种操作(原因在下文中会解释)。
◎ operator++(自增)操作,可以在自增之后返回下一个迭代子的位置。
◎ operator!=(判不等操作)操作。
◎ operator*(解引用,dereference)操作。
下面是一个自定义对象支持for-each循环的例子:
![](https://epubservercos.yuewen.com/EE4394/20637464301305906/epubprivate/OEBPS/Images/41263_54_3.jpg?sign=1739348562-zVlZJGuCZl0fPTEL8MCMjLeBFSRqHpol-0-3557e51e187db66bf8a818b975d7668b)
![](https://epubservercos.yuewen.com/EE4394/20637464301305906/epubprivate/OEBPS/Images/41263_55_1.jpg?sign=1739348562-uhz84VD5aagjHJUBIU9S9shdk1cJbGta-0-0a5af119f636d10df93bc90e5eb52950)
注意:在以上代码中,迭代子Iterator是T*,是指针类型,本身就支持operator++和operator!=操作,所以这里并没有提供这两个方法的实现。那么为什么迭代子要支持operator++和operator!=操作呢?我们来看一下编译器是如何实现这种for-each循环的。
1.8.2 for-each循环的实现原理
上述for-each循环可被抽象成如下公式:
![](https://epubservercos.yuewen.com/EE4394/20637464301305906/epubprivate/OEBPS/Images/41263_55_2.jpg?sign=1739348562-zlhlbKddRLnv15pHsuEEQNV5tUpj6KP6-0-5ba59a0270d4d818c71728ca9070a539)
![](https://epubservercos.yuewen.com/EE4394/20637464301305906/epubprivate/OEBPS/Images/41263_55_3.jpg?sign=1739348562-7RFBp63s3NSnEB9Om6zj6F741G73ontB-0-20766f61af62e021515ee339b2196c7d)
C++14标准是这样解释上面的公式的:
![](https://epubservercos.yuewen.com/EE4394/20637464301305906/epubprivate/OEBPS/Images/41263_55_4.jpg?sign=1739348562-VB12ymLBeoJcwh7r0TBLDAVIUCweepMb-0-88beab2797eafc05a615c0c633f4e83f)
在这个循环中,begin-expr返回的迭代子__begin 需要支持自增操作,且每次循环时都会与end-expr返回的迭代子__end做判不等比较,在循环内部通过调用迭代子的解引用(*)操作取得实际的元素。这就是上文说的迭代子对象需要支持 operator++、operator!=和operator*的原因了。
但是在上面的公式中,一个逗号表达式中的“auto__begin=begin-expr,__end=end-expr;”只使用了一个类型符号 auto,导致起始迭代子__begin和结束迭代子__end 是同一类型,这样不太灵活。在某些设计中,可能希望结束迭代子是另一种类型。
因此在C++17标准中要求编译器解释for-each循环为如下形式:
![](https://epubservercos.yuewen.com/EE4394/20637464301305906/epubprivate/OEBPS/Images/41263_55_5.jpg?sign=1739348562-vLf38DatCqkTzcJZyQcLGntslVnhLVy4-0-ced8f4070f8873d21543a51277212d1a)
![](https://epubservercos.yuewen.com/EE4394/20637464301305906/epubprivate/OEBPS/Images/41263_56_1.jpg?sign=1739348562-2Ee6Z8FvGskcJTNZxiuF1pSW6flsoQgO-0-69144c973142047a0787882294ea7df6)
看到了吧,代码第 2 行和第 3 行将获取起始迭代子__begin 和结束迭代子__end 分开写,这样这两个迭代子就可以是不同的类型了。虽然类型可以不一样,但这两种类型之间仍然支持operator!=操作。C++17对C++14的这种改变,对旧的代码不会产生任何影响,但可以让之后的开发更加灵活。