读书频道 > 网站 > 网页设计 > 深入应用C++11:代码优化与工程级应用
1.4.2 基于范围的 for循环的使用细节
15-07-07    下载编辑
收藏    我要投稿   
在StackOverflow的最近一次世界性调查中,C++11在所有的编程语言中排名第二, C++11受到程序员的追捧是毫不意外的,因为它就像C++之父Bjarne Stroustrup说的:它看起来就像一门新的语言。C++11新增加了相当多的立即去当当网订购

从上一节的示例中可以看出,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.f?irst << " -> " << val.second << std::endl;
   }

   return 0;
}

这里需要注意两点:

1)for循环中val的类型是std::pair。因此,对于map这种关联性容器而言,需要使用val.f?irst或val.second来提取键值。

2)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->f?irst << " -> " << ite->second << std::endl;
}

for(auto& val : mm) // 使用基于范围的for循环
{
   std::cout << val.f?irst << " -> " << 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<int>中元素的引用,希望能够在循环中对set的值进行修改,但std::set的内部元素是只读的——这是由std::set的特征决定的,因此,for循环中的auto&会被推导为const int &。

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

接下来,看看基于范围的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循环的这个特点,就可以方便地分析每次迭代的结果,提前避免算法的错误。

点击复制链接 与好友分享!回本站首页
分享到: 更多
您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:1.3 功能
下一篇:1.5 小结
相关文章
图文推荐
JavaScript网页动画设
1.9 响应式
1.8 登陆页式
1.7 主题式
排行
热门
文章
下载
读书

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训
版权所有: 红黑联盟--致力于做最好的IT技术学习网站