C++ 标准模板库-STL概述
一、基本概念
1.1 泛型程序设计
C++ 语言的核心优势之一就是便于软件的重用,重用在两个方面有体现:
- 面向对象的思想:继承和多态,标准类库
- 泛型程序设计(generic programming) 的思想: 模板机制,以及标准模板库 STL
简单地说就是使用模板的程序设计法。将一些常用的数据结构(比如链表,数组,二叉树)和算法(比如排序,查找)写成模板,以后则不论数据结构里放的是什么对象,算法针对什么样的对象,则都不必重新实现数据结构,重新编写算法。
标准模板库 (Standard Template Library) 就是一些常用数据结构和算法的模板的集合。有了STL,不必再写大多的标准数据结构和算法,并且可获得非常高的性能。
1.2 STL中基本的概念
容器: 可容纳各种数据类型的通用数据结构,是类模板
迭代器: 可用于依次存取容器中元素,类似于指针
算法: 用来操作容器中的元素的函数模板
二、容器概述
容器指可以用于存放各种类型的数据( 基本类型的变量,对象等)的数据结构,都是类模版,分为三种:
- 顺序容器
vector, deque,list
- 关联容器
set, multiset, map, multimap
- 容器适配器
stack, queue, priority_queue
对象被插入容器中时,被插入的是对象的一个复制品。许多算法,比如排序,查找,要求对容器中的元素进行比较,有的容器本身就是排序的,所以,放入容器的对象所属的类,往往还应该重载 == 和 < 运算符。
2.1 顺序容器
顺序容器并非指容器内的元素是排序的,元素的插入位置同元素的值无关。有vector,deque,list 三种。
vector 指动态数组。元素在内存连续存放。随机存取任何元素都能在常数时间完成,在尾端增删元素具有较佳的性能(大部分情况下是常数时间)。头文件是< vector>
deque指双向队列。元素在内存连续存放。随机存取任何元素都能在常数时间完成(但次于vector)。在两端增删元素具有较佳的性能(大部分情况下是常数时间)。
list指双向链表。元素在内存不连续存放。在任何位置增删元素都能在常数时间完成。 不支持随机存取。
2.2 关联容器
关联容器有以下几个特点:
- 元素是排序的
- 插入任何元素,都按相应的排序规则来确定其位置
- 在查找时具有非常好的性能
- 通常以平衡二叉树方式实现, 插入和检索的时间都是 O(log(N))
有两类:
- set/multiset(头文件 < set>)
set 即集合。 set中不允许相同元素, multiset中允许存在相同的元素。
- map/multimap(头文件 < map>)
map与set的不同在于map中存放的元素有且仅有两个成员变量,一个名为first,另一个名为second, map根据first值对元素进行从小到大排序,并可快速地根据first来检索元素。
map同multimap的不同在于是否允许相同first值的元素
2.3 容器适配器
容器适配器有3类:stack、queue、priority_queue。
stack指栈。是项的有限序列,并满足序列中被删除、检索和修改的项只能是最近插入序列的项(栈顶的项)。 后进先出。头文件是< stack>
queue指队列。插入只可以在尾部进行,删除、检索和修改只允许从头部进行。 先进先出。头文件是< queue>。
priority_queue指优先级队列。最高优先级元素总是第一个出列。头文件是< queue>。
2.4 成员函数
顺序容器和关联容器中都有的成员函数:
begin 返回指向容器中第一个元素的迭代器
end 返回指向容器中最后一个元素后面的位置的迭代器
rbegin 返回指向容器中最后一个元素的迭代器
rend 返回指向容器中第一个元素前面的位置的迭代器
erase 从容器中删除一个或几个元素
clear 从容器中删除所有元素
front :返回容器中第一个元素的引用
back : 返回容器中最后一个元素的引用
push_back : 在容器末尾增加新元素
pop_back : 删除容器末尾的元素
erase :删除迭代器指向的元素(可能会使该迭代器失效),或删除一个区间,返回被删除元素后面的那个元素的迭代器
三、迭代器
3.1 基本概念
迭代器用于指向顺序容器和关联容器中的元素,用法和指针类似,有const 和非 const两种。通过迭代器可以读取它指向的元素,通过非const迭代器还能修改其指向的元素。
定义一个容器类的迭代器的方法可以是:
容器类名::iterator 变量名;
容器类名::const_iterator 变量名;
访问一个迭代器指向的元素:
* 迭代器变量名
迭代器上可以执行 ++ 操作, 以使其指向容器中的下一个元素。如果迭代器到达了容器中的最后一个元素的后面,此时再使用它,就会出错,类似于使用NULL或未初始化的指针一样。
3.2 迭代器示例
#include <vector>
#include <iostream>
using namespace std;
int main() {
vector<int> v; //一个存放int元素的数组,一开始里面没有元素
v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4);
vector<int>::const_iterator i; //常量迭代器
for( i = v.begin();i != v.end();++i )
cout << * i << ",";
cout << endl;
vector<int>::reverse_iterator r; //反向迭代器
for( r = v.rbegin();r != v.rend();r++ )
cout << * r << ",";
cout << endl;
vector<int>::iterator j; //非常量迭代器
for( j = v.begin();j != v.end();j ++ )
* j = 100;
for( i = v.begin();i != v.end();i++ )
cout << * i << ",";
}
输出:
1,2,3,4,
4,3,2,1,
100,100,100,100,
3.3 迭代器类别
3.3.1 双向迭代器
若p和p1都是双向迭代器,则可对p、 p1可进行以下操作:
++p, p++ 使p指向容器中下一个元素
--p, p-- 使p指向容器中上一个元素
* p 取p指向的元素
p = p1 赋值
p == p1 , p!= p1 判断是否相等、不等
3.3.2 随机访问迭代器
若p和p1都是随机访问迭代器,则可对p、 p1可进行以下操作:
双向迭代器的所有操作
p += i 将p向后移动i个元素
p -= i 将p向向前移动i个元素
p + i 值为: 指向 p 后面的第i个元素的迭代器
p - i 值为: 指向 p 前面的第i个元素的迭代器
p[i] 值为: p后面的第i个元素的引用
p < p1, p <= p1, p > p1, p>= p1
3.3.3 容器和迭代器
容器 | 容器对应的迭代器 |
vector | 随机访问 |
deque | 随机访问 |
list | 双向 |
set/multiset | 双向 |
map/multimap | 双向 |
stack | 不支持迭代器 |
queue | 不支持迭代器 |
priotiry_queue | 不支持迭代器 |
四、算法简介
4.1 基本概念
算法就是一个个函数模板, 大多数在< algorithm> 中定义。
STL中提供能在各种容器中通用的算法,比如查找,排序等算法通过迭代器来操纵容器中的元素。许多算法可以对容器中的一个局部区间进行操作,因此需要两个参数,一个是起始元素的迭代器,一个是终止元素的后面一个元素的迭代器。比如,排序和查找。有的算法返回一个迭代器。比如 find() 算法,在容器中查找一个元素,并返回一个指向该元素的迭代器。
算法可以处理容器,也可以处理普通数组。
4.2 算法示例
template<class InIt, class T>
InIt find(InIt first, InIt last, const T& val);
first 和 last 这两个参数都是容器的迭代器,它们给出了容器中的查找区间起点和终点[first,last)。区间的起点是位于查找范围之中的,而终点不是。 find在[first,last)查找等于val的元素用 == 运算符判断相等。
函数返回值是一个迭代器。如果找到,则该迭代器指向被找到的元素。如果找不到,则该迭代器等于last。
五、STL中“大”、“小”、“相等”的概念
5.1 基本概念
关联容器内部的元素是从小到大排序的
- 有些算法要求其操作的区间是从小到大排序的,称为“ 有序区间算法”
例: binary_search
- 有些算法会对区间进行从小到大排序,称为“排序算法”
例: sort
- 还有一些其他算法会用到“大”,“小”的概念
使用STL时,在缺省的情况下,以下三个说法等价:
- x比y小
- 表达式“ x < y”为真
- y比x大
关于“相等”:
- 有时,“ x和y相等”等价于“ x==y为真”
例:在未排序的区间上进行的算法,如顺序查找find
……
- 有时“ x和y相等”等价于“ x小于y和y小于x同时为假”
例:
有序区间算法,如binary_search
关联容器自身的成员函数find
……
5.2 程序示例
#include<vector>
#include<algorithm>
using namespace std;
class A
{
int v;
public:
A(int v):v(v) { }
bool operator<(const A &a) const{
cout<<v<<"<"<<a.v<<"?"<<endl;
return false;
}
bool operator==(const A &a) const{
cout<<v<<"=="<<a.v<<"?"<<endl;
return v==a.v;
}
};
int main()
{
//重载运算符 < 后的二分查找
A a[] = {A(1), A(2), A(3), A(4), A(5)};
cout<<binary_search(a, a+5, A(9));
return 0;
}
输出:
3<9?
2<9?
1<9?
9<1?
1
折半查找,最后找到1没有更多的可以找了。比较9是否小于1,结果为假;比较1是否小于9,结果为假;这时候认为9==1,输出1。
六、小结
- STL 三个基本概念:容器、迭代器、算法
- 容器分为顺序容器、关联容器、容器适配器
- 顺序容器分为vector(动态数组)、deque(双向队列)、list(双向链表)
- 关联容器分为set/multiset、map/multimap
- 容器适配器stack、queue、priority_queue
- 迭代器分为双向迭代器、随机访问迭代器,不同的容器可以使用的迭代器类别不同
- 算法就是一个个函数模板, 大多数在< algorithm> 中定义
- STL 中的”大“、”小“、”相等“等概念可以自己定义,可能和常规意义上的概念不同