zoukankan      html  css  js  c++  java
  • 线性表的链式存储——静态单链表的实现

    1,单链表的一个缺陷:

           1,触发条件:

                  1,长时间使用单链表对象频繁增加和删除数据元素;

           2,可能的结果:

                  1,堆空间产生大量的内存碎片,导致系统运行缓慢;

                         1,增加一个节点,就会在堆空间创建一个结点,但是频繁创建删除就会有大量碎片;

                        

    2,解决方案,设计新的线性表:

           1,设计思路:

                  1,在“单链表”的内部增加一片预留的空间,所有的 Node 对象都在这片空间中动态创建和动态销毁;

                  2,顺序表 + 单链表 = 静态单链表;

                  3,除了内存分配的不同外,静态单链表和单链表在其他操作上完全一样,只用改写 creat()和destory();

                    

    3,静态单链表继承层次结构:

     

          

    4,静态单链表的实现思路(核心):

           1,通过模板定义静态单链表(StaticLinkList);

           2,在类中定义固定大小的空间(unsigned char[]);

                  1,创建 Node 结点;

           3,重写 creat() 和 destroy() 函数,改写内存分配和归还方式;

           4,在 Node 类中重载 operator new,用于在指定内存上创建对象;

    5,StaticLinkList 静态单链表的实现  :

     1 #ifndef STATICLINKLIST_H
     2 #define STATICLINKLIST_H
     3 
     4 #include "LinkList.h"
     5 
     6 using namespace std;
     7 
     8 namespace DTLib
     9 {
    10 
    11 template <typename T, int N>
    12 class StaticLinkList : public LinkList<T>
    13 {
    14 protected:
    15    typedef typename LinkList<T>::Node Node;  // 这里的 typename 是因为编译器不知道这里的 Node 是类型还是静态成员变量,所以编译器建议加上 typename,然后又用 typedef 来简化一个类型;
    16 
    17     struct SNode : public Node  // 定义新的类型来重载 new 操作符;
    18     {
    19         void* operator new(unsigned int size, void* loc)    
    20         {
    21             (void)size;  // 这里是因为编译时候,没有使用 size 参数,然后加入的 C 语言中的暴力的编译方式;
    22 
    23             return loc;
    24         };
    25    };
    26 
    27     unsigned char m_space[sizeof(SNode) * N];  // 在这片内存里面来分配静态链表的内存;
    28    int m_used[N];  // 标记数组,通过相应位置上为 1 可用,为 0 不可用;
    29 
    30     Node* creat()  // 申请空间,调用构造函数
    31     {
    32         SNode* ret = NULL;
    33 
    34         for(int i=0; i<N; i++)  // 遍历指定内存每个空间;
    35         {
    36             if( !m_used[i] )  // 如果可以使用,就用;
    37             {
    38                 ret = reinterpret_cast<SNode*>(m_space) + i;  // 这里只是单纯的分配内存,并没有涉及到 Node 的构造函数调用,内存的分配和初始化是两个不同的步骤,这里需要重新解释内存空间才可以进行指针运算;
    39                 ret = new(ret)SNode();   // 内存分配好后,还要在指定的内存上面调用构造函数,调用重载的 new 函数;这里重写后将 new 申请的地址放在了 ret 上面,而不是默认的堆空间上,然后继续像普通的 new 一样调用构造函数来初始化内存;
    40                 m_used[i] = 1;  // 标记被分配;
    41                 break;   //这里要跳出 for 循环,因为不是依靠 i 来结束的
    42             }
    43         }
    44 
    45         return ret;  // 这里的返回值运用了赋值兼容性;
    46    }
    47 
    48     void destory(Node *pn)  // 归还空间,调用析构函数
    49     {
    50         SNode* space = reinterpret_cast<SNode*>(m_space);   // 指针运算,所以要转换指针类型
    51         SNode* psn = dynamic_cast<SNode*>(pn);
    52 
    53         for(int i=0; i<N; i++)
    54         {
    55             if( pn == (space + i) )
    56             {
    57                 m_used[i] = 0; // 标记当前内存单元可用,也就是将其归还给固定空间;
    58                 psn->~SNode();  // 调用析构函数,释放对象;
    59                 break;  // 释放对象后就直接跳出循环,提高效率;
    60             }
    61         }
    62    }
    63 
    64 public:
    65     StaticLinkList()
    66     {
    67         for(int i=0; i<N; i++)  // 标记每一个内存单元都是可用的
    68         {
    69             m_used[i] = 0;
    70         }
    71    }
    72 
    73     int capacity()
    74     {
    75         return N;
    76    }
    77 
    78     ~StaticLinkList()   // 根据资源申请原则,不许用定义这个函数,但前提是这个类是独立的类,但这里是继承的类,所以还要调用父类的析构函数但是父类中析构函数调//用 clear() 不会发生多态(虽然父类和子类中就只有一个这样的函数),然后再调用 destroy()也不会发生多态,只调用父类中的 destroy(),这就造成了 delete 非堆空间上的不稳定性,程序容易发生错误;
    79     {
    80         this->clear();   // 这里不会发生多态,调用的是自己的 destroy(),因为这里是在析构函数中;
    81     }
    82 };
    83 
    84 }
    85 
    86 #endif // STATICLINKLIST_H

    6,LinkList 中封装 create 和 destroy 函数的意义是什么?

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

           2,仅重写创建和销毁函数就可以了,其他的直接继承;

           3,create() 和 destroy() 调用要用到多态;

          

    7,小结:

           1,顺序表与单链表相结合后衍生出静态单链表;

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

           3,静态单链表在预留的空间中创建结点对象;

           4,静态单链表适合于频繁增删数据元素的场合(最大元素个数固定,如果确定不了,还是要用单链表);

  • 相关阅读:
    .Net5开发MQTT服务器
    使用Docker搭建MQTT服务器
    使用Docker搭建MQTT服务器
    在Raspberry Pi上安装Docker
    阿里云=>RHSA-2019:1884-中危: libssh2 安全更新
    MediaAPIController
    CentOS7 mysql支持中文
    设置centos7中的mysql5.7不区分表名大小写有关操作
    CentOS7 安装mysql(YUM源方式)
    centos7下安装nginx
  • 原文地址:https://www.cnblogs.com/dishengAndziyu/p/10921947.html
Copyright © 2011-2022 走看看