静态单链表是一种新的数据结构类型。
我们往线性表中添加的元素的个数是固定的,例如最大100个。
只是这100个元素会经常的变动。
这时候是顺序表还是单链表合适呢?
显然是单链表,但是单链表也有问题。
缺陷:
解决方案:
我们在顺序表的内部预留了空间,这片空间用来增加删除数据元素。配合单链表就形成了静态单链表。
在静态单链表中的操作和普通单链表几乎一样,只有两个函数有差异:create和destroy
静态单链表的继承层次结构:
示例:
编译时,第15行报错,因为第15行的写法中,编译器不知道Node是一个类型还是一个静态变量。因此,我们需要加上typename关键字。
写成以下方式就可以了:
1 unsigned char m_space[sizeof(typename LinkList<T>::Node) * N];
但是这样写太繁琐了,而且第18行的Node也要修改,我们改成以下的形式:
这样就没有错误了。
create函数如下:
获得空间的指针后,由于Node中可能含有自定义类型T对象,因此,我们还需要调用Node的构造函数。通过重载new来实现。
我们自定义一个类型SNode,并且重载new,在指定的地址上调用构造函数,然后直接返回这个调用的地址即可。
完整的StaticLinkList.h如下:
1 #ifndef STATICLINKLIST_H 2 #define STATICLINKLIST_H 3 4 #include "LinkList.h" 5 6 namespace DTLib 7 { 8 9 template< typename T, int N > 10 class StaticLinkList : public LinkList<T> 11 { 12 protected: 13 // Node和泛指类型T有关系,因此,不能直接在子类中使用sizeof(Node),而应该 14 // sizeof(LinkList<T>::Node) 15 // unsigned char m_space[sizeof(typename LinkList<T>::Node) * N]; // 预留空间 16 typedef typename LinkList<T>::Node Node; 17 18 struct SNode : public Node 19 { 20 void* operator new(unsigned int size, void* loc) 21 { 22 (void)size; // 消除 size没有使用的警告 23 return loc; 24 } 25 }; 26 27 unsigned char m_space[sizeof(SNode) * N]; // 预留空间 28 int m_used[N]; //预留空间的标记数组 29 30 Node* create() 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; 39 ret = new(ret)SNode(); //在指定空间ret上调用SNode类的构造函数。 40 m_used[i] = 1; 41 break; 42 } 43 } 44 45 return ret; 46 } 47 48 void destroy(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( psn == (space + i)) 56 { 57 m_used[i] = 0; 58 psn->~SNode(); 59 } 60 } 61 } 62 63 public: 64 StaticLinkList() 65 { 66 for(int i = 0; i < N; i++) 67 { 68 m_used[i] = 0; 69 } 70 } 71 72 int capacity() 73 { 74 return N; 75 } 76 }; 77 78 } 79 80 #endif // STATICLINKLIST_H
第39行的程序会调用到自己重载的new(这里是placement new)操作符,在重载的new操作符中返回的还是ret(相当于申请了空间并返回地址)。然后在指定的地址ret上调用构造函数,
第39行就是placement new的使用,第20行重载的也是placement new。
对于自定义的类,如果想在指定的地址上调用构造函数,必须要重载placement new,否则编译报错。
自定义类在指定地址上调用构造函数就和本程序中第39行类似,new的行为是先返回申请的地址(placement new的重载中返回了loc),然后在这个地址上调用构造函数。
如果我们在SNode中重载普通的new,并且在这个new函数中搜索预留的空间,找到空间后返回首地址。这样的话我们调用new SNode()时就会返回首地址,并且在这个地址上调用构造函数。这样的话在其他成员函数中向获得Node对象时就得使用new SNode()这种操作。而我们想统一接口,使用create返回Node对象,因此,在create中先搜索地址,又调用了placement new,placement new中只返回地址。
create还有一种可能的实现方式就是,在create函数中直接调用普通的new,在SNode中重载普通的new,在这个new中搜索地址,并返回地址,返回地址到create后会自动调用构造函数。如果是在堆上分配空间,或者预留空间是在静态区上,那么这种实现方式没有问题,而在我们的程序中,m_space和m_used都是普通的成员变量,搜索空间必然会用到这几个变量,而重载的new(placement new)默认就是静态的,因此,在重载new函数中不能使用m_space和m_used,而如果把它们定义成静态的,那就会被所有对象共用,这又不符合逻辑,所以这种create实现方式被否定了。
测试程序如下:
1 #include <iostream> 2 #include "StaticLinkList.h" 3 4 5 using namespace std; 6 using namespace DTLib; 7 8 9 int main() 10 { 11 StaticLinkList<int, 5> list; 12 13 for(int i = 0; i<5; i++) 14 { 15 list.insert(0,i); 16 } 17 18 //遍历时必须先调用move函数 19 for(list.move(0); !list.end(); list.next()) 20 { 21 cout << list.current() << endl; 22 } 23 24 return 0; 25 }
结果如下:
第二个测试程序如下:
1 #include <iostream> 2 #include "StaticLinkList.h" 3 4 5 using namespace std; 6 using namespace DTLib; 7 8 9 int main() 10 { 11 StaticLinkList<int, 5> list; 12 13 for(int i = 0; i<5; i++) 14 { 15 list.insert(0,i); 16 } 17 18 list.insert(6); 19 20 //遍历时必须先调用move函数 21 for(list.move(0); !list.end(); list.next()) 22 { 23 cout << list.current() << endl; 24 } 25 26 return 0; 27 }
结果:
这是由于,在第18行我们又尝试插入一个元素,而我们定义的空间大小本身就是5,没有了足够的空间,StaticLinkList类中的create函数就返回NULL,这个类中的insert函数是继承自LinkList类的,在insert中会判断指针是否为空,如果为空就抛出异常。这里就和我们的运行结果对应上了。
更改测试程序:
1 #include <iostream> 2 #include "StaticLinkList.h" 3 4 5 using namespace std; 6 using namespace DTLib; 7 8 9 int main() 10 { 11 StaticLinkList<int, 5> list; 12 13 for(int i = 0; i<5; i++) 14 { 15 list.insert(0,i); 16 } 17 18 try 19 { 20 list.insert(6); 21 } 22 catch(Exception& e) 23 { 24 cout << e.message() << endl; 25 } 26 27 //遍历时必须先调用move函数 28 for(list.move(0); !list.end(); list.next()) 29 { 30 cout << list.current() << endl; 31 } 32 33 return 0; 34 }
结果如下:
小结: