本节我们要实现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函数实现需要保证异常安全