C++STL:容器与迭代器

1. 容器

简单的理解容器,它就是一些模板类的集合,但和普通模板类不同的是,容器中封装的是组织数据的方法(也就是数据结构)。 STL 提供有 3 类标准容器,分别是顺序容器、排序容器和哈希容器,其中后两类容器有时也统称为关联容器。它们各自的含义如下表所示:

容器种类 功能
顺序容器 主要包括 vector 向量容器、list 列表容器以及 deque 双端队列容器。之所以被称为顺序容器,是因为元素在容器中的位置同元素的值无关,即容器不是排序的。将元素插入容器时,指定在什么位置,元素就会位于什么位置。
排序容器 包括 set 集合容器、multiset多重集合容器、map映射容器以及 multimap 多重映射容器。排序容器中的元素默认是由小到大排序好的,即便是插入元素,元素也会插入到适当位置。所以排序容器在查找时具有非常好的性能。
哈希容器 C++ 11 新加入 4 种关联容器,分别是 unordered_set 哈希集合、unordered_multiset 哈希多重集合、unordered_map 哈希映射以及 unordered_multimap 哈希多重映射。和排序容器不同,哈希容器中的元素是未排序的,元素的位置由哈希函数确定。

2. 迭代器

无论是顺序容器还是关联容器,最常做的操作无疑是遍历容器中存储的元素,而实现此操作,多数情况会选用“迭代器(iterator)”来实现。那么,迭代器到底是什么呢?

我们知道,尽管不同容器的内部结构各异,但它们本质上都是用来存储大量数据的,换句话说,都是一串能存储多个数据的存储单元。因此,诸如数据的排序、查找、求和等需要对数据进行遍历的操作方法应该是类似的。

既然类似,完全可以利用泛型技术,将它们设计成适用所有容器的通用算法,从而将容器和算法分离开。但实现此目的需要有一个类似中介的装置,它除了要具有对容器进行遍历读写数据的能力之外,还要能对外隐藏容器的内部差异,从而以统一的界面向算法传送数据。

这是泛型思维发展的必然结果,于是迭代器就产生了。简单来讲,迭代器和 C++ 的指针非常类似,它可以是需要的任意类型,通过迭代器可以指向容器中的某个元素,如果需要,还可以对该元素进行读/写操作。

2.1 迭代器类别

STL 标准库为每一种标准容器定义了一种迭代器类型,这意味着,不同容器的迭代器也不同,其功能强弱也有所不同。容器的迭代器的功能强弱,决定了该容器是否支持 STL 中的某种算法。

常用的迭代器按功能强弱分为输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器 5 种。其中,输入迭代器和输出迭代器比较特殊,它们不是把数组或容器当做操作对象,而是把输入流/输出流作为操作对象。其他3种迭代器的功能如下所示:

  • 前向迭代器(forward iterator)
    假设 p 是一个前向迭代器,则 p 支持 ++p,p++,*p 操作,还可以被复制或赋值,可以用 == 和 != 运算符进行比较。此外,两个正向迭代器可以互相赋值。

  • 双向迭代器(bidirectional iterator)
    双向迭代器具有前向迭代器的全部功能,除此之外,假设 p 是一个双向迭代器,则还可以进行 --p 或者 p-- 操作(即一次向后移动一个位置)。

  • 随机访问迭代器(random access iterator)
    随机访问迭代器具有双向迭代器的全部功能。除此之外,假设 p 是一个随机访问迭代器,i 是一个整型变量或常量,则 p 还支持以下操作:

    p+=i:使得 p 往后移动 i 个元素。
    p-=i:使得 p 往前移动 i 个元素。
    p+i:返回 p 后面第 i 个元素的迭代器。
    p-i:返回 p 前面第 i 个元素的迭代器。
    p[i]:返回 p 后面第 i 个元素的引用。
    

    此外,两个随机访问迭代器 p1、p2 还可以用 <、>、<=、>= 运算符进行比较。另外,表达式 p2-p1 也是有定义的,其返回值表示 p2 所指向元素和 p1 所指向元素的序号之差(也可以说是 p2 和 p1 之间的元素个数减1)。

下表所示,是 C++ 11 标准中不同容器指定使用的迭代器类型:

容器 对应的迭代器类型
array 随机访问迭代器
vector 随机访问迭代器
deque 随机访问迭代器
list 双向迭代器
set / multiset 双向迭代器
map / multimap 双向迭代器
forward_list 前向迭代器
unordered_map / unordered_multimap 前向迭代器
unordered_set / unordered_multiset 前向迭代器
stack 不支持迭代器
queue 不支持迭代器

注意,容器适配器 stack 和 queue 没有迭代器,它们包含有一些成员函数,可以用来对元素进行访问。

2.2 迭代器的定义方式

尽管不同容器对应着不同类别的迭代器,但这些迭代器有着较为统一的定义方式,具体分为 4 种,如下表所示:

迭代器定义方式 具体格式
正向迭代器 容器类名::iterator 迭代器名;
常量正向迭代器 容器类名::const_iterator 迭代器名;
反向迭代器 容器类名::reverse_iterator 迭代器名;
常量反向迭代器 容器类名::const_reverse_iterator 迭代器名;

值得一提的是,上表中的反向迭代器全称为 “反向迭代器适配器”。

通过定义以上几种迭代器,就可以读取它指向的元素,*迭代器名就表示迭代器指向的元素。其中,常量迭代器和非常量迭代器的分别在于,通过非常量迭代器还能修改其指向的元素。另外,反向迭代器和正向迭代器的区别在于:对正向迭代器进行 ++ 操作时,迭代器会指向容器中的后一个元素;而对反向迭代器进行 ++ 操作时,迭代器会指向容器中的前一个元素。

注意,以上 4 种定义迭代器的方式,并不是每个容器都适用。有一部分容器同时支持以上 4 种方式,比如 array、deque、vector;而有些容器只支持其中部分的定义方式,例如 forward_list 容器只支持定义正向迭代器,不支持定义反向迭代器。

2.3 迭代器实例

  1. 以上对迭代器做了很详细的介绍,下面就以 vector 容器为例,实际感受迭代器的用法和功能。通过前面的学习,我们知道 vector 支持随机访问迭代器,因此遍历 vector 容器有以下几种做法。下面的程序中,每个循环演示了一种做法:
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; // 初始化v,有10个元素

    cout << "第一种遍历方法:" << endl;
    for (int i = 0; i < v.size(); ++i)
        cout << v[i] << " ";    // 像普通数组一样使用vector容器

    
    vector<int>::iterator i;    // 创建一个正向迭代器,当然,vector也支持其他3种定义迭代器的方式

    cout << endl << "第二种遍历方法:" << endl;
    for (i = v.begin(); i != v.end(); ++i)  // 用 "!=" 比较两个迭代器
        cout << *i << " ";

    cout << endl << "第三种遍历方法:" << endl;
    for (i = v.begin(); i < v.end(); ++i) // 用 "<" 比较两个迭代器
        cout << *i << " ";

    cout << endl << "第四种遍历方法:" << endl;
    i = v.begin();
    while (i < v.end()) {
        cout << *i << " ";
        i += 1; // 随机访问迭代器支持 "+=" 的操作
    }

    return 0;
}

运行结果为:

第一种遍历方法:
1 2 3 4 5 6 7 8 9 10
第二种遍历方法:
1 2 3 4 5 6 7 8 9 10
第三种遍历方法:
1 2 3 4 5 6 7 8 9 10
第四种遍历方法:
1 2 3 4 5 6 7 8 9 10
  1. 再举一个例子,我们知道,list 容器的迭代器是双向迭代器。假设 v 和 i 的定义如下:
// 创建一个list容器 v
list<int> v;
// 创建一个常量正向迭代器,同样,list也支持其他三种定义迭代器的方式
list<int>::const_iterator i;

则以下代码是合法的:

for(i = v.begin(); i != v.end(); ++i)
    cout << *i;

以下代码则不合法,因为双向迭代器不支持用 “<” 进行比较:

for(i = v.begin(); i < v.end(); ++i)
    cout << *i;

以下代码也不合法,因为双向迭代器不支持用下标随机访问元素:

for(int i = 0; i < v.size(); ++i)
    cout << v[i];

2.4 补充知识

其实在 C++ 中,数组也是容器,数组的迭代器就是指针,而且是随机访问迭代器。例如,对于数组 int a[10],int * 类型的指针就是其迭代器,则 a、a+1、a+2 都是 a 的迭代器。

猜你喜欢

转载自blog.csdn.net/crossoverpptx/article/details/131603363