STL指的是C++的标准模板库(Standard Template Library,STL),本篇将介绍STL的基本概念。

一、容器

容器是用于存放数据的类模板。我们在使用容器类模板时,要将它实例化,指明容器中存放的元素是什么类型。

1.容器的分类

容器有以下几种分类:

(1)顺序容器

元素在容器中的位置与元素的值无关,你想在什么位置插入元素就在什么位置插入。顺序容器分为三种:可变长动态数组vector、双端队列deque、双向链表list。

(2)关联容器

关联容器内的元素是排好序的,它是按照某种规则将元素插入到合适的位置上。因为是排好序的,所以关联容器在查找时具有非常好的性能。关联容器有四种:set、multiset、map、multimap。

(3)容器适配器

STL还在两类容器的基础上屏蔽了一部分功能,突出或增加另一部分的功能,实现了容器适配器。容器适配器分为三种:栈stack、队列queue、优先级队列priority_queue。

2.容器对象的定义方法

如何定义一个容器对象呢?跟以前所说的模板类似,写成例如vector<int> a;就行了,其中vector<int>叫容器类的名字。这样就定义了一个容器对象a。

3.容器的成员函数

所有容器有以下成员函数:

成员函数 用途
size() 返回容器对象中元素的个数,返回值类型是int
empty() 判断容器对象是否为空,返回值类型是bool
顺序容器和关联容器还有以下成员函数:
成员函数 用途
begin() 返回指向容器中第一个元素的迭代器
end() 返回指向容器中最后一个元素后面的位置的迭代器
rbegin() 返回指向容器中最后一个元素的反向迭代器
rend() 返回指向容器中第一个元素前面的位置的迭代器
erase(…) 从容器中删除一个或几个元素
clear() 从容器中删除所有元素
顺序容器还有以下成员函数:
成员函数 用途
front() 返回容器中第一个元素的引用
back() 返回容器中最后一个元素的引用
push_back() 在容器末尾增加新元素
pop_back() 删除容器末尾的元素
insert(…) 插入一个或多个元素

4.容器对象的比较

任何两个容器对象,只要类型相同,就可以用大于号、小于号、…等进行词典一样的比较运算。总之就是跟字符串的比较方式一样就是了!

二、迭代器

要访问顺序容器和关联容器中的元素,就要用到迭代器了。迭代器与指针比较类似,指到哪就能访问哪的元素。

1.迭代器的定义方法

迭代器分为四种:

  • 正向迭代器:格式为容器类名::iterator 迭代器名;,例如vector<int>::iterator i;,这是一个可变长数组的正向迭代器,下面的类似
  • 反向迭代器:例如vector<int>::reverse_iterator i;
  • 常量正向迭代器:例如vector<int>::const_iterator i;,这个只能访问容器内的元素而不能修改,下同
  • 常量反向迭代器:例如vector<int>::const_reverse_iterator i;

访问迭代器指向的元素的格式:* 迭代器名。假如i是一个非常量正向迭代器,则访问方式为:* i。对于非常量的迭代器可以修改其元素变量的值,例如:* i++能修改元素的值。而i++表示迭代器指向后面一个元素了。

2.迭代器的功能分类

迭代器的功能分类,按功能强弱分为输入、输出、正向、双向、随机访问,我们只说最常用的三种:

(1)正向迭代器

假设p和q是一个正向迭代器且同类型,则它可以实现:++pp++*pp = q。还可以实现比较:p == qp != q。注意,<、>这些符号不能实现!。

(2)双向迭代器

双向迭代器具有正向迭代器的全部功能。除此之外,还能实现:--pp--p--就是朝着与p++相反的方向进行移动。

(3)随机访问迭代器

随机访问迭代器具有双向迭代器的全部功能。除此之外,还能实现这些操作:p += i,p往后移动一个元素;p + i,返回p后面第i个元素的迭代器;p[i],返回p后面第i个元素的引用。p -= ip - i是相似的,不再重复。

除此之外,两个随机访问迭代器还能进行运算符比较操作(大于小于这些也可以实现了)。它们还能进行相减操作:q - p,含义是q指向的元素序号和p指向的元素序号之差。

不同容器所支持的迭代器功能不同:

容器 迭代器功能
vector 随机访问
deque 随机访问
list 双向
set/multiset 双向
map/multimap 双向
stack 不支持迭代器
queue 不支持迭代器
priority_queue 不支持迭代器

3.迭代器的辅助函数

注意,调用下面的函数模板需要头文件algorithm。

函数模板 用途
advance(p, n) 使迭代器p向前或向后移动n个元素
distance(p, q) 计算两个迭代器的距离,即迭代器p经过多少次++操作后和迭代器q相等
iter_swap(p, q) 交换迭代器p和q指向的值

三、算法

STL提供各种容器中通用的算法,其实质是函数模板。

算法可以操作容器,也可以操作数组。

如果算法操作的是一个容器,那么操作的是容器内的某个区间,这要用到两个参数:区间的起点元素a的迭代器和终点元素b后面位置的迭代器,其实质就是一个左闭右开的区间[a, b)。

有的算法会返回一个迭代器,有的返回是一个数值。

想知道有什么算法,或者算法怎么用,可以自己上网查,或者查手册。

四、函数对象

C++的函数对象就好比C的函数指针,如果一个类将“()”运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象。总之,函数对象与函数指针很类似。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<iostream>
using namespace std;

template <class T>
class Accumlate{
private:
T init;
public:
Accumlate(T _init):init(_init){}
T operator() (T a)
{
init = init + a;
return init;
}
};

int main()
{
Accumlate<int> accumlate_1(10);
cout<<accumlate_1(6)<<" ";
cout<<accumlate_1(10)<<endl;

Accumlate<double> accumlate_2(1.5);
cout<<accumlate_2(3.5)<<" ";
cout<<accumlate_2(4.5)<<endl;
return 0;
}

程序输出结果:

1
2
16 26
5 9.5

我们可以看到,这个函数对象做了一个类似累加的工作,比如 accumlate_1,其初始值为10,通过反复调用accumlate_1.operator()函数,达到了累加的效果。值得注意的是,这个程序我定义了两个不同的函数对象,初始值和类型都不同,这有点类似于函数模板——但有所不同的是,函数模板替换的是类型,而函数对象“替换”的是值和类型,功能更加强大。

普通函数能不能做到像函数对象那样能初始化不同的值?能!但是比较麻烦,而且不易维护程序。对于普通函数有两种办法:

一、把初始化的参数写进形参内,例如上面程序我可以写成T accumlate(T init, T a),这样做似乎代码量减少了,但是如果函数的初始化变量很多,那这个形参就变得又臭又长,而且增加了阅读量,每次调用都要写那么一长串。为了达到累加的效果,你不得不再定义一个静态变量,把init的值赋值给它。

二、直接定义一个全局变量用来记录累加的和。这个主意更加不妙,因为这个全局变量与累加函数分离了,不好维护,也不符合逻辑,而且如果你想像上面的程序一样——A数据累加在一起,B数据累加在一起,而且两者互不干扰——你不得不再设置一个全局变量,既浪费空间,又减慢效率。

因此,函数对象的成员函数可以根据对象内部的不同状态来执行不同的操作,而普通函数无法做到这一点。毫无疑问,函数对象的功能比普通函数更强大。