C++相较于其他语言一个显著的优点为,它封装了一些stl(standared template library,标准模板库)容器,使用起来就不需要自己动手实现了,就可以节省更多时间用于思考代码的思路以及代码主要部分。本篇文章介绍常用的stl容器极其操作。
目录
1、vector(变长数组)
要使用vector容器,首先要包含头文件<vector>,其次,要声明一个vector,应该使用==vector<数据类型>==的方式,此外,还有一些初始化操作:
vector<int> a //初始为空
vector<int> a(10) //初始有10个位置
vector<int> a(10, 3) //初始有10个位置,且都被初始化为了3
vector<int> a[10] //声明一个vector数组,每个数组元素都是vector<int>
常用vector操作:
- size():返回vector元素数量
- empty():返回vector是否为空(空为1,不空为0)
- clear():清空vector
- front():返回首元素
- back():返回尾元素
- push_back(a):在尾插入a
- pop_back():弹出尾元素
- begin():迭代器,可以看作指向第一个元素的指针
- end() :迭代器,可以看作指向最后一个元素的后一个位置的指针
- [a]:支持随机寻址,返回下标为a的元素(下标从0开始)
遍历vector有三种常用操作
- 用下标
- 用迭代器
- 用范围for语句
演示一下,执行以下代码:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> a;
for (int i = 1; i <= 10; i ++ ) a.push_back(i);
//用下标
for (int i = 0; i < 10; i ++ ) cout << a[i] << ' ';
cout << endl;
//用迭代器
for (auto i = a.begin(); i != a.end(); i ++ ) cout << *i << ' ';
cout << endl;
//用范围for语句
for (auto x : a) cout << x << ' ';
cout << endl;
return 0;
}
得到:
还有必要说一下vector变长的倍增思想。首先要说一下计算机分配内存的时间,每次分配内存和大小关系不大,和分配次数有很大关系。比如一次分配1000个位置,和1000次分配1个位置,后者要慢很多很多。所以vector在变长的时候,要尽量减少分配次数。在gcc环境下,假设当前有n个位置,如果用的位置超过了n个,那就申请2n个位置,并把前n个位置上的元素复制到新分配的前n个里。平均下来,vector插入一个数的时间复杂度是O(1)的。且这样分配次数为logn,有效减少申请次数。用程序展示一下:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> a;
while (1)
{
int x;
cin >> x;
a.push_back(x);
cout << "当前元素数量" << a.size() << endl;
cout << "当前分配空间" << a.capacity() << endl;
}
}
2、string(字符串)
C++对字符串进行了封装,让字符串的操作更加简单。要使用string类,要包含<string>头文件。初始化string可以直接使用赋值运算符。
常用string操作:
- size():返回字符串长度
- empty():返回string是否为空(空为1,不空为0)
- clear():清空string
- length():和size()完全一样
- substr():截取子串
- c_str():返回字符串地址(首字符地址)
substr()的用法:substr接受两个参数,substr(a, b),a表示截取子串的其实下标,b表示截取长度。如果越界,则截取到最后停止。此外,substr还可以只接受一个参数a,那么就从下标a开始截取到最后。
一些额外的操作:string还可以使用+=操作。
举例:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string a = "abcdef";
string b = a;
string c = a.substr(1, 3);
cout << c << endl;
cout << a.substr(2) << endl;
cout << endl;
b += "1234";
cout << b << endl;
b += '*';
cout << b << endl;
return 0;
}
输出:
3、queue(队列)
使用queue容器,要包含<queue>头函数。就是一个已经封装好的先进先出的队列。
常用queue操作:
- size():返回队列长度
- empty():返回队列是否为空(空为1,不空为0)
- push(a):在队尾插入a
- front():返回队头元素
- back():返回队尾元素
- pop():弹出队头元素
注意,queue没有clear操作。但是也可以用重新构造的方法清空队列:
#include <iostream>
#include <queue>
using namespace std;
int main()
{
queue<int> q;
q.push(1), q.push(2);
cout << q.front() << endl;
//重新构造来清空队列
q = queue<int>();
if (q.empty()) cout << "队列为空" << endl;
return 0;
}
4、priority_queue(优先队列)
优先队列其实就是一个堆。要使用priority_queue,需要包含头文件<queue>。
常用priority_queue操作:
- size():返回堆元素数量
- empty():返回堆是否为空(空为1,不空为0)
- push(a):向堆中加入一个元素a
- top():返回堆顶元素
- pop():弹出堆顶元素
默认情况下,priority_queue构造的是一个大根堆。但是有一个固定的语法构造小根堆,要包含<vector>头文件。这里以int类型举个例子:
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main()
{
//默认大根堆
priority_queue<int> q1;
for (int i = 1; i <= 10; i ++ ) q1.push(i);
//构造小根堆
priority_queue<int, vector<int>, greater<int>> q2;
for (int i = 1; i <= 10; i ++ ) q2.push(i);
cout << q1.top() << endl << q2.top() << endl;
return 0;
}
要改变存储的数据类型,只要改变3个int即可。注意,priority_queue也没有clear操作。
5、stack(栈)
使用stack容器,要包含头文件<stack>。就是一个封装好的栈。
常用stack操作:
- size():返回栈元素数量
- empty():返回栈是否为空(空为1,不空为0)
- push(a):在栈顶加入a
- top():返回栈顶元素
- pop():弹出栈顶元素
注意,stack没有clear操作。另外,栈的所有操作都是O(1)的。
6、deque(双端队列)
要使用deque容器,要包含头文件<deque>。deque是一个功能很强的容器,可以看作是升级的vector。但是缺点就是它的操作太慢了。
常用deque操作:
- size():返回deque元素数量
- empty():返回deque是否为空(空为1,不空为0)
- clear():清空deque
- front():返回首元素
- back():返回尾元素
- push_back(a):在结尾加入元素a
- pop_back():弹出尾元素
- push_front(a):在开头加入元素a
- pop_front():弹出首元素
- begin() :迭代器
- end():迭代器
同时,deque支持随机寻址操作,即取下标。
7、set(集合)以及multiset(多重集合)
要使用set和multiset容器,要包含头文件<set>。它们是基于红黑树(一种特殊的平衡二叉树)实现的动态维护有序序列的容器。区别就是,set不能存储重复值,但是multiset可以存储重复值。
常用set和multiset操作:
- size():返回集合元素数量
- empty():返回集合是否为空(空为1,不空为0)
- clear():清空集合
- insert(a):插入一个数a,O(logn)
- find(a):返回a在集合中的个数。如果是set,则只可能是0、1,如果是multiset,那么可以是其他
- erase(a):如果a是一个值,那么删除所有a;如果a是迭代器,那么删除迭代器位置元素,O(k + logn),其中k是待删除元素个数
- lower_bound(a):返回 大于等于 a的最小元素的迭代器。如果不存在,返回end()
- upper_bound(a):返回 大于 a的最小元素的迭代器。如果不存在返回end()
- count(a):返回元素 i 在集合中出现的次数。由于set容器仅包含唯一元素,因此只能返回1或0;multiset中可返回多个。
8、map(映射)和multimap(多重映射)
要使用map和multimap容器,要包含头文件<map>。它们是基于红黑树(一种特殊的平衡二叉树)实现的动态维护有序序列的容器。区别就是,map不能存储重复值,但是multimap可以存储重复值。
它们和set的区别就是,这俩存储的是一对关联值pair。
常用map和multimap操作:
- size():返回映射元素数量
- empty():返回映射是否为空(空为1,不空为0)
- clear():清空映射
- insert({a, b}):插入一个pair{a, b}
- erase():输入pair或迭代器,效果同set
- lower_bound(a):返回 大于等于 a的最小元素的迭代器,比较时依据类型1。如果不存在,返回end()
- upper_bound(a):返回 大于 a的最小元素的迭代器,比较时依据类型1。如果不存在返回end()
map和multimap还支持一个很强的操作:像使用数组一样使用map。意思是:a[类型一元素] = 类型二元素。
举个例子:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
map<string, int> a;
a["abc"] = 1;
a["bcd"] = 2;
a["cde"] = 3;
cout << (*a.upper_bound("a")).second << endl;
cout << (*a.upper_bound("b")).second << endl;
return 0;
}
10、unordered_set、unordered_multiset、unordered_map以及unordered_multimap
这一坨容器是基于哈希表实现的。要使用unordered_set和unordered_multiset容器,要包含头文件<unordered_set>。要使用unordered_map和unordered_multimap容器,要包含头文件<unordered_map>。它们和上面四个容器类似,不同之处在于这几个是unordered的,也即无序的。所以,它们不支持lower_bound和upper_bound操作。但是好处就是,因为不需要维护顺序,所以这几个的速度会比上面那几个快很多。各有利弊。
11、bitset(压位存储)
使用的话,要包含头文件<bitset>。用处就是可以把每个数据压缩到只有一位的大小。
最常用的用处就是,当我们需要一个10000 * 10000的bool矩阵时,如果存成bool类型,那就是108个字节(C++中bool类型占一个字节),直接爆内存了。但是如果使用压位存储,那就只需要108位了,减少了内存消耗,并且能满足需求。
#include <iostream>
#include <bitset>
using namespace std;
int main()
{
//声明时,括号内是位数
bool a[1000][1000];
bitset<1000> b[1000];
cout << sizeof a << endl << sizeof b << endl;
return 0;
}
可以看到内存明显小了很多。
常用bitset操作:
- count():返回1的个数
- any():返回 是否至少有一个1
- none():返回 是否全为0
- set():将所有位置为1,如果是set(k, v),则将第k位置为1
- reset():将所有位置为0
- flip():将所有位取反,如果是flip(k),则将第k位取反
此外,bitset还支持~(非)、&(与)、|(或)、^(异或)、>>(右移位)和<<(左移位)操作。