顺序容器就是数据结构里的线性表,一共有 5 种:array、vector、deque、list、forward_list
如果按照存储的方式又可以分为链式存储(list 和 forward_list)和连续地址存储(array、vector 和 deque)
连续地址存储 | 链式存储 | |
---|---|---|
优点 | 查找效率高 | 插入删除效率高 |
缺点 | 插入删除效率低 | 查找效率低,存储成本略高 |
对于array和vector在内存中的数据存放与C语言完全兼容,所以对于数据的操作速度很快。这两种容器的区别在于array是不能扩容的,而vector可以动态的扩容。
#include <iostream>
#include <vector>
#include <array>
#include <algorithm>
int main()
{
std::array<int, 10> arr;
std::vector<int> v;
std::for_each(
cbegin(arr), cend(arr),
[&v](const auto& i)
{
v.emplace_back(i);
});
std::cout << arr.size() << std::endl; // 10
std::cout << v.size() << std::endl; // 10
return 0;
}
deque与vector的最大的区别是deque可以两头进行插入。删除的操作,vector只能在尾部进行插入和删除的操作。
#include <iostream>
#include <deque>
int main()
{
std::deque<int> q;
q.emplace_back(1);
q.emplace_front(2);
for(auto i : q)
{
std::cout << i << " "; // 2 1
}
return 0;
}
list 是双向链表,可以向前或者向后遍历,而 forward_list则是单向链表,只能向前遍历,查找效率会比list低。
#include <iostream>
#include <list>
#include <forward_list>
int main()
{
std::list<int> l;
l.emplace_back(1);
l.emplace_front(2);
l.emplace_back(3);
for(auto i : l)
{
std::cout << i << " "; // 2 1 3
}
std::cout << std::endl;
std::forward_list<int> fl;
fl.emplace_front(2);
fl.emplace_front(1);
for(auto i : fl)
{
std::cout << i << " "; // 1 2
}
std::cout << std::endl;
return 0;
}
虽然vector、deque、list和forward_list都可以进行动态的扩容,但是它们的机制是完全不同的。我们先看看vector是怎么扩容的:
#include <iostream>
#include <vector>
struct Point
{
int m_a;
int m_b;
Point (int a, int b) : m_a(a), m_b(b)
{
std::cout << "构造函数" << std::endl;
}
Point (const Point& p) : m_a(p.m_a), m_b(p.m_b)
{
std::cout << "拷贝构造" << std::endl;
}
Point (Point &&p) : m_a(p.m_a), m_b(p.m_b)
{
std::cout << "移动构造" << std::endl;
}
};
int main()
{
std::vector<Point> v;
for (int i = 1; i <= 5; i++)
{
std::cout << "*********" << i << "***********" << std::endl;
v.emplace_back(i, i + 1);
std::cout << "v.size = " << v.size() << std::endl;
}
return 0;
}
输出结果如下:
大家有没有发现,有时候会莫名其妙的调用拷贝构造函数,这是为什么呢?这是因为当 vector 的容量不够的时候,它会去寻找一块两倍大小的新内存,然后把旧元素拷贝或者移动过去,最后把新数据插入进去。这个问题之前我在工作中遇到过,所以影响特别深。我记得当时的业务中需要在vector容器中进行大量的数据插入,等数据达到一定量级的时候,接口返回时间越来越长。最后找到是这个原因导致,通过reserve接口进行所需容器的空间预估,可以减少动态扩容的调用拷贝构造的次数,从而提高程序性能。
而deque、list和forward_list相对比较简单,插入一个数据,扩容一个数据的大小。但是如果短时间有大量的数据插入,这时候一次性分配足够大的空间的vector的优势就比较明显了。所以实际开发中vector出现的记录很高。