1.STL容器详解 — 基础篇
容器主要包括三类
**顺序容器:**主要包括vector(向量)、list(链表)、deque(双端队列)
**关联容器:**map(集合)、set(映射)、multimap(多重集合)、multiset(多重映射)
**容器适配器:**让一种已经存在的容器类型采用另一种不同的抽象类型的工作方式实现,包括stack(栈)、queue(队列)、priority_queue(优先级队列)
2.顺序容器
2.1 vector
传统数组存在的缺点是:
- 需要预先根据使用的大小来进行空间的申请,空间过小导致需要重新申请,空间过大则会导致资源的浪费。
- 程序运行时,数组是不允许动态增加删除元素的。
- 数组不允许拷贝和赋值,不能将数组的内容拷贝给其它数组作为初始值,也不能用数组为其它数组数组赋值。
vector的出现解决了上述存在的问题,它实现了动态的空间扩容,提供了多个函数来对vector进行操作:
- 定义和初始化
vector<int> vec1; // 默认初始化,vector为空, size为0,表明容器中没有元素,而且 capacity 也返回 0,意味着还没有分配内存空间。这种初始化方式适用于元素个数未知,需要在程序中动态添加的情况。
vector<int> vec2(10); // 容器的大小为10,默认值为0
vector<vector<int>> vec3; // 二维数组
vector<vector<int>> vec4(n,vector<int>(m,1)); // 二维容器的大小为n*m,默认值为1
vector<int> ilist2(ilist); // 用一个容器初始化另一个容器,容器的类型要是一样的
vector<int> ilist = {1,2,3.0,4,5,6,7}; // 大括号初始化
vector<int> ilist3(ilist.begin()+2,ilist.end()-1); // 迭代器初始化
**注:**在定义变量的时候没有指定初值,则变量被默认初始化,至于默认值到底是什么,取决于变量的类型,int的默认值为0,double的默认值为0.0,char的默认值为空字符…
2.容量函数
vector<int> a(10);
a.size(); // 返回容器中元素的个数
a.capacity(); // 返回实际存储空间的大小
a.empty(); // 容器是否为空
/*
强制vector容器的容量至少为n。注意,如果 比当前vector容器的容量小,则该方法什么也不会做;反之如果 n比当前vector容器的容量大,则vector容器就会扩容。同时reverse预分配出的空间没有被初始化,所以不可访问。reverse是与capacity相关联的。
同时如果reverse之后继续添加元素导致空间扩容,那么扩容的大小就将是2n的大小。
*/
a.reserve(n);
/*
resize是改变容器的大小,且创建对象。resize是与size相关联的,调用函数后容器的size即为n,至于是否影响到capacity则取决于调整后的大小是否大于capacity。小就删除,大就填充。
- 如果n<当前容器的size,则将元素减少到前n个,移除多余的元素(并销毁)。
- 如果n>当前容器的size,则在容器中追加元素,如果val指定了,则追加的元素为val的拷贝,否则,默认初始化。
- 如果n>当前容器容量,内存会自动重新分配。
*/
a.resize(n,t);
a.resize(n);
- 元素位置
a.front(); // 返回第一个元素的值
a.back(); // 返回最后一个元素的值
a.begin(); // 正序第一个元素的位置,指针的形式
a.end(); // 正序第一个元素的位置
a.rbegin(); // 反序的第一个元素的位置,也是正序最后一个元素
a.rend(); // 反序的最后一个元素下一个位置
a.at(idx); //传回索引idx所指的数据,如果idx越界,抛出out_of_range
- 增加和删除
c.pop_back(); // 删除最后一个数据。
c.push_back(elem) // 在尾部加入一个数据。
c.insert(pos,elem); // 在pos位置插入一个elem拷贝,传回新数据位置。
c.insert(pos,n,elem) // 在pos位置插入n个elem数据。无返回值。
c.insert(pos,beg,end);// 在pos位置插入在[beg,end)区间的数据。无返回值。
c.erase(pos); // 删除pos位置的数据,传回下一个数据的位置。
c.erase(beg,end); // 删除[beg,end)区间的数据,传回下一个数据的位置。
5.元素交换
c1.swap(c2); // 将c1和c2元素互换。
swap(c1,c2); // 同上操作。
- 算法
reverse(a.begin(),a.end()); // 元素翻转
sort(a.begin(),a.end()); // 元素排序,默认是升序排列
// 排序默认是升序排列,这里可以使用自定义的比较器
// 升序:sort(begin,end,less<data-type>());
// 降序:sort(begin,end,greater<data-type>()).
// 自定义比较器
sort(a.begin(),a,end(),[&](int x,int y){ return x < y; })
2.2 list
list由双向链表实现而成,元素存放在堆上,空间是按需分配的,用到一个元素的空间就分配一个元素的空间,通过指针来进行数据的访问,不支持随机存储,但是在插入删除上有很高的效率,只需要O(1)的时间复杂度。
- 定义即初始化
list<int> a; // 定义一个int类型的列表a
list<int> a(10); // 定义一个int类型的列表a,并设置初始大小为10
list<int> a(10, 1); // 定义一个int类型的列表a,并设置初始大小为10且初始值都为1
list<int> b(a); // 定义并用列表a初始化列表b
int n[] = { 1, 2, 3, 4, 5 };
list<int> a(n, n + 5); // 将数组n的前5个元素作为列表a的初值
- 容量函数
lst.size(); // 返回容器的大小
lst.resize(); // 改变容器的大小
lst.empty(); // 判断容器是否为空
- 添加删除元素
lst.push_front(const T& x); // 在容器的头部添加元素
lst.push_back(const T& x); // 在容器的尾部添加元素
lst.insert(iterator it, const T& x); // 在it指向的位置插入元素x
lst.insert(iterator it, int n, const T& x); // 在it指向的位置插入n个元素
.insert(iterator it, iterator first, iterator last); // 在it指向的位置插入另一个向量的 [forst,last]区间的数据
lst.pop_front(); // 删除容器的头部元素
lst.pop_back(); // 删除容器的尾部元素
lst.erase(iterator it); // 删除指定位置的元素
lst.erase(iterator first, iterator last); // 删除区间[first,last]的全部元素
lst.clear(); // 清空所有元素
- 访问元素
lst.front(); // 访问容器的第一个元素
lst.back(); // 访问容器的最后一个元素
- 其他函数
多个元素赋值:lst.assign(int nSize, const T& x); // 类似于初始化时用数组进行赋值
交换两个同类型容器的元素:swap(list&, list&); 或 lst.swap(list&);
合并两个列表的元素(默认升序排列):lst.merge();
在任意位置拼接入另一个list:lst.splice(iterator it, list&);
删除容器中相邻的重复元素:lst.unique();
- 迭代器和算法
lst.begin(); // 容器开始位置的迭代器指针
lst.end(); // 容器结尾位置的迭代器指针,指向最后一个元素的下一个位置
lst.cbegin(); // 常量类型的迭代器开始的位置的指针,意思就是不能通过这个指针来修改所指的内容,但还是可以通过其他方式修改的,而且指针也是可以移动的。
lst.cend(); // 常量类型的的末尾迭代器指针
lst.rbegin(); // 反向迭代器指针,指向最后一个元素:
lst.rend(); // 反向迭代器指针,指向第一个元素的前一个元素、
sort(); // 排序函数
reverse(); // 容器翻转
注1: clear的作用是把size设置为0,为capacity不变。
注2: list的迭代器类型为双向迭代器,是不支持 it += i这样的操作的,但是其重载了++/–运算符,这两种操作将迭代器的指针移动到下一个节点\或上一个节点。
2.3 deque
deque是由一段一段定长的连续空间构成的,可以向两端发展,因此其在头部和尾部删除插入元素的时间很复杂很低均为O(1),而在中间插入元素则比较复杂,需要进行大量的元素移动。
- 定义及初始化
deque<int> a; // 定义一个int类型的双端队列。
deque<int> a(10); // 定义一个int类型的双端队列a,并设置初始大小为10,默认值为0。
deque<int> a(10, 1); // 定义一个int类型的双端队列a,并设置初始大小为10且初始值都为1。
deque<int> b(a); // 定义并用双端队列a初始化双端队列。
deque<int> b(a.begin(), a.begin()+3); // 将双端队列a中从第0个到第2个(共3个)作为双端队列b的初始值,这里是一个半开半闭的区间。
int n[] = { 1, 2, 3, 4, 5 };
// 将数组n的前5个元素作为双端队列a的初值
// 说明:当然不包括arr[4]元素,末尾指针都是指结束元素的下一个元素,
// 这个主要是为了和deque.end()指针统一。
deque<int> a(n, n + 5);
deque<int> a(&n[1], &n[4]); // 将n[1]、n[2]、n[3]作为双端队列a的初值。
2.容量函数
deq.size(); // 容器的元素个数
deq.max_size(); // 容器的空间大小
deq.resize(); // 改变容器的大小
deq.empty(); // 判断容器是否为空
deq.shrink_to_fit(); // 减少容器大小到满足元素所占存储空间的大小
3.添加删除元素
deq.push_front(const T& x); // 在队列的头部插入元素
deq.push_back(const T& x); // 在队列的尾部插入元素
deq.insert(iterator it, const T& x); // 在队列的指定位置插入元素
deq.insert(iterator it, int n, const T& x); // 在队列的指定位置插入n个元素
deq.insert(iterator it, iterator first, iterator last); // 在指定位置插入另一个向量的 [forst,last]区间的数据
deq.pop_front(); // 删除头部元素
deq.pop_back(); // 删除尾部元素
deq.erase(iterator it); // 删除指定位置的元素
deq.erase(iterator first, iterator last); // 删除[first,last]区间的元素。
deq.clear(); // 清空所有元素
- 访问函数
deq[1]; // 下标访问并不会检查是否越界
deq.at(1); // 以上两者的区别就是 at 会检查是否越界,是则抛出 out of range 异常
deq.front(); // 访问第一个元素
deq.back(); // 访问最后一个元素
注: 在C++11新标准中,我们可以调用shrink_to_fit来要求deque、vector或string退回不需要的内存空间。可以理解为把capacity的大小变为size的大小。
3.关联容器
关联容器是c++标准模板库中的一套类模板,实现了有序关联数组,可以存放任意类型的元素。关联容器分为有序关联容器和无序关联容器,有序关联容器的底层是红黑树、无序关联容器的底层是哈希表。
3.1 set
set里面每个元素只存有一个key,它支持高效的关键字查询操作。能够根据元素的值自动排序大小,同时每一个值都是唯一的。
- 常用函数
set<T> a;
a.begin(); // 返回指向第一个元素的迭代器
a.end(); // 返回指向超尾的迭代器
a.clear(); // 清空容器
a.empty(); // 判断容器是否为空
a.size(); // 返回当前容器元素个数
a.count(x); // 返回容器中元素x的个数
- 元素插入和删除
a.insert(x); // 插入元素x
a.insert(first,second); // 将区间first到second的元素插入其中,左闭右开
a.erase(x); // 删除建值为x的元素
a.erase(first,second); // 删除first到second区间内的元素(左闭右开)
a.erase(iterator); // 删除迭代器指向的元素
3.其他函数
lower_bound(x); // 返回第一个大于等于键值参数x的元素的迭代器
upper_bound(x); // 返回第一个大于键值参数x的元素的迭代器
set_union(); // 对两个集合取并集
set_intersection(); // 对两个集合取交集
// 代码示例
#include<iostream>
#include<set>
#include<algorithm>
using namespace std;
int main()
{
set<int> A = {1,2,3}, B= {2,4,5},C;
set_union(A.begin(),A.end(),B.begin(),B.end(),
insert_iterator<set<int> >(C,C.begin()));
for(auto it = C.begin();it != C.end();it++)
cout << *it <<" ";
return 0;
}
注: set中的删除操作是不进行任何的错误检查的,比如定位器的是否合法等等,使用的时候需要自己注意。
3.2 map
不同于set保存的是值,map保存的是key-value这种的映射关系,其中key是关键字起到索引的作用,而value是与key相对应的值,支持[]的下标操作。
- 常用函数
m.count(key); // 查询map中key出现的次数
m.find(key); // 返回指向key位置的迭代器,如果没有但会end()
m.size(); // 返回map中的元素的个数
m.clear(); // 清除map中的元素
m.empty(); // 判断容器是否为空
m.lower_bound(key); // 返回第一个键不小于key的元素的迭代器
m.upper_bound(key); // 返回第一个键大于key的元素的迭代器
- 插入删除操作
// 使用insert函数插入pair类型的元素
map<int,string> m;
m.insert(make_pair(1,"abc"));
// 或者使用下标访问符直接插入元素
m[2] = "bcd";
// 直接通过键key来进行删除
m.erase(key);
// 或者通过迭代器来进行删除
m.erase(it);
注: 下标访问如果key值不存在会直接插入对应的key和value,如果value没有显示指定会使用默认值。
3.3 mutimap和mutiset
mutimap和mutiset的使用与上述的set和map类似,唯一不同的是,两者均支持重复值的一个插入操作。
4.容器适配器
目前已有的一些容器比如(vector、list、deque),它们支持的操作已经有很多了,比如常见的插入、删除、访问等。而对于一些有特殊要求的容器,比如栈要求先进后出、后进先出,那这里其实就没有必要重新取写一个新的容器,我们可以在原来容器的基础上做一些封装,改变它的接口。c++中定义了3中容器适配器,包括:栈stack、队列queue和优先级队列priority_queue。
4.1 stack
stack栈是一种先进先出、后进后出的数据结构。
- 常用函数
strck<int> st;
st.empty(); // 判断栈是否为空
st.top(); // 返回栈顶元素
st.size(); // 返回栈中元素的个数
st.swap(st2); // 交换两个容器适配器中的数据
- 元素的插入和删除
st.push(x); // 插入元素x
st.pop(x); // 删除栈顶元素
st.emplace(); // 构造对象并直接插入栈中
4.2 queue
队列是一种先进先出、后进后出的数据结构,queue容器适配器有2个开口,其中一个开口专门用来输入数据、另一个专门用来输出数据。
- 常用函数
queue<int> q;
q.empty(); // 判断队列是否为空
q.front(); // 返回队头元素
q.back(); // 返回队尾元素
q.size(); // 返回栈中元素的个数
q.swap(q2); // 交换两个容器适配器中的数据
- 元素的插入和删除
q.push(x); // 在队尾插入元素x
q.pop(x); // 删除对头的元素
q.emplace(); // 构造对象并直接插入栈中
4.3 priority_queue
与queue队列不同的是,优先级队列中的元素并不是遵循的先进先出的原则,即先进队列的元素并不一定会先出队列,而是优先级最大的元素先出队列,同时优先级队列每次只能方位priority_queue中位于对头的元素。对于优先级的评定,每个priority_queue容器适配器在创建的时候,都会制定一种排序规则,根据此规则,该容器适配器中存储的元素就有了优先级高低之分。
// 基本的定义
template <typename T,
typename Container=std::vector<T>,
typename Compare=std::less<T> >
class priority_queue{
//......
}
// 1.typename T:指定存储元素的具体类型
// 2.typename Container:指定priority_queue底层使用的基础容器,默认使用的是vector
// 3.typaname compare:指定容器中元素优先级所遵循的规则,默认的是std::less<T>。
- 常用函数
priority_queue<int> pq;
pq.empty(); // 判断优先级队列是否为空
pq.top(); // 返回priority_queue中第一个元素的引用形式
pq.size(); // 返回优先级中存储的元素的个数
pq.swap(q2); // 交换两个容器适配器中的数据
- 元素的插入和删除
q.push(x); // 将x插入到优先级队列中的适当位置
q.pop(x); // 移除优先级队列中的第一个元素
q.emplace(); // 根据特定的排序规则在优先级队列中插入元素
STL容器的使用
vector的初始化操作
深入理解vector的初始化
c++ vector中resize和reserve的区别
clear函数的使用
list使用详解
使用shrink_to_fit要求容器回退不需要的内存空间
关联容器set和map
c++容器适配器总结