双向循环链表的模板封装

程序员对双向循环链表的熟悉程度,那就像周星驰扮演的唐伯虎说《椿树秋香图》一样——“画了几百次啦,熟得很”!。所以直接进入正题模板封装,封装之后模板名称是rubbish::structlist。

1、两个指针

template 
class structlist
{
    class stdnode
    {
	public:
        stdnode * prev;
        stdnode * next;
        void init(void){ prev = next = this;}
    public:
        stdnode(){init();}//保证未参加队列的元素自成闭环
        void quit(void)
        {
             this->prev->next = this->next;
             this->next->prev = this->prev;
             init();//保证未参加队列的元素自成闭环
        }
        friend class structlist;
    };
    stdnode        m_head;//唯一成员变量,只占两个指针的空间
    ……
};

双向循环链表的每个节点都包含前、后两个指针,后指针指向直接后继节点,前指针指向直接前驱节点。所以,从双向链表中的任意一个节点出发,即可以一直向前回到出发点,也可以一直向后回到出发点,就像是有双向车道的环城公路,而且在每个节点上都可以随时调头返向。但是为了封装方便,这里专门拿出一个节点来代表整个链表,并且规定:

  • 1、无论是向前遍历还是向后遍历,只要遍历到这个专门节点就表示遍历结束。
  • 2、空的循环链表是指仅包含这个专门节点,不包含其它任何节点元素的循环链表,且这个专门节点的前、后两个指针都指向这个专门节点本身。

2、经模板封装后的双向循环链表是作为容器使用的。

使用前要先构造双向循环链表的实例。由于双向循环链表的实例只包含前、后两个指针字段,其占用的空间极少,所以在函数堆栈空间中直接构造也没问题。

使用方法就以rubbish::RBSFrame窗口框架类的实现为例。为了管理MDI动态创建的数个客户窗口,RBSFrame声明了一个成员变量m_ViewList。m_ViewList就是一个双向循环链表的实例,它被作为Window对象的容器使用。下面是rubbish::structlist在的RBSFrame中的声明语法,rubbish::structlist在其它地方也是这么用。

class RBSFrame:public Window
{
    ……
    rubbish::structlist m_ViewList;
    ……
};

3、添加和移除节点元素。

需要注意,由于这里采用的封装方式,一个普通的数据对象是无法直接添加到双向循环链表的实例中去的。例如前面例子中m_ViewList虽然是Window对象的容器,但却无法直接把Window对象添加到ViewList中去。这是因为普通的数据对象无法作为双向循环链表的节点,它至少缺少前、后指针这两个字段。模板为此专门定义了普通数据T的“包装类”,普通数据经过“包装”以后就满足成为链表节点的条件了。程序员需要麻烦一点,需直接构造普通数据的“包装类”对象,而不直接构造普通数据的对象。为什么要给程序员添麻烦呢,先看一眼模板定义的 “包装类”再解释:

template 
class structlist
{
    ……
public:
    struct wrap_t:public T
    {
        stdnode  n;
        void addt (structlist &l) { l.addt(&this->n);}//添加到链表尾
        void addf (structlist &l) { l.addf(&this->n);}//添加到链表首
        void quit (void) { n.quit();}//跳出当前链表
    };
    ……
};

这个包装类,也就是rubbish::structlist<普通数据>::wrap_t,继承自普通数据T,在其后直接追加了一个stdnode字段,以提供存放前、后两个指针的空间。这样一来,假如程序员使用new操作符构造rubbish::structlist<普通数据>::wrap_t的话,两个指针也同时被申请了出来。反过来说,如果允许普通数据T直接加入容器的话,模板本身就必须解决两个指针存在哪里的问题。无论是由模板再次申请内存还是另的其它机制,肯定都比不上由程序员一次性申请一整块内存来得高效。

虽然“包装类”继承自普通数据,但这种封装方式却使得“普通数据”不能拥有带参数的构造函数。这恐怕才是此模板最大的麻烦,对此有一个很不完美的解决之道,就以窗口类的创建为例来说明此解决之道吧。创建窗口对象时不允许构造函数带参数将限制很多奇思妙想的实现,但众多窗口类的基类却不必一定需要参数构造。那就声明一个不带参数构造的基窗口类,比如说Window,然后其它需要参数构造的窗口都是从rubbish::structlist::wrap_t派生,而不是直接从Window派生。例如:

class RBSView:public rubbish::structlist::wrap_t
{
    DECLARE_MSG_TABLE(RBSView)
public:
    RBSFrame & m_frame;
    ……
    RBSView(RBSFrame & frame):m_frame(frame){}
    virtual ~RBSView(){this->quit();}//从Frame的View队列中移除自已
    BOOL Create(LPCTSTR szTitle);
    virtual void OnDestroy(void);
    ……
};

这样的RBSView类既可以满足作为双向循环链表的节点条件又可拥有带参数的构造了。对于从rubbish::structlist<普通数据>::wrap_t派生而来的实例,想加入到容器里是非常简单的,因为作基类的wrap_t实现了addt和addf两个函数。addt负责把元素放入容器底(最尾)部,addf负责把元素放入容器顶(最前)部。这两个函数的唯一参数就是元素要放入容器的本身。任何元素想要跳出它当前所在的容器,只需要调用rubbish::structlist<普通数据>::wrap_t提供的quit函数就可以了,quit函数没有任何参数。

4、双向循环链表和节点元素的关系

双向循环链表虽然是节点元素的容器,但是节点元素并不属于双向循环链表。什么意思呢?其实是说,如果双向循环链表突然被销毁析构了,容器中的所有元素并不会被销毁析构。原因之一,是模板并不知道某个元素当初是怎么被创建出来的。是new出来的?还是在线程的堆栈里创建的?模板不知道这些,模板也就不去负责这些了。

5、迭代器的实现

template 
class structlist
{
    ……
    iterator begin(void) const {return iterator(m_head.next);}
    iterator end  (void) const {return iterator(m_head.next->prev);}
    ……
};


rubbish::structlist通过begin和end两个函数返回迭代器位置。其中begin返回链表首部的位置,end返回链表尾部的位置,如果链表首部和链表尾部指向同一个位置,那就表示链表为空。迭代器通常的用法如下:

​
for(rubbish::structlist::iterator it = m_ViewList.begin(); 
            it != m_ViewList.end(); ++ it )
{
     ……
} 

​

从迭代器的用法中可见,迭代器位置除了++和 -- 这样的向前/向后迭代,还需比较两个迭代器位置是否相同,以判断迭代是否已到尽头。

迭代器的本质是指针

迭代器倒底是个什么呢?其实这里的迭代器就是stdnode的指针,stdnode中包含着前、后两个指针,想向前迭代就向前迭代,想向后迭代就向后迭代。就这么简单!而且迭代器就是stdnode的指针,只占一个指针的空间,比stdnode本身占的空间更小,更适合在函数内作临时局部变量使用。


template 
class structlist
{
    ……
    class iterator
    {
        stdnode * it;//包含着前、后两个指针,想向前迭代就向前迭代,想向后迭代就向后迭代
    public:
        iterator():it(0){}
        iterator(stdnode * node):it(node){}
		……
    };
    ……
};

向前迭代


template 
class structlist
{
    ……
    class iterator
    {
        ……
        iterator & operator ++ ()   
        { 
            this->it = this->it->next;
            return *this;
        }
        stdnode * operator ++ (int)
        {
            iterator itor(this->it);
            ++ *this; 
            return itor.it;//返回后itor将被析构,所以理论上不能返回iterator的引用
        }
        ……
    };
};


向前迭代,即指针形式上的++操作,所以iterator需要重载++操作符。"it++" 和 "++it"两种形都应该重载才能方便程序员。上面的代码中两个重载故意写成一个返回iterator的引用,另一个返回stdnode的指针(为什么故意写成这样?),然而iterator的两个构造函数屏蔽它们的区别。

提领迭代器

迭代器的行为应该看起来像指针,所以指针的两操作符“*”和“->”也必须可以对迭代器使用。


template 
class structlist
{
    ……
    class iterator
    {
        ……
        wrap_t * get(void) const
        {
            static const wrap_t * ptr = 0;//方便计算偏移量( & ptr->n )
            return   ((wrap_t*) (((size_t)this->it) - (size_t)& ptr->n));
        }
        wrap_t & operator  * () const {return *this->get();}
        wrap_t * operator -> () const {return  this->get();}
        ……
    };
    ……   
};


上面的代码中,无论是“*”还是“->”的重载实现都委托给了get函数。而get函数的实现就稍嫌不够优雅了。“包装类”继承自普通数据T,而且它把包含前、后指针的stdnode安排在了基类所有变量的最后面。所以为了能从stdnode的指针计算出“包装类”自身的地址,get函数对两者地址进行了野蛮的加减计算,即用“stdnode的地址”减去stdnode在包装类中的偏移量,通过原始的二进制计算愣是得到了“包装类”自身的起始地址。这大概是此模板中最脏的活儿了。

完整的模板代码

#pragma once
namespace rubbish
{
template 
class structlist
{
    class stdnode
    {
	public:
        stdnode * prev;
        stdnode * next;
        void init(void){ prev = next = this;}
    public:
        stdnode(){init();}
        void quit(void)
        {
             this->prev->next = this->next;
             this->next->prev = this->prev;
             init();
        }
        friend class structlist;
    };
    stdnode        m_head;
    static void static_add(stdnode * prev,stdnode * next,stdnode * node)
    {
        node->prev = prev;
        node->next = next;
        prev->next = next->prev = node;
    }
	void addt (stdnode *n) { structlist::static_add( m_head.prev, &m_head,n);}
    void addf (stdnode *n) { structlist::static_add( &m_head ,m_head.next,n);}
public:
    struct wrap_t:public T
    {
        stdnode  n;
        void addt (structlist &l) { l.addt(&this->n);}
        void addf (structlist &l) { l.addf(&this->n);}
        void quit (void) { n.quit();}
    };

    //void rob(structlist & l)
    //{
    //    while(l.begin() != l.end())
    //    {
    //        stdnode* it = l.begin();
    //        it->quit();
    //        addf(it);
    //    }
    //}

    class iterator
    {
        stdnode * it;
    public:
        iterator():it(0){}
        iterator(stdnode * node):it(node){}

		void insert_t (wrap_t & w) { structlist::static_add( this->it ,this->it->next,&w.n);}
		void insert_f (wrap_t & w) { structlist::static_add( this->it->prev ,this->it,&w.n);}

        iterator & operator ++ ()   
        { 
            this->it = this->it->next;
            return *this;
        }
        stdnode * operator ++ (int)
        {
            iterator itor(this->it);
            ++ *this; 
            return itor.it;
        }
        iterator & operator -- ()
        { 
            this->it = this->it->prev;
            return *this;
        }
        stdnode * operator -- (int)
        { 
            iterator itor(this->it);
            -- itor; 
            return itor.it;
        }

        bool operator == (iterator itor) const {return this->it == itor.it;}
        bool operator != (iterator itor) const {return this->it != itor.it;}

        wrap_t * get(void) const
        {
            static const wrap_t * ptr = 0;
            return   ((wrap_t*) (((size_t)this->it) - (size_t)& ptr->n));
        }
        wrap_t & operator  * () const {return *this->get();}
        wrap_t * operator -> () const {return  this->get();}
        friend class structlist;
    };
    iterator begin(void) const {return iterator(m_head.next);}
    iterator end  (void) const {return iterator(m_head.next->prev);}
    friend struct wrap_t;
};
}//namespace

你可能感兴趣的:(链表,数据结构)