数据结构 第22课 单链表的具体实现------------------狄泰软件学院

参考前辈的文章

LinkList.h

#ifndef LINKLIST_H
#define LINKLIST_H

#include "List.h"
#include "Exception.h"

namespace DTLib
{
    
    

template < typename T >
class LinkList : public List<T>
{
    
    
protected:
    struct Node : public Object
    {
    
    
        T value;        // 数据域,用来保存数据
        Node* next;     // 指针域, 用来保存下一个节点
    };

     //mutable Node m_header;


    // 头节点存在隐患,进行代码优化  ///    此处不太理解,需要多看几遍
    mutable struct
    {
    
    
        char reserved[sizeof(T)];
        Node* next;
    }m_header;



    int m_length;

public:
    LinkList()
    {
    
    
        m_header.next = NULL;
        m_length = 0;
    }

     bool insert(const T& e)    // 重载          最后一个位置插入
     {
    
    
         return insert(m_length, e);
     }

    bool insert(int i, const T& e)
    {
    
    
        bool ret = ((0 <= i) && (i <= m_length));

        if(ret)
        {
    
    
            Node* node = new Node();

            if( node != NULL )
            {
    
    
                //Node* current = &m_header;   // 指针指向头节点
                Node* current = reinterpret_cast<Node*>(&m_header);// 改变头节点以后的做法,为什么要这么做?

                for(int p=0; p<i; p++)
                {
    
    
                    current = current->next;  // 目标位置为 i, 就移动 i 次
                }

                node->value = e;
                node->next = current->next;
                current->next = node;

                m_length++;
            }
            else
            {
    
    
                THROW_EXCEPTION(NoEnoughMemoryException, "No memery to insert new element...");
            }
        }
        return ret;
    }

    bool remove(int i)
    {
    
    
       bool ret = ((0 <= i) && (i <= m_length));


       if(ret)
       {
    
    
           //Node* current = &m_header;
            Node* current = reinterpret_cast<Node*>(&m_header);// 改变头节点以后的做法

           for(int p=0; p<i; p++)
           {
    
    
               current = current->next;
           }

           Node* toDel = current->next;  // 指针指向要删除的位置
           current->next = toDel->next;  // 链接

           delete toDel;

           m_length--;

       }

       return ret;
    }

    bool set(int i, const T& e)
    {
    
    
         bool ret = ((0 <= i) && (i <= m_length));


         if( ret )
         {
    
    
            // Node* current = &m_header;
             Node* current = reinterpret_cast<Node*>(&m_header);// 改变头节点以后的做法

             for(int p=0; p<i; p++)
             {
    
    
                 current = current->next;
             }

             current->next->value = e;
         }
         return ret;
    }

    // get 函数使用不是很方便, 重新加载一个, 直接返回需要的值  ///
    T get(int i) const
    {
    
    
        T ret;

        if( get(i, ret) )
        {
    
    
            return ret;
        }
        else
        {
    
    
            THROW_EXCEPTION(IndexOutOfBoundsException, "Invalid parameter i to get element ...");
        }

        return ret;

    }



    bool get(int i, T& e) const
    {
    
    

        bool ret = ((0 <= i) && (i <= m_length));

        if( ret )
        {
    
    
            //Node* current = &m_header;       // 在const函数中不能修改任何成员变量的值,然而这里要取头节点的地址,
                                             // 此时编译器会认为有可能会修改成员变量的值
                                             // 解决方案: 将对应的成员加上 mutable 就可以了
            Node* current = reinterpret_cast<Node*>(&m_header);// 改变头节点以后的做法

            for(int p=0; p<i; p++)
            {
    
    
                current = current->next;
            }

            e = current->next->value;
        }
        return ret;
    }

    bool length() const
    {
    
    
        return m_length;
    }

    bool clear()
    {
    
    
        while( m_header.next )            // 通过第0号位置(头节点)的节点开始释放
        {
    
    
            Node* toDel = m_header.next;  // 指向了第第0号位置(头节点)的节点,是即将被删除的节点
            m_header.next = toDel->next;  // 头节点的指针指向下一个节点

            delete toDel;                 // 循环到头节点的指针为空时结束
        }

        m_length = 0;

    }

    ~LinkList()
    {
    
    
        clear();
    }


};

}


#endif // LINKLIST_H



main.cpp(测试一)

#include <iostream>
#include "LinkList.h"


using namespace std;
using namespace DTLib;


int main()
{
    
    

    LinkList<int> list;

    for(int i = 0; i<5; i++)
    {
    
    
        list.insert(i);
    }


    for(int i = 0; i < list.length(); i++)
    {
    
    

        cout << list.get(i) << endl;


        /* 未重载get() 函数
        int v = 0;
        list.get(i, v);
        cout << v << endl;
        */
    }



    return 0;
}

main.cpp(测试二)

/*
*  此处需要做详细记录:
*
*/


using namespace std;
using namespace DTLib;

class Test
{
    
    
public:
    Test()
    {
    
    
        throw 0;
    }
};

int main()
{
    
    

    LinkList<Test> list;

    cout << "Hello World" << endl;

    return 0;
}

LinkList.h(优化)

#ifndef LINKLIST_H
#define LINKLIST_H

#include "List.h"
#include "Exception.h"


 /*
  * 头节点的隐患: 自定义的头节点中仅仅使用了头节点的指针,数据与并没有使用
  *     如果将泛指类型T 指定为自定义的类类型
            class Test
            {
            public:
                Test()
                {
                    throw 0;
                }
            };
  * 这个Test 是使用DTLib的程序员犯下的错误,和我们无关; 这样使用的结果: 报错
  *
  * 我们根本没有创建有问题的类的对象,我们创建的是DTLib里的单链表对象,
  * 如果DTLib是一个商用的库,其他公司的程序员会打电话来抱怨库的质量问题;
  * 只看代码,出问题的应该是用户编写的Test类,但是当前代码中并没有创建Test类对象,
  *  那为什么会抛出这个异常呢?
  *
  * 查找后:
  * //mutable Node m_header; 有这行代码,,,
  * 主函数中创建LinkList对象,必然会调用构造函数,
  * 先构造成员对象 : mutable Node m_header;
  * 构造头节点的时候又会构造 value  (是用户自定义的 Test 对象,是一个有问题的对象,所以就抛异常了)

  * 技术支持肯定会说:是你代码的问题,是你代码导致这个异常出现,

  * 其他程序员会说: 确实创建了有问题的类,但是我在main函数中并没有创建这个类对象,为什么会抛异常?

  * 口水帐:::::::::::::

  * 为了避免口水战,就要想方设法的使得在:    构造头节点的时候,不去构造泛指类型的构造函数;

  * 这里必须定义一个内部的新类型了,并且这个类型是匿名的类型(类类型,没有具体的名字),这个类型的定义仅仅是为了头节点;

        mutable struct
        {
            char reserved[sizeof(T)];   // 这个数组没有实际作用,仅仅为了占空间
            Node* next;
        }m_header;

        构造头节点的时候,这里的实现当中不可能调用任何的构造函数了, 在内存布局上和之前没有差异,差异是:不管泛指类型是什么
        都不会调用构造函数了

        这就解决了刚刚的问题;;;;
  *
 */


namespace DTLib
{
    
    

template < typename T >
class LinkList : public List<T>
{
    
    
protected:
    struct Node : public Object
    {
    
    
        T value;        // 数据域,用来保存数据
        Node* next;     // 指针域, 用来保存下一个节点
    };

     //mutable Node m_header;

    // 头节点存在隐患,进行代码优化 , 此处改写为了避开泛指类型T 构造函数的调用, 匿名类和Node类应该内存相同,继承Object
    mutable struct : public Object
    {
    
    
        char reserved[sizeof(T)];
        Node* next;
    }m_header;


    int m_length;

    //  代码优化->元素定位
    Node* position(int i) const
    {
    
    
        Node* ret = reinterpret_cast<Node*>(&m_header);    //  由于类型不同, 不能直接进行初始化

        for(int p=0; p<i; p++)
        {
    
    
            ret = ret->next;
        }

        return ret;

    }

public:
    LinkList()
    {
    
    
        m_header.next = NULL;
        m_length = 0;
    }

     bool insert(const T& e)    // 重载          最后一个位置插入
     {
    
    
         return insert(m_length, e);
     }

    bool insert(int i, const T& e)
    {
    
    
        bool ret = ((0 <= i) && (i <= m_length));

        if(ret)
        {
    
    
            Node* node = new Node();

            if( node != NULL )
            {
    
    

                Node* current = position(i);
                /*
                //Node* current = &m_header;   // 指针指向头节点
                Node* current = reinterpret_cast<Node*>(&m_header);// 改变头节点以后的做法,为什么要这么做?

                for(int p=0; p<i; p++)
                {
                    current = current->next;  // 目标位置为 i, 就移动 i 次
                }
                */

                node->value = e;
                node->next = current->next;
                current->next = node;

                m_length++;
            }
            else
            {
    
    
                THROW_EXCEPTION(NoEnoughMemoryException, "No memery to insert new element...");
            }
        }
        return ret;
    }

    bool remove(int i)
    {
    
    
       bool ret = ((0 <= i) && (i <= m_length));


       if(ret)
       {
    
    

           Node* current = position(i);

           /*
           //Node* current = &m_header;
            Node* current = reinterpret_cast<Node*>(&m_header);// 改变头节点以后的做法

           for(int p=0; p<i; p++)
           {
               current = current->next;
           }
            */

           Node* toDel = current->next;  // 指针指向要删除的位置
           current->next = toDel->next;  // 链接

           delete toDel;

           m_length--;

       }

       return ret;
    }

    bool set(int i, const T& e)
    {
    
    
         bool ret = ((0 <= i) && (i <= m_length));


         if( ret )
         {
    
    
             /*
              Node* current = position(i);
            // Node* current = &m_header;
             Node* current = reinterpret_cast<Node*>(&m_header);// 改变头节点以后的做法

             for(int p=0; p<i; p++)
             {
                 current = current->next;
             }
             current->next->value = e;
             */

             position(i)->next->value = e;
         }
         return ret;
    }

    // get 函数使用不是很方便, 重新加载一个, 直接返回需要的值  ///
    T get(int i) const
    {
    
    
        T ret;

        if( get(i, ret) )
        {
    
    
            return ret;
        }
        else
        {
    
    
            THROW_EXCEPTION(IndexOutOfBoundsException, "Invalid parameter i to get element ...");
        }

        return ret;

    }



    bool get(int i, T& e) const
    {
    
    

        bool ret = ((0 <= i) && (i <= m_length));

        if( ret )
        {
    
    

            //Node* current = position(i);
            /*
            //Node* current = &m_header;       // 在const函数中不能修改任何成员变量的值,然而这里要取头节点的地址,
                                             // 此时编译器会认为有可能会修改成员变量的值
                                             // 解决方案: 将对应的成员加上 mutable 就可以了
            Node* current = reinterpret_cast<Node*>(&m_header);// 改变头节点以后的做法

            for(int p=0; p<i; p++)
            {
                current = current->next;
            }
            */

            e = position(i)->next->value;
        }
        return ret;
    }

    bool length() const
    {
    
    
        return m_length;
    }

    bool clear()
    {
    
    
        while( m_header.next )            // 通过第0号位置(头节点)的节点开始释放
        {
    
    
            Node* toDel = m_header.next;  // 指向了第第0号位置(头节点)的节点,是即将被删除的节点
            m_header.next = toDel->next;  // 头节点的指针指向下一个节点

            delete toDel;                 // 循环到头节点的指针为空时结束
        }

        m_length = 0;

    }

    ~LinkList()
    {
    
    
        clear();
    }


};

}


#endif // LINKLIST_H


main.cpp(优化)

#include <iostream>
#include "LinkList.h"


using namespace std;
using namespace DTLib;


/*  测试一:
 * 可以看到是先打印的Hello World然后报的异常,这就是用户定义Test类对象的问题了,这应该由用户自己负责了。
 *
 *
class Test
{
public:
    Test()
    {
        throw 0;
    }
};

int main()
{

    LinkList<Test> list;

    cout << "Hello World" << endl;

    Test t;

    list.insert(t);

    return 0;
}
*/

/* 测试二:

这时只打印出了16,这不是我们想要的,可见重构代码出现了问题。

这时因为我们的匿名类型变量m_header没有继承自Object,
这样的话导致m_header和Node的内存布局有可能不一样,

*/

class Test
{
    
    
public:
    Test()
    {
    
    
        throw 0;
    }
};

int main()
{
    
    
    LinkList<Test> lt;
    LinkList<int> list;

    for(int i = 0; i<5; i++)
    {
    
    
        list.insert(0,i);
        list.set(0, i * i);
    }

    for(int i = 0; i < list.length(); i++)
    {
    
    
        cout << list.get(i) << endl;
    }

    list.clear();

    for(int i=0; i<list.length(); i++)
    {
    
    
        cout << list.get(i) << endl;
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/dashuu/article/details/114851817