文章目录
- 容器
- 一、vector容器
- 二、deque容器
- 三、List容器
- 四、set&multiset容器
- 五、map&multimap容器
- 六、Queue容器
- 七、优先级队列priority_queue容器
- 八、stack容器
- [容器部分总结:]
STL分为3个类分别是算法(algorithm),容器(container)和迭代器(Iterator)
容器和算法通过迭代器进行无缝衔接
在下面是各类容器的用法由于每个容器的很多API都差不多,从List容器开始会列举一些自己特有的API,就不全写了。
容器
一、vector容器
其实vector容器就是一个动态数组(和在存储数据的方面和普通数组的区别是vector容器会因为元素的插入而调整空间大小)
①容器的大小和存储的元素的数量
vector<int>v;
v.capacity(); //容器的大小
v.size(); //存储元素的个数
对于capacity()和size()来说一般capacity()会比size()大,这是因为vector的性质(动态数组)其实vector在插入元素后是通过重新开辟一个空间把数据进行复制来实现动态的,而为了不要频繁地重新开辟空间在有时候插入一个数后重新分配空间时系统会给当前vector容器的容量多分配一点保证下次有数据插入时不会重新开辟一个空间。
②vector的几种构造函数
1.vector<int>v;
2.vector<int>v(10);//分配空间,初值全部为0
3.vector<int>v(10,elem);//分配空间并且全部赋初值
4.vector<int>v(v2.begin(),v2.end());//这个是通过迭代器的方式对其他vector容器的数进行复制(迭代器是可以移动的可以有选择的进行复制左闭右开(左边可以取到右边刚好不取到))
③vector的赋值
1.v[0]=1,v[1]=2;//直接利用下标来赋值
2.assign(sum,elem);//改变值并且改变元素的个数
3.assign(v1.begin(),v1.end());//利用迭代器来赋值,同样可以利用数组中的指针直接来赋值
4.v1=v2;//调用赋值运算符=把整个数据全部复制过来
④vector调整容器的大小
1.v.resize(sum);//如果sum是大于原来的容量的时候扩大容量后补0,如果小于原来的容量就保留sum个元素,后面的元素全部删除。
2.v.resize(sum,elem);//这个resize方法的重载只对扩大容量有意义,后面多出来的容量追加elem(和1.对比的话就把0变成elem)。
⑤vector删除元素
v.pop_back();//删除尾部的元素,只改变元素的个数,不改变容器的容量(这主要还是因为取决于vector的性质)
⑥vector插入元素
1.v.insert(pos,elem);//pos为迭代器,在pos位置插入elem
2.v.insert(pos.sum,elem);//在pos位置插入sum个elem
3.v.insert(pos.v2.begin(),v2.end());//在pos位置,把v2的全部数据都添加过来
同样可以使用rbegin()和rend()进行操作,rbegin()其实就是最后一个元素的位置,而rend()则是第一个元素的再前面一个元素。
⑦vector数据的存取
1.利用下标v[1],v[2];
2.利用at来存取:v.at(0),v.at(1);//同样可以利用这个改值
3.利用v.front(),v.back();//读取头头的元素和尾部的元素
⑧vector数据的删除
1.把全部元素全删除v.clear();
2.删除单个元素:v.erase(pos);//pos是位置也是迭代器
3.删除多个元素:v.erase(pos1,pos2);//删除pos1到pos2的元素同样也是半闭半开
二、deque容器
其实和vector是差不多的动态数组只是最大的不同则是deque可以实现头部的插入是一个双向数组,而vector是一个单向的数组。如下图👇
在大多数方法上vector和deque其实是一样的,对于特点也是类似:在头插尾插时效率很高,但是在往中间插元素时是比较麻烦的(会伴随着很多元素的移动)
注意:在deque进行头部插入的时候是重新分配空间,由多个数组一起组成deque。
①deque的构造函数
无参构造函数
deque <int> deqInt; //存放int的deque容器。
有参构函数
方式1:deque(beg,end); //构造函数将[beg, end)区间中的元素拷贝给本身。
方式2:deque(n,elem); //构造函数将n个elem拷贝给本身。
方式3:deque(const deque &deq); //拷贝构造函数。
②deque头部和末尾的添加移除操作
deque.push_back(element); //容器尾部添加一个数据
deque.push_front(element); //容器头部插入一个数据
deque.pop_back(); //删除容器最后一个数据
deque.pop_front(); //删除容器第一个数据
③deque的数据存取
第一 使用下标操作 deqIntA[0] = 100;
第二 使用at 方法 如: deqIntA.at(2) = 100;//返回引用
第三 接口返回的引用 deqIntA.front() 和 deqIntA.back()//同样可以利用这个改值deqInt.front()=999;
利用下标访问时要注意越界问题。
④deque的迭代器
deque.begin(); //返回容器中第一个元素的迭代器。
deque.end(); //返回容器中最后一个元素之后的迭代器。
deque.rbegin(); //返回容器中倒数第一个元素的迭代器。
deque.rend(); //返回容器中倒数最后一个元素之后的迭代器。
deque.cbegin(); //返回容器中第一个元素的常量迭代器。
deque.cend(); //返回容器中最后一个元素之后的常量迭代器。
deque<int> deqIntA;
deqIntA.push_back(1);
例子:
//普通迭代器
for(deque<int>::iterator it = deqIntA.begin();it!=deqIntA.end(); ++it){
(*it)++;
cout<<it<<" ";
}
*//常量迭代器
deque<int>::const_iterator cit = deqIntA.cbegin();
for( ; cit!=deqIntA.cend(); cit++){
cout<<cit<<" ";
}
//逆转的迭代器
for(deque<int、>::reverse_iterator rit=deqIntA.rbegin(); rit!=deqIntA.rend(); ++rit){
cout<<rit<<" ";
}
想要用什么类型的迭代器就要定义什么样的类型:(常量迭代器)const_iterator,(逆转迭代器)reverse_iterator,(普通迭代器)iterator…
⑤deque的插入和删除
插入:
deque.insert(pos,num);//在pos位置插入num
deque.insert(pos,2,888);//在pos位置插入2个888
删除:
deque.clear(); //移除容器的所有数据
deque.erase(beg,end); //删除[beg,end)区间的数据,返回下一个数据的位置。
deque.erase(pos); //删除pos位置的数据,返回下一个数据的位置。
三、List容器
list就是一个双向链表容器,可高效地进行插入删除元素。
①List 特点:
1.list不可以随机存取元素,所以不支持at.(position)函数与[]操作符。可以对其迭代器执行++,但是不能这样操作迭代器:it+3(这是因为链表的特点,链表中的元素不是存储在相邻的内存中,所以容器的容量也是用多少分多少)。
2.使用时包含 #include <list>
②list对象的构造函数
和前面容器的是一样的,不一个一个列举了。
③list头尾的添加移除操作
和前面和前面容器的是一样的,值得注意的就还是双向链表的性质,可头插尾插也可中间插。
llist.push_back(elem); //在容器尾部加入一个元素
list.pop_back(); //删除容器中最后一个元素
list.push_front(elem); //在容器开头插入一个元素
list.pop_front(); //从容器开头移除第一个元素
④list数据的存取
list.front();
list.back();
⑤list的大小赋值和插入
和之前的容器的API一样
⑥list的删除操作
值得注意的是list.remove(elem);这是前面两个容器没有的
list.remove(elem); //删除容器中所有与elem值匹配的元素。
⑦list的反序排列
list.reverse(); //反转链表,比如list包含1, 2, 3, 4, 5五个元素,运行此方法后,list就包含5, 4, 3, 2, 1元素。
四、set&multiset容器
set和multiset是个集合容器,set所包含的元素是唯一的,是采用红黑树变体的数据结构实现,查找和删除的效率非常高。
Set 和 multiset 特点
1.set中元素插入过程是按排序规则插入,所以不能指定插入位置。
2. set不可以直接存取元素。(不可以使用at.(pos)与[]操作符)。
3. multiset与set的区别:set支持唯一键值,每个元素值只能出现一次;而multiset中同一值可以出现多次。
4. 不可以直接修改set或multiset容器中的元素值,因为该类容器是自动排序的。如果希望修改一个元素值,必须先删除原有的元素,再插入新的元素
5. 头文件 #include <set>
set的构造函数
set<string> setString; //一个存放string的set容器。
multiset<int> mulsetInt; //一个存放int的multi set容器。
其实例如set<int>setInt的完整的形式是set<int,less<int>>setInt,less<int>叫做仿函数也叫函数对象,在set的默认仿函数是less(从小到大排)
仿函数:
仿函数概念
1.尽管函数指针被广泛用于实现函数回调,但C++还提供了一个重要的实现回调函数的方法,那就是函数对象。
2.functor,翻译成函数对象,伪函数,它是是重载了“()”操作符的普通类对象。从语法上讲,它与普通函数行为类似。
3.functional头文件中包含的 greater<>与less<>就是函数对象。
见代码:
#include <set>
#include <iostream>
#include <functional>
#include <algorithm>
using namespace std;
class student {
public:
student(int age) {
this->age = age;
}
bool operator < (const student &right) const{
return this->age > right.age;//为了测试
//return this->age<right.age;
}
int getAge() const { return age; }
private:
int age;
string name;
};
class FunStudent{
public:
bool operator () (const student &left, const student &right){//仿函数
cout<<"调用了 FunStudent ."<<endl;
ret = left < right;
return ret;
}
public:
int ret;
};
int main(void) {
//less 函数对象实现比较,为排序提供依据
//less 和greater 都是函数对象,有叫仿函数
//set<int,less<int>> set1;
set<int,greater<int>> set1;
for(int i=5; i>0; i--){
set1.insert(i);
}
//less<student>
set<student, FunStudent> setStu; //等同于 set<student,less<student>>
student lixiaohua(18);
student wangdachui(19);
//函数对象(仿函数)可以像函数一样直接调用
FunStudent funStu;
funStu(lixiaohua, wangdachui);
cout<<"比较结果:"<<funStu.ret<<endl;
setStu.insert(lixiaohua);
setStu.insert(wangdachui);
setStu.emplace(20);
for (set<student, FunStudent>::iterator it = setStu.begin(); it != setStu.end(); it++) {
cout << it->getAge() ;
cout << " ";
}
system("pause");
return 0;
}
运行结果:
总结:
本质上来说其实容器中的比较是通过函数对象来实现的所以如果要放自己定义的类的话自己要定义一个重载函数来比较(利用返回值来排序)。
①set的插入和pair的用法
pair表示一个对组,它将两个值视为一个单元,把两个值捆绑在一起。pair<T1,T2>用来存放的两个值的类型,可以不一样,也可以一样,如T1为int,T2为float。T1,T2也可以是自定义类。
- pair.first是pair里面的第一个值,是T1类型。
- pair.second是pair里面的第二个值,是T2类型。
set<int> setInt;
for(int i=5; i>0; i--){
pair<set<int>::iterator, bool> ret = setInt.insert(i);
if(ret.second){
cout<<"插入 "<<i<<" 成功!"<<endl;
}else {
cout<<"插入 "<<i<<" 失败!"<<endl;
}
}
cout<<"--------再插入一个5:【5已经存在】看是否还能成功----------"<<endl;
pair<set<int>::iterator, bool> ret =setInt.insert(5);
if(ret.second){
cout<<"插入 "<<5<<" 成功!"<<endl;
}else {
cout<<"插入 "<<5<<" 失败!"<<endl;
}
运行结果:
②set/multiset的查找
1.set.find(elem); //查找elem元素,返回指向elem元素的迭代器。
2.set.count(elem); //返回容器中值为elem的元素个数。对set来说,要么是0,要么是1。对multiset来说,值可能大于1。
3.set.lower_bound(elem); //返回第一个>=elem元素的迭代器。
4.set.upper_bound(elem); // 返回第一个>elem元素的迭代器。
5.set.equal_range(elem); //返回容器中与elem相等的上下限的两个迭代器。上限是闭区间,下限是开区间,如[beg,end)。以上函数返回两个迭代器,而这两个迭代器被封装在pair中。
总结:对于set来说要注意的就是唯一性还有仿函数的概念和用法,还要注意的是对于改变元素是要先删除再添加,不能直接改值,而multiset和set的唯一区别就是可以有多个相同的值。
五、map&multimap容器
map是标准的关联式容器,一个map里存储的元素是一个键值对序列,叫做(key,value)键值对。它提供基于key快速检索数据的能力。
1.map中key值是唯一的。集合中的元素按一定的顺序排列。元素插入过程是按排序规则插入,所以不能指定插入位置。
2.map底层的具体实现是采用红黑树变体的平衡二叉树的数据结构。在插入操作、删除和检索操作上比vector快很多。
3.map可以直接存取key所对应的value,支持[]操作符,如map[key]=value。
4.#include <map>
multimap与map的区别:
map支持唯一键值,每个键只能出现一次;而multimap中相同键可以出现多次。multimap不支持[]操作符
map[1]返回的是键值为1对应的value
①map的构造函数
1.map<类型1,类型2>maps;//类型1为键值一般为int,类型2为value
2.map<int,string>maps(map2.begin(),map2.end());
②map的插入与迭代器
map.insert(…); //往容器插入元素,返回pair<iterator,bool>
map中插入元素的四种方式:
假设 map<int, string> mapStu;
方式一、通过pair的方式插入对象mapStu.insert( pair<int,string>(1,“张三”) );
方式二、通过pair的方式插入对象mapStu.inset(make_pair(2, “李四”));
方式三、通过value_type的方式插入对象mapStu.insert( map<int,string>::value_type(3,“王五”) );
方式四、通过数组的方式插入值mapStu[4] = “赵六”;mapStu[5] = “小七";
注意:
- 前三种方法,采用的是insert()方法,该方法返回值为pair<iterator,bool>
- 第四种方法非常直观,但碰到相同的键时会进行覆盖操作。比如插入key 为4的键值时,先在mapStu中查找主键为4的项,若不存在,则将一个键为4,值为默认初始化值的对组插入到mapStu中,然后再将值修改成“赵六”。若发现已存在4这个键,则修改这个键对应的value。
- string strName = mapStu[8]; //取值操作或插入操作
- 只有当mapStu存在8这个键时才是正确的取操作,否则会自动插入一个实例,键为8,值为默认构造时的初始化值。
#include <map>
#include <iostream>
#include <functional>
#include <algorithm>
#include <string>
using namespace std;
int main(void) {
map<int, string> mapStu;
//方式一 构造一个pair ,然后插入
pair<map<int, string>::iterator,bool> ret = mapStu.insert(pair<int, string>(1, "张三"));
if(ret.second==true){
cout<<"插入成功! value: "<<(*(ret.first)).second<<endl;
}else {
cout<<"插入失败!"<<endl;
}
//如果键存在,则插入会失败
ret = mapStu.insert(pair<int, string>(1, "小张三"));
if(ret.second==true){
cout<<"插入成功! value: "<<(*(ret.first)).second<<endl;
}else {
cout<<"插入小张三失败! "<<endl;
}
//方式二 使用make_pair
mapStu.insert(make_pair(2, "李四"));
//方式三 使用value_type, 相当于pair<int, sting>
mapStu.insert(map<int, string>::value_type(3, "王五"));
//方式四 直接使用[]重载,如果键值对已经存在,则覆盖原值
mapStu[4]="赵六";
mapStu[4] = "小赵六";
mapStu[5] = mapStu[6];//不存在键值为6的元素,所以创建一个mapStu[6],对应value为空串
mapStu[7] = mapStu[4];
for(map<int, string>::iterator it=mapStu.begin(); it!=mapStu.end(); it++){
cout<<"key: "<<(*it).first << " value: "<<(*it).second <<endl;
}
system("pause");
return 0;
}
③ map/multimap 排序
map<T1,T2,less<T1>> mapA; //该容器是按键的升序方式排列元素。未指定函数对象,默认采用less<T1>函数对象。(和set和multiset一样)
map<T1,T2,greater<T1>> mapB; //该容器是按键的降序方式排列元素。
less<T1>与greater<T1> 可以替换成其它的函数对象functor。
可编写自定义函数对象以进行自定义类型的比较,使用方法与set构造时所用的函数对象一样。
④ map对象的拷贝构造与赋值
map(const map &mp); //拷贝构造函数
map& operator=(const map &mp); //重载等号操作符
map.swap(mp); //交换两个集合容器
代码:
map<int,string>mapStu1;
map<int,string>mapStu2;
mapStu1.insert(pair<int,string>(1,"小李"));
mapStu1.insert(map<int,string>::value_type(2,"小华"));
mapStu2.insert(pair<int,string>(2,"花花"));
mapStu2.insert(map<int,string>::value_type(4,"小张"));
cout<<"mapStu1:"<<endl;
map<int,string>::iterator it=mapStu1.begin();
for(it;it!=mapStu1.end();it++){
cout<<"key:"<<it->first<<" value:"<<it->second<<endl;
}
cout<<"mapStu2:"<<endl;
map<int,string>::iterator it2=mapStu2.begin();
for(it2;it2!=mapStu2.end();it2++){
cout<<"key:"<<it2->first<<" value:"<<it2->second<<endl;
}
cout<<"**************************swap后***********************"<<endl;
mapStu1.swap(mapStu2);
cout<<"mapStu1:"<<endl;
it=mapStu1.begin();//重新指向第一个元素的迭代器
for(it;it!=mapStu1.end();it++){
cout<<"key:"<<it->first<<" value:"<<it->second<<endl;
}
cout<<"mapStu2:"<<endl;
it2=mapStu2.begin();//重新指向第一个元素的迭代器
for(it2;it2!=mapStu2.end();it2++){
cout<<"key:"<<it2->first<<" value:"<<it2->second<<endl;
}
运行结果:
⑤ map的大小
map.size(); //返回容器中元素的数目
map.empty();//判断容器是否为空
⑥ map的删除
map.clear(); //删除所有元素
map.erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器。map.erase(beg,end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。 map.erase(key); //删除容器中key为key的对组,返回删除的对组个数
Map.erase(key_type *first, key_type *last) //删除数组指定的半闭半开的区间中特定的key对应的所有队组
map<int, string> mapA;
mapA.insert(pair<int,string>(2, "李四"));
mapA.insert(pair<int,string>(1, "张三"));
mapA.insert(pair<int,string>(3, "王五"));
mapA.insert(pair<int,string>(4, "赵六"));
//删除区间内的元素,迭代器指示区间(半闭半开)
map<int,string>::iterator itBegin=mapA.begin();
++ itBegin;
map<int,string>::iterator itEnd=mapA.end();
mapA.erase(itBegin,itEnd); //此时容器mapA仅仅包含{1,"张三"}一个元素。
mapA.insert(pair<int,string>(2, "李四"));
mapA.insert(pair<int,string>(3, "王五"));
mapA.insert(pair<int,string>(4, "赵六"));
//删除容器中的第一个元素,使用迭代器指示位置
mapA.erase(mapA.begin()); //mapA包含{2,"李四"}{3,"王五"}{4,"赵六"}三个元素
//删除容器中key为4的元素
mapA.erase(4);
//删除mapA的所有元素
mapA.clear(); //容器为空
⑦ map/multimap的查找
1.map.find(key); 查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回map.end();
2.map.count(key); //返回容器中键值为key的对组个数。对map来说,要么是0,要么是1;对multimap来说,值>=0。
3. map.lower_bound(keyElem); //返回第一个key>=keyElem元素的迭代器。
4.map.upper_bound(keyElem); // 返回第一个key>keyElem元素的迭代器。 5.map.equal_range(keyElem); //返回容器中key与keyElem相等的上下限的两个迭代器。上限是闭区间,下限是开区间,如[beg,end)。
六、Queue容器
queue是队列容器,是一种“先进先出”的容器。
无论是插入还是删除都要遵从队列的特点:先进先出,所以只能在尾部插入头部删除。
①默认情况下queue是利用deque容器实现的一种容器。
②它只允许在队列的前端(front)进行删除操作,而在队列的后端(back)进行插入操作
③#include \<queue\>
① queue 对象的带参构造
queue<int, list<int>> queueList; //内部使用list 来存储队列元素的queue 容器.
错误: queue<int, vector<int>> queueList; //内部不能使用vector来存储队列元素(是因为vector容器中没有queue中的方法所以不行)
② queue的push()与pop()方法
queue.push(elem); //往队尾添加元素
queue.pop(); //从队头处移除队首元素
七、优先级队列priority_queue容器
优先队列: 它的入队顺序没有变化,但是出队的顺序是根据优先级的高低来决定的。优先级高的优先出队。
①最大值优先级队列、最小值优先级队列
②用来开发一些特殊的应用
③#include <queue>
#include <queue>
#include <iostream>
#include <list>
#include <vector>
#include <deque>
#include <set>
using namespace std;
int main(void) {
//priority_queue<int> pqA;//默认情况下是值越大,优先级越大
//priority_queue<int, vector<int>, greater<int>> pqA; //使用 vector 值越小,优先级越大
priority_queue<int, deque<int>, greater<int>> pqA; //使用deque 值越小,优先级越大
//priority_queue<int, list<int>, greater<int>> pqA; //不可以使用list,不兼容
pqA.push(1);
pqA.push(2);
pqA.push(3);
pqA.push(3);
pqA.push(4);
pqA.push(5);
pqA.push(3);
while(!pqA.empty()){
cout<<pqA.top()<<" ";//读取队首的元素,但元素不出列
pqA.pop(); //出队列
}
cout<<endl;
system("pause");
return 0;
}
八、stack容器
stack是堆栈容器,是一种“先进后出”的容器。
和队列queue一样无论是要插入还是删除都得遵守该容器的特点先进后出。
①stack是基于deque容器而实现的容器。
②#include <stack>
① stack的push()与pop()方法
stack.push(elem); //往栈头添加元素
stack.pop(); //从栈头移除第一个元素
stack<int> stkInt;
stkInt.push(1);
stkInt.push(2);
stkInt.push(3);
cout<<stkInt.top()<<" ";
stkInt.pop();
cout<<stkInt.top()<<" ";
stkInt.pop();
cout<<stkInt.top()<<" ";
system("pause");
[容器部分总结:]
对于STL容器部分其实在代码设计方面我感觉还是真的很好的,很多容器的构造函数,插入,删除…等等代码都是一样的有通用性,让我们不用去背很多的语法,我们在使用容器的时候就是要去注意该容器的特点,这样会更好的去运用它,还有就是在用的时候要去注意每个方法的返回类型这样我们才知道怎么去接。