【c++】双向链表容器list

C++模板list容器详解:
文章出自:http://classfoo.com/ccby/article/Hdj4y#sec_F8wpd2

std::list


// <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});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值