在所有已知的STL容器中,forward_list是唯一一个不提供size()的容器。不提供的原因在于计算一个forward_list的长度需要线性的时间,库用户有时无法忍受这样的时间开销。其他容器提供的size()操作皆可以在常数时间内完成(在C++98时,list也是线性时间)。为了节省内存,forward_list甚至不跟踪序列的长度,要想获得某个forward_list对象的长度,用户需要通过distance()来计算。这带来了一些不便,但使得用户远离了size()带来的高消耗。每个容器类型都有三个与大小相关的操作:max_size(),empty(),size(),而forward_list只提供了前两个。
int main() { forward_list<int> flist; for (int i = 0; i < 10; i++) { flist.push_front(i); } std::cout << std::distance(flist.begin(), flist.end()); //输出10 getchar(); }
不同之二:forward_list是唯一一个在给定位置之后插入新元素的容器。为此,forward_list提供了如下的插入接口:
接口描述insert_after 在给定位置之后插入新元素
emplace_after 在给定位置之后构造新元素
erase_after 删除给定位置之后的元素
splice_after 将另一个forward_list的元素移动到本forward_list的指定位置之后
其他所有STL容器都是在指定位置之前插入元素(除了std::array,它不允许插入)。forward_list的这种特殊处理,还是出于效率的考虑。对于单链表我们应该很熟悉,为了在某个指定节点之前插入插入节点,我们必须改变插入位置的前一个节点的指向。换句话说,为了在指定节点之前插入新元素,我们必须要先获得插入位置前一个位置的节点,为了获取前面这个节点,需要线性的操作时间。
而如果我们是在指定位置之后插入新元素,则无需线性时间的查找操作,这样可实现常数时间的插入:
同样的,处于性能的考虑,forward_list没有提供在尾部进行操作的接口,包括push_back(),pop_back()和emplace_back(),这些操作对单列表来说都至少要花费O(n)来完成。
迭代器失效问题指向被删除元素的迭代器,在删除之后失效。
list 底层数据结构list同样是一个模板类,它底层数据结构为双向循环链表。因此,它支持任意位置常数时间的插入/删除操作,不支持快速随机访问。
迭代器类型list的迭代器具备前移、后移的能力,所以list提供的是Bidirectional iterator(双向迭代器)。由于采用的是双向迭代器,自然也很方便在指定元素之前插入新节点,所以list很正常地提供了insert()操作与push_back()/pop_back()操作。在C++11中,list新增了三个接口,以支持在指定位置构造对象后插入容器中:
接口(C++11新增)描述emplace 在指定位置之前插入新构造的元素
emplace_front 在链表头插入新构造的元素
emplace_back 在链表尾插入新构造的元素
内存分配策略
list的空间配置策略,自然是像我们普通双向链表那样,有多少元素申请多少内存。它不像vactor那样需要预留空间供新元素的分配,也不会因找不到连续的空间而引起整个容器的内存迁移。
迭代器失效问题list 有一个重要性质:插入操作(insert)与接合操作(splice)都不会造成原有的list迭代器失效。这在vector是不成立的,因为vactor的插入可能引起空间的重新配置,导致原来的迭代器全部失效。list的迭代器失效,只会出现在删除的时候,指向删除元素的那个迭代器在删除后失效。
通常来说,forward_list在使用灵活度上比不上list,因为它只能单向迭代元素,且提供的接口没有list多。然而,在内存的使用上,它是比list占优势的。当对内存的要求占首要位置时,应该选择forward_list。
vector 底层数据结构