本系列文章将主要介绍两种“基础数据结构”——数组和链表,及其变种。(参考教材:Foundational Data Structures)
数组与链表是两种最基本的数据结构。 在《数据结构与算法》的学习中,我们将遇到多种"Abstract Data Types (ADTs)",包括:stacks, queues, deques, ordered lists, sorted lists, hash and scatter tables, trees, priority queues, sets and graphs等。对于这些ADTs中的任意一个,我们都可以选择使用数组或链表来实现。因此,深入理解数组和链表是学好其他数据结构的基础。
首先,我们来学习一下数组。数组可能是最常用的一种聚集数据的方式,C++语言也提供了对数组内置(built-in)支持,但这种支持也不是没有缺陷,具体如下:
1,没有“数组值”表达式,因此数组不能直接作为函数参数和返回值,也不能直接进行数组间的赋值(需要使用指针转换);
2,数组不自动进行下标越界检查;
3,数组的大小必须在编译期确定,不能延迟到运行时确定;
4,采用指针形式,不能确定数组的size,不能直接区分指针指向的是数组还是一个普通值;
下面我们指针C++普通数组的缺点,我们设计一个自定义的“Dynamic Array”。
如上图所示,这个“Dynamic Array”设计了两个结构,左边的结构包含了三个field,右边的结构是一个普通数组。具体设计见如下代码:
#ifndef DYNAMIC_ARRAY_H
#define DYNAMIC_ARRAY_H
#include <stdexcept> //std::out_of_range
// Two structures are used. The first is a structure which comprises three fields--data, base and length.
// The member variable data is a pointer to the array data. Variables base and length are used in the array subscript calculation.
// The second structure comprises contiguous memory locations which hold the array elements.
namespace FoundationalDataStructure
{
template <typename T>
class Array
{
public:
Array();
Array(unsigned int, unsigned int = 0);
~Array();
Array(const Array &);
Array & operator=(const Array &);
T const& operator[](unsigned int) const; // called by const Array Object
T & operator[](unsigned int);
T const* Data() const;
unsigned int Base() const;
unsigned int Length() const;
void SetBase(unsigned int);
void SetLength(unsigned int);
protected:
T * data;
unsigned int base;
unsigned int length;
};
template <typename T>
Array<T>::Array()
: data(new T[0]{})
, base(0)
, length(0)
{}
template <typename T>
Array<T>::Array(unsigned int n, unsigned int m)
: data(new T[n]{})
, base(m)
, length(n)
{}
template <typename T>
Array<T>::~Array()
{
delete[] data;
}
template <typename T>
Array<T>::Array(const Array & array)
: data(new T[array.Length()]{})
, base(array.Base())
, length(array.Length())
{
for (unsigned int i = 0; i < length; ++i)
data[i] = array[i];
}
template <typename T>
Array<T> & Array<T>::operator=(const Array & array)
{
data = new T[array.Length()]{};
base = array.Base();
length = array.Length();
for (unsigned int i = 0; i < length; ++i)
data[i] = array.Data()[i];
return *this;
}
template <typename T>
T const * Array<T>::Data() const
{
return data;
}
template <typename T>
unsigned int Array<T>::Base() const
{
return base;
}
template <typename T>
unsigned int Array<T>::Length() const
{
return length;
}
template <typename T>
T const & Array<T>::operator[](unsigned int position) const
{
unsigned int const offset = position - base;
if (offset >= length)
throw std::out_of_range("invalid position");
return data[offset];
}
template <typename T>
T & Array<T>::operator[](unsigned int position)
{
unsigned int const offset = position - base;
if (offset >= length)
throw std::out_of_range("invalid position");
return data[offset];
}
template <typename T>
void Array<T>::SetBase(unsigned int newBase)
{
base = newBase;
}
template <typename T>
void Array<T>::SetLength(unsigned int newLength)
{
if (length == newLength)
return;
T * const newData = new T[newLength]{};
unsigned int const min =
length < newLength ? length : newLength;
for (unsigned int i = 0; i < min; ++i)
newData[i] = data[i];
delete[] data;
data = newData;
length = newLength;
}
} // namespace FoundationalDataStructure
#endif // DYNAMIC_ARRAY_H
PS:原书代码有少许错误,以上代码我进行了调优。经验如下:
1,关于std::out_of_range,它是标准库中的一个类,继承关系:exception <-- logic_error <-- out_of_range,参考std::out_of_range。
2,添加了一个namespace FoundationalDataStructure,防止与标准库冲突。本意是想将模版类的声明和函数实现分离到.h和.cpp文件,才添加namespace,但未成功。
3,对new出来的数组,及时初始化(添加花括符)。
4,模版类代码要全面测试,函数的实现在用到的时候才会报错。
5,可以用智能指针管理heap空间。