STL简介:标准模板库(Standard Template Library,简称STL)提供了一些非常常用的数据结构和算法
标准模板库(Standard Template Library,简称STL)定义了一套概念体系,为泛型程序设计提供了逻辑基础
STL中的各个类模板、函数模板的参数都是用这个体系中的概念来规定的。
使用STL的模板时,类型参数既可以是C++标准库中已有的类型,也可以是自定义的类型——只要这些类型是所要求概念的模型。
STL的基本组件:容器(container);迭代器(iterator);函数对象(function object);算法(algorithms)
STL的基本组件间的关系
Iterators(迭代器)是算法和容器的桥梁。将迭代器作为算法的参数、通过迭代器来访问容器而不是把容器直接作为算法的参数。
将函数对象作为算法的参数而不是将函数所执行的运算作为算法的一部分。
使用STL中提供的或自定义的迭代器和函数对象,配合STL的算法,可以组合出各种各样的功能。
STL的基本组件——容器(container):
容纳、包含一组元素的对象。
基本容器类模板:顺序容器:array(数组)、vector(向量)、deque(双端队列)、forward_list(单链表)、list(列表)(有序)
关联容器:set(集合)、multiset(多重集合)、map(映射)、multimap(多重映射)
无序关联容器:unorderedset (无序集合)、unorderedmultiset(无序多重集合)unorderedmap(无序映射)、unordermultimap(无序多重映射)
容器适配器:stack(栈)、queue(队列)、priority_queue(优先队列)
使用容器,需要包含对应的头文件
STL的基本组件——迭代器(iterator):
迭代器是泛化的指针,提供了顺序访问容器中每个元素的方法
提供了顺序访问容器中每个元素的方法;
可以使用“++”运算符来获得指向下一个元素的迭代器;
可以使用“*”运算符访问一个迭代器所指向的元素,如果元素类型是类或结构体,还可以使用“->”运算符直接访问该元素的一个成员;
有些迭代器还支持通过“--”运算符获得指向上一个元素的迭代器;
迭代器是泛化的指针:指针也具有同样的特性,因此指针本身就是一种迭代器;
使用独立于STL容器的迭代器,需要包含头文件。
STL的基本组件——函数对象(function object):
一个行为类似函数的对象,对它可以像调用函数一样调用。
函数对象是泛化的函数:任何普通的函数和任何重载了“()” 运算符的类的对象都可以作为函数对象使用
使用STL的函数对象,需要包含头文件
STL的基本组件——算法(algorithms):
STL包括70多个算法,例如:排序算法,消除算法,计数算法,比较算法,变换算法,置换算法和容器管理等
可以广泛用于不同的对象和内置的数据类型。
使用STL的算法,需要包含头文件。
#include "pch.h"
#include <iostream>
#include<vector>
#include<iterator>
#include<algorithm>
#include<functional>
using namespace std;
int main(){
const int N = 5;
vector<int> s(N);
for (int i = 0; i < N; i++)
cin >> s[i];
transform(s.begin(), s.end(), ostream_iterator<int>(cout, " "), negate<int>());
cout << endl;
return 0;
}
transform算法的一种实现:
template <class InputIterator, class OutputIterator, class UnaryFunction>
OutputIterator transform(InputIterator first, InputIterator last, OutputIterator result, UnaryFunction op) {
for (;first != last; ++first, ++result)
*result = op(*first);
return result;
}
transform算法顺序遍历first和last两个迭代器所指向的元素;
将每个元素的值作为函数对象op的参数;
将op的返回值通过迭代器result顺序输出;
遍历完成后result迭代器指向的是输出的最后一个元素的下一个位置,transform会将该迭代器返回
迭代器
迭代器是算法和容器的桥梁:迭代器用作访问容器中的元素;算法不直接操作容器中的数据,而是通过迭代器间接操作
算法和容器独立:增加新的算法,无需影响容器的实现;增加新的容器,原有的算法也能适用
输入流迭代器和输出流迭代器
输入流迭代器: 以输入流(如cin)为参数构造;可用*(p++)获得下一个输入的元素
istream_iterator<T>
输出流迭代器: 构造时需要提供输出流(如cout);可用(*p++) = x将x输出到输出流
ostream_iterator<T>
二者都属于适配器:适配器是用来为已有对象提供新的接口的对象;输入流适配器和输出流适配器为流对象提供了迭代器的接口
#include "pch.h"
#include <iostream>
#include<algorithm>
#include<iterator>
using namespace std;
template<class T>
T square(T x) {
return x * x;
}
int main(){
//从标准输入读入若干个实数,分别将它们的平方输出
//template<class T>
transform(istream_iterator<double>(cin), istream_iterator<double>(),
ostream_iterator<double>(cout, "\t"), square<double>);
cout << endl;
return 0;
}
迭代器的分类
迭代器支持的操作
迭代器是泛化的指针,提供了类似指针的操作(诸如++、*、->运算符)
输入迭代器:可以用来从序列中读取数据,如输入流迭代器
输出迭代器:允许向序列中写入数据,如输出流迭代器
前向迭代器:既是输入迭代器又是输出迭代器,并且可以对序列进行单向的遍历
双向迭代器:与前向迭代器相似,但是在两个方向上都可以对数据遍历
随机访问迭代器:也是双向迭代器,但能够在序列中的任意两个位置之间进行跳转,如指针、使用vector的begin()、end()函数得到的迭代器
迭代器的区间:
两个迭代器表示一个区间:[p1, p2)
STL算法常以迭代器的区间作为输入,传递输入数据
合法的区间:p1经过n次(n > 0)自增(++)操作后满足p1 == p2
区间包含p1,但不包含p2
//将来自输入迭代器的n个T类型的数值排序,将结果通过输出迭代器result输出
template<class T,class InputIterator,class OutputIterator>
void mySort(InputIterator first, InputIterator last, OutputIterator result) {
//通过输入迭代器将输入数据存入向量容器s中
vector<T> s;
for (; first != last; ++first)
s.push_back(*first);
//对s进行排序,sort函数的参数必须是随机访问迭代器
sort(s.begin(), s.end());
copy(s.begin(), s.end(), result);//将s序列通过输出迭代器输出
}
int main() {
//将s数组的内容排序后输出
double a[5] = { 1.2,2.4,0.9,3.3,3.2 };
mySort<double>(a, a + 5, ostream_iterator<double>(cout, " "));
cout << endl;
//从标准输入读入若干个整数,将排序后的结果输出
mySort<int>(istream_iterator<int>(cin), istream_iterator<int>(),
ostream_iterator<int>(cout, " "));
cout << endl;
return 0;
}
运行的结果:
0.9 1.2 2.4 3.2 3.3
2 -4 5 9 -1 3 6 -5
-5 -4 -1 2 3 5 6 9
迭代器的辅助函数
advance(p, n):对p执行n次自增操作
distance(first, last):计算两个迭代器first和last的距离,即对first执行多少次“++”操作后能够使得first == last
容器的基本功能与分类:
容器类是容纳、包含一组元素或元素集合的对象;基于容器中元素的组织方式:顺序容器、关联容器;按照与容器所关联的迭代器类型划分:可逆容器随机访问容器
顺序容器:array(数组)、vector(向量)、deque(双端队列)、forward_list(单链表)、list(列表)
(有序)关联容器:set(集合)、multiset(多重集合)、map(映射)、multimap(多重映射)
无序关联容器:unorderedset (无序集合)、unorderedmultiset(无序多重集合);unorderedmap(无序映射)、unordermultimap(无序多重映射)
容器的分类
容器的通用功能
容器的通用功能:用默认构造函数构造空容器;支持关系运算符:==、!=、<、<=、>、>=;
begin()、end():获得容器首、尾迭代器;clear():将容器清空;empty():判断容器是否为空;size():得到容器元素个数;s1.swap(s2):将s1和s2两容器内容交换
相关数据类型(S表示容器类型):S::iterator:指向容器元素的迭代器类型;S::const_iterator:常迭代器类型
对可逆容器的访问:
STL为每个可逆容器都提供了逆向迭代器,逆向迭代器可以通过下面的成员函数得到:rbegin() :指向容器尾的逆向迭代器;rend():指向容器首的逆向迭代器
逆向迭代器的类型名的表示方式如下:S::reverse_iterator:逆向迭代器类型;S::constreverseiterator:逆向常迭代器类型
随机访问容器:随机访问容器支持对容器的元素进行随机访问:s[n]:获得容器s的第n个元素
顺序容器的特性:
顺序容器:向量、双端队列、列表、单向链表、数组
向量(Vector)
特点:一个可以扩展的动态数组;随机访问、在尾部插入或删除元素快;在中间或头部插入或删除元素慢
向量的容量:容量(capacity):实际分配空间的大小;s.capacity() :返回当前容量;s.reserve(n):若容量小于n,则对s进行扩展,使其容量至少为n
双端队列(deque)
特点:在两端插入或删除元素快;随机访问较快,但比向量容器慢;在中间插入或删除元素慢
先按照从大到小顺序输出奇数,再按照从小到大顺序输出偶数。
#include "pch.h"
#include <iostream>
#include<deque>
#include<vector>
#include<algorithm>
#include<iterator>
using namespace std;
int main(){
istream_iterator<int> i1(cin), i2;//建立一对输入流迭代器
vector<int> s1(i1, i2);//通过输入流迭代器从标准输入流中输入数据
sort(s1.begin(), s1.end());//将输入的整数排序
deque<int> s2;
//以下循环遍历s1
for (vector<int>::iterator iter = s1.begin(); iter != s1.end(); ++iter) {
if (*iter % 2 == 0)//偶数放到s2尾部
s2.push_back(*iter);
else
s2.push_front(*iter);
}
//将s2的结果输出
copy(s2.begin(), s2.end(), ostream_iterator<int>(cout, " "));
cout << endl;
return 0;
}
列表(list)
特点:在任意位置插入和删除元素都很快;不支持随机访问
接合(splice)操作:s1.splice(p, s2, q1, q2):将s2中[q1, q2)移动到s1中p所指向元素之前
#include "pch.h"
#include <iostream>
#include<string>
#include<list>
#include<iterator>
using namespace std;
int main(){
string names1[] = { "Alice","Helen","Lucy","Susan" };
string names2[] = { "Bob","David","Levin","Mike" };
//用names1数组的内容构造列表s1
list<string>s1(names1, names1 + 4);
//用names2数组的内容构造列表s2
list<string>s2(names2, names2 + 4);
//将s1的第一个元素放到s2的最后
//等价于s2.splice(s2.end(), s1, s1.begin(),++s1.begin());
s2.splice(s2.end(), s1, s1.begin()); //s2.end()指向s2最后元素的后一个
list<string>::iterator iter1 = s1.begin();//iter1指向s1首
advance(iter1, 2);//iter1前进2个元素,它将指向s1第3个元素
list<string>::iterator iter2 = s2.begin();//iter2指向s2首
++iter2;//iter2前进1个元素,它将指向s2第2个元素
list<string>::iterator iter3 = iter2;//用iter2初始化iter3
advance(iter3, 2);//iter3前进2个元素,它将指向s2的第4个元素
//将[iter2,iter3)范围内的节点接到s1中iter1指向的节点前
s1.splice(iter1, s2, iter2, iter3);
//分别将s1和s2输出
copy(s1.begin(), s1.end(), ostream_iterator<string>(cout, " "));
cout << endl;
copy(s2.begin(), s2.end(), ostream_iterator<string>(cout, " "));
cout << endl;
return 0;
}
顺序容器的插入迭代器:
用于向容器头部、尾部或中间指定位置插入元素的迭代器
包括前插迭代器(frontinserter)、后插迭代器(backinsrter)和任意位置插入迭代器(inserter)
list<int> s;
back_inserter iter(s);
*(iter++) = 5; //通过iter把5插入s末尾
顺序容器的适配器:
以顺序容器为基础构建一些常用数据结构,是对顺序容器的封装
栈(stack):最先压入的元素最后被弹出
队列(queue):最先压入的元素最先被弹出
优先级队列(priority_queue):最“大”的元素最先被弹出
栈和队列模板
栈模板
template <class T, class Sequence = deque<T> > class stack;
队列模板
template <class T, class FrontInsertionSequence = deque<T> > class queue;
栈可以用任何一种顺序容器作为基础容器,而队列只允许用前插顺序容器(双端队列或列表)
栈和队列共同支持的操作:
s1 op s2 op可以是==、!=、<、<=、>、>=之一,它会对两个容器适配器之间的元素按字典序进行比较
s.size() 返回s的元素个数
s.empty() 返回s是否为空
s.push(t) 将元素t压入到s中
s.pop() 将一个元素从s中弹出,对于栈来说,每次弹出的是最后被压入的元素,而对于队列,每次被弹出的是最先被压入的元素
不支持迭代器,因为它们不允许对任意元素进行访问
栈和队列不同的操作:
栈的操作:s.top() 返回栈顶元素的引用
队列操作:s.front() 获得队头元素的引用;s.back() 获得队尾元素的引用
优先级队列:优先级队列也像栈和队列一样支持元素的压入和弹出,但元素弹出的顺序与元素的大小有关,每次弹出的总是容器中最“大”的一个元素。
template <class T, class Sequence = vector<T> > class priority_queue;
优先级队列的基础容器必须是支持随机访问的顺序容器。
支持栈和队列的size、empty、push、pop几个成员函数,用法与栈和队列相同。
优先级队列并不支持比较操作。
与栈类似,优先级队列提供一个top函数,可以获得下一个即将被弹出元素(即最“大”的元素)的引用。
#include "pch.h"
#include <iostream>
#include<queue>
#include<time.h>
#include<stdlib.h>
using namespace std;
const int SPLIT_TIME_MIN = 500;//细胞分裂最短时间
const int SPLIT_TIME_MAX = 2000;//细胞分裂最长时间
class Cell;
priority_queue<Cell> cellQueue;
class Cell { //细胞类
private:
static int count; //细胞总数
int id; //当前细胞编号
int time; //细胞分裂时间
public:
Cell(int birth) :id(count++) {//birth为细胞诞生时间
//初始化,确定细胞分裂时间
time = birth + (rand() % (SPLIT_TIME_MAX - SPLIT_TIME_MIN)) + SPLIT_TIME_MIN;
}
int getId() const { return id; } //得到细胞编号
int getSplitTime() const { return time; } //得到细胞分裂时间
bool operator <(const Cell& s) const{ //定义“<”
return time > s.time;
}
void split() { //细胞分裂
Cell child1(time), child2(time); //建立两个子细胞
cout << time << "s: Cell #" << id << " splits to #" << child1.getId()
<< " and #" << child2.getId() << endl;
cellQueue.push(child1);//将子细胞压入优先级队列
cellQueue.push(child2);//将子细胞压入优先级队列
}
};
int Cell::count = 0;
int main(){
srand(static_cast<unsigned>(time(0)));
int t;//模拟时间长度
cout << "Simulation time: ";
cin >> t;
cellQueue.push(Cell(0));//将细胞压入优先级队列
while (cellQueue.top().getSplitTime() <= t) {
Cell x = cellQueue.top();
x.split();//模拟下个细胞的分裂
cellQueue.pop();//将刚刚分裂的细胞弹出
}
return 0;
}