C++ STL standard template libaray 标准模板库
目录
- C++ STL standard template libaray 标准模板库
- 一、标准容器
- 顺序容器
- vector
- deque
- list
- vector deque list对比
- 容器适配器
- stack
- queue
- priority_queue
- 关联容器
- (1)无序关联容器
- unordered_set
- unordered_multiset
- unordered_map
- unordered_multimap
- (2)有序关联容器
- set
- multiset
- map
- mutimap
- 二、迭代器
- iterator和const iterator
- reverse_iterator和const_reverse_iterator
- 三、函数对象
- 四、泛型算法
一、标准容器
顺序容器
vector
底层数据结构:动态开辟的数组,每次以原来空间大小的2倍进行扩容(开辟两倍大小的新数组,拷贝构造原数组对象,析构原数组对象,回收原数组内存)
vector<int> vec
;
增加:
vec.push_back(20);
末尾添加元素 O(1) 导致容器扩容
vec.insert(it,20);
it迭代器指向的位置添加一个元素20 O(n) 导致容器扩容
删除:
vec.pop_back();
末尾删除元素 O(1)
vec.erase(it);
删除it迭代器指向的元素 O(n)
查询:
operator[]
下标的随机访问 vec[5] O(1)
iterator
迭代器进行遍历
find,for_each
foreach
=> 底层通过iterator来实现的
常用方法介绍:
size()
empty()
reserve(20)
:vector预留空间的 只给容器底层开辟指定大小的内存空间,并不会添加新的元素
resize(20)
:容器扩容用的 不仅给容器底层开辟指定大小的内存空间,还会会添加新的元素
swap
:两容器进行元素交换
注意1:resize和reserver的区别
reserve预留空间 只给容器底层开辟指定大小的内存空间,并不会添加新的元素
resize:容器扩容 不仅给容器底层开辟指定大小的内存空间,还会添加新的元素
vector<int> ve;ve.reserve(20);//预留内存空间 cout<<ve.empty()<<endl;//输出 1 cout<<ve.size()<<endl;//输出 0ve.push_back(1); ve.push_back(2); ve.push_back(3); for(int x:ve) cout<<x<<" ";//输出 1 2 3
vector<int> ve;ve.resize(10);//开辟空间 添加元素cout<<ve.empty()<<endl;//输出 0 cout<<ve.size()<<endl;//输出 10 ve.push_back(1); ve.push_back(2); ve.push_back(3); for(int x:ve) cout<<x<<" ";//输出 0 0 0 0 0 0 0 0 0 0 1 2 3
注意2:vector调用clear()或者size(0),会清除容器内的所有元素,但是不会释放内存,如果想释放内存需要调用shrink_to_fit()函数
vector<int> ve{1,2,3,4,5,6,7,8,9,10};cout<<ve.size()<<endl;//输出10 cout<<ve.capacity()<<endl;//输出10ve. clear(); //ve.resize(0);cout<<ve.size()<<endl;//输出0 cout<<ve.capacity()<<endl;//输出10cout<<"ve[2]="<<ve[2]<<endl;//输出3 因为元素清空了 内存没有清空 ve.shrink_to_fit();cout<<ve.size()<<endl;//输出0 cout<<ve.capacity()<<endl;//输出0
注意3:对容器进行连续插入或者删除操作(insert/erase),一定要更新迭代器,否则第一次insert或者erase完成,迭代器就失效了,比如下面代码经典错误
#include <iostream>
#include <vector>
using namespace std;
int main() {vector<int> ve{1,2,3,4,5,6,7,8,9,10};//把容器中所有的偶数删除auto it = ve.begin();for(;it!=ve.end();it++) {if(*it%2==0){ve.erase(it);//错误迭代器已经失效了 }}for(int x:ve){cout<<x<<" ";//输出为空}return 0;
}
正确写法:
#include <iostream>
#include <vector>
using namespace std;
int main() {vector<int> ve{1,2,3,4,5,6,7,8,9,10};//把容器中所有的偶数删除auto it = ve.begin();while(it!=ve.end()){if(*it%2==0){it = ve.erase(it);//删除之后返回迭代器位置 }else//迭代器指向的不是偶数才后移 {++it;}}for(int x:ve){cout<<x<<" ";//输出1 3 5 7 9 }cout<<endl;//给vector容器中所有的奇数前面都添加一个小于奇数1的偶数 for(it=ve.begin();it!=ve.end();++it){if(*it%2!=0){it = ve.insert(it,*it-1);//插入之后返回新的迭代器位置 ++it;//迭代器需要向后多移动一次保证不重复插入 }}for(int x:ve){cout<<x<<" ";//输出0 1 2 3 4 5 6 7 8 9 }return 0;
}
deque
底层数据结构:动态开辟的二维数组(二维双端队列),一维数组从大小2开始,以2倍的方式进行扩容,每次扩容后,原来的二维数组(固定长度),从新的第一维数组的下标oldsize/2开始存放,上下都预留相同的空行,方便支持deque的首尾元素添加
注意:deque底层每一个第二维本身是连续的,但是所有的二维不是连续的,是重新new出来的(分段连续)
底层原理:
一般编译器默认大小
#define MAP_SIZE 2
第一维度大小
#define QUE_SIZE 4096/sizeof(T)
第二维度大小 T为数据类型
1.初始的队头队尾指针在队列中间(双端队列方便两头都可以插入删除)
2.随着元素的插入队头队尾指针移动
3.当队列满的时候,开辟新的二维空间
4.当所有队列满的时候进行2倍扩容(二维会放在一维中间位置)
操作:
deque<int> deq
;
增加:
deq.push_back(20);
从末尾添加元素 O(1) 可能扩容
deq.push_front(20);
从首部添加元素 O(1) 可能扩容
deq.insert(it,20);
it指向的位置添加元素 O(n) 可能扩容
删除:
deq.pop_back()
; 从末尾删除元素 O(1)
deq.pop_front()
; 从首部删除元素 O(1)
deq.erase(it)
; 从it指向的位置删除元素 O(n)
查询搜索
iterator (连续的insert和erase一定要考虑迭代器失效的问题)
list
底层数据结构:双向的循环列表
list mylist;
增加:
mylist.push_back(20);
从末尾添加元素 O(1) 可能扩容
mylist.push_front(20);
从首部添加元素 O(1) 可能扩容
mylist.insert(it,20);
it指向的位置添加元素 O(1) 可能扩容 往往需要先query查询操作
对于链表来说,查询操作效率就比较慢
删除:
mylist.pop_back()
; 从末尾删除元素 O(1)
mylist.pop_front()
; 从首部删除元素 O(1)
mylist.erase(it)
; 从it指向的位置删除元素 O(1)
查询搜索
iterator (连续的insert和erase一定要考虑迭代器失效的问题)
vector deque list对比
vector特点:动态数组,内存连续,2倍的方式进行扩容
deque特点:动态开辟的二维数组空间,第二维是固定长度的数组空间,扩容的时候(第一维的数组进行2倍扩容)
list特点:双向循环链表
vector和deque之间的区别?
1.底层数据结构:
2.前中后插入删除元素的时间复杂度:中间O(n),末尾都是O(1), 前插deque O(1) vector O(n)
3.对于内存的使用效率:vector需要的内存空间必须连续,deque可以分块进行数据存储,不需要内存必须连续
4.在中间进行insert或者erase,虽然时间复杂度都是O(n),但是vector都是连续空间比较方便,deque的第二维内存空间不是连续的,所以在deque中间进行元素的inset或者erase,指令肯定更多,造成元素移动的时候比vector要慢
deque和list比vector容器多出来的增加删除函数接口:push_front() pop_front()
vector和list之间的区别?
数组和链表的区别
数组:增加删除O(n) 查询O(n) 随机访问O(1)
链表:增加删除O(1) 查询(n)
容器适配器
怎么理解适配器?
1.适配器底层没有自己的数据结构,它是另一个容器的封装,它的方法,全部由底层依赖的容器进行实现
2.没有实现自己的迭代器
底层实现
template<typename T,typename Container=deque<T>>
class Stack
{
public:void push(const T &val){con.push_back(val);}void pop(){con.pop_back();}T top()const{return con.back();}
private:Container con;
};
stack
先进后出
push():入栈
pop():出栈
top():查看栈顶元素
empty():判断栈空
size():返回元素个数
queue
先进先出
push():入队
pop():出队
front():查看队头元素
back():查看队尾元素
empty():判断队空
size():返回队列元素个数
priority_queue
底层数据结构:大根堆
push():入队
pop():出队
top():查看队顶元素
empty():判断队空
size():返回元素个数
面试问题:
stack和queue底层依赖deque,为什么不依赖vector?
1.vector的初始内存使用效率太低了!没有deque好(vector需要二倍扩容,deque初始二维就开辟了1024个数组空间)
2.对于queue来说,需要支持尾部插入,头部删除O(1),如果queue依赖vector,其出队效率很低
3.vector需要大片连续内存,而deque只需要分段的内存,当存储大量的数据时,显得deque对于内存的利用率更好一些
priority_queue底层依赖vector,为什么不依赖deque?
底层默认把数据组成一个大根堆结构,在一个内存连续的数组上构建一个大根堆和小根堆(父子节点需要通过下标计算)
关联容器
(1)无序关联容器
底层数据结构:链式哈希表 增删查O(1)
unordered_set
#include<unordered_set>
增加:insert(val)
遍历:iterator
自己搜索,调用find
成员方法
删除:erase(key) erase(it)
存在:count()
unordered_multiset
#include<unordered_set>
#include<unordered_set>
增加:insert(val)
遍历:iterator
自己搜索,调用find
成员方法
删除:erase(key) erase(it)
个数:count()
unordered_map
#include<unordered_map>
unordered_multimap
#include<unordered_map>
#include<iostream>
#include<unordered_map>
#include<string>
using namespace std;
/*map存储的是pair对象
class pair
{first;second;
}
*/
int main()
{unordered_map<int,string> map;//插入 map.insert(make_pair(1000,"张三"));map.insert({1010,"李四"});map[1020] = "王五";cout<<map.size()<<endl;map.erase(1020);//删除 //查询 方式1 cout<<map[1020]<<endl;//查询 方式2 auto it = map.find(1000);if(it!=map.end()){cout<<" key:"<<it->first<<" value:"<<it->second;}//遍历方式一 for(const pair<int,string> &p:map){cout<<" key:"<<p.first<<" value:"<<p.second;} //遍历方式二 it = map.begin();for(;it!=map.end();it++){cout<<" key:"<<it->first<<" value:"<<it->second;} return 0;
}
注意:
1.map的operator[]不仅有查询功能,如果key不存在,它会插入一对数据[key,type()]
2.map存储的是pair对象
(2)有序关联容器
底层数据结构:红黑树 增删查 O(log2n) n是底数(树的层数 树的高度)
set
multiset
#include
增加:insert(val)
遍历:iterator
自己搜索,调用find
成员方法
删除:erase(key) erase(it)
注意自定义类型需要重载运算符
#include<iostream>
#include<set>
#include<map>
#include<string>
using namespace std;
class Student
{
public:Student(int id,string name):_id(id),_name(name){}//重载小于运算符 bool operator<(const Student &stu)const{return _id<stu._id;}
private:int _id;string _name; friend ostream& operator<<(ostream &out,const Student &stu);
};
ostream& operator<<(ostream &out,const Student &stu)
{out<<"id:"<<stu._id<<" name:"<<stu._name<<endl;return out;
}
int main()
{set<Student> set;set.insert(Student(1020,"李四"));set.insert(Student(1000,"张文"));for(auto it=set.begin();it!=set.end();it++){cout<<*it<<endl;}return 0;
}
map
#include
mutimap
#include
注意使用operator[]需要提供默认参数构造函数
#include<iostream>
#include<set>
#include<map>
#include<string>
using namespace std;
class Student
{
public:Student(int id=0,string name=""):_id(id),_name(name){}
private:int _id;string _name; friend ostream& operator<<(ostream &out,const Student &stu);
};
ostream& operator<<(ostream &out,const Student &stu)
{out<<"id:"<<stu._id<<" name:"<<stu._name<<endl;return out;
}
int main()
{map<int,Student> mp;mp.insert({1000,Student(1000,"张文")});mp.insert({1020,Student(1000,"李广")});mp.insert({1030,Student(1000,"高阳")});cout<<mp[1020]<<endl;//使用[]需要提供默认参数构造函数 (因为可能查询的key不存在,则会构造一个新的对象)auto it = mp.begin();for(;it!=mp.end();it++){cout<<"key:"<<it->first<<" value:"<<it->second;} return 0;
}
二、迭代器
iterator和const iterator
const_iterator:常量的正向迭代器 只能读不能写
iterator:普通的正向迭代器
两种迭代器其实是继承关系class const_iterator{public:const T& operator*(){//返回常引用 所以不能赋值 return *_ptr;}};class iterator : public const_iterator{public:T& operator*(){//返回普通引用 可以赋值 return *_ptr;}}vector<int> const_iterator it = vec.begin();//子类的值赋值给父类 没毛病
reverse_iterator和const_reverse_iterator
reverse_iterator:普通的反向迭代器
const_reverse_iterator:常量的反向迭代器 只能读不能写
#include<iostream>
#include<vector>
using namespace std;
int main()
{vector<int> vec;for(int i=0;i<20;i++){vec.push_back(i);}//rbegin():返回的是最后一个元素的反向迭代器表示//rend():返回的是首元素前驱位置的迭代器的表示 auto rit = vec.rbegin();for(;rit!=vec.rend();++rit){cout<<*rit<<" ";}return 0;
}
三、函数对象
(1)什么是函数对象
把有operator()小括号运算符重载函数的对象,称作为函数对象或者称为仿函数
(小括号运算符重载函数一个参数叫一元函数对象,二个参数叫二元函数对象)
C语言实现两数之和
int sum(int a,int b)
{return a+b;
}
int ret = sum(10,20);
C++实现两数之和(函数对象)
class Sum
{
public:operator()(int a,int b){return a+b;}
};
Sum sum;
int ret = sum(10,20);
(2)为什么使用函数对象
假如我们要实现比较两个数的大小,使用C语言实现
template<typename T>
bool greater(T a,T b)
{return a>b;
}
template<typename T>
bool less(T a,T b)
{return a<b;
}
cout<<greater(10,20)<<endl;
cout<<less(10,20)<<endl;
使用函数指针实现
template<typename T>
inline bool mygreater(T a,T b)
{return a>b;
}template<typename T>
inline bool myless(T a,T b)
{return a<b;
}template<typename T,typename Compare>
bool compare(T a,T b,Compare comp)//第三个参数接收函数指针
{//通过函数指针调用函数,是没有办法内联的,效率很低,因为有函数调用开销 return comp(a,b);
}
int main()
{cout<<compare(10,20,mygreater<int>)<<endl;cout<<compare(10,20,myless<int>)<<endl;return 0;
}
使用C++函数对象的版本实现
template<typename T>
class mygreater
{
public:bool operator()(T a,T b){return a>b;}
};
template<typename T>
class myless
{
public:bool operator()(T a,T b){return a<b;}
};
template<typename T,typename Func>
bool compare(T a,T b,Func fun)//第三个参数为函数对象
{return fun(a,b);
}
int main()
{cout<<compare(10,20,mygreater<int>())<<endl;//第三个参数为函数对象 cout<<compare(10,20,myless<int>())<<endl;return 0;
}
1.通过函数对象调用operator(),可以省略函数的调用开销,比通过函数指针调用函数(不能够inline内联调用)效率高
2.因为函数对象是用类生成的,所以可以添加相关的成员变量,用来记录函数对象使用时的更多信息
(3)STL函数对象的使用
greater:函数对象 从大到小
less:函数对象 从小到大
举例1:priority_queue 模板类原型,第一个参数为类型
,第二个参数为容器
,第三个参数为函数对象
(默认less)
//默认大根堆 从小到大排序 priority_queue<int> queMax;for(int i=0;i<10;i++){queMax.push(rand()%100+1);}while(!queMax.empty()){cout<<queMax.top()<<" ";queMax.pop(); }//小根堆 从大到小排序 using MinHeap = priority_queue<int,vector<int>,greater<int>>;MinHeap queMin; for(int i=0;i<10;i++){queMin.push(rand()%100+1);}while(!queMin.empty()){cout<<queMin.top()<<" ";queMin.pop(); }
举例2:set模板类原型,第一个参数是类型
,第二个参数是函数对象
(默认less),第三个参数是容器空间配置器
set<int,greater<int>> set;//小根堆 从大到小排列 for(int i=0;i<10;i++){set.insert(rand()%100);}for(int v:set){cout<<v<<endl;}
四、泛型算法
泛型算法 = template + 迭代器 + 函数对象
特点一:泛型算法的参数接收的都是迭代器
特点二:泛型算法的参数还可以接收函数对象(C函数指针)
绑定器
bindlst:把二元函数对象的operator()(a,b)的第一个形参绑定起来
bind2nd:把二元函数对象的operator()(a,b)的第二个参数绑定起来
sort
sort(vec.begin(),vec.end());//从小到大排序
sort(vec.begin(),vec.end(),greater<int>());//从大到小排序
find
遍历查找 O(n)
auto it1 = find(vec.begin(),vec.end(),43);if(it1!=vec.end()){cout<<"43存在"<<endl;}
binary_search
有序容器二分查找 O(log2n)
if(binary_search(vec.begin(),vec.end(),21)){cout<<"52存在"<<endl;}
for_each
可以遍历容器的所有元素,可以自行添加合适的函数对象对容器的元素进行过滤
//输出所有偶数 for_each(vec.begin(),vec.end(),[](int val)->void{if(val%2==0){cout<<val<<" ";}});
测试代码:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;int main()
{int arr[] = {12,4,78,9,21,43,56,52,42,31};vector<int> vec(arr,arr+sizeof(arr)/sizeof(arr[0]));for(int v:vec){cout<<v<<" ";} sort(vec.begin(),vec.end());//从小到大排序 for(int v:vec){cout<<v<<" ";} //有序容器二分查找 O(log2n)if(binary_search(vec.begin(),vec.end(),21)){cout<<"52存在"<<endl;}//遍历查找 O(n)auto it1 = find(vec.begin(),vec.end(),43);if(it1!=vec.end()){cout<<"43存在"<<endl;}sort(vec.begin(),vec.end(),greater<int>());//从大到小排序 for(int v:vec){cout<<v<<" ";} //输出所有偶数 for_each(vec.begin(),vec.end(),[](int val)->void{if(val%2==0){cout<<val<<" ";}});}