一、概述
list和上一章的vector都是我们平时开发最常用到的容器。但与vector不一样的是,list并非一个绝对固定的连续空间的容器,之所以设计成这样还是考虑了连续空间删除插入的时间消耗。STL的list事实上是一个双向链表,了解链表概念的应该明白,这种前后可移动的链表结构提供了非常灵活的操作性。
二、定义
2.1 节点形式
它的节点形式可以比较形象化地展示为下图的样子:
而具体的代码也很简单:
template <class T> struct __list_node {
typedef void* void_pointer;
void_pointerprev; //型別為void*。其實可設為__list_node<T>* void_pointer next;
T data;
};
2.2 迭代器
这里过多地赘述迭代器的介绍,但不得不提的一点是:list的迭代器和vector是有比较大区别的,也就是list不能使用原生指针作为迭代器,而是需要另外设计结构的。
它的结构很容易理解,也就是我们需要把拼接工作和抽离工作封装的足够简单即可。
template<class T, class Ref, class Ptr>
struct __list_iterator {
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, Ref, Ptr> self;
typedef bidirectional_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef __list_node<T>* link_type;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
link_type node; // 迭代器內部當然要有一個原生指標,指向 list 的節點
// constructor
__list_iterator(link_type x) : node(x) {}
__list_iterator() {}
__list_iterator(const iterator& x) : node(x.node) {}
bool operator==(const self& x) const { return node == x.node; }
bool operator!=(const self& x) const { return node != x.node; }
// 以下對迭代器取值(dereference),取的是節點的資料值。
reference operator*() const { return (*node).data; }
// 以下是迭代器的成員存取(member access)運算子的標準作法。
pointer operator->() const { return &(operator*()); }
// 對迭代器累加 1,就是前進一個節點
self& operator++()
node = (link_type)((*node).next);
return *this;
}
self operator++(int)
self tmp = *this;
++*this;
return tmp;
}
// 對迭代器遞減 1,就是後退一個節點
self& operator--()
node = (link_type)((*node).prev);
return *this;
}
self operator--(int) self tmp = *this;
--*this;
return tmp;
}
};
2.3 节点结构
上面提到,STL的list是一个双向的链表结构。因此整个完整的链可以展示为以下代码:
template <class T, class Alloc = alloc> // 預設使用 alloc 為配置器 class list {
protected:
typedef __list_node<T> list_node;
public:
typedef list_node* link_type;
protected:
link_type node; // 只要一個指標,便可表示整個環狀雙向串列
...
};
试想我们需要一个不断向后添加节点的标记,那么我们整个操作其实都必须要依赖一个节点的指针去进行。link_type 就是对整个node指针换了个名。然后把所有的头和尾都标记起来,让我们用的时候可以直接找到头尾。
iterator begin() { return (link_type)((*node).next); }
iterator end() { return node; }
bool empty() const { return node->next == node; }
size_type size() const {
size_type result = 0;
distance(begin(),end(),result); //全域函式,第3章。
return result;
}
// 取頭節點的內容(元素值)。
reference front() { return *begin(); }
// 取尾節點的內容(元素值)。
reference back() { return *(--end()); }
它前后取值的操作都可以形象地展示为下图:
三、操作中理解list
最关键的操作就是基于transfer()这个函数来实现的:
// 將 [first,last) 內的所有元素搬移到 position 之前。
void transfer(iterator position, iterator first, iterator last) {
if (position != last) {
(*(link_type((*last.node).prev))).next = position.node;
(*(link_type((*first.node).prev))).next = last.node;
(*(link_type((*position.node).prev))).next = first.node;
link_type tmp = link_type((*position.node).prev);
(*position.node).prev = (*last.node).prev;
(*last.node).prev = (*first.node).prev;
(*first.node).prev = tmp;
}
}
它的逻辑实际上是下图的形式实现的:
3.1 环形链表
前面中提到list是一个双向链表,当我们不断地向后迭代,它会打印出什么情况呢?如列代码,我们放入4个节点后就不断地进行迭代器偏移:
#include <iostream>
#include <list>
int main() {
std::list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_back(4);
auto iter = l.begin();
for(int i=0; i<l.size()*2;i++) {
std::cout << *iter << " ";
iter++;
}
std::cout << std::endl;
std::cout << "Hello, World!" << std::endl;
return 0;
}
得到的结果是:
最后一个节点竟然被打印了两次,事实上是因为最后的一个节点后面还有一个标记结束的end节点,这个节点的内容和最后一个节点的值相同。
#include <iostream>
#include <list>
int main() {
std::list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_back(4);
auto iter = l.begin();
for(int i=0; i<=l.size()*2;i++) {
std::cout << *iter << " ";
iter++;
}
std::cout << std::endl;
auto iter2 = l.end();
std::cout << *iter2 << std::endl;
l.push_back(5);
auto iter3 = l.end();
std::cout << *iter3 << std::endl;
std::cout << "Hello, World!" << std::endl;
return 0;
}
3.2 合并算法
#include <iostream>
#include <list>
int main() {
std::list<int> l1;
std::list<int> l2;
l1.push_back(1);
l1.push_back(2);
l2.push_back(3);
l2.push_back(4);
l1.merge(l2);
for(auto iter = l1.begin();iter!=l1.end();iter++) {
std::cout << *iter << " ";
}
std::cout << std::endl;
std::cout << "Hello, World!" << std::endl;
return 0;
}
3.3 翻转算法
#include <iostream>
#include <list>
int main() {
std::list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_back(4);
for(auto iter = l.begin();iter!=l.end();iter++) {
std::cout << *iter << " ";
}
std::cout << std::endl;
l.reverse();
for(auto iter = l.begin();iter!=l.end();iter++) {
std::cout << *iter << " ";
}
std::cout << std::endl;
std::cout << "Hello, World!" << std::endl;
return 0;
}
它的源码实现为:
// reverse() 將 *this 的內容逆向重置
template <class T, class Alloc> void
list<T, Alloc>::reverse() {
// 以下判斷,如果是空白串列,或僅有一個元素,就不做任何動作。
// 使用 size() == 0 || size() == 1 來判斷,雖然也可以,但是比較慢。
if (node->next == node || link_type(node->next)->next == node)
return;
iterator first = begin();
++first;
while (first != end()) {
iterator old = first;
++first;
transfer(begin(), old, first);
} }
整体的逻辑还是比较简单的。
3.4 排序算法
#include <iostream>
#include <list>
int main() {
std::list<int> l;
l.push_back(1);
l.push_back(3);
l.push_back(2);
l.push_back(4);
l.sort();
for(auto iter = l.begin();iter!=l.end();iter++) {
std::cout << *iter << " ";
}
std::cout << std::endl;
std::cout << "Hello, World!" << std::endl;
return 0;
}
实际在STL中使用的是快排来实现排序的,以上都使用了transfer的方法来进行。
//list不能使用STL演算法 sort(),必須使用自己的 sort()memberfunction, // 因為 STL 演算法 sort() 只接受 RamdonAccessIterator.
// 本函式採用 quick sort.
template <class T, class Alloc>
void list<T, Alloc>::sort() {
// 以下判斷,如果是空白串列,或僅有一個元素,就不做任何動作。
// 使用 size() == 0 || size() == 1 來判斷,雖然也可以,但是比較慢。
if (node->next == node || link_type(node->next)->next == node)
return;
// 一些新的 lists,做為中介資料存放區 list<T, Alloc> carry;
list<T, Alloc> counter[64];
int fill = 0;
while (!empty()) {
carry.splice(carry.begin(), *this, begin());
int i = 0;
while(i < fill && !counter[i].empty()) {
counter[i].merge(carry);
carry.swap(counter[i++]);
}
carry.swap(counter[i]);
if (i == fill) ++fill;
}
for (int i = 1; i < fill; ++i)
counter[i].merge(counter[i-1]);
swap(counter[fill-1]);
}