泛型编程
STL标准模板库
包含三个部分
1.容器
存储和管理数据对象的集合,是数据结构的泛型化封装
2.迭代器
不暴露容器内部细节的前提上,位用户提供一组统一的访问容器的方法
3.泛型算法
借助迭代器,以泛型的方式操作容器中的数据元素
特性
所有的STL组件全部通过模板来实现,支持数据类型的泛化
STL强调的是将数据结构和算法与具体的数据类型分离开来
STL的设计思想是在最小的框架内实现最大的弹性
STL容器
1.线性容器
vector(向量/动态数组) list(列表) deque(双端队列)
C++11 ------- array(静态数组) foward_list(单链表)
2.适配器容器
stack(栈) queue(队列) priority_queue(优先队列)
3.关联容器
map(映射) multimap(多重映射) set(集合) multiset(多重集合)
C++11 ------- 四大无序关联容器(unordered_xxx)
容器的共性:
1.所有的STL容器都支持深拷贝的拷贝构造和赋值
但是元素对象本身要支持深拷贝
2.同类型的容器都重载"=="和"!="运算,容器相等的条件是
元素类型相同,元素个数相同,对应元素满足"=="运算
3.存入容器中的是对象的副本,而不是对象本身
向量(动态数组) -------- vector
基本特性
1.使用连续的空间存储数据元素,支持下标运算
2.使用动态内存管理数据,支持自动扩容,但是不自动收缩
3.通过预分配内存空间降低动态内存分配的消耗
4.支持随机的插入和删除,但是在尾部操作的效率最高
使用
需要包含头文件:#include
构造对象
1.空向量
vector<元素类型> 向量对象;
比如:vector<int> vi;
2.指定初始大小
vector<元素类型> 向量对象(初始大小);
比如:vector<int> vi(10);
3.指定初始大小和初始值
vector<元素类型> 向量对象(初始大小,初始值);
比如:vector<int> vi(10,5);
4.使用其他容器来初始化
int a[5] = {1,2,3,4,5};
vector<int> vi(a,a+5);//5个 vi(a,a+4);//4个
向量的迭代器
vector有4个迭代器
正向迭代器:iterator
反向迭代器:reverse_iterator
常正向迭代器:const_iterator
常反向迭代器:const_reverse_iterator
//迭代器都支持+/-运算(整数)
//同类型的迭代器支持比较和-运算
//迭代器重载类指针的运算符(* ->)
成员函数
iterator begin();//返回起始正向迭代器
iterator end();//返回末尾正向迭代器(最后一个的下一个)
reverse_iterator rbegin();//返回起始反向迭代器
reverse_iterator rend();//返回末尾反向迭代器(第一个的前一个)
size_t size();//大小
size_t capacity();//容量
//调整大小,可增可减,第二个参数时增加时的初始值,增构造,减析构
void resize(size_t size,初始值);
void clear();//清空vector,相当于resize(0)
//调整容量,只增大,不缩小
void reserve(size_t size);
value_type& at(int n);//通过下标访问元素
value_type& front();//首元素
value_type& back();//尾元素
iterator insert(iterator pos,const value_type &value);//从指定位置插入
void push_back(const value_type &value);//从尾部插入
iterator erase(iterator pos);//删除指定位置的元素
void pop_back();//尾部删除
bool empty();//判空
//对容器的修改可能导致之前的迭代器失效
//修改容器后要重置之前的迭代器
//需要顺序存储并且可能频繁在尾部操作就可以选择使用vector
练习:
求最长公共子串
使用反向迭代器反向遍历vector
1.直观暴力的方法
分别遍历两个字符串,每到一个位置就计算当前公共子串长度
在比较中记录最长的公共子串的起始位置和长度,循环结束找到最长公共子串
效率较低 ------ 时间复杂度n^3
2.矩阵法求解
str1:abcdefg
str2:abkdefx
构造一个矩阵,将str1的长度作为列数,将str2的长度作为行数
//比较每一行和每一列,如果字符相同矩阵对应位置置1,不同置0
a b k d e f x
a 1 0 0 0 0 0 0
b 0 1 0 0 0 0 0
c 0 0 0 0 0 0 0
d 0 0 0 1 0 0 0
e 0 0 0 0 1 0 0
f 0 0 0 0 0 1 0
g 0 0 0 0 0 0 0
//公共子串的长度就是对应矩阵中连续为1的对角线长度
//2 3
优化一下处理,将之前相同字符的个数(左上角)加入到数字中
a b k d e f x
a 1 0 0 0 0 0 0
b 0 2 0 0 0 0 0
c 0 0 0 0 0 0 0
d 0 0 0 1 0 0 0
e 0 0 0 0 2 0 0
f 0 0 0 0 0 3 0
g 0 0 0 0 0 0 0
//在比较的同时把对角线相同字符的个数也记录下来
//1 2 1 2 3
/*06-双端队列的使用*/
#include <iostream>
#include <deque>
using namespace std;
int main()
{
deque<int> di(10,5);//长度为10,初始值为5
cout<<"正向迭代:";
for(deque<int>::iterator itt=di.begin();itt!=di.end();itt++){
cout<<*itt<<" ";
}
cout<<endl;
//头部插入
di.push_front(100);
di.push_back(200);
cout<<"插入后:";
for(deque<int>::iterator itt=di.begin();itt!=di.end();itt++){
cout<<*itt<<" ";
}
cout<<endl;
//头部删除
di.pop_front();
cout<<"删除后:";
for(deque<int>::iterator itt=di.begin();itt!=di.end();itt++){
cout<<*itt<<" ";
}
cout<<endl;
//头部删除
cout<<"反向迭代:";
for(deque<int>::reverse_iterator itt=di.rbegin();itt!=di.rend();itt++){
cout<<*itt<<" ";
}
cout<<endl;
return 0;
}
静态数组 ------- array
基本特性
1.使用连续空间存储元素,支持下标运算
2.容量空间固定,不支持扩容和收缩
3.支持随机位置访问,末尾操作效率较高
使用
需要包含头文件,#include
构造array对象
array<元素类型,数组长度> array对象;
比如:array<int,5> ai;
//静态数组支持C语言数组初始化的语法
array<int,5> ai = {1,2,3,4,5};
迭代器
array有4个迭代器
正向迭代器:iterator
反向迭代器:reverse_iterator
常正向迭代器:const_iterator
常反向迭代器:const_reverse_iterator
//迭代器都支持+/-运算(整数)
//同类型的迭代器支持比较和-运算
//迭代器重载类指针的运算符(* ->)
成员函数
size_t size();//大小
value_type& at(int n);//通过下标访问元素
value_type& front();//首元素
value_type& back();//尾元素
bool empty();//判空
void fill(const value_type &value);//填充
void swap(array &other);
//array的效率高于vector
练习:
使用vector存储矩阵实现求最大公共子串
/*04-使用vector实现子最长公共子串*/
#include <iostream>
#include <vector>
using namespace std;
string getLCS(const string &str1,const string &str2)
{
//记录最长子串在str1中的结束位置和长度
int max_end = 0,max_len = 0;
//创建比较矩阵(二维向量)
vector<vector<int>> arr(str1.length(),vector<int>(str2.length()));
for(size_t i=0;i<str1.length();i++){
for(size_t j=0;j<str2.length();j++){
//比较字符,将比较结果写入矩阵
if(str1.at(i)==str2.at(j)){//赋值 左上角+1
//如果左上角不存在
if(i==0||j==0)
arr.at(i).at(j) = 1;
else
arr.at(i).at(j) = arr.at(i-1).at(j-1)+1;
//记录最长长度和此时str1中最后一个字符的位置
if(arr.at(i).at(j)>max_len){
max_len = arr.at(i).at(j);
max_end = i;
}
}
else{
arr.at(i).at(j) = 0;
}
}
}
//返回最长公共子串
return str1.substr(max_end-max_len+1,max_len);
}
int main()
{
string str1 = "kkkhellowebyebye";
string str2 = "ghsasabyehellod";
cout<<getLCS(str1,str2)<<endl;
return 0;
}
//vector可以递归实例化
vector<vector<int>> -------- 二维
双端队列 ------- deque
包含头文件:#include
特性
1.deque的物理结构几乎和vector一样,唯一的区别是deque的两端都是开放的,都可以插入和删除
2.接口比vector多了push_front/pop_front,去掉了capacity和reserve函数
3.性能上deque略低于vector,内存消耗高于vector,元素访问的时间长于vector
4.deque空间堆成,在头部和尾部操作的时间复杂度相同
列表 ----------- list
需要包含头文件:#include
特性
1.列表的底层使用线性双向链表来实现
2.列表在任意位置插入和删除的操作都是常数时间
3.列表不支持随机访问([] 和 at)
成员函数
begin/end/rbegin/rend ------ 迭代器操作
front/push_front/pop_front ----- 头部操作
back/push_back/pop_back ----- 尾部操作
insert/erase ------ 指定位置插入和删除
size/resize/clear/empty ------ 大小操作
//删除所有匹配的元素
void remove(const value_type &val);
//排序
void sort();//元素支持"<"运算
void sort(比较器);//传入两个元素获得比较结果(布尔值) ----- 函数/函数对象
//将某个列表中的 指定/全部 元素到调用列表中的指定位置
void splice(const_iterator pos,list &other);
void splice(const_iterator pos,list &other,const_iterator first,const_iterator last);;
//合并两个已排序的链表,将参数列表合并到调用列表中,合并后任然有序
//合并的比较器和排序的比较器要保持一致
void merge(list &other);//使用<比较
void merge(list &other,比较器);//提供比较器
//删除连续的重复元素
void unique();
类类型的对象作为容器元素
能够放入容器中的对象必须满足以下条件:
1.必须支持无参构造
2.支持深拷贝的拷贝构造和赋值运算符函数
3.如果需要查找,重载"=="运算符
4.如果需要排序,重载"<"运算符或者提供比较器
单向列表 -------- forward_list
使用需要包含头文件:#include
特性
和list基本一致
唯一的区别底层实现使用单链表,单向访问
和list不同的接口
1.forward_list只提供正向迭代器,成员函数之后begin/end,没有rbegin/rend
2.forward_list不提供尾部访问的接口,没有back/push_back/pop_back
3.forward_list不提供size成员函数
//forward_list只维护一个方向的链表,效率略高于list
作业:
班长和学位分别统计了学生的信息(student类),使用list将班长和学委的信息合并,去除重名后按照学号从小到大输出,年龄从大到小输出。
class student{
int id;
string name;
int age;
};
list<student> Three_Kingdom;
Three_Kingdom.push_back(student(4,"张飞",23));
....
班长:
4 张飞 23
2 关羽 26
5 赵云 19
1 刘备 31
6 黄忠 50
学委:
9 曹操 35
7 张辽 27
11 关羽 26
8 典韦 24
10 刘备 31
#include <iostream>
#include <list>
using namespace std;
class student {
public:
student(int id = 0, string name = "", int age = 0) : id(id), name(name), age(age) {}
//按名字去重,==判断name,想要去重哪个值就要去重那个
bool operator==(const student &s) const { //为什么是返回布尔值的
return this->name == s.name;
}
//默认按id从小到大排序
bool operator<(const student &s) const {
return this->id < s.id;
}
bool operator>(const student &s) const {
return this->age > s.age;
}
//自己作为函数对象用作比较器 name排序
bool operator()(const student &s1, const student &s2) {
return s1.name < s2.name;
}
//重载输出
friend ostream &operator<<(ostream &os, const student &s) {
return os << s.id << ":" << s.name << ":" << s.age;
}
private:
int id;
string name;
int age;
};
//函数比较器 T-student ===> 年龄从大到小
template<typename T>
bool comp(const T &a, const T &b) {
return a > b;
}
int main() {
list<student> ban;
list<student> xue;
ban.push_back(student(4, "张飞", 23));
ban.push_back(student(2, "关羽", 26));
ban.push_back(student(5, "赵云", 19));
ban.push_back(student(1, "刘备", 31));
ban.push_back(student(6, "黄忠", 50));
xue.push_back(student(9, "曹操", 35));
xue.push_back(student(7, "张辽", 27));
xue.push_back(student(11, "关羽", 26));
xue.push_back(student(8, "典韦", 24));
xue.push_back(student(10, "刘备", 31));
//合并
ban.splice(ban.end(), xue);
//合并结果
cout << "合并后:" << endl;
for (list<student>::iterator it = ban.begin(); it != ban.end(); it++) {
cout << *it << endl;
}
cout << "------------------------------------------------" << endl;
//去除重名 ----- 按名字排序(函数对象比较器),去重
ban.sort(student());
ban.unique();//需要去重载---operator==
cout << "姓名排序去重后:" << endl;
for (list<student>::iterator it = ban.begin(); it != ban.end(); it++) {
cout << *it << endl;
}
cout << "------------------------------------------------" << endl;
//按学号升序排序
ban.sort(); //需要去重载 operator<
cout << "学号升序排序后:" << endl;
for (list<student>::iterator it = ban.begin(); it != ban.end(); it++) {
cout << *it << endl;
}
cout << "------------------------------------------------" << endl;
//按年龄降序排序
ban.sort(comp<student>); //需要去重载 operator>
cout << "年龄降序排序后:" << endl;
for (list<student>::iterator it = ban.begin(); it != ban.end(); it++) {
cout << *it << endl;
}
cout << "------------------------------------------------" << endl;
return 0;
}