一.容器的定义
在数据存储上,有一种对象类型,它可以持有其它对象或指向其它对像的指针,这种对象类型就叫做容器。在c++中,容器指的是能够容纳各种数据类型的通用数据数据结构,是类模板。容器就是保存其它对象的对象,当然这是一个朴素的理解,这种“对象”还包含了一系列处理“其它对象”的方法。
二.容器的种类
STL中的常用容器包括:序列容器(vector、deque、list)、关联容器(map、set)、容器适配器(queue、stac)。
1.序列容器:是一种各元素之间有顺序关系的线性表,是一种线性结构的可序群集。顺序性容器中的每个元素均有固定的位置,除非用删除或插入的操作改变这个位置。顺序容器的元素排列次序与元素值无关,而是由元素添加到容器里的次序决定。序列容器包括:vector(向量)、list(列表)、deque(队列)。
2.关联容器:关联式容器是非线性的树结构,更准确的说是二叉树结构。各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素置入容器时的逻辑顺序。但是关联式容器提供了另一种根据元素特点排序的功能,这样迭代器就能根据元素的特点“顺序地”获取元素。元素是有序的集合,默认在插入的时候按升序排列。关联容器包括:map(集合)、set(映射)、multimap(多重集合)、multiset(多重映射)。
3.容器适配器:本质上,适配器是使一种不同的行为类似于另一事物的行为的一种机制。容器适配器让一种已存在的容器类型采用另一种不同的抽象类型的工作方式实现。适配器是容器的接口,它本身不能直接保存元素,它保存元素的机制是调用另一种顺序容器去实现,即可以把适配器看作“它保存一个容器,这个容器再保存所有元素”。STL 中包含三种适配器:栈stack 、队列queue 和优先级队列priority_queue 。
容器类自动申请和释放内存,因此无需new和delete操作。
三.序列容器
序列容器维护你指定的插入元素的顺序。
1.vector
vector
容器的行为类似于数组,但可以根据要求自动增长。它可以随机访问、连续存储,长度也非常灵活。 基于上述和其他原因,vector
是多数应用程序的首选序列容器。 若不确定要使用哪种序列容器,请首先使用矢量! 有关详细信息,请参阅 vector类。
vector动态数组 头文件<vetor>
元素在内存中是连续存放的。
随机存取时间:常数时间(因为可以通过下标直接访问到地址)。
在尾部增删元素通常是常数时间(正常是常数时间,如果超出了默认分配的元素个数,会重新分配存储空间,此时会消耗更多时间)。
在中间或者头部增删元素:o(n)(会移动其他元素的位置)。
迭代器类型:随机访问(支持下标访问、随机移动,例:a[i])。
查询时间:o(n)(因为没有排序,只能现行查找,效率较低)。
可见vector在中间或者头部增删元素性能较低。
优点:内存和C完全兼容、高效随机访问、节省空间
缺点:内部插入删除元素代价巨大、动态大小查过自身容量需要申请大量内存做大量拷贝。
构造函数:
vector();
vector(int n);
vector(int n, const T &a); //把n个元素初始化为a
vector(iterator first,iterator last); /
/初始化为其他容器上区间[first,last)一致的内容
常用成员函数:
void pop_back();
void push_back(const T &val);
int size();
T & font();
T & back();
2.deque
deque
(双端队列)容器支持在容器的起点和终点进行快速插入和删除。 它享有 vector
随机访问和长度灵活的优点,但是不具备连续性。 有关详细信息,请参阅 deque 类。
元素在内存中连续存放(连续内存的容器有个明显的缺点,就是有新元素插入或老元素删除的时候,为了给新元素腾出位置或者填充老元素的空缺,同一块内存中的其他数据需要进行整体的移位,这种移位的拷贝代价有时是非常巨大的。deque实际上是分配到不同内存块,通过链表把内存块连在一起,再进行连续存放,是list与vector的折中)。
随机存取时间:常数时间(仅次于vector,因为有可能存在尾部的内存位置在头部之前的场景)。
在两端增删元素通常是常数时间。(deque不像vector没有容量,不需要重新分配内存空间。这是因为deque由动态分配的连续空间,即缓冲区,组合而成,随时可以增加一段新的空间链接起来。它没有必要像vector那样“因旧空间不足而重新分配2倍的空间,然后复制元素,再释放旧空间”。当重新分配缓冲区时,耗时增加)。
在中间插入:时间复杂度较高。
迭代器类型:随机访问(效率低于vector)。
查询时间:o(n)(原因同上)。
优点:高效随机访问、内部插入删除元素效率方便、两端push、pop效率很高
缺点:内存占用比较高
3.list
list
容器是双向链表,在容器内的任意位置启用了双向访问、快速插入和快速删除,但是你不能随机访问此容器中的元素。 有关详细信息,请参阅 list 类。
元素在内存中不连续分配(因为指针可以获取前后元素的地址),所以不支持随机存取。
在任何位置增删元素时间:常数时间。
查询时间:o(n)(原因同上)。
迭代器类型:双向(不支持下标访问,不支持迭代器的比较运算符,和+-运算符)
优点:任意位置插入删除元素常量时间复杂度、两个容器融合是常量时间复杂度
缺点:不支持随机访问、比vector占用更多的存储空间
常用成员函数:(注意这些在顺序容器中都是list独有的)
push_front
pop_front
sort //不支持STLsort算法
remove
unique
merge
reverse
splice
特别说明,list的sort函数有无参和compare两个版本
list<T> classname
classname.sort(compare); //compare自定义
classname.sort();
4.array
array
容器具备 vector
的某些优点,但长度不够灵活。有关详细信息,请参阅 array 类。
5.forward_list
forward_list
容器是单独链表,list
的向前访问版本。 有关详细信息,请参阅 forward_list 类。
四.关联容器
在关联容器中,按照预定义的顺序插入元素,例如按升序排序。 无序的关联容器也可用。 关联容器可分为两个子集:映射和组集。
1.map
map
,有时称为字典,包含键/值对。 键用于对序列排序,值与该键关联。 例如,map
可能包含许多键(代表文本中每个独特的单词)和相应的值(代表每个单词在文本中出现的次数)。 map
的无序版本是 unordered_map
。 有关详细信息,请参阅 map 类 和 unordered_map 类。
2.set
set
仅是按升序排列每个元素的容器,值也是键。 set
的无序版本是 unordered_set
。 有关详细信息,请参阅 set 类 和 unordered_set类。
3.其他
map
和 set
都仅允许将键或元素的一个实例插入容器中。 如果需要元素的多个实例,请使用multimap
或multiset
。 无序版本是 unordered_multimap
和 unordered_multiset
。 有关详细信息,请参阅 多重映射类, ,unordered_multimap 类, ,多重集合的类, ,和unordered_multiset 类。
有序的映射和组集支持双向迭代器,其未排序副本支持向前迭代器。 有关详细信息,请参阅 迭代器
关联容器中的异类查找 (C++14)
排序关联容器(映射、多重映射、集与多重集)现在支持异类查找,这意味着,你将不再需要将完全相同的对象类型作为键或元素在成员函数(如find()
和 lower_bound()
)中传递。 相反,可以传递为定义了重载 operator<
以启用对键类型的比较的类型。
五.容器适配器
容器适配器是序列容器或关联容器的变体,为了简单明确起见,它对接口进行限制。
容器适配器不支持迭代器。
queue
容器遵循 FIFO(先进先出)语义。第一个元素 推送— 即插入队列 — 是第一个要 弹出—,即从队列中删除。 有关详细信息,请参阅 queue 类。
priority_queue
容器也是如此组织,因此具有最高值的元素始终排在队列的第一位。 有关详细信息,请参阅 priority_queue 类。
stack
容器遵循 LIFO(后进先出)语义。 堆栈上最后推送的元素将第一个弹出。有关详细信息,请参阅 stack 类。
由于容器适配器不支持迭代器,因此无法与 STL 算法一起使用。 有关详细信息,请参阅 算法。
六.总结
- 如果需要随机访问,用vector
- 如果存储元素的数目已知,用vector
- 需要任意位置随机插入删除,用list
- 经常在容器的首部尾部插入删除元素,用deque
- 元素结构复杂用list,也可以用vector存储指针(需要额外的精力去维护内存),看需求
- 如果操作是基于键值,用set/map
- 如果需要经常的搜索,用map/set
七.参考文献