静态单链表是一种新的数据结构类型。

我们往线性表中添加的元素的个数是固定的,例如最大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 }

结果如下:

 

 

 

 小结:

 

posted on 2018-09-15 14:41  周伯通789  阅读(379)  评论(0编辑  收藏  举报