数据结构--单链表实现

上一篇讲到了链式存储结构的基本操作,本篇就对具体操作在代码层上进行实现。

我们需要设计一个单链表的类LinkList:

使用类模板实现。

继承自List类。

通过头结点访问后继结点。

定义内部结点类型Node,用于描述数据域和指针域。

类里实现链表的关键操作(增删待查等等)。

看一个LinkList类的声明:

    template <typename T>
    class LinkList : public List<T>
    {
    protected:
        struct Node : public Object//顶层父类实现一些操作符重载如new、delete、[]、==、!=等等
        {
            T value;
            Node* next;
        };
        mutable Node m_header;
        int m_length;


    public:
        LinkList();
        bool insert(int i,const T& e);  
        bool insert(const T& e);      
        bool remove(int i);       
        bool set(int i,const T& e);     
        bool get(int i,T& e )const;      
        T get(int i)const;
        int length()const;
        void clear();
        ~LinkList();

    };
定义了一个Node类,继承自顶层父类Object,包含数据变量和Node类型指针。

定义一个Node类型的头结点用于辅助获取元素位置。

定义一个表示链表长度的变量m_length。

类成员函数包括构造函数和析构函数、插入及其重载、删除、获取元素、设置元素值、获取链表长度、清空链表等操作。

下面看功能具体实现:

一、构造函数

构造函数用于设置头结点的指向(NULL)和初始化链表长度。


二、插入

链表插入元素上一篇已经讲过,操作步骤是先定位到待插入位置的前一个位置,再进行插入操作。

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

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

                if(node != NULL)
                {

                    Node* current = reinterpret_cast<Node*>(&m_header);
                    for(int p = 0; p < i; p++)
                    {
                        current = current->next;
                    }
                    node->value = e;
                    node->next = current->next;
                    current->next = node;
                    m_length++;

                }
                else
                {
                    THROW_EXCEPTION(NoEnoughMemoryException,"No memory to new ");
                }
            }

            return ret;
        }

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


三、删除元素

执行顺序也是先定位后删除。

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

            if(ret)
            {
                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 = (i >= 0) && (i < m_length);

            if(ret)
            {
                Node* current = reinterpret_cast<Node*>(&m_header);
                for(int p = 0; p < i; p++)
                {
                    current = current->next;
                }
                current->next->value = e;
            }

            return ret;
        }
        bool get(int i,T& e )const
        {
            bool ret = (i >= 0) && (i < m_length);

            if(ret)
            {
                Node* current = reinterpret_cast<Node*>(&m_header);

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

            return ret;

        }

        T get(int i)const//根据索引直接获取元素值
        {
            T ret;

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

            return ret;
        }


五、获取长度

返回m_length即可。


六、析构函数
执行清空线性表操作。


七、头结点的隐患

前面设计中我们将头结点定义为Node类型,这本身是没有什么问题的,但是当我们在使用过程中要是自定义类类型时就可能出错,为什么呢?

定义一个在构造函数中抛出异常的类:

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


执行:

LinkList<Test> list;//Test为类名
当执行这条语句时就会创建一个Test类型的头结点,在头结点中就会执行 T value;但是T在此时为类类型,所以就会调用构造函数,所以就会抛出异常打断程序执行流,这显然不是我们期望的,因为我们就没有创建Test对象为什么会执行构造函数呢?这时我们就需要想办法阻止T value;在T为类类型时调用构造函数。

由于在头结点中value并没有实际作用,所以我们就想办法避开,定义一个匿名结构,包含一个字符数组,长度为T的大小,这样就能使字符数组在内存排布上与value相同,这样就避开了构造函数的调用,具体实现如下:

       mutable struct : public Object//如果未继承自Object可能导致内存布局和Node m_header内存布局不同。
        {

            char reserved[sizeof(T)];
            Node* next;

        }m_header;
为什么加上mutable关键字?因为在类的实现中编译器以为我们修改了成员变量(const函数内不允许修改成员变量)。

最后我们将new和delete封装起来:

        virtual Node* create()
        {
            return new Node();
        }

        virtual void destroy(Node* pn)
        {
            delete pn;
        }
为什么这么做?请看我下下篇里的描述。提醒一下,虚函数用于重写函数实现多态特性。

总结;

通过类模板实现链表,包含头结点成员和长度成员。

定义结点类型,并通过堆中的结点对象构成链式存数。

为了避免构造错误的隐患,头结点类型需要重定义。








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值