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