序列式容器详解
序列式容器
所谓STL序列式容器,其共同的特点是不会对存储的元素进行排序,元素排列的顺序取决于存储它们的顺序。不同序列式容器的适用场景不同。常用的序列式容器有:array、vector、deque、list
序列式容器共有函数:
下面是序列式容器的共有方法、主要是基于迭代器的一些方法和一些常用的方法。
迭代器成员函数
成员函数 | 功能 |
---|---|
begin() | 返回指向容器中第一个元素的随机访问迭代器。 |
end() | 返回最后一个元素的后一个位置的随机访问迭代器 |
rbegin() | 返回指向最后一个元素的随机访问迭代器 |
rend() | 返回指向第一个元素之前一个位置的随机访问迭代器。 |
cbegin() | 和 begin() 功能相同,只不过在其基础上增加了 const 属性,不能用于修改元素。 |
cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 |
crbegin() | 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 |
crend() | 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 |
常用其他成员函数
成员函数 | 功能 |
---|---|
size() | 返回容器中当前元素的数量,初始值通常为容器构造函数的第二个参数 |
empty() | 判断容器是否为空,和通过 size()==0 的判断条件功能相同,但其效率更快 |
at(n) | 返回容器中 n 位置处元素的引用 自动越界检查,越界则抛出 out_of_range 异常。list容器没有此成员函数 |
erase() | 移出一个元素或一段元素 |
clear() | 移出所有的元素,容器大小变为 0 |
insert() | 在指定的位置插入一个或多个元素array由于不能添加元素,不支持 |
resize() | 改变实际元素的个数array由于固定长度,不支持 |
swap() | 交换两个容器中的元素,必须保证这两个容器中存储的元素类型是相同的如果是array,还需要保证长度相等 |
front() | 返回容器中第一个元素的直接引用该函数不适用于空的 array 容器 |
back() | 返回容器中最后一个元素的直接应用,该函数同样不适用于空的 array 容器 |
<1> array
array 容器是 C++ 11 标准中新增的序列容器,简单地理解,它就是在 C++ 普通数组的基础上,添加了一些成员函数和全局函数。在使用上,它比普通数组更安全,且效率并没有因此变差。
array 容器以类模板的形式定义在 <array>
头文件,并位于命名空间 std 中
namespace std{
template <typename T, size_t N>
class array;
}
初始化
头文件:#include<array>
创建具有 4 个 int 类型元素的 array 容器,但是array不会初始化默认值,里面的值是不确定的
std::array<double, 10> values;
所以要创建初始化为0的array容器要采用下面的方式:
std::array<double, 10> values {};
也可以像创建常规数组那样对元素进行初始化:
std::array<double, 10> values {0.5,1.0,1.5,,2.0};
array 特有常用函数
成员函数 | 功能 |
---|---|
fill(val) | 将 val 赋值给容器中的每个元素 |
operator[ ] | array 重载了[]运算符,意味着array也可以像访问数组一样使用[]访问元素 |
data() | 返回一个指向容器首个元素的指针。利用该指针,可实现复制容器中所有元素等类似功能 |
<2> vector
vector 容器是 STL 中最常用的容器之一,它和 array 容器非常类似,都可以看做是对 C++ 普通数组的“升级版”。不同之处在于,array 实现的是静态数组(容量固定的数组),而 vector 实现的是一个动态数组,即可以进行元素的插入和删除,在此过程中,vector 会动态调整所占用的内存空间,整个过程无需人工干预。
该容器擅长在尾部插入或删除元素,在常量时间内就可以完成,时间复杂度为O(1);而对于在容器头部或者中部插入或删除元素,则花费时间要长一些(移动元素需要耗费时间),时间复杂度为线性阶O(n)。原因在于其是基于数组的数据结构。
vector 容器以类模板 vector( T 表示存储元素的类型)的形式定义在 <vector>
头文件中,并位于 std 命名空间中。
初始化
头文件:#include <vector>
初始化一个 向量数组 vec
vector<int> vec;
可以调用其构造函数指定一个初始容量为10的vector
vector<int> vec(10);
或者 利用其成员函数reserve()
改变容量为10
vector<int> vec;
vec.reserve(10);
也可以像数组一样初始化(C++11 新增)
vector<int>vec = {1,2,3,4,5};
vector常用函数
成员函数 | 功能 |
---|---|
push_back() | 在序列的尾部添加一个元素,复杂度仅O(1) |
pop_back() | 移出序列尾部的元素,复杂度仅O(1) |
capacity() | 返回当前容量,vector是动态数组,所以有size(实际元素个数)和capacity(容量)两个属性 |
operator[ ] | 重载了 [ ] 运算符,可以向访问数组中元素那样访问元素 |
assign() | 用新元素替换原有内容 |
<3>deque
双端队列deque(double-end-queue),和vector非常相似,都是通过动态数组管理内存,支持随机访问,并且和vector用相同的接口。不同点在于,deque的头尾两端都是开放的。因此,deque在头尾插入和删除都是O(1)。
- deque 容器中存储元素并不能保证所有元素都存储到连续的内存空间中,因为deque中存储的是元素的指针,而非元素本身(详情)
- deque不支持内存重新分配控制,因此,当有元素插入到容器中间时(非两端),那么这时候指向deque的指针、迭代器和引用都会失效!
deque 的使用场景:
-
需要在容器前后两端插入和删除元素的时候
-
不需要指向容器中的元素
初始化
deque的初始化方式与vector差不多,有以下几种:
头文件:#include <deque>
创建一个空的deque容器 (常用方式)
std::deque<int> deq;
也可以创建一个 初始长度为10的队列,里面的元素为对应类型的默认值
deque<int> deq(10);
还可以创建一个具有 n 个元素的 deque 容器,并为每个元素都指定初始值
deque<int> deq(10,2);
deque常用函数
deque的成员函数用法与vector差不多,最大的差别在于:deque里面没有capacity()函数
成员函数 | 功能 |
---|---|
push_back() | 在序列的尾部添加一个元素 |
push_front() | 在序列的头部添加一个元素 |
pop_back() | 移出序列尾部的元素 |
pop_front() | 移除容器头部的元素 |
emplace_front() | 在容器头部生成一个元素。和 push_front() 的区别是, 该函数直接在容器头部构造元素,省去了复制移动元素的过程。 |
emplace_back() | 在容器尾部生成一个元素。和 push_back() 的区别是 该函数直接在容器尾部构造元素,省去了复制移动元素的过程。 |
deque 的特殊底层原理
和 vector 容器采用连续的线性空间不同,deque 容器存储数据的空间是由一段一段等长的连续空间构成,各段空间之间并不一定是连续的,可以位于在内存的不同区域。为了管理这些连续空间,deque 容器用数组存储着各个连续空间的首地址。也就是说, 数组中存储的都是指针,指向那些真正用来存储数据的各个连续空间。
当 deque 容器需要在头部或尾部增加存储空间时,它会申请一段新的连续空间,同时在 map 数组的开头或结尾添加指向该空间的指针,由此该空间就串接到了 deque 容器的头部或尾部。
<4>list
list是一个双向链表(doubly linked list),list的内部结构和vector,array,deque完全不同。list里面有两个指针(anchors),分别指向第一个元素和最后一个元素,每个元素都有指向自己前驱和后驱的指针。基于这样的存储结构,list 容器具有一些其它容器(array、vector 和 deque)所不具备的优势,即它可以在序列已知的任何位置快速插入或删除元素(时间复杂度为O(1))。并且在 list 容器中移动元素,也比其它容器的效率高。
初始化
list 的创建方式和deque的方式完全一样:
头文件:#include <list>
创建一个空的list容器 (常用方式)
std::list<int> l;
也可以创建一个 初始长度为10的队列,里面的元素为对应类型的默认值
list<int> l(10);
还可以创建一个具有 n 个元素的 deque 容器,并为每个元素都指定初始值为2
list<int> l(10,2);
list 常用函数
成员函数 | 功能 |
---|---|
push_front() | 在容器头部插入一个元素 |
pop_front() | 删除容器头部的一个元素 |
push_back() | 在容器尾部插入一个元素 |
pop_back() | 删除容器尾部的一个元素 |
insert() | 在容器中的指定位置插入元素。 |
emplace_front() | 在容器头部生成一个元素。该函数和 push_front() 的功能相同,但效率更高 |
emplace_back() | 在容器尾部直接生成一个元素。该函数和 push_back() 的功能相同,但效率更高。 |
splice() | 将一个 list 容器中的元素插入到另一个容器的指定位置。 |
remove(val) | 删除容器中所有等于 val 的元素。 |
remove_if() | 删除容器中满足条件的元素。 |
sort() | 通过更改容器中元素的位置,将它们进行排序。 |
merge() | 合并两个事先已排好序的 list 容器,并且合并之后的 list 容器依然是有序的。 |
forward_list
forward_list 是 C++ 11 新添加的一类容器,其底层实现和 list 容器一样,采用的也是链表结构,只不过 forward_list 使用的是单链表,而 list 使用的是双向链表
整理资源来自:C语言中文网