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)的循环相比,不仅具备代码更简洁的优点,性能上也更具备优势。当然这个结论只在无序容器迭代遍历(只读)的情况下才有效(无序容器只提供forward迭代器),具备随机访问迭代器(random-access iterator)的容器(比如 vector,array),直接用下标访问才是最快的。
如果你还是"坚持传统",习惯直接使用迭代器来工作,那么建议对代码做一些改进,还以最前面的代码为例,在循环开始时调用一次end()
函数保存成临时变量end
,然后每次循环比较的时候不再调用end()
函数,而是直接与临时变量end
相比,就避免了重复调用end()
。
for(auto itor=lst.begin(),end=lst.end();itor!=end;itor++){
cout<<(*itor)<<endl;
//do something
}