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
}
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐