C++遍历方法

C++遍历方法

    • 容器
      • 序列式
      • 关联式
      • 适配器
    • 下标
      • vector
      • map
    • 迭代器
    • STL算法
      • for_each
      • copy
    • 基于范围循环
      • range-for
      • 特别说明
    • 条款
      • 调用算法优先于手写循环
    • 参考资料

容器

序列式

序列式容器的数据结构是线性结构

中文名 英文名
向量 vector
双向链表 list
双端队列 deque

关联式

关联式容器的数据结构是非线性结构

中文名 英文名
集合 set
多重集合 multiset
映射 map
多重映射 multimap

适配器

适配器用于适配容器,作为特殊用途的容器,允许自由指定接口匹配的容器。

中文名 英文名 默认容器
stack deque
队列 queue deque
优先队列 priority_queue vector

下述所有示例以下标例子为模板,其改动只有函数print和头文件。

下标

下标适用于遍历连续性的线性结构容器,对于非连续性的线性结构和非线性结构容器,可以重载operator []或者增加接口函数,并且构建索引表访问对应元素。
以vector和map为例:

vector

#include 
#include 

using DynamicArray = std::vector<long>;

void print(const DynamicArray &dynamicArray)
{
	using std::cout;
	for (DynamicArray::size_type index = 0; index < dynamicArray.size(); ++index)
		cout << dynamicArray[index] << ' ';
	cout << dynamicArray.size();
}

int main()
{
	DynamicArray dynamicArray = { 0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L };
	print(dynamicArray);
	return 0;
}

map

#include 
#include 
#include 
#include 

int main()
{
	using std::string;
	using StringArray = std::vector<string>;
	StringArray keys = { "Eternal", "ZGMF-X09A", "ZGMF-X10A" };
	std::map<string, string> map = { {keys[0], "Meteor"}, {keys[1], "Justice"}, {keys[2], "Freedom"} };
	for (StringArray::size_type index = 0; index < keys.size(); ++index)
	{
		const auto &key = keys[index];
		std::cout << key << ' ' << map[key] << std::endl;
	}
	return 0;
}

迭代器

STL由容器、迭代器、算法三层结构组成,为不同的容器定制接口相同的迭代器,线性结构与非线性结构的容器都能够通过迭代器进行遍历。

#include 
#include 

using DynamicArray = std::vector<long>;

void print(const DynamicArray &dynamicArray)
{
	using std::cout;
	for (auto iterator = dynamicArray.cbegin(); iterator != dynamicArray.cend(); ++iterator)
		cout << *iterator << ' ';
	cout << dynamicArray.size();
}

int main()
{
	DynamicArray dynamicArray = { 0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L };
	print(dynamicArray);
	return 0;
}

对于一些容器,如果使用迭代器遍历,向容器添加或删除元素,将导致迭代器失效(不同容器的迭代器失效的时机不同),后续遍历操作是未定义行为,因此每次在插入或删除元素之后都应该重新定位迭代器。

STL算法

算法由于以迭代器为基础,因此对线性结构和非线性结构的容器都适用,以for_each和copy为例:

for_each

引用头文件algorithm

#include 
#include 
#include 

using DynamicArray = std::vector<long>;

void print(const DynamicArray &dynamicArray)
{
	using std::cout;
	std::for_each(dynamicArray.cbegin(), dynamicArray.cend(), 
		[](const DynamicArray::value_type &element) 
	{
		cout << element << ' ';
	});
	cout << dynamicArray.size();
}

int main()
{
	DynamicArray dynamicArray = { 0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L };
	print(dynamicArray);
	return 0;
}

copy

引用头文件algorithmiterator

#include 
#include 
#include 
#include 

using DynamicArray = std::vector<long>;

void print(const DynamicArray &dynamicArray)
{
	using std::cout;
	std::copy(dynamicArray.cbegin(), dynamicArray.cend(), 
		std::ostream_iterator<DynamicArray::value_type>(cout, " "));
	cout << dynamicArray.size();
}

int main()
{
	DynamicArray dynamicArray = { 0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L };
	print(dynamicArray);
	return 0;
}

基于范围循环

range-for

C++11标准新增range-for语法,其底层实现依赖于迭代器,对于线性结构和非线性结构的容器通用。

#include 
#include 

using DynamicArray = std::vector<long>;

void print(const DynamicArray &dynamicArray)
{
	using std::cout;
	for (const auto &element : dynamicArray)
		cout << element << ' ';
	cout << dynamicArray.size();
}

int main()
{
	DynamicArray dynamicArray = { 0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L };
	print(dynamicArray);
	return 0;
}

当使用range-for遍历容器时,不可以减少容器容量,也不可以任意添加或者删除元素,只有在迭代器不失效的情况下可以进行这些操作。

特别说明

C++标准并不支持以下语法:

for each (auto var in collect) { visit(var); }

Visual Studio中使用C++17标准,编译此语法代码,提示下述错误信息:

error C4496: 使用了非标准扩展“for each”: 替换为 ranged-for 语句

Qt运用宏模仿此语法:

QStringList words = {"freedom", "justice"};
foreach (QString word, words) { qDebug() << word; }

C#也有类似用法,其语法格式如下:

foreach (type object in collection/array) {
	visit(object);
}

条款

调用算法优先于手写循环

调用算法相比于手写循环,具有以下优势:

  • 效率:算法的效率通常高于自写循环。
  • 正确性:自写循环比使用算法更容易出错。
  • 可维护性:使用算法的代码通常比手写循环的代码更加简洁明了。

STL算法使用复杂的计算机科学算法,有些算法非常复杂,并非一般程序员所能够达到。
类库实现者比使用者更清楚内部实现细节,能够根据对于容器实现的了解程度优化遍历过程,这是库的使用者难以做到的事情。

如果代码作用与算法功能相近,调用算法有利于提高代码清晰度。但是,若循环代码简单清晰,用以显示实现细节,而调用算法可能需要混合绑定器和配接器,或者封装单独的函数子类,则手写循环更合适。

参考资料

[1] Effective STL 中文版:50条有效使用STL的经验 / (美) 梅耶 (Meyers,S.) 著;潘爱民,陈铭,邹开红译. --北京:电子工业出版社,2013.5

你可能感兴趣的:(C/C++)