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