1、一些问题
顺序存储结构的线性表存在着两个方面的问题:
- 功能方面:数组操作符的重载,线性表有可能被误用为数组使用
- 效率方面:在一些场合中,效率上是有隐患的
解决方案:当前的库中没有可以代替原生数组的实现,所以有可能会被误用,需要创建一个数组类代替原生数组。
2、数组类抽象类模板的创建
需求分析:创建数组类代替原生数组的使用
- 如何通过类的对象来模拟数组的行为?
- 原生数组使用过程中存在的问题:
- 数组类长度信息丢失:定义一个数组,长度信息必须指定,但是指定之后,长度信息不能在数组本身中找到,需要用另一个变量来保存
- 数组越界问题:数组是一片连续的内存空间,但是原生数组发生越界时,不能立即发现,这是工程中bug来源之一,需求:数组类能主动发现越界访问
Array
设计要点:
- 抽象类模板,存储空间的位置和大小由子类完成
- 重载数组操作符,判断访问下标是否合法
- 提供数组长度的抽象访问函数
- 提供数组对象间的复制操作(C++原生数组之间是不能直接通过赋值操作符进行相互复制)
Array
类的声明
templateclass 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;}
templateclass Array : public Object{protected: T* m_array;public: virtual bool set(int i, const T& e) { bool ret = ((i >= 0) && (i < length())); if (ret) { m_array[i] = e; } return ret; } virtual bool get(int i, T& e) { bool ret = ((i >= 0) && (i < length())); if (ret) { e = m_array[i]; } return ret; } virtual int length() const = 0; // 数组访问操作符 T& operator [] (int i) { if ((i >=0) && (i < length())) { return m_array[i]; } else { THROW_EXCEPTION(NoEnoughMemoryException, "Parameter i is invalid..."); } } T operator [](int i) const { return (const_cast &>(*this)[i]); }};
3、StaticArray类模板创建
StaticArray
设计要点:类模板
- 封装原生组:父类只定义了操作,没有具体定义保存数据的地方,
StaticArray
中将数据保存在原生数组中,原生数组的表现形式就是StaticArray
中的一个成员 - 使用模板参数决定原生数组大小
- 实现函数返回数组长度
- 拷贝构造和赋值操作
template < typename T, int N >class StaticArray : public Array{protected: T m_space[N];public: StaticArray(); // 拷贝构造和赋值操作 StaticArray(const StaticArray & obj); StaticArray & operator = (const StaticArray & obj); int length() const;};
template < typename T, int N >class StaticArray : public Array{protected: T m_space[N];public: StaticArray() { this->m_array = m_space; } // 拷贝构造和赋值操作 StaticArray(const StaticArray & obj) { this->m_array = m_space; for(int i = 0; i < N; i++) { m_space[i] = obj.m_space[i]; } } StaticArray & operator = (const StaticArray & 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; }};
4、DynamicArray类模板创建
StaticArray
的对象,数组的大小是明确指定的,创建动态数组类模板,数组大小可以动态指定
DynamicArray
设计要点:类模板
- 动态确定内部数组空间的大小
- 实现函数返回数组长度
- 拷贝构造和赋值操作
DynamicArray
类的声明:
template < typename T >class DynamicArray : public Array{public: int m_length;public: DynamicArray(int m_length); DynamicArray(const DynamicArray & obj); DynamicArray & operator = (const DynamicArray & obj); int length() const; void resize(int length); // 动态重置数组的长度 ~DynamicArray();};
template < typename T >class DynamicArray : public Array{public: int m_length;public: DynamicArray(int length) { // 在堆空间中申请内存 this->m_array = new T[length]; if (this->m_array != NULL) { this->m_length =length; } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to creat DynamicArray object..."); } } DynamicArray(const DynamicArray & obj) { // 数组长度以参数对象的长度为准 this->m_array = new T[obj.m_length]; if (this->m_array != NULL) { cout << "DynamicArray(const DynamicArray & obj)" << endl; // 长度设置 this->m_length = obj.m_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 creat DynamicArray object..."); } } DynamicArray & operator = (const DynamicArray & 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 < size; 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; }};
5、代码优化
分析构造函数、拷贝构造函数、赋值操作符重载函数和resize
函数,程序逻辑为:
构造函数:
- 堆空间中申请一片内存
- 对数组类对象的成员变量进行赋值操作
拷贝构造函数:
- 堆空间中申请一片内存
- 进行数据元素的复制操作
- 对成员变量进行赋值
赋值操作符重载函数:
- 堆空间中申请一片内存
- 进行数据元素的复制操作
- 将指定的堆空间作为内部存储数组使用,更新成员变量的值
resize
函数:
- 堆空间中申请一片内存
- 进行数据元素的复制操作,根据长度信息进行复制
- 将指定的堆空间作为内部存储数组使用,更新类成员变量的值
总结:赋值操作符重载和resize
代码中有很多重复的逻辑, 构造函数和拷贝构造函数也有很多重复代码,如何进行代码优化
重复代码逻辑的抽象
init
:对象构造时的初始化操作,对成员变量进行初始化赋值copy
:在堆空间中申请新的内存,并执行拷贝复制操作update
:将指定的堆空间作为内部存储数组使用,更新成员变量的值
void init(T* array, int length){ if (array != NULL) { this->m_array = array; this->m_length = length; } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to init DynamicArray object..."); }}
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 DynamicArray 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 DynamicArray object..."); }}
这三个函数均放在protected
成员函数内
于是代码简化为:
public: DynamicArray(int length) { init(new T[length], length); } DynamicArray(const DynamicArray& obj) { init(copy(obj.m_array, obj.m_length, obj.m_length), obj.m_length); } DynamicArray & operator = (const DynamicArray & obj) { if ( this != &obj) { update(copy(obj.m_array, obj.m_length, obj.m_length), obj.m_length); } return *this; } int length() const { return m_length; } void resize(int length) // 动态重置数组的长度 { update(copy(this->m_array, this->m_length, length), length); } ~DynamicArray() { delete[] this->m_array; }
6、总结
StaticArray
通过封装原生数组的方式实现数组类
DynamicArray
动态申请堆空间,使得数组长度动态可变数组对象能够代替原生数组,并且使用上更安全
代码优化是项目开发过程中不可或缺的环节