0.目录
1.线性表的本质和操作
2.线性表的顺序存储结构
3.顺序存储结构的抽象实现和具体实现
- 3.1 SeqList
- 3.2 StaticList 和 DynamicList
4.顺序存储线性表的分析
5.小结
1.线性表的本质和操作
线性表 ( List ) 的表现形式:
- 零个或多个数据元素组成的集合
- 数据元素在位置上是有序排列的
- 数据元素的个数是有限的
- 数据元素的类型必须相同
线性表 ( List ) 的抽象定义——线性表是具有相同类型的n(≥0)个数据元素的有限序列:
线性表 ( List ) 的性质:
线性表只是一个单纯的概念吗?如何在程序中描述和使用一个线性表?
线性表的一些常用操作:
- 将元素插入线性表
- 将元素从线性表中删除
- 获取目标位置处元素的值
- 设置目标位置处元素的值
- 获取线性表的长度
- 清空线性表
线性表在程序中表现为一种特殊的数据类型:
(在StLib中实现List.h)
#ifndef LIST_H
#define LIST_H
#include "Object.h"
namespace StLib
{
template <typename T>
class List : public Object
{
public:
virtual bool insert(int i, const T& e) = 0;
virtual bool remove(int i) = 0;
virtual bool set(int i, const T& e) = 0;
virtual bool get(int i, T& e) const = 0;
virtual int length() const = 0;
virtual void clear() = 0;
};
}
#endif // LIST_H
2.线性表的顺序存储结构
顺序存储的定义——线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表中的数据元素。
设计思路——可以用一维数组来实现顺序存储结构:
- 存储空间:
T* m_array;
- 当前长度:
int m_length;
顺序存诸结构的元素获取操作:
- 判断目标位置是否合法
- 将目标位置作为数组下标获取元素
顺序存储结构的元素插入操作:
- 判断目标位置是否合法
- 将目标位置之后的所有元素后移一个位置
- 将新元素插入目标位置
- 线性表长度加 1
顺序存储结构的元素插入示例:
顺序存储结构的元素删除操作:
- 判断目标位置是否合法
- 将目标位置后的所有元素前移一个位置
- 线性表长度减 1
顺序存储结构的元素删除示例:
3.顺序存储结构的抽象实现和具体实现
3.1 SeqList
本节目标:
- 完成顺序存储结构线性表的抽象实现
SeqList设计要点:
- 抽象类模板,存储空间的位置和大小由子类完成
- 实现顺序存储结构线性表的关键操作(增,删,查,等)
- 提供数组操作符,方便快速获取元素
(在StLib中实现SeqList.h)
#ifndef SEQLIST_H
#define SEQLIST_H
#include "List.h"
#include "Exception.h"
namespace StLib
{
template <typename T>
class SeqList : public List<T>
{
protected:
T* m_array; // 顺序存储空间
int m_length; // 当前线性表长度
public:
bool insert(int i, const T& e)
{
bool ret = ( (0 <= i) && (i <= m_length) );
ret = ret && ( (m_length + 1) <= capacity() );
if( ret )
{
for(int p=m_length-1; p>=i; p--)
{
m_array[p+1] = m_array[p];
}
m_array[i] = e;
m_length++;
}
return ret;
}
bool remove(int i)
{
bool ret = ( (0 <= i) && (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& e)
{
bool ret = ( (0 <= i) && (i < m_length) );
if( ret )
{
m_array[i] = e;
}
return ret;
}
bool get(int i, T& e) const
{
bool ret = ( (0 <= i) && (i < m_length) );
if( ret )
{
e = m_array[i];
}
return ret;
}
int length() const
{
return m_length;
}
void clear()
{
m_length = 0;
}
// 顺序存储线性表的数组访问方式
T& operator[] (int i)
{
if( (0 <= i) && (i < m_length) )
{
return m_array[i];
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invalid ...");
}
}
T operator[] (int i) const
{
return (const_cast<SeqList<T>&>(*this))[i];
}
// 顺序存储空间的容量
virtual int capacity() const = 0;
};
}
#endif // SEQLIST_H
3.2 StaticList 和 DynamicList
本节目标:
- 完成 StaticList 类的具体实现
- 完成 DynamicList 类的具体实现
StaticList 设计要点:
- 类模板
- 使用原生数组作为顺序存储空间
- 使用模板参数决定数组大小
(在StLib中实现StaticList.h)
#ifndef STATICLIST_H
#define STATICLIST_H
#include "SeqList.h"
namespace StLib
{
template <typename T, int N>
class StaticList : public SeqList<T>
{
protected:
T m_space[N]; // 顺序存储空间,N为模板参数
public:
StaticList() // 指定父类成员的具体值
{
this->m_array = m_space;
this->m_length = 0;
}
int capacity() const
{
return N;
}
};
}
#endif // STATICLIST_H
main.cpp测试
#include <iostream>
#include "StaticList.h"
#include "Exception.h"
using namespace std;
using namespace StLib;
int main()
{
StaticList<int, 5> l;
for(int i=0; i<l.capacity(); i++)
{
l.insert(0, i);
}
for(int i=0; i<l.capacity(); i++)
{
cout << l[i] << endl;
}
l[0] *= l[0];
for(int i=0; i<l.capacity(); i++)
{
cout << l[i] << endl;
}
try
{
l[5] = 5;
}
catch(const Exception& e)
{
cout << e.message() << endl;
cout << e.location() << endl;
}
return 0;
}
运行结果为:
4
3
2
1
0
16
3
2
1
0
Parameter i is invalid ...
f:allcodeqtcreatordatastructurestlibSeqList.h:97
DynamicList 设计要点:
- 类模板
- 申请连续堆空间作为顺序存储空间
- 动态设置顺序存储空间的大小
- 保证重置顺序存储空间时的异常安全性
- 函数异常安全的概念
- 不泄漏任何资源
- 不允许破坏数据
- 函数异常安全的基本保证
- 如果异常被抛出
- 对象内的任何成员仍然能保持有效状态
- 没有数据的破坏及资源泄漏
- 如果异常被抛出
(在StLib中实现DynamicList.h)
#ifndef DYNAMICLIST_H
#define DYNAMICLIST_H
#include "SeqList.h"
namespace StLib
{
template <typename T>
class DynamicList : public SeqList<T>
{
protected:
T m_capacity; // 顺序存储空间的大小
public:
DynamicList(int capacity) // 申请空间
{
this->m_array = new T[capacity];
if( this->m_array != NULL )
{
this->m_length = 0;
this->m_capacity = capacity;
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create DynamicList object ...");
}
}
int capacity() const
{
return m_capacity;
}
/* 重新设置顺序存储空间的大小 */
void resize(int capacity)
{
if( capacity != m_capacity )
{
T* array = new T[capacity];
if( array != NULL )
{
int length = (this->m_length < capacity ? this->m_length : capacity);
for(int i=0; i<length; i++)
{
array[i] = this->m_array[i];
}
T* temp = this->m_array;
this->m_array = array;
this->m_length = length;
this->m_capacity = capacity;
delete[] temp;
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException, "No memory to resize DynamicList object ...");
}
}
}
~DynamicList() // 归还空间
{
delete[] this->m_array;
}
};
}
#endif // DYNAMICLIST_H
main.cpp测试
#include <iostream>
#include "DynamicList.h"
#include "Exception.h"
using namespace std;
using namespace StLib;
int main()
{
DynamicList<int> l(5);
for(int i=0; i<l.capacity(); i++)
{
l.insert(0, i);
}
for(int i=0; i<l.length(); i++)
{
cout << l[i] << endl;
}
cout << endl;
l[0] *= l[0];
for(int i=0; i<l.length(); i++)
{
cout << l[i] << endl;
}
try
{
l[5] = 5;
}
catch(const Exception& e)
{
cout << e.message() << endl;
cout << e.location() << endl;
l.resize(10);
l.insert(5, 50);
}
l[5] = 5;
for(int i=0; i<l.length(); i++)
{
cout << l[i] << endl;
}
cout << endl;
l.resize(3);
for(int i=0; i<l.length(); i++)
{
cout << l[i] << endl;
}
return 0;
}
运行结果为:
4
3
2
1
0
16
3
2
1
0
Parameter i is invalid ...
f:allcodeqtcreatordatastructurestlibSeqList.h:97
16
3
2
1
0
5
16
3
2
问题:
是否可以将 DynamicList 作为 StaticList 的子类实现?
(不能将 DynamicList 作为 StaticList 的子类实现,反之也是不可以的,因为这两个类对于顺序存储空间的指定是截然不同没有任何关系的,因此它们两个的地位必然是位于同一层次的。)
4.顺序存储线性表的分析
4.1 效率分析
效率分析:
问题:
长度相同的两个SeqList,插入和删除操作的平均耗时是否相同?
(insert操作最耗时的部分是for循环,究竟有多耗时取决于线性表里面存储的数据元素的类型是什么。如果存储的数据元素的类型是一个自定义的类,并且这个类还非常的庞大,那么插入操作就真的非常耗时了,因为涉及了对象之间的拷贝。)
4.2 功能分析
下面的代码正确吗?为什么?
下面的代码正确吗?为什么?
功能分析:
对于容器类型的类,可以考虑禁用拷贝构造和赋值操作。
代码优化(List.h和SeqList.h):
优化List.h
#ifndef LIST_H
#define LIST_H
#include "Object.h"
namespace StLib
{
template <typename T>
class List : public Object
{
protected:
List(const List&);
List& operator= (const List&);
public:
List() { }
virtual bool insert(const T& e) = 0;
virtual bool insert(int i, const T& e) = 0;
virtual bool remove(int i) = 0;
virtual bool set(int i, const T& e) = 0;
virtual bool get(int i, T& e) const = 0;
virtual int length() const = 0;
virtual void clear() = 0;
};
}
#endif // LIST_H
优化SeqList.h
#ifndef SEQLIST_H
#define SEQLIST_H
#include "List.h"
#include "Exception.h"
namespace StLib
{
template <typename T>
class SeqList : public List<T>
{
protected:
T* m_array; // 顺序存储空间
int m_length; // 当前线性表长度
public:
bool insert(int i, const T& e)
{
bool ret = ( (0 <= i) && (i <= m_length) );
ret = ret && ( (m_length + 1) <= capacity() );
if( ret )
{
for(int p=m_length-1; p>=i; p--)
{
m_array[p+1] = m_array[p];
}
m_array[i] = e;
m_length++;
}
return ret;
}
bool insert(const T& e)
{
return insert(m_length, e);
}
bool remove(int i)
{
bool ret = ( (0 <= i) && (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& e)
{
bool ret = ( (0 <= i) && (i < m_length) );
if( ret )
{
m_array[i] = e;
}
return ret;
}
bool get(int i, T& e) const
{
bool ret = ( (0 <= i) && (i < m_length) );
if( ret )
{
e = m_array[i];
}
return ret;
}
int length() const
{
return m_length;
}
void clear()
{
m_length = 0;
}
// 顺序存储线性表的数组访问方式
T& operator[] (int i)
{
if( (0 <= i) && (i < m_length) )
{
return m_array[i];
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException, "Parameter i is invalid ...");
}
}
T operator[] (int i) const
{
return (const_cast<SeqList<T>&>(*this))[i];
}
// 顺序存储空间的容量
virtual int capacity() const = 0;
};
}
#endif // SEQLIST_H
main.cpp测试
#include <iostream>
#include "DynamicList.h"
using namespace std;
using namespace StLib;
int main()
{
DynamicList<int> l(5);
for(int i=0; i<l.capacity(); i++)
{
l.insert(i);
}
for(int i=0; i<l.length(); i++)
{
cout << l[i] << endl;
}
return 0;
}
运行结果为:
0
1
2
3
4
下面的代码正确吗?为什么?
问题分析:
顺序存储结构线性表提供了数组操作符重载,通过重载能够快捷方便的获取目标位置处的数据元素,在具体的使用形式上类似数组,但是由于本质不同,不能代替数组使用。
5.小结
- 线性表是数据元素的有序并且有限的集合
- 线性表中的数据元素必须是类型相同的
- 线性表可用于描述排队关系的问题
- 线性表在程序中表现为一种特殊的数据类型
- 线性表在C++中表现为一个抽象类
- StaticList 通过模板参数定义顺序存储空间
- DynamicList 通过动态内存申请定义顺序存诸空间
- DynamicList 支持动态重置顺序存储空间的大小
- Dynamiclist 中的 resize() 函数实现需要保证异常安全
- 顺序存储线性表的插入和删除操作存在重大效率隐患
- 线性表作为容器类,应该避免拷贝构造和拷贝赋值
- 顺序存储线性表可能被当成数组误用
- 工程开发中可以考虑使用数组类代替原生数组使用