list的节点
list本身和list的节点是不同的结构,下面是list的节点结构
template <class T>
struct __list_node {
typedef void* void_pointer;
void_pointer next;
void_pointer prev;
T data;
};
显然这是一个双向链表
list的迭代器
list不能够像vecor一样以普通指针作为迭代器,因为其节点不保证在存储空间中连续存在。list迭代器必须有能力指向list的节点,并有能力进行正确的递增、递减、取值、成员存取等操作。
由于STL list是一个双向链表,迭代器必须具备前移、后移的能力,所以list提供的是Bidirectional Iterators。
list的重要性质:插入操作和接合操作都不会造成原有的list迭代器失效。而在vector中的插入操作有可能造成扩容,需要吧元素从一个vector移到另一个vector,这就会会导致原有的迭代器全部失效。
list的数据结构
SGI list不仅是一个双向链表,而且还是一个环状双向链表,所以它只需要一个指针,便可以完整表现整个链表。
iterator begin() { return (link_type)((*node).next); }
iterator end() { return node; }
reference front() { return *begin(); }
const_reference front() const { return *begin(); }
reference back() { return *(--end()); }
const_reference back() const { return *(--end()); }
刻意让node指向置于尾端的一个空白节点,node便能符合STL对于“前闭后开”区间的要求,成为last迭代器。
list的构造与内存管理
list的缺省构造函数
list() { empty_initialize(); }
void empty_initialize() {
node = get_node();
node->next = node;
node->prev = node;
}
link_type get_node() { return list_node_allocator::allocate(); }
list的insert,其实就是在双向链表中插入一个节点
void push_front(const T& x) { insert(begin(), x); }
void push_back(const T& x) { insert(end(), x); }
iterator insert(iterator position, const T& x) {
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源码中最关键的代码 --迁移操作
//将[first,last)内的所有元素都移到pos之前
//注:区间是左闭右开的,将first到last前一个插入pos之前
void transfer(iterator pos,iterator first,iterator last)
{
//last等于pos,就不用移动
if(pos != last)
{
//1-4从左向右断开
//(1)将last的前一个节点后继指向pos位置
(*((link_node*)(*last.node).prev)).next = pos.node;
//(2)将firt到last摘出去
(*((link_node*)(*fisrt.node).prev)).next = last.node;
//(3)将first和position的前一个节点接起来
(*((link_node*)(*pos.node).prev)).next = frst.nod;
//从右向左连接
//(4)标记pos之前的节点,因为(5)断开后找不到这个位置
link_node* tmp = link_node*((*pos.node).prev);
//(5)pos前驱指向last之前的节点
(*pos.node).prev = (*last.node).prev;
//(6)last前驱指向fist之前的节点
(*last.node).prev = (*first.node).prev;
//(7)将first的前驱指向tmp节点
(*first.node).prev = tmp;
}
}
splice,sort,merge、reverse
通过transfer就可以实现这四个函数:将某连续范围的元素迁移到某个特定位置之前,就是一个指针的移动。
C++11新特性emplace操作
在引入右值引用,转移构造函数,转移复制运算符之前,通常使用push_back()向容器中加入一个右值元素(临时对象)的时候,首先会调用构造函数构造这个临时对象,然后需要调用拷贝构造函数将这个临时对象放入容器中。原来的临时变量释放。这样造成的问题是临时变量申请的资源就浪费。
引入了右值引用,转移构造函数后,push_back()右值时就会调用构造函数和转移构造函数。
在这上面有进一步优化的空间就是使用emplace_back
在容器尾部添加一个元素,这个元素原地构造,不需要触发拷贝构造和转移构造。而且调用形式更加简洁,直接根据参数初始化临时对象的成员。
#include <vector>
#include <string>
#include <iostream>
using namespace std;
struct President
{
string name;
string country;
int year;
President(string p_name, string p_country, int p_year)
: name(std::move(p_name)), country(std::move(p_country)), year(p_year)
{
std::cout << "I am being constructed.\n";
}
President(const President& other)
: name(std::move(other.name)), country(std::move(other.country)), year(other.year)
{
std::cout << "I am being copy constructed.\n";
}
President(President&& other)
: name(std::move(other.name)), country(std::move(other.country)), year(other.year)
{
std::cout << "I am being moved.\n";
}
President& operator=(const President& other);
};
int main()
{
vector<President> elections;
cout << "emplace_back:\n";
elections.emplace_back("Nelson Mandela", "South Africa", 1994); //没有类的创建
vector<President> reElections;
cout << "\npush_back:\n";
reElections.push_back(President("Franklin Delano Roosevelt", "the USA", 1936));
cout << "\nContents:\n";
for (President const& president : elections) {
cout << president.name << " was elected president of "
<< president.country << " in " << president.year << ".\n";
}
for (President const& president : reElections) {
cout << president.name << " was re-elected president of "
<< president.country << " in " << president.year << ".\n";
}
system("pause");
return 0;
}