1.1 线性表
1.1.1 生活中的线性表
- 幼儿园小朋友排队的情况
1.1.2 线性表的表现形式
- 零个或多个元素组成的集合
- 数据元素在位置上是
有序
排列的 - 数据元素的
个数是有限
的 - 数据元素的类型
必须
相同
1.1.3 线性表(List)的抽象定义
- 线性表是具有
相同类型
的n(n>= 0)
个数据元素的有限
序列
(a0, a1 , 。。。 an)
ai 是表项(数据元素),n是表长度
1.1.3 线性表(List)的性质
- a0 是线性表的第一个元素,只有一个后继
- an 是线性表的最后一个元素,只有一个前驱
- 除a0 和an 外,其他元素既有后继,又有前驱
- 直接支持逐项访问和顺序存取
1.2 线性表的实现
1.2.1 问题
- 线性表只是一个单纯的概念吗?
- 如何在程序中描述和使用一个线性表
1.2.2线性表常用操作
- 将元素插入线性表
- 将元素从线性表中删除
- 获取目标位置处元素的值
- 设置目标位置处元素的值
- 获取线性表长度
- 清空线性表
1.3 线性表的顺序存储
1.3.1顺序存储
- 线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表中的数据元素
1.3.2 设计思路
- 可以用一维数组来实现顺序存储结构
- 存储空间: T* m_array;
- 当前长度: int m_length
template<typename T>
class SeqList : public List<T>
{
protected:
T * m_array;
int m_length;
};
1.3.3 顺序存储结构元素的获取
- 判断合法性
- 将目标位置作为数组下标获取元素
1.3.4 顺序存储结构元素的插入
- 判断目标位置是否合法
- 将目标位置之后的所有元素后移一个位置
- 将新元素插入目标位置
- 线性表长度+ 1
1.3.5顺序存储结构元素的删除
- 判断目标位置是否合法
- 将目标位置后所有元素前移一个位置
- 线性表长度减1
1.4
1.4.1 继承图
1.4.2 问题
在SeqList这个类中完成了具体的插入,删除,获取,设置,但是这个SeqList这个类还是抽象类型?
- 因为顺序结构
存储空间
的指定并没有在SeqList中完成,而是在StaticList 和DynamicList中完成
1.4.3 SeqList 设计要点
- 抽象类模板,存储空间的
位置
和大小
由子类完成 - 实现顺序存储结构线性表的
关键操作
(增删改查等) 提供数组操作符
,方便快捷获取操作
1.4.4 SeqList中成员函数实现的注意事项
- 获取容量的成员函数
Capacity
要设置成纯虚函数,因为容量需要到SeqList的子类中才可以确定
virtual int Capacity() const = 0;
- 需要重载两个版本的数组操作符,一个是const的,一个是非const的。
T& operator[](int i) ;
T& operator[](int i) const ;
1.5 staticList 和DynamicList的实现
1.5.1 思考
- staticList 和DynamicList的如何实现,差异在哪里?是否可将Dynamic作为StaticList的子类实现?
1.5.2 static 设计实现
- 设计要点
- 类模板
- 使用原生数组作为顺序存储空间
- 使用模板参数决定数组大小
template < typename T, int N>
class StaticList : public SeqList<T>
{
protected:
T m_space[N]; // 顺序存储空间, N 为模板参数
public:
StaticList(); // 指定父类成员的具体指
int capacity() const;
}
1.5.3 DynamicList 设计要点
- 类模板
- 申请
连续堆空间
作为顺序存储空间 动态设置
顺序存储空间的大小保证重置顺序存储空间时的异常安全性
- 申请
1.5.4DynamicList 设计要点
函数异常安全的概念
- 不泄露任何资源
- 不允许破坏数据
函数异常安全的基本保证
- 如果异常被抛出
- 对象内的任何成员仍然能保持有效状态(如果这个函数抛出了异常,那么在捕获完异常以后,这个对象仍然是有用的)
- 没有数据的破坏及资源的泄漏
- 如果异常被抛出
1.5.5 DynamicList 设计要点
template <typename T>
class DynamicList : public SeqList<T>
{
protected:
int m_capacity; // 顺序存储空间的大小
public:
DynamicList(int capacity); //申请空间
int capacity() const;
void resize(int capacity); //重新设置顺序存储空间的大小
~DynamicList() ; //归还空间
}
1.5.6 DynamicList 异常安全分析
1.6 顺序存储线性表的分析
1.6.1 效率分析(大O表示法)
- 问题: 长相相同的两个SeqList ,插入和删除操作的平均耗时是否相同?
不一定:
举例
SeqList<int > s1 ; //5
SeqList<string> s2 //5
s1.insert(0,1);
s2.insert(0,"zhangsan"); //这里可能会发生类对象的copy,所以两个SeqList的耗时需要具体对象,具体分析,不仅仅是大O表示法
1.6.2 思考,下面的代码正确吗
StaticList<int*, 5> s1;
StaticList<int*, 5> s2;
for(int i = 0; i < s1.capacity(); i++)
{
s1.insert(0, new int(i));
}
s2 = s1; //copy
for(int i =0; i<s1.length(); i++)
{
delete s1[i];
delete s2[i];
}
- 问题是 同一块内存空间被释放了两次
1.6.3 思考,下面的代码正确吗
void func()
{
DynamicList<int> d1(5);
DynamicList<int> d2 = d1; // copy construct
for(int i = 0; i<d1.capacity(); i++)
{
d1.insert(i, i);
d2.insert(i, i * i);
}
for(int i =0; i < d1.length(); i++)
{
cout << d1[i] << endl;
}
}
1.6.4 如何解决上面问题
- 分析,对于容器类型的类,可以考虑禁用copy构造函数和赋值操作
template <typename T>
class List : public Object
{
protected:
List(const List&);
List& operator=(const List&);
public:
List(){} // 必须有,要不然会报错,因为定义了copy构造以后,编译器不会提供默认构造函数
};
- 这种方法合理吗? 从生活的角度来看是合理的,因为是容器的类。两个水杯的例子,
- 第一个水杯中的水不可能复制一份给第二个杯子
- 从面向对象来看,我们希望从生活中的例子直接搬移到面向对象编程中,生活中的容器的东西是不能
复制的。
int main()
{
DynamicList<int> d1(5);
DynamicList<int> d2 = d1;
DynamicList<int> d3(5);
d3 =d1;
return 0;
}
- 禁用了copy构造和赋值以后,就会报这种错
- 我想着,子类的copy 构造函数会默认调用父类的构造函数,要不然不会报错,证明一下
- 在子类中不定义copy构造函数的时候,会默认调用父类的copy构造函数
- 但是在子类中定义copy构造函数的时候,会默认调用父类的无参构造函数
- 比较下面的两个例子
class Parent
{
protected:
int m_i;
//Parent(const Parent & e);
public:
Parent()
{
cout << "Parent()" << endl;
}
Parent(const Parent & e)
{
cout << "Parent(const Parent & e)" << endl;
m_i = e.m_i;
}
};
class Child : public Parent
{
public:
Child()
{
cout << "Child()" << endl;
}
/*
Child(const Child &obj)
{
cout << "Child(const Child &obj)" << endl;
}
*/
};
int main()
{
Child c1;
Child c2 =c1;
return 0;
}
class Parent
{
protected:
int m_i;
//Parent(const Parent & e);
public:
Parent()
{
cout << "Parent()" << endl;
}
Parent(const Parent & e)
{
cout << "Parent(const Parent & e)" << endl;
m_i = e.m_i;
}
};
class Child : public Parent
{
public:
Child()
{
cout << "Child()" << endl;
}
Child(const Child &obj) // 默认调用父类无参数构造函数
{
cout << "Child(const Child &obj)" << endl;
}
};
int main()
{
Child c1;
Child c2 =c1;
return 0;
}
1.6.5 问题
int main()
{
StaticList<int, 5> list;
for(int i =0; i < list.capacity(); i++)
{
list[i] = i * i;
}
return 0;
}
- 问题的原因是线性表必须先插入元素,才能使用操作符[]访问元素。
- 问题分析
- 顺序存储结构线性表提供了输出操作符重载,通过重载能快捷方便的获取目标位置处的数据元素,在具体的使用形式上类型数组,但是由于本质不同,不能代替数组使用。
- 这样就有了数组类的需求
参考一 : 狄泰软件课程
如有侵权:请联系邮箱 1986005934@qq.com