C++模板list容器详解:
文章出自:http://classfoo.com/ccby/article/Hdj4y#sec_F8wpd2
// <list>
template < class T, class Alloc = allocator<T> > class list;
列表(List)是一个允许在序列中任何一处位置以常量耗时插入或删除元素且可以双向迭代的顺序容器(Sequence container)
容性特性:
顺序序列
顺序容器中的元素按照严格的线性顺序排序。可以通过元素在序列中的位置访问对应的元素。
双向链表
容器中的每个元素保存了定位前一个元素及后一个元素的信息,允许在任何一处位置以常量耗时进行插入或删除操作,但不能进行直接随机访问(Direct random access)。
能够感知内存分配器的(Allocator-aware)
容器使用一个内存分配器对象来动态地处理它的存储需求。
T
存储在容器中的元素的数据类型。
在类模板内部,使用其别名为 value_type 的成员类型。
Alloc
容器内部用来管理内存分配及释放的内存分配器的类型。
这个参数是可选的,它的默认值是 std::allocator,这个是一个最简单的非值依赖的(Value-independent)内存分配器。 在类模板内部,使用其别名为 allocator_type 的成员类型。
标准模板库(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,提取)、移动元素等操作中表现出更出色的性能,对那些密集使用上述操作的算法,使用列表同样能提升性能,比如排序算法。
双向链表(std::list)及正向链表(std:: forward_list C++11)相比于其它顺序容器的主要缺点是它们不能够通过元素在容器中的位置直接访问(Direct access)元素。举个例子:如果想访问一个列表中的第六个元素,那么就必须从一个已知位置(比如开头或末尾)处开始迭代,这会消耗与两个位置之间距间相关的线性时间。而且它们还为保存各个元素间的链接信息消耗更多额外的内存(这点对由小尺寸元素组成的大列表尤为明显)。
在定义一个列表对象时,所有你需要做的就是给出保存在列表中的元素的类型(更多定义方式可以参考 list::list 成员函数)。举个例子,下述代码定义了一个整数列表:
std::list<int> integer_list;
模板类中提供了成员函数 push_front 及 push_back,你可以分别用它们在列表头或尾插入一个新的元素。举个例子:
std::list<int> 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<type>::iterator iterator_name; // type要替换成元素的具体类型
所要注意的是标准模板库中的列表同时支持正向(Forward)及反向(Reverse)迭代(因为它是基于双链表实现的)。
使用 insert 及 end 函数可以实现 push_back (及 push_front)的功能:
std::list<int> integer_list;
integer_list.insert(integer_list.end(), item);
正如所料的,list 类模板包含了由标准容器定义的 size 及 empty 函数。这里有一点必须要注意:size 函数的时间复杂度为 O(n)(从 C++11 开始,时间复杂度变为 O(1))。所以,如果你想简单判断一个列表是否为空,可以使用 empty 而不是 检查它的大小是否为 0。如果希望保证当前列表是空的,可以使用 clear 函数。具体例子如下:
std::list<int> 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> 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<int>::iterator list_iter = int_list.begin();
list_iter != int_list.end(); list_iter++)
{
std::cout<<*list_iter<<endl;
}
成员 | 解释 |
---|---|
(constructor) | 创建 list |
(destructor) | 释放 list |
operator= | 值赋操作 |
Iterators:
成员 | 解释 |
---|---|
begin | 返回指向容器起始位置的迭代器(iterator) |
end | 返回指向容器末尾位置的迭代器 |
rbegin | 返回指向容器逆序起始位置的逆序迭代器(reverse_iterator) |
rend | 返回指向容器逆序末尾位置的逆序迭代器 |
cbegin C++11 | 返回指向容器起始位置的常迭代器(const_iterator) |
cend C++11 | 返回指向容器末尾位置的常迭代器 |
crbegin C++11 | 返回指向容器逆序起始位置的常逆序迭代器(const_reverse_iterator) |
crend C++11 | 返回指向容器逆序末尾位置的常逆序迭代器 |
Capacity:
成员 | 解释 |
---|---|
empty | 判断是否为空 |
size | 返回有效元素个数 |
max_size | 返回 list 支持的最大元素个数 |
Element access:
成员 | 解释 |
---|---|
front | 访问第一个元素 |
back | 访问最后一个元素 |
Modifiers:
成员 | 解释 |
---|---|
assign | 将新的内容赋值给容器 |
emplace_front C++11 | 在容器开头构造及插入一个元素 |
push_front | 在容器开头插入一个元素 |
pop_front | 删除第一个元素 |
emplace_back C++11 | 在容器末尾构造及插入一个元素 |
push_back | 在末尾增加一个元素 |
pop_back | 删除最后一个元素 |
emplace C++11 | 构造及插入一个元素 |
insert | 插入元素 |
erase | 删除元素 |
swap | 交换内容 |
resize | 改变有效元素的个数 |
clear | 清空内容 |
Operations:
成员 | 解释 |
---|---|
splice | 使元素从一个列表移动到另一个列表 |
remove | 删除值为指定值的元素 |
remove_if | 删除满足指定条件的元素 |
unique | 删除重复值 |
merge | 合并已排序的列表 |
sort | 为容器中的所有元素排序 |
reverse | 反转元素的顺序 |
Observers:
成员 | 解释 |
---|---|
get_allocator | 获得内存分配器 |
- | 解释 |
---|---|
operator==、operator!=、operator<、operator<=、operator>、operator>= | 关系操作符 |
std::swap | 交换两个列表的内容 |
综合
下面这个例子简单地介绍了 list 的常用使用方法。
#include <iostream>
#include <list>
#include <numeric>
#include <algorithm>
namespace ClassFoo{
using namespace std;
//创建一个list容器的实例LISTINT
typedef list<int> LISTINT;
//创建一个list容器的实例LISTCHAR
typedef list<int> LISTCHAR;
void ListExample1(){
//--------------------------
//用list容器处理整型数据
//--------------------------
//用LISTINT创建一个名为foo1的list对象
LISTINT foo1;
//声明i为迭代器
LISTINT::iterator i;
//从前面向foo1容器中添加数据
foo1.push_front (2);
foo1.push_front (1);
//从后面向foo1容器中添加数据
foo1.push_back (3);
foo1.push_back (4);
//从前向后显示foo1中的数据
cout<<"foo1.begin()--- foo1.end():"<<endl;
for (i = foo1.begin(); i != foo1.end(); ++i)
cout << *i << " ";
cout << endl;
//从后向后显示foo1中的数据
LISTINT::reverse_iterator ir;
cout<<"foo1.rbegin()---foo1.rend():"<<endl;
for (ir =foo1.rbegin(); ir!=foo1.rend();ir++) {
cout << *ir << " ";
}
cout << endl;
//使用STL的accumulate(累加)算法
int result = accumulate(foo1.begin(), foo1.end(),0);
cout<<"Sum="<<result<<endl;
cout<<"------------------"<<endl;
//--------------------------
//用list容器处理字符型数据
//--------------------------
//用LISTCHAR创建一个名为foo1的list对象
LISTCHAR foo2;
//声明i为迭代器
LISTCHAR::iterator j;
//从前面向foo2容器中添加数据
foo2.push_front ('A');
foo2.push_front ('B');
//从后面向foo2容器中添加数据
foo2.push_back ('x');
foo2.push_back ('y');
//从前向后显示foo2中的数据
cout<<"foo2.begin()---foo2.end():"<<endl;
for (j = foo2.begin(); j != foo2.end(); ++j)
cout << char(*j) << " ";
cout << endl;
//使用STL的max_element算法求foo2中的最大元素并显示
j=max_element(foo2.begin(),foo2.end());
cout << "The maximum element in foo2 is: "<<char(*j)<<endl;
}
}
int main()
{
ClassFoo::ListExample1();
return 0;
}
输出:
foo1.begin()--- foo1.end():
1 2 3 4
foo1.rbegin()---foo1.rend():
4 3 2 1
Sum=10 ------------------
foo2.begin()---foo2.end():
B A x y
The maximum element in foo2 is: y
用 list 实现的简单的对象池
#include <list>
#include <iostream>
namespace ClassFoo{
using namespace std;
template<class T>
class object_pool
{
list<void *> data_list;
public:
void* malloc_data()
{
if(!data_list.empty())
{
list<void *>::iterator it = data_list.begin();
void *p = *it;
data_list.pop_front();
return p;
}
else
{
void* p = malloc(sizeof(T));
return p;
}
}
void free_data(void* p)
{
data_list.push_back(p);
}
};
class A
{
public:
int value;
A(int a):value(a){cout<<"A("<<a<<") called"<<endl;}
~A() {cout<<"~A("<<value<<") called"<<endl;}
static object_pool<A> pool_;
void* operator new (size_t size)
{
return pool_.malloc_data();
}
void operator delete(void* p)
{
pool_.free_data(p);
}
};
object_pool<A> A::pool_;
void ListExample2() {
A* a1=new A(1);
A* a2=new A(2);
delete a1;
delete a2;
}
}
void main()
{
ClassFoo::ListExample2();
}
输出:
A(1) called
A(2) called
~A(1) called
~A(2) called
排序自定义结构体对象列表
#include <iostream>
#include <list>
#include <string>
#define CLASSFOO_LIST(type, name, ...) \
static const type name##_a[] = __VA_ARGS__; \
std::list<type> name(name##_a, name##_a + sizeof(name##_a) / sizeof(*name##_a))
namespace ClassFoo{
struct ClassFoo {
int i;
std::string s;
};
bool compare(ClassFoo first, ClassFoo second)
{
if (first.i < second.i)
return true;
else
return false;
}
void ListExample3(){
CLASSFOO_LIST(ClassFoo,foo,{{2,"我"},{1,"你"},{3,"他"}});
std::list<ClassFoo>::iterator it;
foo.sort(compare);
for (it = foo.begin(); it != foo.end(); it++ )
std::cout << it->i << " " << it->s << std::endl;
}
}
int main()
{
ClassFoo::ListExample3();
return 0;
}
输出:
1 你
2 我
3 他
一个有用的宏
类似 C++11 标准中新增的初始化列表构造函数
#define CLASSFOO_LIST(type, name, ...) \
static const type name##_a[] = __VA_ARGS__; \
std::list<type> name(name##_a, name##_a + sizeof(name##_a) / sizeof(*name##_a))
可以这样使用:
CLASSFOO_LIST(int,foo,{1,2,3,4,5,6,7});