本节我们要实现StaticList和DynamicList,如下:

StaticList的设计要点:

StaticList是一个类模板,使用原生数组作为顺序存储空间,使用模板参数决定数组大小

  在StaticList的类模板中我们定义了一个元素数组作为顺序存储空间。这就是static的含义。因此,需要在构造函数当中将这个顺序存储空间挂接到父类的m_array上。

StaticList.h如下:

 #ifndef STATICLIST_H
#define STATICLIST_H #include "SeqList.h" namespace DTLib
{ 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 = ;
} int capacity() const
{
return N;
}
}; } #endif // STATICLIST_H

main函数测试程序如下:

 #include <iostream>
#include "List.h"
#include "SeqList.h"
#include "StaticList.h" using namespace std;
using namespace DTLib; int main()
{
StaticList<int, > l; for(int i = ; i < l.capacity(); i++)
{
l.insert(, i);
} for(int i = ; i < l.capacity(); i++)
{
cout << l[i] << endl;
} return ;
}

执行结果如下:

更改测试程序如下:

 #include <iostream>
#include "List.h"
#include "SeqList.h"
#include "StaticList.h"
#include "Exception.h" using namespace std;
using namespace DTLib; int main()
{
StaticList<int, > l; for(int i = ; i < l.capacity(); i++)
{
l.insert(, i);
} for(int i = ; i < l.capacity(); i++)
{
cout << l[i] << endl;
} try
{
l[] = ;
}
catch(IndexOutOfBoundsException& e)
{
cout << e.message() << endl;
cout << e.location() << endl;
} return ;
}

第27行我们执行越界赋值操作,执行结果如下:

DynamicList的设计:

使用类模板

  申请连续堆空间作为顺序存储空间(StaticList使用的存储空间是“栈空间”(当我们定义的StaticList类对象也在栈上时))

  动态设置顺序存储空间的大小(StaticList中的存储空间大小是固定的,不能动态设置)

  保证重置顺序存储空间时的异常安全性

DynamicList中的存储空间是动态申请的,而且可以动态设置大小,实现上会比StaticList复杂。

DynamicList的设计要点:

函数异常安全的概念

  不泄露任何资源

  不允许破坏数据

函数异常安全的基本保证

  如果异常被抛出

    对象内的任何成员仍然能保持有效状态

    没有数据的破坏及资源泄漏

  DynamicList中就没有原生数组了,只有一个m_capacity代表存储空间的大小。这个大小就不是通过模板参数来指定了,而是通过构造函数的参数来指定。在构造函数中申请堆空间。此外,还是先了resize函数,用于设置顺序存储空间的大小。在析构函数中归还空间。

DynamicList.h程序如下:

 #ifndef DYNAMICLIST_H
#define DYNAMICLIST_H #include "SeqList.h"
#include "Exception.h" namespace DTLib
{ template <typename T>
class DynamicList : public SeqList<T>
{
protected:
int m_capacity; //顺序存储空间的大小
public:
DynamicList(int capacity) //申请空间
{
this->m_array = new T(capacity); if( this->m_array != NULL)
{
this->m_length = ;
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 = ; i < length; i++)
{
array[i] = this->m_array[i]; //这里必须用this->m_array,不能直接用m_array,其他在父类中定义的成员同理
} 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

  第41行中我们没有直接操作this->m_array,因为重置空间后我们原来的数据元素还需要保留,因此,我们在47-50行中将原来的数据元素拷贝到新空间中。所以,我们不能让m_array指向新申请的空间,必须让它指向原来的存储空间。

  52行我们定义了一个临时指针指向原来的存储空间,到了58行才delete原来的空间。为什么要这样做呢?因为delete有可能触发数据元素的析构函数,而在这个析构函数中有可能抛出异常。如果我们在52行直接delete this->m_array,就有可能触发析构函数,并在析构对象是引发异常(数据元素对象是用户定义的),函数就在52行异常返回了,就意味着下面的赋值操作全都不能执行到,那样的话当前的线性表就不是合法可用的了。也就是这个resize函数就不是异常安全的了。而用这种临时指针的方式,可以保证54-57被执行到,而且这三行不会发生异常。执行完这些赋值,再执行58行,即使这时候发生异常,那m_array,m_length,m_capacity也已经是合法的了,也可以保证我们的线性表对象也是合法可用的,这就做到了异常安全。

  第49行的赋值操作也有可能发生异常,在这里发生异常,m_array,m_length,m_capacity这几个成员变量的值没有发生任何改变,所以当前这个线性表对象依然是合法可用的。只是array指向的堆空间会被泄漏。这一点对于resize函数来说就无法顾全了,因为数据类型T是用户指定的,赋值操作符也可能被重载,并且重载的函数中可能发生异常,这些我们都无法顾全。这是第三方工程师代码的问题。这样的问题只能交给第三方工程师自己来考虑。

main函数测试程序如下:

 #include <iostream>
#include "List.h"
#include "SeqList.h"
#include "StaticList.h"
#include "Exception.h"
#include "DynamicList.h" using namespace std;
using namespace DTLib; int main()
{ DynamicList<int> l(); for(int i = ; i < l.capacity(); i++)
{
l.insert(, i);
} for(int i = ; i < l.length(); i++)
{
cout << l[i] << endl;
} try
{
l[] = ;
}
catch(const Exception& e)
{
cout << e.message() << endl;
cout << e.location() << endl; l.resize();
l.insert(, );
} l[] = ; for(int i = ; i < l.length(); i++)
{
cout << l[i] << endl;
} return ;
}

执行结果如下:

DynamicList不能作为StaticList的子类实现,反之也是不可以的,因为这两个类对于顺序存储空间的指定是没有任何关系的,截然相反的。

小结:

  StaticList通过模板参数定义顺序存储空间,并且将原生的数组作为顺序存储空间使用

  DynamicList通过动态内存申请定义顺序存储空间

  DynamicList支持动态重置顺序存储空间的大小

  DynamicList中的resize函数实现需要保证异常安全

第十七课 StaticList和DynamicList实现的更多相关文章

  1. NeHe OpenGL教程 第四十七课:CG顶点脚本

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  2. NeHe OpenGL教程 第三十七课:卡通映射

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  3. NeHe OpenGL教程 第二十七课:影子

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  4. NeHe OpenGL教程 第十七课:2D图像文字

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  5. python六十七课——网络编程(基础知识了解)

    网络编程: 什么是网络编程? 网络:它是一种隐形的媒介:可以将多台计算机使用(将它们连接到一起) 网络编程:将多台计算机之间可以相互通信了(做数据交互) 一旦涉及到网络编程,划分为两个方向存在,一方我 ...

  6. 四、StaticList 和 DynamicList

    1.StaticList类模板 StaticList的设计要点:类模板 使用原生数组作为顺序存储空间 使用模板参数决定数组大小 template <typename T, int N> c ...

  7. Python学习第十七课——组合

    组合1 #组合 1 class Hand: pass class Foot: pass class Trunk: pass class Head: pass class Person: def __i ...

  8. 如何使用webgl(three.js)实现3D储能,3D储能站,3D智慧储能、储能柜的三维可视化解决方案——第十七课

    前言 上节课我们讲了<3D光伏发电>,与之配套的就是能量存储 这节课我们主要讲讲储能,储能站,在分布式能源系统中起到调节用对电的尖峰平谷进行削峰填谷的作用.特别是小型储能站,更加灵活,因地 ...

  9. 斯坦福第十七课:大规模机器学习(Large Scale Machine Learning)

    17.1  大型数据集的学习 17.2  随机梯度下降法 17.3  微型批量梯度下降 17.4  随机梯度下降收敛 17.5  在线学习 17.6  映射化简和数据并行 17.1  大型数据集的学习

随机推荐

  1. Python第一弹--------初步了解Python

    Python是一种跨平台的语言,这意味着它能够运行在所有主要的操作系统中. 语法规范几乎同C语言. 字符串: 当像Python输入一个字符串时,首先要输入一个引号.单引号.双引号.三引号三者等价.通常 ...

  2. 了解Flask 信号机制

    Flask框架中的信号基于blinker,其主要就是让开发者可是在flask请求过程中定制一些用户行为. pip3 install blinker 1. 内置信号 request_started = ...

  3. RabittMQ实践(二): RabbitMQ 与spring、springmvc框架集成

    一.RabbitMQ简介 1.1.rabbitMQ的优点(适用范围)1. 基于erlang语言开发具有高可用高并发的优点,适合集群服务器.2. 健壮.稳定.易用.跨平台.支持多种语言.文档齐全.3. ...

  4. python openpyxl 封装Execl常用操作的方法

    封装Excel操作方法:先装openpyxl:pip install openpyxl==2.4.5(可以指定版本) 封装脚本:#encoding=utf-8 from openpyxl import ...

  5. Javaworkers团队第一周项目总结

    项目名称:游戏贪吃蛇 项目介绍: 贪吃蛇是一款相当经典的小游戏,我们团队决定用我们现有的java知识来实现它. 具体设计: 对象:蛇.果实 方向键:控制蛇的运动. 空格键:暂停游戏 ESC:推出游戏( ...

  6. Codeforces Round #319 (Div. 2) C. Vasya and Petya's Game 数学

    C. Vasya and Petya's Game time limit per test 1 second memory limit per test 256 megabytes input sta ...

  7. JQuery中width和JS中JS中关于clientWidth offsetWidth scrollWidth 等的含义

    JQuery中: width()方法用于获得元素宽度: innerWidth()方法用于获得包括内边界(padding)的元素宽度: outerWidth()方法用于获得包括内边界(padding)和 ...

  8. nodejs项目的model操作mongo

    想想以前学习hibernate的时候,学习各种表和表之间的映射关系等一对多,多对一,多对多,后来到了工作中,勇哥告诉我, 那时在学习的时候,公司中都直接用外键关联. 这里我们学习下,如何在Nodejs ...

  9. MOBA游戏学会这些知识,你才算真的入门了!

    <英魂之刃口袋版>是一个标准的MOBA游戏,MOBA指的是多人在线战术竞技游戏,游戏模式始于1998年<星际争霸>中的一张自定义地图,经过近20年的优化和调整逐渐演变成了我们现 ...

  10. STL中的map

    map 容器 提供 1 对 1 的关系 定义方式: map<string,int>mp; 写在前面的是关键字. 数据插入: 1.使用 insert 插入 pair 数据 mp.insert ...