目录
相比于vector容器,list容器要更加得复杂,但是好处是对于每次插入和删除一个元素,就配置或释放一个元素空间,因此,list对于空间得运用更加地精准,一点也不浪费。
一.list的节点
list的节点主要由指向上一个节点的指针,指向下一个节点的指针和数据组成。其中,指针的变量类型是void* 类型,这样在调用时需要在指针前面加上类型强制转换。
template <class T>
struct _list_node{
typedef void* void_pointer;
void_pointer prev;
void_pointer next;
T data;
};
二.list的迭代器
由于list的节点不能保证在空间中连续存在,因此不能直接用普通指针作为迭代器进行使用。因此要专门构造一个具有指向list节点的类似指针功能的迭代器,并拥有正确的递增(指向下一个节点),递减,取值,成员存取等操作。由于list属于双向链表,迭代器具有前移后移功能,list提供的是Bidirectional iterators。
由于list迭代器相当于指针,因此,迭代器中有一个普通指针来指向list节点:
template<class T, class Ref, class Ptr>
struct _list_iterator{
/....../
typedef _list_node<T>* link_type;
/....../
link_type node;//指向list节点
/....../
}
当然了,既然介绍list迭代器,那我们该如何初始化呢,于是乎,我们来看看list类的构造函数有哪些:
template<class T, class Ref, class Ptr>
struct _list_iterator{
/....../
_list_iterator(link_type x):node(x){} //构造函数1
_list_iterator(){} //构造函数2
_list_iterator(const iterator& x):node(x.node){}//构造函数3
}
对于构造函数1,是属于c++对象模型中的有参构造函数;对于构造函数2,是属于c++对象模型中的无参构造函数;对于构造函数3,是属于c++对象模型中的拷贝构造函数。对于这三种构造函数,表面list迭代器有三种初始化方式:
int iv[5] = {5,6,7,8,9};
list<int> ilist(iv, iv+5);//初始化list容器
//构造函数1,直接赋值list容器中的list节点成员函数
list<int>::iterator ite1 = ilist.node->next;
list<int>::iterator ite2;//构造函数2
//构造函数3,直接赋值list迭代器(类比于指针给指针赋值)
list<int>::iterator ite3 = ite1;
cout << ite1.node->data << endl;//输出:5,也可以写为cout<< *ite1 << endl;
cout << ite3.node->data << endl;//输出:5
然后再简单讲一下list迭代器的运算符重载:
为了叙述简单,本文将以源码+实例的方式来让读者体会list迭代器内部如何通过运算符重载的方式使得迭代器拥有指针一样的功能(本文主要展示取值*,前置++(--),后置++(--)等功能的实现):
取值*:由于指针作为迭代器的成员函数,如果直接在迭代器变量前面加“*”的话,是无法直接获得成员函数指向的值的,此时就需要运算符重载:
reference operator*() const { return (*node).data; }
++运算符:由于list节点在内存中不是连续的,所以为了实现++运算符使得迭代器能够指向下一个节点,则需要运算符重载(--运算符同理):
//前置++
self& operator++()//返回引用的原因是使得迭代器能够反复迭代前置++:++(++(ite))
{
node = (link_type)((*node).next);
return *this;
}
//后置++
self operator++(int)//由于后置++没有返回迭代器本身,所以不能迭代++
{
self tmp = *this;
++*this;
return tmp;
}
接下来,演示运算符重载带来的好处:
int iv[5] = {5,6,7,8,9};
list<int> ilist(iv, iv+5);//初始化list容器
//构造函数1,直接赋值list容器中的list节点成员函数
list<int>::iterator ite1 = ilist.node->next;
list<int>::iterator ite2 = ite1;
++ite2;//前置++运算符重载
cout << *ite1 << endl;//5,*运算符重载
cout << *ite2 << endl;//6
三.list的数据结构
list中只需要一个_list_node<T>*类型的指针指向头节点来表示整个环状双向链表:
template<class T, class Alloc = alloc>
class list
{
protect:
typedef _list_node<T> list_node;
public:
typedef list_node* link_type;
protected:
link_type node;
}
其中,list成员函数begin()返回的是指向node头节点的下一个节点的迭代器(也就是第一个存储数据的节点),end()函数返回的是指向node头节点的迭代器,front()函数返回的是begin()所返回的迭代器指向节点的数据,back()函数返回的是node头节点前一个节点的数据(也就是最后一个存储数据的节点的数据值)。
四.list的构造和内存管理
list提供了多种构造函数,其中一种就是不指定任何参数,做出一个空的list出来:
public:
list() { empty_initialize(); }//构造空list
protected:
void empty_initialize()
{
node = get_node();//配置一个节点空间,令node指向它
node->next = node;//初始化node的头尾都指向自己,不设置元素
node->prev = node;
}
当我们调用push_back()成员函数时,函数内部会调用insert()成员函数,将节点插入list双向链表尾部,即头节点node之前:
void push_back(const T& x) { insert(end(), x); }//end()指向头节点node,因此是插入到node之前
iterator insert(iterator position, const T& x)//将数据x插入到position之前
{
link_type tmp = create_node(x);
tmp->next = position.node;
tmp->prev = position.node->prev;
(link_type(position.node->prev))->next = tmp;
position.node->prev = tmp;
return tmp;
}
五.list的元素操作
由于与元素操作相关的源码比较清楚明了,在此就先不作详述,本节主要讲述与元素操作相关的成员函数该如何去使用:
1.push_front()函数,用法与push_back()类似,它是将元素插入到begin()之前。
2.erase(iterator position)函数,用于删除迭代器position指向的节点,并且返回position指向的节点下一个节点的迭代器。
3.pop_front()函数,调用erase()函数,删除begin()函数所指向的节点。
4.pop_back()函数,调用erase()函数,删除end()函数所指向的头节点node前面一个节点。
5.clear()函数,清除整个链表所有节点。
6.remove(const T& value)函数,删除链表中值为value的所有节点。
7.unique()函数,用于删除链表中重复多余的节点。
int iv[5] = {5,6,7,8,9};
list<int> ilist(iv, iv+5);//初始化list容器
ilist.push_back(10);
cout << ilist.back() << endl;//10 5,6,7,8,9,10
cout << ilist.front() << endl;//5
ilist.push_front(4);
cout << ilist.front() << endl;//4 4,5,6,7,8,9,10
ilist.remove(4);
cout << ilist.front() << endl;//5 5,6,7,8,9,10
ilist.push_front(5);
list<int>::iterator ite1 = begin();
cout << *(ite1) << "->" << *(++ite1) << endl;//5->5 5,5,6,7,8,9,10
ilist.unique();
list<int>::iterator ite2 = begin();
cout << *(ite2) << "->" << *(++ite2) << endl;//5->6 5,6,7,8,9,10
list<int>::iterator ite3 = begin();
++ite3;
cout << *ite3 << endl;//6 5,6,7,8,9,10
list<int>::iterator ite4 = erase(ite3);
cout << *ite4 << endl;//7 5,7,8,9,10