C++11 你真的会用迭代器(iterator)么?

10km
博客专家认证
2015-11-22 10:56:06
加精
C++ STL提供了丰富的标准容器(Container)对象(vector,array,queue,list,set,unordered_map/set...),让我们可以根据需求选择不同的容器管理各种类型的数据。说到使用容器,不用迭代器(iterator)是不可能的,所有的容器对象都根据容器的特点都提供了类似但不同的iterator,用于访问容器中的数据。

迭代器(iterator)循环
---------------

一般来说,如果要遍历一个容器中的所有数据,程序员们最常用的写法是:
#include <list>
#include <iostream>
int main(){
list<int> lst;
for(list<int>::iterator itor=lst.begin();itor!=lst.end();itor++){
cout<<(*itor)<<endl;
//do something
}
}



基于范围的for循环
----------

C++11提供了关于for循环的新特性:基于范围的for循环( the range-base for statement),再加上"类型推导"特性可以将上面的代码进一步简化:

for(auto &node:lst){
cout<<node<<endl;
//do something
}



没有区别吗?
--

显然,新的for循环写法更简洁,但新的for循环写法的优点仅此而已吗?
仔细琢磨,你会注意到,第一种写法,每次循环,都要调用`lst.end()`,

这是list.end()函数的源代码(来自C++11中头文件`stl_list.h`):


/**
* Returns a read/write iterator that points one past the last
* element in the %list. Iteration is done in ordinary element
* order.
*/
iterator
end() _GLIBCXX_NOEXCEPT
{ return iterator(&this->_M_impl._M_node); }


可以看出,每一次调用end()函数,都会返回一个`iterator`对象,根据迭代器的特性我们可以知道在整个迭代循环过程中,每次调用`end()`返回的对象其实都是完全一样的,而每次的调用都不可避免会发生对象构造、复制。。。等等动作,这对于要求高性能的应用场合,这种无意义的重复是不可接受的。

那么基于范围的for循环( the range-base for statement)会不会是同样的处理方式呢?
为了验证这个问题,我做了一个试验:
在我的上一篇文章
C++11 为自定义容器实现标准的forward迭代器》中我实现了一个基于自定义哈希表(`HashTableAbstract`)的标准forward迭代器。于是我在HashTableAbstract 的end()函数中加入了调试代码,这样每次`end()`都会输出调试信息:

iterator end()noexcept
//{ return iterator(this->m_table,this->m_table.capacity); }//原代码
{
cout << "return a end iterator" << endl;//输出调试信息
return iterator(this->m_table, this->m_table.capacity);
}


然后运行如下测试代码:

HashSetCl<int> testhash;//HashSetCl是基于HashTableAbstract子类,实现哈希集合
testhash.insert(2000);//加入3条数据
testhash.insert(65535);
testhash.insert(12345);
cout<<"testing for statement using iterator:"<<endl;//使用迭代器循环
for (auto itor = testhash.begin(); itor != testhash.end(); itor++) {
cout << *itor << endl;
}
cout<<"testing for the range-base for statement:"<<endl;//使用基于范围的for循环
for (auto n : testhash) {
cout << n << endl;
}

以下是程序输出(debug/release结果一样)
引用
testing for statement using iterator://注,循环中调用了三次end()
return a end iterator
2000
return a end iterator
12345
return a end iterator
65535
return a end iterator
testing for the range-base for statement://注,循环中调用了一次end()
return a end iterator
2000
12345
65535

总结
--

上面的输出可以证实,基于范围的for循环( the range-base for statement)只会在循环开始的时候调用一次`end()`,与一般直接使用迭代器(iterator)的循环相比,不仅具备代码更简洁的优点,性能上也更具备优势

如果你还是"坚持传统",习惯直接使用迭代器来工作,那么建议对代码做一些改进,还以最前面的代码为例,在循环开始时调用一次`end()`函数保存成临时变量`end`,然后每次循环比较的时候不再调用`end()`函数,而是直接与临时变量`end`相比,就避免了重复调用`end()`。

for(auto itor=lst.begin(),end=lst.end();itor!=end;itor++){
cout<<(*itor)<<endl;
//do something
}
...全文
7675 17 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
17 条回复
切换为时间正序
请发表友善的回复…
发表回复
mmadd3 2015-12-02
  • 打赏
  • 举报
回复
vb 好 、
taanniu1 2015-12-02
  • 打赏
  • 举报
回复
引用 9 楼 gemo 的回复:
整天研究这些东西是c++的悲哀
每个知识点,总要有人深入研究的!对于规范使用C++,高效使用C++ 是有帮助的
zhxingway 2015-12-02
  • 打赏
  • 举报
回复
c++11编译时有什么限制么?
钮牛 2015-12-01
  • 打赏
  • 举报
回复
迭代器很方便啊,用着挺好的
Will. Liu 2015-12-01
  • 打赏
  • 举报
回复
引用 9 楼 gemo 的回复:
整天研究这些东西是c++的悲哀
正解!
蓝鹰 2015-11-30
  • 打赏
  • 举报
回复
可以少敲几个键,但我老大用的旧的编译器。他编不过,所以算了。
小弟是菜鸟 2015-11-27
  • 打赏
  • 举报
回复
存在,即合理。只operator(),使用for_each的飘过。
YouNeverCanTell 2015-11-26
  • 打赏
  • 举报
回复
循环过程中迭代器可能会有失效情况发生,每次循环计算一次end()还是有必要的;不然可以先定义一个变量保存end()
gemo 2015-11-24
  • 打赏
  • 举报
回复
整天研究这些东西是c++的悲哀
  • 打赏
  • 举报
回复
以前不都是用 std::for_each 么
xiaoxiangqing 2015-11-23
  • 打赏
  • 举报
回复
迭代器越来越方便,为什么不用呢?
D41D8CD98F 2015-11-22
  • 打赏
  • 举报
回复
引用 楼主 10km 的回复:
一般来说,如果要遍历一个容器中的所有数据,程序员们最常用的写法是:
#include <list>
#include <iostream>
int main(){
list<int> lst;
for(list<int>::iterator itor=lst.begin();itor!=lst.end();itor++){
	cout<<(*itor)<<endl;
	//do something
}
}
cbegin() / cend() 表示不服。
10km 2015-11-22
  • 打赏
  • 举报
回复
引用 1 楼 zhousitiaoda 的回复:
这跟迭代器有什么关系,这是C++11的优化,而且楼主最后的建议也是有局限性的,遍历的过程中end()的值有可能会发生,比如erase。。
引用 2 楼 paschen 的回复:
开了优化以后函数会内联,基本没有效率上的开销 另推荐看;http://blog.csdn.net/ls306196689/article/details/35787955
两位说的没错,这个结论有局限性,准确的说应该是,无序容器迭代遍历(只读)的情况下,这个结论才有效 因为我是自己写了一个无序的容器,只实现forward迭代器,不能使用下标访问,而且每次循环都是只读访问没有删除动作,所以 所以 http://blog.csdn.net/ls306196689/article/details/35787955这里第一,二种方式都不在考虑之列,能选择的遍历方式只能是第3,4,5种
fefe82 2015-11-22
  • 打赏
  • 举报
回复
a range-based for statement is equivalent to
{
  auto && __range = range-init;
  for ( auto __begin = begin-expr,
    __end = end-expr;
    __begin != __end;
    ++__begin ) {
      for-range-declaration = *__begin;
      statement
  }
}
paschen 版主 2015-11-22
  • 打赏
  • 举报
回复
开了优化以后函数会内联,基本没有效率上的开销 另推荐看;http://blog.csdn.net/ls306196689/article/details/35787955
zhousitiaoda 2015-11-22
  • 打赏
  • 举报
回复
这跟迭代器有什么关系,这是C++11的优化,而且楼主最后的建议也是有局限性的,遍历的过程中end()的值有可能会发生,比如erase。。

65,187

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧