1.泛型程序设计基本概念
1)概念:用来描述泛型程序设计中作为参数的数据类型所需具备的功能
- 概念的内涵: 这些功能
- 概念的外延: 具备这些功能的所有数据类型
- 模型: 具备一个概念所需要功能的数据类型称为这一概念的一个模型
2. STL简介
一)STL组成包括
- 容器
- 迭代器
- 函数对象
- 算法
1) 容器
容器是容纳包含一组元素的对象分为顺序容器,关联容器
- 顺序容器: 将一组具有相同类型的元素以严格的线性形式组织起来如向量、双端队列、列表容器
- 关联容器: 根据索引查找元素,比如集合和映射容器
2) 迭代器
指针就是一种迭代器,迭代器是泛化的指针
3)函数对象
函数对象是泛化的函数,使用STL的函数对象需要包含头文件<functional>,任何普通函数和重载了"()"运算符的类的对象都可以作为函数对象使用,函数对象是泛化的函数
迭代器作为容器和算法之间的中间对象
3.迭代器
迭代器是泛化的指针,指向容器中的一个位置
一)输入流迭代器和输出流迭代器
1)输入流迭代器
//输入流迭代器是一个类模板
template<class T>
istream_iterator<T>; //T是使用该迭代器从输入流中输入数据的类型
类型T要满足两个条件
- 有默认构造函数
- 对该类型的数据可以使用">>"从输入流输入
一个输入流迭代器实例用下面的构造函数进行构造
istream_iterator(istream& in);
2)输出流迭代器
template<class T>
ostream_iterator<T>;
一个输入流迭代器实例可以用下面两个构造函数进行构造:
1)ostream_iterator(ostream& out);
2)ostream_iterator(ostream& out, const char * delimiter); //out表示将数据输出到输出流,参数delimiter是可选的,表示两个输出数据之间的分隔符
二)迭代器的分类
所有迭代器都具备的功能:
- ++p1: 对迭代器实例使用前置++可以指向下一个元素,且该表达式的返回值为p1自身的引用
- p1++: 该表达式返回类型不确定
1)输入迭代器
支持对序列进行不可重复的单向遍历, 除了通用功能外
- p1 == p2
- p1 != p2
- * p1
- p1->m : 等价于(*p1).m
- *p1++ : { T t = *p1; ++p1; return t; }
2)输出迭代器
- *p1 = t : 想迭代器所指位置写入一个元素,返回类型不确定
- p1++ = t : 等价于{*p1 = t; ++p1;} 返回类型不确定
3)前向迭代器(输入输出迭代器的子概念)
子概念拥有父概念的功能,此外还有
- *p1 : 返回结果具有T& 类型
- p1++: {P p2 = p1; ++p1; return p2; }
4)双向向迭代器(是单向迭代器的子概念)
- – --p1: 使迭代器指向上一个元素,返回值为p1自身的引用
- p1-- : { P p2 = p1; --p1; return p2; }
5)随机访问迭代器(双向迭代器的子概念)
- p1+=n: 将迭代器p1向前移动n个元素
- p1-=n:向后移动n个元素
- p1+n 或 n+p1 : 获得指向迭代器p1前第n个元素的迭代器
- p1-n : 获得p1后第n个元素的迭代器
- p1-p2 : 返回一个满足p1 == p2 + n的整数n
- p1 op p2 : 比较p1和p2所指向位置的前后关系 op可以是 ‘>’ , ‘<’ , ‘>=’ , ‘<=’
- p1[n] : *(p1 + n)
三)迭代器的区间
若p1 p2是两个输入迭代器,以后将使用[p1, p2)形式来表示它们构成的区间,包括p1但是不包括p2
四)迭代器的辅助函数
1)advance函数
template<class InputIterator, class Distance>
void advance(InputIterator& iter, Distance n); //使迭代器前进n个元素
2)distance函数
template<class InputIterator>
unsigned distance(InputIterator first, InputIterator last); //last - first算出距离
4.容器
STL中有7种容器 :
一)容器的基本功能和分类
1)基本功能
- S s1 : 容器都有一个默认构造函数,用于构造一个没有任何元素的空容器
- s1 op s2 : op可以使 == , !=, <, <=, >, >= ,对两个容器之间的元素按字典顺序进行比较
- s1.begin(): 返回指向s1第一个元素的迭代器
- s1.end() : 返回指向s1最后一个元素的下一个位置的迭代器
- s1.clear(): 将s1的内容清空
- s1.size(): 返回s1的元素个数
- s1.swap(s2): 将s1容器和s2容器的内容交换
2)显式的写出容器的迭代器类型
- S::iterator
- S::const_iterator
vector<int>::iterator iter = s1.begin(); 显式写出迭代器类型
3)容器的分类
容器 | 顺序容器 | 关联容器 |
---|---|---|
可逆容器 | list | set 、 multiset、map、multimap |
随机访问容器 | vector、deque |
1.STL为每个可逆容器都提供了逆向迭代器
- s1.rbegin() : 得到指向容器的最后一个元素的逆向迭代器
- s1.rend(): 得到指向容器的第一个元素的前一个位置的逆向迭代器
2.显式写出逆向迭代器类型名
- S::reverse_iterator
- S::const_reverse_iterator
3.逆向迭代器是普通迭代器的适配器
逆向迭代器的++运算和普通迭代器的–对应 逆向迭代器的–和普通迭代器的++对应
copy(s.rbegin(), s.rend(),ostream_iterator<int>(cout," ")); //将容器s的内容逆向输出
- 逆向迭代器提供一个成员函数base,用它可以得到用于构造该逆向迭代器的那一个迭代器,存在下面的等式关系
s1.rbegin() == S::reverse_iterator(s1.end()), s1.rbegin().base() == s1.end()
s1.rend() == S::reverse_iterator(s1.begin()), s1.rend().base() == s1.begin()
二)顺序容器 : 向量、双端队列、链表
1)顺序容器的基本功能
(1)构造函数
S s(n,t); //构造一个由n个t元素构成的容器实例s
S s(n); //构造一个由n个元素的容器实例s,每个元素都是T()
S s(q1, q2); //使用将[q1, q2)区间内的数据作为s的元素构造s
(2)赋值函数
s.assign(n,t); //赋值后的容器由n个t元素构成
s.assign(n); //赋值后的容器由n个元素的容器实例,每个元素都是T()
s.assign(q1,q2); //赋值后的容器的元素为[q1,q2)区间内的数据
(3)元素的插入
s.insert(p1, t); //在s容器中p1所指向的位置插入一个新的元素t,插入后的元素夹再原p1和p1-1所指向的元素之间,该函数会返回一个迭代器指向新插入的元素
s.insert(p1, n, t); //在s容器中p1所指向的位置插入n个新的元素t,插入后的元素夹在原p1和p1-1所指向的元素之间,没有返回值
s.insert(p1,q1,q2); //将[q1,q2)区间内的元素顺序插入到s容器中p1位置处,新元素夹在原p1和p1-1所指向的元素之间
(4)元素的删除
s1.erase(p1); //删除s1容器中p1所指向的元素,返回被删除的下一个元素的迭代器
s1.erase(p1,p2); //删除s1容器中[p1,p2)区间内的元素,返回最后一个被删除元素的下一个元素的迭代器(即在删除前p2所指向元素的迭代器)
(5)改变容器的大小
s1.resize(n);
(6)首尾元素的直接访问
s.front(); //获得容器首元素的引用
s1.back(); //获得容器尾元素的引用
(7)在容器尾部插、删除元素
s.push_back(t); //向容器尾部出入元素t
s.pop_back(); //将容器尾部的元素删除
(8)在容器头部插入、删除元素(vector不支持)
s.push_front(); //向容器头部插入元素t
s.pop_front(); //删除容器头部元素
2)实例
//输出指定的整型顺序容器的元素
template<class T>
void printContainer(const char * msg, const T& s) {
cout << msg << ": ";
copy(s.begin(), s.end(), ostream_iterator<int>(cout, " "));
cout << endl;
}
int main()
{
//从标准输入读入10个整数,将它们分别从s的头部加入
deque<int> s;
for (int i = 0; i < 10; i++) {
int x;
cin >> x;
s.push_front(x);
}
printContainer("deque at first", s);
//用s容器的内容的逆序构造列表容器l
list<int> l(s.rbegin(), s.rend());
printContainer("list at first", l);
//将列表容器每相邻两个容器顺序颠倒
list<int>::iterator iter = l.begin();
while (iter != l.end()) {
int v = *iter;
iter = l.erase(iter);
l.insert(++iter, v);
}
printContainer("list at last", l);
//用列表容器l的内容给s赋值,将s输出
s.assign(l.begin(), l.end());
printContainer("deque at last", s);
return 0;
}
输入
0 9 8 6 4 3 2 1 5 4
输出结果
deque at first: 4 5 1 2 3 4 6 8 9 0
list at first: 0 9 8 6 4 3 2 1 5 4
list at last: 9 0 6 8 3 4 1 2 4 5
deque at last: 9 0 6 8 3 4 1 2 4 5
分析
下面这段代码的执行过程
list<int>::iterator iter = l.begin();
while (iter != l.end()) {
int v = *iter;
iter = l.erase(iter);
l.insert(++iter, v);
}
printContainer("list at last", l);
注意两点
- l.earse(iter)执行后iter会指向被删除元素的下一个元素
- l.insert(++iter, v)执行步骤是,先++iter,之后将v插入到iter和iter-1之间
5)三种顺序容器的特性
向量容器中插入新元素有时会使所有迭代器失效,而向列表容器插入新元素不会使任何迭代器失效
(1)向量
(一)区分向量两个概念
- 容量(capacity)
- 大小(size)
s.capacity(); //返回s的容量
s.reserve(n); //若当前的容量大于或者等于n,什么也不做,否则扩大s的容量,使其不小于n
(二)什么是失效
所谓失效 : 是指继续使用这样的迭代器或者指针,结果是不确定的
如果插入操作(也包括使用push_back()向末尾加入新元素)引起了向量容量的扩展,那么在执行插入之前所获得的一切迭代器和指向向量元素的指针、引用都会失效(因为空间被重新分配了,元素的内存地址发生了变化);如果插入操作未引起向量容器容量的扩展,那么只有处于插入位置之后的迭代器和指针、引用会失效(因为这些元素被移动了),对插入位置之前的元素不会有影响.
vector<int> s;
s.reserve(3);
s.push_back(1);
s.puch_back(2);
vector<int>::iterator iter1 = s.begin();
int * p1 = &s[0];
vector<int>::iterator iter2 = s.begin() + 1;
int * p2 = &s[1];
s.insert(s.begin(), 3); //执行插入操作后 在vs的执行中,iter1和iter2都失效
cout<<* iter1<<" "<<*p1<<endl;
cout<<*iter2<<" "<<*p2<<endl;
对于上面的代码 执行插入操作后 iter1和iter2在未重新赋值前不可以再次使用
(3)vector中删除元素
删除元素后 迭代器和指针、引用都会失效
删除向量容器的元素时,并不会使空闲空间被释放,这时可以使用下面的语句达到释放多余空间的目的
vector<T>(s.begin(), s.end()).swap(s);
上面代码的意思: 首先用s的内容创建一个临时的向量容器对象,再将该容器和s交换,这时s原先占有的空间已经属于临时对象该语句执行完成后临时对象会被析构,空间被释放
(2)双端队列
(一)STL中的实现
在很多STL实现中,双端队列的数据被表示为一个分段数组,容器中的元素分段存放在一个个大小固定的数组中,此外容器还需要维护一个存放这些数组首地址的索引数组
(4)列表
STL中的列表实际上就是一个双向链表
列表支持一种特殊操作----接合(spllice) : 是指将一个列表容器的一部分连续的元素从该列表中删除后插入到另一个列表容器中
s1.splice(p,s2) //将s2列表的所有元素插入到s2列表中p-1和p之间,将s2列表清空
s1.splice(p,s2,q1); //将s2列表中q1所指向的元素插入到s1列表中p-1和p之间,将q1所指向的元素从s2列表中删除
s1.splice(p,s2,q1,q2); //将s2列表中[q1,q2)区间内的所有元素插入到s1列表中p-1和p之间,将[q1,q2)区间内的元素从s2列表中删除
3)顺序容器的适配器
(1)优先级队列 priority_queue
包含在头文件 #include <queue>
中
使用条件与特点
- 基础容器必须是支持随机访问的顺序容器(向量容器、双端队列容器),默认为向量容器
- 优先级队列可以像栈和队列一样压入和弹出,但是元素弹出的顺序和元素大小有关和压入顺序无关(每次弹出容器中最大的一个元素)
- 当容器的元素类型是类、结构体时要多 < 进行重载
模拟细胞分裂中使用优先级队列
模拟细胞分裂