STL
C++标准模板库:STL(Standard Template Library)概述
C++中两个方面体现重用:
1. OOP:继承与多态,标准类库
2. 泛型程序设计(Generic Programming):模板,标准模板库STL
STL:就是一些常用的数据结构和算法的模板的合集
1. 基本概念
- 容器、迭代器、算法
容器 | 迭代器 | 算法 |
---|---|---|
可容纳各种数据类型的通用数据结构 | 可用于依次存取容器中的元素 | 用来操作容器中的元素的方法 |
类模板 | 类似指针 | 函数模板 |
算法:本身与他们操作的数据的类型无关,可以在从简单的数组到复杂容器的任何数据结构上使用,如:sort()
对一个vector中数据排序、find()
查找list中的某个对象…
- 实例:
int array[100];
该数组就是容器,而int *
类型的指针变量就可以作为迭代器,sort算法可以作用在该容器上,对其进行排序:
sort(array, array + 70);
//将前70个元素排序
//array、array+70都是迭代器
2. 容器
存放各种类型的数据(基本类型的变量,对象等)的通用数据结构
2.1 容器分类
顺序容器 | vertor (变长数组)、deque (双向队列)、list (双向列表) |
---|---|
关联容器 | set 、multiset 、map 、multimap |
容器适配器 | stack (栈)、queue (队列)、priority_queue (优先级队列) |
2.2 容器概述
- 对象被插入容器中时,被插入的是对象的一个复制品。
- 放入容器的对象所属的类,往往应重载
==
和<
运算符(1.许多算法,要对容器内元素进行比较; 2.一些容器本身就是排序的,插入时要先进行比较)
2.3 顺序容器
- 容器并非排序的
- 元素的插入位置同元素的值无关
- vector、deque、list
- vector: 头文件
<vector>
- 动态数组
- 元素在内存连续存放
- 支持随机存取
- 尾部增删元素具有较好的性能(一般O(1),只有重新分配存储空间时才为O(n)_把原内容拷贝到新空间),但头部或中间增删为O(n) - deque: 头文件
<deque>
- 双向队列
- 元素在内存连续存放
- 支持随机存取(次于vector)
- 头尾增删元素具有较好性能(一般O(1))——队头指针、队尾指针 - list:头文件
list
- 双向链表
- 不连续存放
- 不支持随机存取
- 任何位置增删元素都O(1) (不包括查找元素位置的时间)
2.4 关联容器
- 元素是排序的
- 插入任何元素,都按相应的排序规则来确定其位置
- 便于查找
- 通常以平衡二叉树方式实现,插入和检索时间都是O(log(N))
- set/ multiset、 map/ multimap
- set / multiset:头文件
<set>
- set即集合
- set不允许有相同元素
- multiset中允许有相同 - map / multimap:头文件
<map>
- map与set的区别在于,map里只能放对象,该对象有且仅有两个成员变量,一个名为first(关键字),一个second(值),map根据first值对元素进行从"小"到"大"排序,并可以通过first来快速检索元素
- map不允许有相同元素
- multimap允许
2.5 容器适配器
- stack:头文件
<stack>
- 栈
- 后进先出 - queue:头文件
<queue>
- 单向队列
- 插入在尾部,删除、检索、修改只允许在头部进行
- 先进先出 - priority_queue:头文件
<queue>
- 优先级队列
- 最高优先级元素总是第一个出队(优先级自己定义)
2.6 成员函数
- 顺序容器和关联容器中都有的成员函数
成员函数 | 功能 |
---|---|
begin | 返回指向容器中第一个元素的迭代器 |
end | 返回指向容器中最后一个元素后面的位置的迭代器 |
rbegin | 返回指向容器中最后一个元素的迭代器 |
rend | 返回指向容器中第一个元素前面的位置的迭代器 |
erase | 从容器中删除一个或几个元素 |
clear | 从容器中删除所有元素 |
- 顺序容器中的成员函数
成员函数 | 功能 |
---|---|
front | 返回容器中第一个元素的引用 |
back | 返回容器中最后一个元素的引用 |
push_back | 在容器末尾增加新元素 |
pop_back | 删除容器末尾的元素 |
erase | 删除迭代器指向的元素(可能使该迭代器失效),或删除一个区间,返回被删除元素后面那个元素的迭代器 |
3. 迭代器
- 用于指向顺序容器和关联容器中的元素
- 迭代器用法和指针类似
- 有 const 和 非const 两种
- 通过迭代器可以读取它指向的元素
- 通过 非const 迭代器还可以修改其指向的元素
3.1 迭代器的使用方法
- 定义一个容器类(从容器模板实例化出来的类)的迭代器的方法有:
① 容器类名::iterator 变量名;
② 容器类名::const_iterator 变量名;
- 访问一个迭代器指向的元素:
* 迭代器变量名
- ++操作:迭代器执行++操作,使其指向容器中的下一个元素
TIPS:如果迭代器到达了容器中的最后一个元素的后面,此时再使用它,就会出错,类似于使用NULL或未初始化的指针一样
- 实例
#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 << ",";
return 0;
}
3.2 双向迭代器、随机访问迭代器
1)双向迭代器:p
、p1
◆++p
、p++
:使p指向容器中下一个元素
◆--p
、p--
:使p指向容器中上一个元素
◆* p
:取p指向的元素
◆p = p1
:赋值
◆p == p1
, p != p1
:判断是否相等、不等
2)随机访问迭代器: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)
容器 | 容器上的迭代器类别 |
---|---|
vector | 随机访问 |
deque | 随机访问 |
list | 双向 |
set / multiset | 双向 |
map /multimap | 双向 |
stack | 不支持迭代器 |
queue | 不支持迭代器 |
priority_queue | 不支持迭代器 |
TIPS:有的算法需要随机访问迭代器,如:sort、binary_search…
3.3 遍历方式
- 遍历vector的几种做法(deque同理):vector的迭代器是随机迭代器
vector<int> v(100);
int i;
for(i=0; i<v.size(); i++)
cout << v[i]; //根据下标随机访问
vector<int>::const_iterator ii;
for(ii = v.begin(); ii != v.end(); ii++)
cout << *ii;
for(ii = v.begin(); ii < v.end(); ii++)
//随机迭代器可以用 < 比较
cout << *ii;
//间隔一个输出:
ii = v.begin();
while(ii < v.end()){
cout << *ii;
ii = ii + 2;
}
- 遍历list的几种做法:list是双向迭代器
list<int> v;
list<int>::const_iterator ii;
for(ii = v.begin(); ii != v.end(); ++ii)
//双向迭代器只能用!=比较,且List没有[]成员函数,故不能用v[i]
cout << *ii;
4. 算法简介
- 算法就是一个个函数模板,大多再
<algorithm>
中定义- STL中提供能在各种容器中通用的算法,比如查找,排序等
- 算法通过迭代器来操作容器中的元素。许多算法可以对容器中的一个局部区间进行操作,因此需要2个参数,一个是起始元素的迭代器,一个是终止元素的后面一个元素的迭代器(比如:排序和查找)
- 有的算法返回一个迭代器(比如find()算法)
- 算法可以处理容器,也可以处理普通数组
4.1 算法实例:find()
template<class InIt, class T>
InIt find(InIt first, InIt last, const T& val);
◆first 和 last 这两个参数都是容器的迭代器,它们给出了容器中的查找区间起点和终点[first, last)。区间的起点是位于查找范围之中的,而终点不是。find()
在[first, last)查找等于val的元素
◆用 == 运算符判断相等
◆函数返回值是一个迭代器。如果找到指向被找到元素,找不到,该迭代器等于last
- 实例:find()使用:
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
int main(){
int array[10] = {10, 20, 30, 40};
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::iterator p;
p = find(v.begin(), v.end(), 3);
if(p != v.end())
cout << *p <<endl; //输出3
p = find(v.begin(), v.end(), 9);
if(p == v.end())
cout << "Not Found" <<endl;
p = find(v.begin() + 1, v.end() - 2, 1);
//整个容器:[1, 2, 3, 4],查找区间:[2,3),有效元素:2
if(p != v.end())
cout << *p <<endl;
int *pp = find(array, array+4, 20); //数组名是迭代器
cout << * pp <<endl;
return 0;
}
5. 函数对象
若一个类重载了运算符
()
,则该类的对象就是函数对象
函数对象是对函数的一种集成,如实例2,不需要单独写2次方,3次方和…的函数,只需要一个函数对象SumPowers(…)即可
- 实例1
#include <iostream>
using namespace std;
class CMyAverage{
public:
double operator () (int a1, int a2, int a3){
return (double)(a1 + a2 + a3) / 3;
}
};
int main(){
CMyAverage average;
cout << average(3, 2, 3);
//等价于:average.operator()(3, 2, 3);
return 0;
}
-
TIPS:一般在函数名或函数指针后面带参数
fun(...)
,average是个对象,但是接(参数),看上去就像函数调用,所以叫函数对象 -
实例2
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <functional>
using namespace std;
int SumSquares(int total, int value){
return total + value * value;
}
template<class T>
void PrintInterval(T first, T last){
//输出区间[first, last)中的元素
for(; first != last; ++first)
cout << * first << " ";
cout <<endl;
}
template<class T>
class SumPowers{
private:
int power;
public:
SumPowers(int p):power(p){}
const T operator () (const T & total, const T & value){
//计算value的power次方,加到total上去
T v = value;
for(int i=0; i < power - 1; ++i)
v = v * value;
return total + v;
}
};
int main(){
const int SIZE = 10;
int a1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
vector<int> v(a1, a1 + SIZE);
cout << "1) "; PrintInterval(v.begin(), v.end());
int result = accumulate(v.begin(), v.end(), 0, SumSquares);
cout << "2) 平方和: " << result <<endl;
result =
accumulate(v.begin(), v.end(), 0, SumPowers<int>(3)); //SumPowers<int>(3) 函数对象
cout << "3) 3次方和: " << result <<endl;
result =
accumulate(v.begin(), v.end(), 0, SumPowers<int>(4));
cout << "4) 4次方和: " << result;
return 0;
}
其中
accumulate(v.begin(),v.end(),0,SumSquares)
实例化出:参数一一对应
int accumulate(vector<int>::iterator first, vector<int>::iterator last, int init, int (* op)(int, int){
for(; first != last; ++first)
init = op(init, * first);
return init;
}
TIPS:SumSquares
实例化的参数为int (* op)(int, int)
函数指针
accumulate(v.begin(), v.end(), 0, SumPowers<int>(3));
实例化出:
int accumulate(vector<int>::iterator first, vector<int>::iterator last, int init, SumPowers<int> op){
for(; first != last; ++first)
init = op(init, *first);
return init;
}
- STL中的函数对象类模板: 头文件
<functional>
如:equal_to、greater、less…
- 实例:greater函数对象类模板
template<class T>
struct greater:public binary_function<T, T, bool>{
bool operator () (const T& x, const T& y){
return x < y;
}
};
- 实例:greater的应用
list自带的sort:一种通过<升序排列,另一种自定义规则:将list中的元素按op规定的比较方法升序排列,即x<y
等价于op(x, y)返回true
template<class Compare>
void sort(Compare op);
- 实例:
#include <iostream>
#include <list>
using namespace std;
class MyLess{
//比较个位数,谁个位小谁小
public:
bool operator () (const int& c1, const int& c2){
return (c1 % 10) < (c2 % 10);
}
};
template<class T>
void Print(T first, T last){
for(; first != last; ++first)
cout << *first << ",";
}
int main(){
const int SIZE = 5;
int a[SIZE] = {5, 15, 7, 3, 11};
list<int> lst(a, a+SIZE);
lst.sort(MyLess()); //按MyLess()的规则来sort
//以函数对象作参数传入,相当于传入函数指针
Print(lst.begin(),lst.end());
cout << endl;
lst.sort(greater<int>()); //greater<int>()是个对象
Print(lst.begin(), lst.end());
cout << endl;
return 0;
}
6. 补充
-
STL中的“大”、“小”的概念:
◆关联容器内部元素都是从小到大排序的
◆有些算法要求其操作的区间是从小到大排序的,称为“有序区间算法”:binary_search
◆有些算法会对区间进行从小到大排序,称为“排序算法”:sort
◆在使用STL时,缺省情况下,一下三个说法等价:
1)x比y小
2)y比x大
3)表达式x < y
为true -
STL中的“相等”的概念
◆有时,"x和y相等"
等价于"x == y"为true
,例:在未排序的区间上进行的算法,如顺序查找find…
◆有时,"x和y相等"
等价于"x小于y和y小于x"同时为false
,例:有序区间算法,如binary_search、关联容器自身的成员函数find…
- 实例:
#include <algorithm>
#include <iostream>
using namespace std;
class A{
int v;
public:
A(int n):v(n){}
bool operator < (const A & a2) const{
cout << v << "v" << a2.v << "?" <<endl;
return false;
}
bool operator == (const A & a2) const{
cout << v << "==" << a2.v << "?" <<endl;
return v == a2.v;
}
};
int main(){
A a[] = {A(1), A(2), A(3), A(4), A(5)};
cout << binary_search(a, a + 4, A(9)); //折半查找
return 0;
}
- 输出结果:
3v9?
2v9?
1v9?
9v1?
1