• 十一、静态单链表的实现


    1、静态单链表的提出

    需要频繁增删数据元素,可以选择单链表,如果数据元素的最大个数是固定的,可能需要一种新的数据结构

    单链表的一个缺陷

    • 触发条件:长时间使用单链表对象频繁增删数据元素
    • 可能结果:堆空间产生大量的内存碎片,导致系统运行缓慢

    原因:每增加一个数据元素,都会在堆空间中创建一个数据结点,程序上看没问题,但是在一些系统中,长时间频繁增删堆空间结点,堆空间就可能产生大量内存碎片,直接结果就是系统运行缓慢。

    解决方案:设计一种新的线性表

    设计思路:在单链表的内部增加一片预留的空间,所有的Node对象都在这篇空间中动态创建和动态销毁

    静态单链表需要的只是预留一片固定大小的连续存储空间(栈、堆、全局),不管出处只要是连续的就行,插入数据元素的时候就去这片连续空间中去看,是否有空闲的空间,找到之后就在这个空间中创建Node对象;删除的时候,现在这个内存空间中调用Node对象的析构函数,将这片内存空间标志为可用就行。

    除了内存分布的不同,静态单链表和单链表是一样的,从代码上看,只需要更改creatdestory这样个函数就行了,以前操作的是堆空间中内存,现在操作的是那一片连续的内存空间了。

    2、静态单链表的继承层次结构

    3、静态单链表的实现

    实现思路:

    • 通过模板定义静态单链表类StaticLinkList
    • 在类中定义固定大小的空间unsigned char[]
    • 重写creatdestory函数,改变内存的分配和归还的方式
    • Node类中重载operator new操作符,用于在指定内存上创建对象
    template <typename T, int N>
    class StaticLinkList : public LinkList<T>
    {
        typedef typename LinkList<T>::Node Node;
    protected:
        unsigned char m_space[sizeof(Node) * N];	// 定义内存空间
        int m_used;	// 状态标记
          
        // 分配内存单元,创建Node对象
        Node* create()
        {
            Node ret = NULL;
            
            // 遍历判断m_used是否可用
            for(int i = 0; i < N; i++)
            {
                if ( !m_used[i])	// 当前内存可用的时候,就可以用来分配使用了
                {
                    ret = reinterpret_cast<Node*>(m_used) + i;	// 找到未使用的一个内存空间
                    // 步长为sizeof(Node)
                    m_used[i] = 1;	// 标记使用
                    break;
                }
            }
            
            return ret;
        }
        
        void destroy(Node* pn)
        {
            
        }   
    };
    

    注意这里会存在一个问题, 由于Node存在着一个泛指类类型成员变量value

    struct Node : public Object
    {
        T value;    // 数据域
        Node* next; // 指针域
    };
    

    value可能会是一个用户自定义类类型对象,这里就会涉及到对象的构造问题,那么如何构造这个value,必然涉及到NOde的构造函数的调用,在什么时候调用Node的构造函数?上面的代码只是定义指向了一个新的内存空间,并不会调用构造函数

    解决办法就是重载new操作符在指定内存空间上申请内存,此时就可以调用构造函数,具体实现的时候,我们定一个新的类,里面只有一个new操作符重载函数:

        struct SNode : public Node
        {
            // 在指定内存上调用构造函数,需要两个参数
            // 这就是定位new的方法,允许在一个特定的内存地址上构造对象
            // 只传入一个指针类型的是实参时,定位new表达式构造对象但是不分配内存      
            void* operator new(size_t size, void* loc)
            {
                (void)size;
                return loc;
            }
        };
    
     Node* create()
        {
            Node* ret = NULL;
            
            for(int i = 0; i < N; i++)
            {
                if ( !m_used[i])
                {
                    ret = reinterpret_cast<Node*>(m_used) + i;
                    ret = new(ret) SNode();	// 调用SNode类的构造函数
                    m_used[i] = 1;
                    break;
                }
            }
            
            return ret;
        }
    

    destroy函数归还空间,找到对应空间之后对标志位清0,并调用该对象的析构函数

        void destroy(Node* pn)
        {
            SNode* space = reinterpret_cast<SNode*>(m_space);
            SNode* spn = dynamic_cast<SNode*>(pn);
            for(int i = 0; i < N; i++)
            {
                if (spn == space + i)	// 找到需要归还的内存单元
                {
                    m_used[i] = 0;		// 标记可用
                    spn->~SNode();		// 调用析构函数,这里需要使用的是子类的析构函数,故需要对pn进行一个强制类型转换成子类对象
                }
            }
        }
    

    完善其它函数

    // 构造函数:标记每个内存单元可用
    public:
        StaticLinkList()
        {
            for(int i = 0; i < N; i++)
            {
                m_used[i] = 0;
            }
        }
    
        int capacity()
        {
            return N;
        }
    

    LinkList中封装createdestroy函数意义:

    为静态单链表StaticLinkList的实现做准备,StaticLinkListLinkList的不同仅在于链表结点内存分配上的不同;因此,将仅有的不同封装于父类和子类的虚函数中。

    4、小结

    顺序表与单链表结合衍生出静态单链表。

    静态单链表是LinkList的子类,拥有单链表的所有操作

    静态单链表在预留的空间中创建和删除结点对象。

    静态单链表适合于频繁增删数据元素且最大元素个数固定的场合。

  • 相关阅读:
    tensorflow目标检测API安装及测试
    转 fine-tuning (微调)
    在Keras中导入测试数据的方法
    转 Keras 保存与加载网络模型
    Keras预训练模型下载后保存路径
    软件工程----心得体会
    结对编程项目-四则运算
    PSP记录个人项目耗时情况
    代码复审
    代码规范
  • 原文地址:https://www.cnblogs.com/chenke1731/p/9580708.html
Copyright © 2020-2023  润新知