C++深入学习:STL源码剖析 (3) 从源码深入剖析list

C++深入学习:STL源码剖析 (3) 从源码深入剖析list

vector和list是最常用的容器,vector是连续的线性空间,list则是非连续的用指针串联起的空间。因此list对于空间的利用是非常充分的,对于任意位置的插入删除操作,list是常数时间完成。

list不仅是双向链表,而且是环形双向链表,因此只需要一个指针便可以实现对整个list的迭代器的标记。为了符合STL的前闭后开的区间要求,list实现时将该指针节点指向一个置于尾端的空白节点,则该节点的前面是尾结点,该节点的后面是头结点,根据此便可以实现诸多函数。

学习list,个人觉得最大的收获是在于熟练运用指针完成相应的变换,list的部分函数也是非常经典的算法题,同时list的设计时也有诸多很巧妙的思想。

STL的list实现由三个模块组成,节点__list_node迭代器__list_iterator以及list本身

list 的节点

list是一个双向链表,因此每个节点不仅要存当前的值,还要存储两个指针用来寻找前一个节点和后一个节点。

template<class T>
struct __list_node{
    typedef void* void_pointer;
    void_pointer prev; //前向指针
    void_pointer next; //后向指针
    T data;   //数据
}
list的迭代器

由于list 不是vector一样节点是连续空间存储,故不能以一个普通指针作为迭代器。list的迭代器必须能够指向list 的节点,同时,还能够实现递增(指向下一个节点)、递减(指向上一个节点)、取值(取出节点的值)、成员取用(取节点的成员对象)等操作。

因此,由于list的迭代器只支持前移后移,而不支持随机访问,因此是 Bidirectional Iterators.

list和vector还有一个比较大的区别,list的插入和接合等都不会造成原有的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的节点
    
    //构造函数
    __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;
    }
    //对迭代器取值返回节点存储的数据值
    reference operator*()const{return (*node).data;}
   	//重载->,返回引用
    pointer operator->()const {return &(operator*());}
    
    //重载递增递减
    self& operator++()
    {
        node=(link_type)((*node).next);
        return *this;
    }
    self operator++(int) //后+,返回原值的拷贝,然后原值++
    {
        self tmp=*this;
        ++(*this);
        return tmp;
    }
    self& operator--()
    {
        node=(link_type)((*node).prev);
        return *this;
    }
    self operator--(int) //后-,返回原值的拷贝,然后原值--
    {
        self tmp=*this;
        --(*this);
        return tmp;
    }
    
}
list的数据结构

list是一个环形双向链表,因此只需要一个指针就可以完成整个链表的遍历。

template<class T,class Alloc=alloc>
class list{
protected:
	typedef __list_node<T> list_node;
public:
    typedef list_node* link_type;
protected:
    link_type node;   //作为标记整个环形双向链表的指针
//... 之后都是一系列函数,之后逐一分析
}

将指针node指向刻意置于尾端的一个空白节点,则很容易实现一下函数:

//node的next指向表头,prev就指向队尾
iterator begin(){return (link_type)((*node).next);}
iterator end(){return node;} //根据前闭后开原则,end返回队尾之后的一个元素

bool empty(){return node->next==node;} //判断是否为空

size_type size()const
{
	size_type result=0;
    distance(begin(),end(),result); //全局函数,实现是迭代器遍历直至两个相遇,取遍历经历的次数
    return result;
}
//取头结点和尾结点的元素值
reference front(){return *begin();}
reference back() {return *(--end());}
list的构造和内存管理

构造、销毁、配置、释放节点:

protected:
	//配置一个节点
	link_type get_node(){return list_node_allocator::allocate();}
	//释放一个节点
	void put_node(link_type p){list_node_allocator::deallocate(p);}
	//配置并构造一个节点
	link_type create_node(const T& x){
        link_type p=get_node();
        construct(&p->data,x);
        return p;
    }
	//析构并释放一个节点
	void destroy_node(link_type p){
        destroy(&p->data);
        put_node(p);
    }

list的default constructor函数,创建一个空list,只有一个节点,首尾均指向自己,该节点为空节点

public:
	list(){empty_initialize();}
protected:
	void empty_initialize()  //令该节点的头和尾都指向自己,不设元素值
    {
        node=get_node();
        node->next=node;
        node->prev=node;
    }
list的元素操作函数

insert函数:在指定位置插入值为x的一个节点,该函数可以作为push类函数的基础

iterator insert(iterator position,const T& x)
{
    link_type tmp=create_node(x); //产生一个节点
    //将position的prev节点的next指向新节点,同时新节点的prev指向该节点
    //将positon的prev节点指向新节点
    tmp->next=position.node;
    tmp->prev=position.node->prev;
    (link_type(position.node->prev))->next=tmp;
    position.node->prev=tmp;
    
    return tmp;
}

push_back和push_front函数

//向尾部插入元素
void push_back(const T& x){insert(end(),x);}
//向首部插入元素
void push_front(const T&x){insert(begin(),x);}

erase函数:移除迭代器所指的节点,可以作为pop类函数的基础

//移除position所指节点
//将positon的prev节点的next指向position的next节点,将next节点的prev指针指向position的prev节点
iterator erase(iterator position)
{
    link_type next_node=link_type(position.node->next);
    link_type prev_node=link_type(position.node->prev);
    prev_node->next=next_node;
    next_node->prev=prev_node;
    //将position销毁
    destroy_node(position.node);
    return iterator(next_node);
}

相应的,pop_back()和pop_front函数:

void pop_back()
{
	iterator tmp=end();
    erase(--tmp);
}
void pop_front()
{
    erase(begin());
}

clear函数:清除整个链表的节点

void clear()
{
    link_type cur=(link_type) node->next; //找到头节点
    while(cur!=node) //依次遍历每一个节点
    {
        link_type tmp=cur;
        cur=(link_type) cur->next; //向后遍历
        destrpy_node(tmp);         //析构并释放该节点
    }
    //将node恢复为空list的状态
    node->next=node;
    node->prev=node;
}

remove函数:将数值为value的所有元素消除

void remove(const T& value)
{
    iterator first=begin();
    iterator last=end();
    while(first!=last) //遍历每一个节点
    {
        iterator next=first;
        ++next;
        if(*first==value)  //如果first的值是value,则移除
            erase(first);
        first=next;
    }
}

unique函数:移除数值相同的连续元素(连续且数值相同会倍移除剩一个,常用于排序后的list)

void unique()
{
    iterator first=begin();
    iterator last=end();
    if(first==end)  //空链表不需要操作
        return;
    iterator next=first;
    while(++next!=last) //依次遍历
    {
 		if(*first==*next) //找到相同元素将后者移除
            erase(next);
       	else   //不重复的话将first调整为next
            first=next;
        next=first;  //将next修正为first(遍历时有前置++)
    }

}
list的元素迁移函数

list实现元素的迁移时,即将连续范围的元素迁移到指定位置,只需要处理指针之间的关系即可。该函数为诸多更加复杂的操作提供了基础,包括splice,sort,merge等函数。

这是将一个list的部分移到另一个list中,不仅要处理连接[first,end)的链表,也要处理移除[first,end)的链表

//将[first,last)范围内的元素移到position之前
void transfer(iterator position,iterator first,iterator last)
{
	if(position!=last) //
    {
        //将尾结点(last.node->prev)的next指针指向positon节点
        (*(link_type((*last.node).prev))).next=position.node;
        //将first的prev的next指针直接指向last
        (*(link_type((*first.node).prev))).next=last.node;
        //将position的prev的next节点指向first
        //---------上述均只完成了单向,下面将每条边的令一方向建立连接即可
        (*(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;
    }
}

transfer是list的一个protected对象,list提供了splice函数完成接合操作;

一个简单的程序说明splice和reverse以及sort

    list<int> list1{1, 2, 5, 3, 6};
    list<int> list2{10,30,20,50,40};
    list<int>::iterator ite = find(list1.begin(),list1.end(),5);
	//将list2放入value=5的节点之前
    list1.splice(ite,list2);
    for_each(list1.begin(), list1.end(), [](int &a) { cout << a << ' '; });
    cout << endl;
	//reverse函数 倒置
    list1.reverse();
    for_each(list1.begin(), list1.end(), [](int &a) { cout << a << ' '; });
    cout << endl;
	//sort函数,排序
    list1.sort();
    for_each(list1.begin(), list1.end(), [](int &a) { cout << a << ' '; });

//1 2 10 30 20 50 40 5 3 6 
//6 3 5 40 50 20 30 10 2 1 
//1 2 3 5 6 10 20 30 40 50 

splice实现:

//将x接合到position之前
void splice(iterator position,list& x)
{
    if(!x.empty())
        transfer(position,x.begin(),x.end());
}
//将i所指元素接合于position所指位置之前,position和i可以指代同一元素
void splice(iterator position,list&,iterator i)
{
    iterator j=i;
    ++j;
    if(position==i||position==j)
        	return;
   	transfer(position,i,j);
}
//将[first,end)结合于position之前
//position和[first,end)可以是同一个list,但position不可以是[first,end)之间
void splice(iterator position,list&,iterator first,iterator last)
{
    if(first!=last)
        transfer(position,first,last);
}
merge,reverse,sort

这三个算法都是链表相关问题中非常常见的,可以学习一下STL如何实现的

merge函数:将两个升序的list合并

template <class T,class Alloc>
void merge(list<T,Alloc> &x)
{
    iterator first1=begin(); //标记当前链表的头尾
    iterator last1=end();
    iterator first2=begin(); //标记另一个链表的头尾
    iterator last2=end();
    
    while(first1!=last1&&first2!=last2) //遍历至其中一个结束
    {
        if(*firs2<*first1)
        {
            iterator next=first2;
            transfer(first1,first2,++next); //将该节点移动到first1之前
            first2=next; //first2向后迭代
        }
        else  //否则只需要first1向后迭代,因为返回的是原链表
            ++first1;
    }
    if(first2!=last2) //如果第二个链表还没有遍历结束,则将剩余全部移动至尾端即可
        transfer(last1,first2,last2); 
}

reverse函数:将链表倒置

template <class T,class Alloc>
void reverse()
{
    //只有一个节点或者是空链表直接返回即可
    if(node->next==node||linke_type(node->next)->next==node)
        return;
   	iterator first=begin();
    ++first;
    while(first!=end())  //每次将遍历到的这个节点放到最前面
    {
        iterator old=first;
        ++first;
        transfer(begin(),old,first);
    }
}

sort函数:排序链表

由于STL的sort算法只接收RandomAccessIterator,故list需要定义自己的sort函数

list的sort实现应该是归并排序,并且是一个非递归的归并排序,实现思路非常巧妙,当然理解起来也是相当困难,STL源码剖析中说这是快排应该有错。

该博文对list的sort函数讲解非常好:https://www.cnblogs.com/avota/archive/2016/04/13/5388865.html

template<class T,class Alloc>
void sort()
{
    //只有一个节点或者是空链表直接返回即可
    if(node->next==node||linke_type(node->next)->next==node)
        return;
    //中介数据存放
    list<T,Alloc> carry;  // 辅助链表,用于从a中提取元素以及临时保存两个链表的合并结果
    list<T,Alloc> counter[64]; // 保存着当前每一个归并层次的结果
    int fill=0;
    while(!empty())
    {   // 将链表的第一个元素移动至carry开头
        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[fill - 1]
    	counter[i].merge(counter[i-1]);
    swap(counter[fill-1]);
}

猜你喜欢

转载自blog.csdn.net/dingdingdodo/article/details/106927721