《C++ primer》中所提的评价容器性能的主要两个方面:
- 向容器添加或删除元素的代价
- 非顺序访问容器中元素的代价
要想理解和熟练运用好标准库的容器,就要明白它们在实现上的不同,才能分辨出范式算法在不同容器上的不同之处。
array
array是固定大小的容器,物理存储地址是连续的。
vector
vector的物理存储地址是连续的。vector的内存空间是向后扩展的,支持push_back(),不支持push_front(),若在尾部之外的地方插入或删除,后面的数据都要重新操作
deque
deque是顺序存储的容器,但是不同于array和vector是整块地址连续,deque的实现是分段连续的,通过指向不同地址的buffer,形成逻辑上和物理上的连续,支持push_front()和push_back()。
formward_list和list
作为比较特殊的容器,与 list(单向链表)相比,forward_list的迭代器只支持 ++递增运算符,不支持--递减运算符,因为单向链表的节点只存储一个向后的指针,而双向链表则存储向前和向后的指针。forward_list设计出来是为了实现相当性能的容器,没有back(),也不计算链表的长度size(),只支持push_front(),不支持push_back()。
原因是
若要在末节点插入元素,单向链表没有记录back()和size(),只能由头结点循序找到末节点,这样的效率是很低的,而若要在头结点插入,只需要构建一个新结点,指向原来的头结点,成为新的头结点,这样的时间成本就很低了。
struct ListNode
{
ListNode * pNext;
int value;
}
ListNode * push_front(int value)
{
ListNode * p=new ListNode(_pHead,value);
_pHead=p;
}
但是相对于其他顺序的容器,链表的物理存储顺序和逻辑顺序并不连续,是用指针的指向实现结点的顺序,在内存分配上很灵活,可以随意进行插入和删除,但是从内存消耗来讲,链表的内存额外消耗大,用于存储指针了,若是用forward_list存储int类型的数据,那么就多消耗了一倍的内存。
关联容器
map multimap set multimap 是由红黑树实现的,逻辑上是连续的。
无序关联容器
unordered_map unordered_set unordered_multimap unordered_multiset 是根据哈希表实现的,没有顺序,维护代价小,性能也更加优秀。