线性表的链式存储——静态单链表的实现

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

posted @ 2019-05-25 11:55  子宇24  阅读(338)  评论(0编辑  收藏  举报