本节我们要实现StaticList和DynamicList,如下:

 

StaticList的设计要点:

StaticList是一个类模板,使用原生数组作为顺序存储空间,使用模板参数决定数组大小

 

  在StaticList的类模板中我们定义了一个元素数组作为顺序存储空间。这就是static的含义。因此,需要在构造函数当中将这个顺序存储空间挂接到父类的m_array上。

StaticList.h如下:

 1 #ifndef STATICLIST_H
 2 #define STATICLIST_H
 3 
 4 #include "SeqList.h"
 5 
 6 namespace DTLib
 7 {
 8 
 9 template < typename T, int N >
10 class StaticList : public SeqList<T>
11 {
12 protected:
13     T m_space[N];    //顺序存储空间,N为模板参数
14 public:
15     StaticList()    //指定父类成员的具体值
16     {
17         this->m_array = m_space;
18         this->m_length = 0;
19     }
20 
21     int capacity() const
22     {
23         return N;
24     }
25 };
26 
27 }
28 
29 #endif // STATICLIST_H

main函数测试程序如下:

 1 #include <iostream>
 2 #include "List.h"
 3 #include "SeqList.h"
 4 #include "StaticList.h"
 5 
 6 using namespace std;
 7 using namespace DTLib;
 8 
 9 
10 int main()
11 {
12     StaticList<int, 5> l;
13 
14     for(int i = 0; i < l.capacity(); i++)
15     {
16         l.insert(0, i);
17     }
18 
19     for(int i = 0; i < l.capacity(); i++)
20     {
21         cout << l[i] << endl;
22     }
23 
24     return 0;
25 }

执行结果如下:

更改测试程序如下:

 1 #include <iostream>
 2 #include "List.h"
 3 #include "SeqList.h"
 4 #include "StaticList.h"
 5 #include "Exception.h"
 6 
 7 using namespace std;
 8 using namespace DTLib;
 9 
10 
11 int main()
12 {
13     StaticList<int, 5> l;
14 
15     for(int i = 0; i < l.capacity(); i++)
16     {
17         l.insert(0, i);
18     }
19 
20     for(int i = 0; i < l.capacity(); i++)
21     {
22         cout << l[i] << endl;
23     }
24 
25     try
26     {
27         l[5] = 5;
28     }
29     catch(IndexOutOfBoundsException& e)
30     {
31         cout << e.message() << endl;
32         cout << e.location() << endl;
33     }
34 
35     return 0;
36 }

第27行我们执行越界赋值操作,执行结果如下:

 

 DynamicList的设计:

使用类模板

  申请连续堆空间作为顺序存储空间(StaticList使用的存储空间是“栈空间”(当我们定义的StaticList类对象也在栈上时))

  动态设置顺序存储空间的大小(StaticList中的存储空间大小是固定的,不能动态设置)

  保证重置顺序存储空间时的异常安全性

DynamicList中的存储空间是动态申请的,而且可以动态设置大小,实现上会比StaticList复杂。

DynamicList的设计要点:

函数异常安全的概念

  不泄露任何资源

  不允许破坏数据

函数异常安全的基本保证

  如果异常被抛出

    对象内的任何成员仍然能保持有效状态

    没有数据的破坏及资源泄漏

 

 

  DynamicList中就没有原生数组了,只有一个m_capacity代表存储空间的大小。这个大小就不是通过模板参数来指定了,而是通过构造函数的参数来指定。在构造函数中申请堆空间。此外,还是先了resize函数,用于设置顺序存储空间的大小。在析构函数中归还空间。

 DynamicList.h程序如下:

 

 1 #ifndef DYNAMICLIST_H
 2 #define DYNAMICLIST_H
 3 
 4 #include "SeqList.h"
 5 #include "Exception.h"
 6 
 7 namespace DTLib
 8 {
 9 
10 template <typename T>
11 class DynamicList : public SeqList<T>
12 {
13 protected:
14     int m_capacity;    //顺序存储空间的大小
15 public:
16     DynamicList(int capacity)   //申请空间
17     {
18         this->m_array = new T(capacity);
19 
20         if( this->m_array != NULL)
21         {
22             this->m_length = 0;
23             this->m_capacity = capacity;
24         }
25         else
26         {
27             THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create DynamicList object ...");
28         }
29     }
30 
31     int capacity() const
32     {
33         return m_capacity;
34     }
35 
36     //重新设置存储空间的大小
37     void resize(int capacity)
38     {
39         if(capacity != m_capacity)
40         {
41             T* array = new T[capacity];
42 
43             if(array != NULL)
44             {
45                 int length = (this->m_length < capacity ? this->m_length : capacity);
46 
47                 for(int i = 0; i < length; i++)
48                 {
49                     array[i] = this->m_array[i];   //这里必须用this->m_array,不能直接用m_array,其他在父类中定义的成员同理
50                 }
51 
52                 T* temp = this->m_array;
53 
54                 this->m_array = array;
55                 this->m_length = length;
56                 this->m_capacity = capacity;
57 
58                 delete[] temp;
59             }
60             else
61             {
62                 THROW_EXCEPTION(NoEnoughMemoryException, "No memory to resize DynamicList object ...");
63             }
64         }
65     }
66 
67     ~DynamicList()
68     {
69         delete[] this->m_array;
70     }
71 };
72 
73 }
74 
75 #endif // DYNAMICLIST_H

  第41行中我们没有直接操作this->m_array,因为重置空间后我们原来的数据元素还需要保留,因此,我们在47-50行中将原来的数据元素拷贝到新空间中。所以,我们不能让m_array指向新申请的空间,必须让它指向原来的存储空间。

  52行我们定义了一个临时指针指向原来的存储空间,到了58行才delete原来的空间。为什么要这样做呢?因为delete有可能触发数据元素的析构函数,而在这个析构函数中有可能抛出异常。如果我们在52行直接delete this->m_array,就有可能触发析构函数,并在析构对象是引发异常(数据元素对象是用户定义的),函数就在52行异常返回了,就意味着下面的赋值操作全都不能执行到,那样的话当前的线性表就不是合法可用的了。也就是这个resize函数就不是异常安全的了。而用这种临时指针的方式,可以保证54-57被执行到,而且这三行不会发生异常。执行完这些赋值,再执行58行,即使这时候发生异常,那m_array,m_length,m_capacity也已经是合法的了,也可以保证我们的线性表对象也是合法可用的,这就做到了异常安全。

  第49行的赋值操作也有可能发生异常,在这里发生异常,m_array,m_length,m_capacity这几个成员变量的值没有发生任何改变,所以当前这个线性表对象依然是合法可用的。只是array指向的堆空间会被泄漏。这一点对于resize函数来说就无法顾全了,因为数据类型T是用户指定的,赋值操作符也可能被重载,并且重载的函数中可能发生异常,这些我们都无法顾全。这是第三方工程师代码的问题。这样的问题只能交给第三方工程师自己来考虑。

 main函数测试程序如下:

 

 1 #include <iostream>
 2 #include "List.h"
 3 #include "SeqList.h"
 4 #include "StaticList.h"
 5 #include "Exception.h"
 6 #include "DynamicList.h"
 7 
 8 using namespace std;
 9 using namespace DTLib;
10 
11 
12 int main()
13 {
14 
15     DynamicList<int> l(5);
16 
17     for(int i = 0; i < l.capacity(); i++)
18     {
19         l.insert(0, i);
20     }
21 
22     for(int i = 0; i < l.length(); i++)
23     {
24         cout << l[i] << endl;
25     }
26 
27     try
28     {
29         l[5] = 5;
30     }
31     catch(const Exception& e)
32     {
33         cout << e.message() << endl;
34         cout << e.location() << endl;
35 
36         l.resize(10);
37         l.insert(5, 50);
38     }
39 
40     l[5] = 5;
41 
42     for(int i = 0; i < l.length(); i++)
43     {
44         cout << l[i] << endl;
45     }
46 
47     return 0;
48 }

 

执行结果如下:

 

DynamicList不能作为StaticList的子类实现,反之也是不可以的,因为这两个类对于顺序存储空间的指定是没有任何关系的,截然相反的。

小结:

  StaticList通过模板参数定义顺序存储空间,并且将原生的数组作为顺序存储空间使用

  DynamicList通过动态内存申请定义顺序存储空间

  DynamicList支持动态重置顺序存储空间的大小

  DynamicList中的resize函数实现需要保证异常安全

 

posted on 2018-08-19 17:13  周伯通789  阅读(520)  评论(0编辑  收藏  举报