线性表是具有相同类型的n(n>=0)个数据元素的有序(中间不能空位置)有限序列,如:
X0, X1, … Xn-1
其中Xi为表项,n为长度。线性表具有如下性质:
(1)线性表的首元素X0,只有一个后继。
(2)Xn-1为线性表的最后一个元素,只有一个前驱。
(3)除X0和Xn-1外的其它元素既有前驱又有后继。
(4)支持逐项(下标)访问和顺序存取。
顺序表是基于顺序存储结构的数据结构,是线性表中的一种(另一种是链表)。所谓顺序存储结构指的是用一段地址连续的存储单元依次存储数据元素。这段地址连续的存储空间可以在栈上也可以在堆中,因此c++语言实现线性表设计可以将线性表的常用操作设计抽象基类SeqList,两种地址连续空间的分配方式定义成子类静态线性表StaticList和动态线性表DynamicList。线性表的常用操作有:
(1)将元素插入线性表。
(2)将元素从线性表中删除。
(3)获取目标位置处元素的值。
(4)设置目标位置处元素的值。
(5)获取线性表的长度。
(6)清空线性表。
事实上这些操作并不仅局限于线性表,链表也是同样支持,所以进一步将这些操作定义成List抽象类。
静态线性表代码实现:
//List.h
#ifndef __LIST_H__
#define __LIST_H__
#include <iostream>
#include <exception>
template<typename T>
class List
{
public:
virtual bool insert(int i, const T& obj) = 0;
virtual bool remove(int i) = 0;
virtual bool set(int i, const T& obj) = 0;
virtual bool get(int i, T& obj) const = 0;
virtual int length() const = 0;
virtual void clear() = 0;
List() {}
protected:
List(const List&); //保护拷贝构造和赋值操作符重载函数以禁止子类对象间的拷贝操作,避免发生浅拷贝发生的程序错误
List& operator= (const List& ); //只声明不定义,只要函数不被调用就没问题,若函数被调用是在链接时报错,
//但是因为是protected的所以只要是调用,在编译阶段因为属性问题就报错了
};
//SeqList.h
#ifndef __SEQLIST_H__
#define __SEQLIST_H__
#include "List.h"
template <typename T>
class SeqList : public List<T>
{
public:
bool insert(int i, const T& obj)
{
//合法性检查
bool ret = m_length < capacity();
ret = ret && ((i >= 0) && (i <= m_length));
if (ret)
{
//目标位置以及之后的元素需往后移以让出位置供新元素插入
for (int p = m_length - 1; p >= i; p--)
m_array[p + 1] = m_array[p];
m_array[i] = obj;
++m_length;
}
return ret;
}
bool remove(int i)
{
//合法性检查
bool ret = ((i >= 0) && (i < m_length));
if (ret)
{
//目标删除位置之后的位置往前移动一个位置
for (int p = i; p < m_length - 1; p++)
m_array[p] = m_array[p + 1];
--m_length;
}
return ret;
}
bool set(int i, const T& obj)
{
bool ret = ((i >= 0) && (i <= m_length));
if (ret)
m_array[i] = obj; //调用[]操作符重载函数
return ret;
}
bool get(int i, T& obj) const
{
bool ret = ((i >= 0) && (i <= m_length));
if (ret)
obj = m_array[i]; //调用[]操作符重载函数
return ret;
}
int length() const
{
return m_length;
}
void clear()
{
m_length = 0;
}
T& operator[](int i) //[]操作符重载函数
{
if ((i >= 0) && (i < m_length))
return m_array[i];
else //i输入有误抛出一个标注库的一个异常类对象
throw(std::out_of_range("SeqList operator[] out of range"));
}
//纯虚函数,由子类实现返回其分配的空间的大小
virtual int capacity() const = 0;
protected:
T* m_array; //指向子类(静态/动态)分配的空间
int m_length; //当前线性表有多少个数据元素
};
//StaticList.h
#ifndef __STATICLIST_H__
#define __STATICLIST_H__
#include "SeqList.h"
template<typename T, int N>
class StaticList : public SeqList<T>
{
public:
StaticList()
{
this->m_array = m_space;
this->m_length = 0;
}
int capacity() const { return N; }
protected:
T m_space[N]; //一旦定义本类对象就会定义本数组
};
#endif /* __STATICLIST_H__ */
#include <iostream>
#include "StaticList.h"
#include "DynamicList.h"
//#include <stdexcept>
int main(void)
{
StaticList<int, 5> list;
//list[0] = 6; //operator[]()函数抛出异常,需先插入元素才能使用该元素
for (int i = 0; i < 5; i++)
list.insert(i, i * 2);
for (int i = 0; i < 5; i++)
std::cout << list[i] << " ";
std::cout << std::endl;
getchar();
return 0;
}
运行:
动态线性表的实现:
//DynamicList.h
#ifndef __DYNAMICLIST_H__
#define __DYNAMICLIST_H__
#include "SeqList.h"
template <typename T>
class DynamicList : public SeqList<T>
{
public:
DynamicList(int capacity)
{
//动态分配数组空间
this->m_array = new T[capacity];
if (this->m_array)
{
this->m_length = 0;
this->m_capacity = capacity;
}
}
int capacity() const { return m_capacity; }
//改变动态的数组的大小
void resize(int capacity)
{
if (capacity != m_capacity)
{
T* tmp_array = new T[m_capacity];
if (tmp_array != NULL)
{
//将旧空间的数据拷贝到新空间
int length = (this->m_length < capacity ? this->m_length : capacity);
for (int i = 0; i < length; i++)
tmp_array[i] = m_array[i];
//保证异常安全,即确保异常抛出且被捕捉后线性表仍然可用
T* tmp_p = this->m_array; //若在这里直接delete m_array若抛出异常时,m_array被破坏了且没有指向新空间
this->m_array = tmp_array;
this->m_length = length;
this->m_capacity = capacity;
delete[] tmp_p; //在这里delete即使抛出异常,旧m_array已经没用,m_array已指向新空间,线性表仍然可用
}
}
~DynamicList() { delete[] this->m-array; }
}
protected:
int m_capacity;
};
#endif /* __DYNAMICLIST_H__ */
//main.cpp
int main(void)
{
DynamicList<int> l(6);
for (int i = 0; i < l.capacity(); i++)
l.insert(0, i);
for (int i = 0; i < 5; i++)
std::cout << l[i] << " ";
std::cout << std::endl;
getchar();
return 0;
}
运行:
需要注意:
(1)在DynamicList::resize()成员函数中需要delete掉旧空间,若旧空间存放的是用于自定义的类类型对象,那么delete操作会调用到对象的析构函数,在析构函数中可能会抛出异常,所以我们的delete操作必须要保证异常安全。所谓的异常安全指得是抛出异常后不泄漏任何资源和破坏任何数据,这样当用户成功捕捉该异常后仍可以正常使用。上述的的delete操作保证了这一点。
(2)线性表作为一种容器类型的数据结构,应该禁止使用使用类的默认拷贝构造函数和赋值操作重载函数,因为它们实现的是浅拷贝。(参考c++的默认拷贝构造函数,从深度拷贝和浅拷贝说起)
(3)因为线性表中重载了[]操作符,所以用户可能会将线性表当做数组类来使用,即在没有插入元素的情况下使用[]操作符访问线性表数据,这会[]操作符重载函数抛出越界异常类。
注:参照狄泰唐老师c++数据结构课程。