我们上节博客说到了自己完成一个数组类,以此来区分数组和线性表。那么我们来看看数组类的具体实现,它也同样有两个子类 StaticArray 和 DynamicArray。如下
下来我们先来看看 StaticArray 的实现,那么我们创建的数组类是用来代替原生数组的使用。这个数组类应包含长度信息,数组类也能主动发现越界访问。那么 Array 的设计要点有哪些呢?1、抽象类模板,存储空间的位置和大小由子类完成;2、重载数组操作度,判断访问下标是否合法;3、提供数组长度的抽象访问函数;4、提供数组对象间的赋值操作。
Array 类的声明如下
template < typename T > class Array : public Object { protected: T* m_array; public: virtual bool set(int i, const T& e); virtual bool get(int i, T& e) const; virtual int length() const = 0; // 数组访问操作符 T& operator[] (int i); T operator[] (int i) const; }
下来我们来看看 Array 类的具体实现
Array.h 源码
#ifndef ARRAY_H #define ARRAY_H #include "Object.h" #include "Exception.h" namespace DTLib { template < typename T > class Array : public Object { protected: T* m_array; public: virtual bool set(int i, const T& e) { int ret = ((0 <= i) && (i < length())); if( ret ) { m_array[i] = e; } return ret; } virtual bool get(int i, T& e) const { int ret = ((0 <= i) && (i < length())); if( ret ) { e = m_array[i]; } return ret; } T& operator[] (int i) { int ret = ((0 <= i) && (i < length())); if( ret ) { return m_array[i]; } else { THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invalid ..."); } } T operator[] (int i) const { return (const_cast<Array<T>&>(*this))[i]; } virtual int length() const = 0; }; } #endif // ARRAY_H
A、下来我们来实现 StaticArray 类,先来看看 StaticArray 类的设计要点(以类模板的形式):1、封装原生数组;2、使用模板参数决定数组大小;3、实现函数返回数组长度;4、拷贝构造和赋值操作。
StaticArray.h 源码
#ifndef STATICARRAY_H #define STATICARRAY_H #include "Array.h" #include "Exception.h" namespace DTLib { template < typename T, int N > class StaticArray : public Array<T> { protected: T m_space[N]; public: StaticArray() { this->m_array = m_space; } StaticArray(const StaticArray<T, N>& obj) { this->m_array = obj.m_array; for(int i=0; i<N; i++) { m_space[i] = obj.m_space[i]; } } StaticArray<T, N>& operator= (const StaticArray<T, N>& obj) { if( this != &obj ) { for(int i=0; i<N; i++) { m_space[i] = obj.m_space[i]; } } return *this; } int length() const { return N; } }; } #endif // STATICARRAY_H
下来我们来测试下这个 StaticArray 类,看看是否完整实现,main.cpp
#include <iostream> #include "StaticArray.h" using namespace std; using namespace DTLib; int main() { StaticArray<int, 5> s1; for(int i=0; i<s1.length(); i++) { s1[i] = i * i; } for(int i=0; i<s1.length(); i++) { cout << s1[i] << endl; } StaticArray<int, 5> s2; s2 = s1; for(int i=0; i<s2.length(); i++) { cout << s2[i] << endl; } int s3[5]; s3[6] = 100; return 0; }
我们定义了数组 s1,它一共有5个元素。我们将其赋值 i*i,再定义数组 s2,将 s1 直接赋值 s2。那么 s2 的结果应该和 s1 是一样的。再次定义了 s3[5] ,访问 s3[6],明显看出越界了。我们看看结果
我们看到编译运行正常结束了,和我们分析的是一样的。s3 的越界问题也没有暴露出来,那么这个 bug 究竟会何时出问题呢?谁也不知道。我们再次在程序中访问 s2[6] ,来看看编译是否会通过
我们看到运行直接出错了,越界异常。
B、我们下来看看 DynamicArray 类的实现。那么我们先来看看 DynamicArray 类的设计要点(也是以类模板的形式):1、动态确定内部数组空间的大小;2、实现函数返回数组长度;3、拷贝构造和赋值操作。
DynamicArray.h 源码
#ifndef DYNAMICARRAY_H #define DYNAMICARRAY_H #include "Array.h" #include "Exception.h" namespace DTLib { template < typename T > class DynamicArray : public Array<T> { protected: int m_length; T* copy(T* array, int len, int newlen) { T* ret = new T[newlen]; if( ret != NULL ) { int size = (len < newlen) ? len : newlen; for(int i=0; i<size; i++) { ret[i] = array[i]; } } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to copy object ..."); } return ret; } void update(T* array, int length) { if( array != NULL ) { T* temp = this->m_array; this->m_array = array; this->m_length = length; delete[] temp; } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to update object ..."); } } void init(T* array, int length) { if( array != NULL ) { this->m_array = array; this->m_length = length; } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create DynamicArray object ..."); } } public: DynamicArray(int length) { this->m_array = new T[length]; if( this->array != NULL ) { this->m_length = length; } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create DynamicArray object ..."); } } DynamicArray(const DynamicArray<T>& obj) { this->m_array = new T[obj.m_length]; if( this->array != NULL ) { this->m_length = length; for(int i=0; i<obj.m_length; i++) { this->m_array[i] = obj.m_array[i]; } } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create DynamicArray object ..."); } } DynamicArray<T>& operator= (const DynamicArray<T>& obj) { if( this != &obj ) { T* array = new T[obj.m_length]; if( array != NULL ) { for(int i=0; i<obj.m_length; i++) { array[i] = obj.m_array[i]; } T* temp = this->m_array; this->m_array = array; this->m_length = obj.m_length; delete[] temp; } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to copy DynamicArray object ..."); } } return *this; } int length() const { return m_length; } void resize(int length) { if( length != m_length ) { T* array = new T[length]; if( array != NULL ) { int size = (length < m_length) ? length : m_length; for(int i=0; i<obj.m_length; i++) { array[i] = this->m_array[i]; } T* temp = this->m_array; this->m_array = array; this->m_length = length; delete[] temp; } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to resize DynamicArray object ..."); } } } ~DynamicArray() { delete[] this->m_array; } }; } #endif // DYNAMICARRAY_H
我们还是写个 main.cpp 测试下我们实现的 DynamicArray 类
#include <iostream> #include "DynamicArray.h" using namespace std; using namespace DTLib; int main() { DynamicArray<int> s1(5); for(int i=0; i<s1.length(); i++) { s1[i] = i * i; } for(int i=0; i<s1.length(); i++) { cout << s1[i] << endl; } s1.resize(8); s1.set(6, 60); for(int i=0; i<s1.length(); i++) { cout << s1[i] << endl; } return 0; }
我们猜想的结果应该是先打印出 0, 1, 4,9, 16。接着再次打印它们,s1[6] = 60,s1[5] 和 s1[7] 应该是随机数。我们来看看结果
我们看到已经正确实现了效果。我们看到在 DynamicArray 类中的函数实现存在大量的重复逻辑,那么我们该如何进行优化呢?我们可以将重复的代码逻辑进行抽象。可以抽象出三个函数:a> init,用于对象构造时的初始化操作;b> copy,用以在堆空间中申请新的内存,并执行拷贝操作;c> update,用于将指定的堆空间作为内部存储数组使用。我们来看看优化后的 DynamicArray 类。
DynamicArray.h 源码
#ifndef DYNAMICARRAY_H #define DYNAMICARRAY_H #include "Array.h" #include "Exception.h" namespace DTLib { template < typename T > class DynamicArray : public Array<T> { protected: int m_length; T* copy(T* array, int len, int newlen) { T* ret = new T[newlen]; if( ret != NULL ) { int size = (len < newlen) ? len : newlen; for(int i=0; i<size; i++) { ret[i] = array[i]; } } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to copy object ..."); } return ret; } void update(T* array, int length) { if( array != NULL ) { T* temp = this->m_array; this->m_array = array; this->m_length = length; delete[] temp; } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to update object ..."); } } void init(T* array, int length) { if( array != NULL ) { this->m_array = array; this->m_length = length; } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create DynamicArray object ..."); } } public: DynamicArray(int length) { this->m_array = new T[length]; if( this->array != NULL ) { this->m_length = length; } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create DynamicArray object ..."); } } DynamicArray(const DynamicArray<T>& obj) { this->m_array = new T[obj.m_length]; if( this->array != NULL ) { this->m_length = length; for(int i=0; i<obj.m_length; i++) { this->m_array[i] = obj.m_array[i]; } } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create DynamicArray object ..."); } } DynamicArray<T>& operator= (const DynamicArray<T>& obj) { if( this != &obj ) { T* array = new T[obj.m_length]; if( array != NULL ) { for(int i=0; i<obj.m_length; i++) { array[i] = obj.m_array[i]; } T* temp = this->m_array; this->m_array = array; this->m_length = obj.m_length; delete[] temp; } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to copy DynamicArray object ..."); } } return *this; } int length() const { return m_length; } void resize(int length) { if( length != m_length ) { T* array = new T[length]; if( array != NULL ) { int size = (length < m_length) ? length : m_length; for(int i=0; i<obj.m_length; i++) { array[i] = this->m_array[i]; } T* temp = this->m_array; this->m_array = array; this->m_length = length; delete[] temp; } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to resize DynamicArray object ..."); } } } ~DynamicArray() { delete[] this->m_array; } }; } #endif // DYNAMICARRAY_H
我们再来编译看看结果
结果还是对的,那么此时我们的 DynamicArray 类已经很一目了然了。通过今天对 StaticArray 和 DynamicArray 的学习,总结如下:1、StaticArray 通过封装原生数组的方式实现数组类;2、DynamicArray 动态申请堆空间,使得数组长度动态可变;3、数组对象能够代替原生数组,并且使用上更加安全;4、代码优化是项目开发过程中不可或缺的环节,这样我们的代码质量会更高。