标准模板库(Standard Template Library,简称STL)中的列表容器采用的是双向链表(Doubly-linked lists)数据结构。双向链表可以保存以不同且无关的储存位置容纳的元素。元素间的顺序是通过各个元素中指向前一个元素的链接及指向后一个元素的链接等联系维护的。
C++11 该特点跟 std:: forward_list
容器的有点相似:主要的区别就是 std:: forward_list
是一个单向链表,因此它只能被正向迭代( Iterated forward),以换取更小更高效的特点。
增加或移动列表中的元素不会使指向它们的指针、引用、迭代器失效,只有当对应的元素被销毁时才会如此。
你可能会好奇,为什么STL同时提供列表(std::list
)跟向量(std::vector
)及其它等多种容器,原因是它们的底层实现方式是不一样的,每一种实现方式都有各自的消耗(空间或时间)及优点。
相比较于其它的基本标准顺序容器(数组 std::array
、向量 std::vector
、双端队列 std::deque
等),列表通常在容器中已获得迭代器的任意位置处插入、获得(Extracting,提取)、移动元素等操作中表现出更出色的性能,对那些密集使用上述操作的算法,使用列表同样能提升性能,比如排序算法。
Since lists allow fast insertion and erasing from the middle of a list, certain operations are provided specifically for them.
C++编程语言国际标准:ISO/IEC 14882:2011
双向链表(std::list
)及正向链表(std:: forward_list
C++11)相比于其它顺序容器的主要缺点是它们不能够通过元素在容器中的位置直接访问(Direct access)元素。举个例子:如果想访问一个列表中的第六个元素,那么就必须从一个已知位置(比如开头或末尾)处开始迭代,这会消耗与两个位置之间距间相关的线性时间。而且它们还为保存各个元素间的链接信息消耗更多额外的内存(这点对由小尺寸元素组成的大列表尤为明显)。
在定义一个列表对象时,所有你需要做的就是给出保存在列表中的元素的类型(更多定义方式可以参考 list::list
成员函数)。举个例子,下述代码定义了一个整数列表:
- std::list integer_list;
模板类中提供了成员函数 push_front
及 push_back
,你可以分别用它们在列表头或尾插入一个新的元素。举个例子:
- std::list integer_list;
-
- integer_list.push_front(1);
- integer_list.push_front(2);
这个例子创建了一个列表,其中有两个元素,元素 1 及之后的元素 2。
从 C++11 开始,你也可以使用 emplace_front
及 emplace_back
分别向列表头或尾插入一个新的元素。
相对地,你也可以通过使用模板类提供的 pop_front
及 pop_back
成员函数来分别删除第一个或最后一个元素。单独地使用其中的部份函数,就能很容易的创建基于列表的队列(Queue)或栈(Stack)等数据结构,当然,最好还是使用由标准模板库提供的容器适配器 std::stack
及 std::deque
。
快速的在容器中间部份插入元素是列表的最大优势。可以使用成员函数 insert
完成这一操作:需要指定插入的元素及指向插入位置的迭代器(新的元素将会插入到当前所指向元素的前面)。从 C++11 开始,你也可以使用 emplace
来完成这一操作,具体区别请查看两个函数的详细介绍。以下是 list::insert
的声明:
- iterator insert(iterator position, const T& element_to_insert);
其中迭代器可以按以下方式定义:
- list::iterator iterator_name; // type要替换成元素的具体类型
所要注意的是标准模板库中的列表同时支持正向(Forward)及反向(Reverse)迭代(因为它是基于双链表实现的)。
使用 insert
及 end
函数可以实现 push_back
(及 push_front
)的功能:
- std::list integer_list;
- integer_list.insert(integer_list.end(), item);
正如所料的,list
类模板包含了由标准容器定义的 size
及 empty
函数。这里有一点必须要注意:size
函数的时间复杂度为O(n)(从 C++11 开始,时间复杂度变为 O(1))。所以,如果你想简单判断一个列表是否为空,可以使用 empty
而不是 检查它的大小是否为 0。如果希望保证当前列表是空的,可以使用 clear
函数。具体例子如下:
- std::list foo;
- // 会让人困惑,即使C++11有所改进
- if(foo.size() == 0)
- ...
- // 推荐的方法
- if(foo.empty())
- ...
-
- foo.clear();
- // 现在,foo 是一个空列表
可以使用 sort
成员函数对列表进行排序,该操作时间复杂度保证为 O(nlogn)。
注意,标准库中提供的
std::sort
排序函数需要容器支持随机访问迭代器,而
list
模板类并未提供该支持(直接原因是
list
提供的迭代器不支持
operator-
操作符函数),所以
std::sort
不支持对
list
的排序。
sort
函数的代码示例:
- foo.sort();
可以使用 reverse
成员函数来反转列表。标准C++库的算法库中存在一个相似的函数:std::reverse
,该函数同样用于反转容器中的元素。当前成员函数(list::reverse
)与 std::reverse
的区别就是调用 list::reverse
后不会影响其它正在使用的迭代器所指向的值。
reverse
成员函数的代码示例:
- foo.reverse()
可以使用 unique
成员函数来“唯一化”元素。该函数删除容器中的所有连续重复元素,仅仅留下每组等值元素中的第一个元素。比如,有如下元素组成列表:
1 1 8 9 7 8 2 3 3
在调用 unique
后,剩余的元素为:
1 8 9 7 8 2 3
注意上述结果中,仍然有两个 8:仅仅删除连续的等值元素。如果希望一个元素在序列中仅仅出现一次,你需要先排序这个列表。具体例子如下:
- std::list int_list;
- int_list.push_back(1);
- int_list.push_back(1);
- int_list.push_back(8);
- int_list.push_back(9);
- int_list.push_back(7);
- int_list.push_back(8);
- int_list.push_back(2);
- int_list.push_back(3);
- int_list.push_back(3);
- int_list.sort();
- int_list.unique();
-
- for(std::list::iterator list_iter = int_list.begin();
- list_iter != int_list.end(); list_iter++)
- {
- std::cout<<*list_iter<<endl;
- }