(2020.12.08 Tues)
STL容器允许重复利用已有的实现构造自己特定类型下的数据结构,通过设置一些模板类,这些模板的参数允许用户指定容器中元素的数据类型,从而提高编程效率。
(容器部分的内容可以对比python中的list, tuple, dict, set等数据结构。)
容器主要由头文件,
数据结构 | 描述 | 实现头文件 |
---|---|---|
向量(vector) | 连续存储的元素 | |
列表(list) | 由节点组成的双向链表,每个结点包含着一个元素 | |
双队列(deque) | 连续存储的指向不同元素的指针所组成的数组 | |
集合(set) | 由节点组成的红黑树,每个结点都包含着一个元素,节点之间以某种作用于元素对的谓词排列,没有两个不同的元素能够拥有相同的次序 | |
多重集合(multiset) | 允许存在两个次序相等的元素的集合 | |
栈(stack) | 后进先出的值的排列 | |
队列(queue) | 先进先出的值的排列 | |
优先队列(priority queue) | 元素的次序是由作用于所存储的值对上的某种谓词决定的一种队列 | |
映射(map) | 由{键,值}对组成的集合,以某种作用于键对上的谓词排列 | |
多重映射(multimap) | 允许键对有相等的次序的映射 |
(2020.12.09 Wed)
STL定义的通用容器分为顺序容器(sequence container),关联容器(associative container)和容器适配器。
顺序容器
顺序容器是一种各元素间有顺序关系的线性表,是线性结构的可序群集,其中的每一个元素有固定的位置,除非用删除或插入的操作改变变量的位置。顺序容器具有插入速度快但查找操作相对较慢的特征。C++ STL(标准模板库)提供3中顺序容器:vector、list和deque。vector和deque类时以数组为基础,list类是以双向链表为基础。
顺序容器中各种类型的比较
vector是动态顺序容器,有连续内存地址的数据结构。相比于数组,vector会消耗更多的内存以有效地动态增长。而相比于其他顺序容器(deques, list),vector能更快的索引元素(如同数组一样),而且能相对高效的在尾部插入和删除元素。在其他位置删除和插入元素,效率没有这些容器高。
list是STL实现的双向链表,相比vector的连续线性空间,list复杂太多,它允许快速的插入和删除,但是随机访问却比较慢。它的数据有若干个节点构成,每个节点包括一个信息块、一个前驱指针和一个后驱指针,可以向前或向后进行访问,但不能随机访问。好处是每次插入或删除会配置或者释放一个元素空间,对空间的运用绝对精准。
deque(double ended queues双向队列)和vector相似,但它允许在容器头部快速插入和删除,如同在尾部一样。
向量vector
使用vector的时候,需包含头文件:
向量声明和初始化
语句 | 作用 |
---|---|
vector<元素类型>向量对象名; | 创建一个没有任何元素的空向量对象 |
vector<元素类型>向量对象名(size); | 创建一个大小为size的向量对象 |
vector<元素类型>向量对象名(n,初始值); | 创建一个大小为n的向量对象,并初始化 |
vector<元素类型>向量对象名(begin, end); | 创建一个向量对象,初始化该对象(begin, end)中的元素 |
vector a;
vector a(10); //初始大小为10的向量
vector a(10,1);//初始大小为10,初始值都是1的向量
vector b(a); //用向量a初始化向量b
vector b(a.begin(), a.begin()+3); //将a向量中第0到第2个作为向量b的初始值
//可以用数组初始化向量
int n[] = {1,2,3,4,5};
vector a(n,n+5); //n的前5个元素作为a的初值
vector a(&n[1], &n[4]); //n[1]到n[4]范围内的元素作为向量a的初值
元素的输入和访问
#include
#include
using namespace std;
int main()
{
vector a(10,1);
int i;
cout << 'initialisaiton:' << endl;
cout << 'insert data:' << endl;
cin >> a[2];
cin >> a[5];
cin >> a[8];
cout << '赋值后遍历:'<
基本操作
语句 | 作用 |
---|---|
a.insert(position, value) | 将数值的一个拷贝插入到有position指定的位置上,并返回新元素的位置 |
a.insert(position,n,value) | 将数值的n个拷贝插入到position指定的位置上 |
a.insert(position,beg,end) | 将从beg到end-1之间的所有元素的拷贝插入到a中由position指定的位置上 |
a.push_back(value) | 在尾部插入 |
a.pop_back() | 删除最后元素 |
a.resize(num) | 将元素个数改为num |
a.resize(num,value) | 将元素个数改为num,如果size()增加,默认的构造函数将这些新元素初始化(用value?) |
a.clear() | 删除容器中所有元素 |
a.erase(position) | 删除由position指定的位置上的元素 |
a.erase(beg, end) | 删除beg到end-1之间的所有元素 |
a.empty() | 判断是否为空 |
a.size(), a.max_size() | 向量a的大小和最大容量 |
a.capacity() | 向量a的真实大小 |
以迭代器访问向量
vector a(10,5);
vector::iterator iter;
for (iter = a.begin(); it != a.end(); iter++)
{
cout<<*iter<
因为迭代器是一个定义在vector类中的typedef,所以必须使用容器名vector、容器元素类型和作用域符来使用iterator。
迭代器是一个指针,用来存取容器中的数据元素,因此迭代器的操作和指针的操作相同。
二维向量
vector> a(3,vector(4)); //相当于数组a[3][4],都是空
vector> a(3, vector(4,0)); //相当于a[3][4],都是0
for(int m = 0; m < a.size(); m++) //a.size()获取行向量大小
{
for (int n = 0; n < a[3].size(); n++) //a[n].size()获取向量中具体每个向量的大小
{
cout<
列表list
STL实现的双向链表,相比vector的连续线性空间,list复杂太多,它允许快速的插入和删除,但是随机访问却比较慢。它的数据有若干个节点构成,每个节点包括一个信息块、一个前驱指针和一个后驱指针,可以向前或向后进行访问,但不能随机访问。好处是每次插入或删除会配置或者释放一个元素空间,对空间的运用绝对精准。列表的定义在头文件中。
其定义方式和vector相似
list lst1;
list lst2(3);
list lst3(3,2); //三个元素的list,初始值都是2
list lst4(lst2);
list lst5(lst.begin(), lst.end()); //用lst初始化lst5
lst.push_front(8); //头部加入元素
lst.pop_front(); //头部元素删除
lst.push_back(xx); //尾部加入元素
lst.pop_back(); //尾部元素删除
遍历list
iterator begin(); //返回指向第一个元素的迭代器
iterator end();
reverse_iterator rbegin(); //返回指向第一个元素的逆向迭代器
reverse_iterator rend();
// 如下
for (list::reverse_iterator rit = lst.rbegin(); rit != lst.rend(); ++rit)
{
cout<<**rit<
列表容器的操作
lst.empty(); //lst为空时返回true
lst.size(); //返回list中元素的个数
lst.max_size(); //返回lst最大能容纳的元素个数
//为容器添加新内容
lst.assign(InputIterator first, InputIterator last);
lst.assign(size_type a, const value_type& val); //给lst赋值n个值为val的元素
//
list lst;
list lst2;
list lst3;
lst.assign(5,10); //添加5元素,值为10
lst2.assign(lst.begin(), lst.end()); //用lst为lst2赋值
int a[]={1,2,3,4};
lst3.assign(a,a+4); //将a的元素都赋值给lst3
插入元素
(2020.12.10 Thur)
list中插入元素需要成员函数insert()实现。分为若干版本。
iterator insert(iterator position, const value& val);
//position是要插入这个list的迭代器,val是要插入的值,比如下面这个例子
list lst;
list::iterator it;
it = lst.begin();
lst.inset(it, 9);
iterator insert(iterator position, size_type n, const value& val);
//插入n个值为val的元素
lst.insert(it, 2, 29); //在迭代器指针it所指的位置加入2个29
template ;
void insert (iterator position, InputIterator first, InputIterator last);
//first和last是选择的把值插入到这个list中的值所在的容器的迭代器
list lst;
list::iterator it;
vector v(2,39);
lst.insert(it, v.begin(), v.end());
删除元素
iterator erase(iterator position);
iterator erase(iterator first, iterator last); //删除[first, last)中的值,可以不用变量接收其返回值
list lst(5,10);
list::iterator it1,it2;
it1 = lst.begin();
it2 = lst.end();
it1 = lst.erase(it1);
lst.erase(it1, it2);
lst.erase(lst.begin(), lst.end()); //清空lst
双队列deque
deque(double ended queue)双向队列,和vector相似,但是它允许在容器头部快速插入和删除,就像在尾部一样。
deque结合了vector和list的优缺点,优化了的对序列两端元素进行添加和删除操作的基本序列容器。
它允许较为快速的随机访问,但不像vector那样把所有的对象保存在一个连续的内存块,而是采用多个连续的存储块,并且在一个映射结构中保存对这些块及其顺序的跟踪。向deque两端添加或删除元素的开销很小,不需要重新分配空间,所以向末端添加元素比vector更高效。
关联容器
关联容器中的元素通过关键字来保存和访问,主要有映射(map)和集合(set)两类,支持通过键来高效的查找和读取元素。map的元素以键-值对(map-value)的形式组织,键用作元素在map类型下进行索引,而值则表示所存储和读取的数据。set仅包含一个键,并有效的支持关于某个键是否存在的查询。set和map类型的对象不允许为同一个键添加第二个元素。如果一个键必须有多个实例,则需使用多重映射(multimap)或多重集合(multiset)类型,其允许多个元素拥有相同的键。
(2020.12.11 Fri)
映射map
map是STL的一个关联容器,提供一对一的数据处理能力,其元素由key和value两个分量足成的对偶(key, value)。key唯一,给定一个key就能唯一确定一个value。map的类型通常称为关联数组,和数组很相似,但下标是关键字key而非整数。map类型定义在头文件#include
map是有序的且不允许重复key(关键字)的关联容器。有序的实现时依靠关键字类型中的'<'来实现。
#include
类型别名 | 说明 |
---|---|
key_type | 关键字类型 |
value_type | pair |
mapped_type | 关键字关联的类型 |
map myMap;
myMap::value_type a; //a为pair类型
myMap::key_type b; //b为int类型
myMap::mapped_type c; //c为string类型
pair类型
pair标准类型被包含在头文件#include
pair a; //保存两个string
pair b; //保存一个string和size_t
pair> c; //
pair author{'hello', 'world'}; //初始化一个pair,名author,两个初值是hello world
pair author('hello', world'); //同上
pair的数据成员是public,成员名为first和second,用户可以用普通的成员访问符'.'来进行访问。
操作 | 说明 |
---|---|
pair |
p的成员数据类型分别是T1和T2,默认初始化 |
pair |
同上,使用v1,v2进行初始化 |
pair |
等价上式 |
make_pair(v1, v2); | 返回一个v1和v2初始化的pair,其类型由v1和v2推断而来 |
p.first | 返回p的first成员 |
p.second | 返回p的second成员 |
p1 relop p2 | 执行关系运算,利用数据类型中的关系运算 |
p1 == p2 | 相等性判断,必须first和second同时满足要求 |
p1!=p2 | 不等判断 |
pair对象也可以作为函数的返回。
map的使用
插入数据
#include
插入value_type数据,value_type类型代表的是这个容器中元素的类型
map m;
m.insert(map::value_type('001', 'm_first')); //插入第一个value_type数据
m.insert(map::value_type('002', 'm_second')); //插入第二个value_type数据
用key键为map赋值
map m;
m['both'] = 1;
int a = m['both'];
其他指令
m.size(); //map m的数据数量
//遍历,正向
for (map::iterator iter = m.begin(); iter!=m.end() ; iter++)
{
cout<first << ' ' << iter->second<< endl;
}
//遍历,反向
for (map::iterator iter = m.rbegin(); iter!=m.rend() ; iter++)
{
cout<first << ' ' << iter->second<< endl;
}
//计数和查找
m.count(k); //m中key k的个数,0或者1
m.find(k); //找到m中的k索引的元素,并返回指向该元素的迭代器
//删除
m.erase(k); //删除key为k的元素,返回size_type的类型,表示删除的元素个数
m.erase(b, e); //删除迭代器b和e中间的元素,返回void型
m.erase(p); //删除迭代器p指向的元素,返回void型
set类容器
set只是单纯的key的集合。当只想知道一个key是否存在时,使用set容器最合适。set容器支持大多数map的操作,但不支持下标操作,没有定义mapped_type类型。在set类型中,value_type不是pair类型,而是与key_type类型相同。set容器中存储的key也是唯一的。
#include
vector ivec;
for (...)
{ ivec.push_back(...);}
set iset(ivec.begin(), ivec.end());
cout<
容器适配器
用基本容器实现新的容器,用于描述更高级的数据结构。
容器适配器有三种stack, queue和priority_queue。默认情况下stack衍生自deque。queue也同样来自deque。
种类 | 默认顺序容器 | 可用顺序容器 | 说明 |
---|---|---|---|
stack | deque | vector, list, deque | |
queue | deque | list, deque | 基础容器必须提供push_front()计算 |
priority_queue | vector | vector, deque | 基础容器必须提供随机访问功能 |
#include
#include //调用queue和priority_queue时需要引入
stack的成员函数中,s.pop()是删除栈顶元素,s.push()是在栈顶插入元素,s.top()获得指向栈顶元素的引用。
queue的成员函数中,q.push()向队尾插入一个元素,q.pop()从队首删除元素,q.front()返回指向队首元素的引用,q.back()指向队尾元素的引用。
priority_queue与queue的不同之处在于,包含最大值的元素位于队首,且只能在队首执行操作。
#include
priority_queue q;
q.push(1); //插入一个元素
q.pop(); //删除队首的元素,即最大的元素
q.size();
q.empty();
q.top(); //队列中最大元素的引用
容器的选择
- vector的特点
- 指定一块如同数组一样的连续存储,但空间可以动态扩展。它可以像数组一样操作,并可以动态操作。通常体现在push_back()和pop_back()
- 随机访问方便,像数组一样被访问
- 节省空间,因连续存储,在存储数据的区域都是没有被浪费的,但是要明确一点,vector大多情况下并不是满存的,在未存储的区域实际是浪费
- 在内部进行插入、删除操作效率非常低,基本上是被禁止的。vector被设计成只能在后端进行追加和删除操作,原因是vector内部的实现是按照顺序表的原理
- 只能在vector的最后进行push和pop,不能在头进行这个操作
- 动态添加的数据超过vector默认分配的大小时,需要对内存重新分配、拷贝和释放,这个操作消耗性能。达到最佳性能,最好创建vector时指定空间大小
- list的特点
- 不使用连续的内存空间,可以随意进行动态操作
- 可以再内部任何位置快速的插入和删除,可在两端进行push和pop
- 不能进行内部的随机访问
- 比vector占用更多内存
- deque的特点
- 随机访问方便,支持[ ]操作符,性能没有vector好
- 可在内部进行插入和删除操作,性能不及list
- 可以在两端进行push和pop
- vector/list/deque三者比较
vector查询性能最好,在末端增加数据的性能也好,除非它重新申请内存段;适合高效的随机存储。
list的插入和删除元素的性能最好,查询性能差;适合大量的插入和删除操作且不关心随机存储的需求。
deque介于两者之间,比list更好查询,比vector更好插入和删除。如果用户需要随机存储又关心两端数据的插入和删除,deque最佳。
Reference
- 聚慕课教育研发中心 编著,C++从入门到项目实践(超值版),清华大学出版社
- 刘蕾编著,21天学通C++(第五版),电子工业出版社