文章目录
问题1 (深入分析 strdup )
#include <iostream>
#include "Exception.h"
using namespace std;
using namespace DTLib;
int main()
{
try
{
NullPointerException npe; // 会调用父类的构造函数
cout << "throw" << endl;
throw npe;
}
catch(const exception& e)
{
cout << "catch" << endl;
}
return 0;
}
/*结果:
throw
terminate called after throwing an instance of 'DTLib::NullPointerException'
至于为啥报错,我也不清楚;;;;;;;;;;;;;;本不应报错
示例程序过于短小,说明不了什么问题,要深入标准C库一探究竟;
在glibc 中查strdup 函数
结果:
glibc 中的 strdup 函数的函数参数是 默认认为字符串不为空的,
但是我们如果如上图那样定义异常对象,相当于调用strdup(0)。
在异常类中,不判断message是否为空,是有问题的
在Exception.h中,一句话经历的改变
//m_message = message; // 参数指针指向的字符串位置未知,这样无法控制外部字符串的生命周期,这种写法是不安全的, 处理方法: 拷贝一份字符串出来
//m_message = strdup(message);// 字符串的复制 到堆空间,这时 m_message 指向堆空间中的字符串
m_message = (message ? strdup(message) : NULL); // 这样的代码才足够安全
通过glibc的strdup形成方式,增强代码健壮性的方法就是这样改。
*/
问题2 (链表逻辑,异常安全性)
#include <iostream>
#include "LinkList.h"
using namespace std;
using namespace DTLib;
class Test : public Object
{
int m_id;
public:
Test(int id = 0)
{
m_id = id;
}
~Test()
{
if( m_id == 1 )
{
throw m_id;
}
}
};
int main()
{
LinkList<Test> list;
Test t0(0), t1(1), t2(2);
try
{
list.insert(t0);
list.insert(t1);
list.insert(t2);
list.remove(1);
}
catch(int e)
{
cout << e << endl;
cout << list.length() << endl;
}
return 0;
}
/*
出现的问题:
Qt 编译器直接崩溃,VS 编译器也直接崩溃,
因为析构函数中不能抛出异常。但是VS 编译器输出了 1 和 3。
按理说我们移除了一个元素,为什么编译器长输出的长度还是3而不是2呢?
这说明了remove()函数没有考虑异常安全性
再LinkList中,remove函数
m_length--;
destroy(toDel); //应该全部完成再销毁
clear() 函数改成:
void clear()
{
while(m_header.next != nullptr)
{
Node* toDel = m_header.next;
m_header.next = toDel->next;
m_length--;
destroy(toDel);
}
}
*/
问题3 ( LinkList 中遍历操作与删除操作的混合使用)
#include <iostream>
#include "LinkList.h"
using namespace std;
using namespace DTLib;
int main()
{
LinkList<int> list;
for(int i = 0; i < 5; i++)
{
list.insert(i);
}
for(list.move(0); !list.end(); list.next())
{
if(list.current() == 3)
{
list.remove(list.current());
cout << list.current() << endl;
}
}
for(list.move(0); !list.end(); list.next())
{
cout << list.current() << endl;
}
return 0;
}
/*
出现的问题: 出现随机值
问题原因:链表被删除后,m_current 依旧指向了原来的内存
解决方案:我们可以把m_current 指向下一个内存。
bool remove(int i)
{
bool ret = ((0 <= i) && (i <= m_length));
if(ret)
{
Node* current = position(i);
Node* toDel = current->next;
if(m_current == toDel) // 判断m_current是否指向了即将被删除的元素
{
m_current = toDel->next; //m_current 指向即将被删除的下一个元素(因为这一块元素要被删除掉了,没有这一步骤,会出现随机值)
}
current->next = toDel->next; // 链接
m_length--;
destroy(toDel);
}
return ret;
}
*/
问题4(StaticLinkList 中数据元素删除时的效率问题)
/*
这里的最终结果依然出现了问题,最后仅得到一个 0 值。
*/
#include <iostream>
#include "StaticLinkList.h"
using namespace std;
using namespace DTLib;
int main()
{
StaticLinkList<int, 10> list;
for(int i=0; i<5; i++)
{
list.insert(i);
}
list.remove(3); // 会调用到 destroy()里去;
for(int i=0; i<list.length(); i++)
{
cout << list.get(i) << endl;
}
}
/*
最后for循环加一个break即可;
void destroy(Node *pn)
{
SNode* space = reinterpret_cast<SNode*>(m_space); // 要做指针运算,所以要做强制类型转换
SNode* pst = dynamic_cast<SNode*>(pn); // 从父类指针转换到子类指针
for(int i=0; i<N; i++) // 查看需要归还的空间是哪一个
{
if(pst == (space + i)) // 当pst 等于(space + i) 的地址时, i 空间需要归还
{
m_used[i] = 0; // 标记当前内存单元可用
pst->~SNode(); // 析构
break; // 归还了就可以跳出循环了,没有必要继续循环下去,虽然在循环下去月不可能走进if语句了,数据结构为了高效
*/
}
}
}
问题5 (非常经典的问题)
#include <iostream>
#include "LinkList.h"
#include "StaticLinkList.h"
using namespace std;
using namespace DTLib;
int main()
{
StaticLinkList<int, 5> list;
for(int i = 0; i < 5; i++)
{
list.insert(i);
}
for(int i = 0; i < 5; i++)
{
cout << list.get(i) << endl;
}
}
// 这个子类对象被析构的时候,会调用父类的析构函数,,父类delete的内存空间来自子类所创建的空间,
// 最终delete的就不是堆空间了,就会造成系统不稳定,程序什么时间会崩溃是无法预测的
/*
是否提供析构函数由资源来决定,申请了资源就要归还(析构)
StaticLinkList.h 在构造函数中没有申请系统资源, 从资源的角度,就没有必要提供析构函数了;
(有一个前提条件,所实现的类是一个单独的类,没有任何继承关系,)
但,是否提供唯一的析构函数,不能以唯一的准则来判断
打开所继承的LinkList类, LinkList提供了析构函数,并且在析构函数中调用了虚函数,但是,多态是不会发生的
构造函数和析构函数中是不会发生多态的,,,
所以,不管在哪里调用,都是唯一的版本,
仔细分析,clear()中又调用了 destory()虚函数,
而 destory()在父类中一个版本,在子类中又实现一个版本
意味着,父类的析构函数被调用的时候,始终调用的destory() 都是父类中的destory(),
子类的destory()是没有办法在析构的时候被调用到的,
构造函数和析构函数中是不会发生多态的,,
不管你是直接调用的虚函数还是间接调用的虚函数,都是当前类中所实现的那个虚函数
解决方案:
在子类中添加自己的析构版本,
~StaticLinkList()
{
this->clear();
}
调用的是父类的clear函数,clear函数调用destory(),就是当前类中的实现
构造函数和析构函数是不会发生多态的,所调用的虚函数,都是当前类中的实现版本,不管是直接调用还是间接调用;
*/
问题6(DTLib 是否有必要增加多维数组类?)
#include <iostream>
#include "StaticLinkList.h"
#include "DynamicArray.h"
using namespace std;
using namespace DTLib;
/*
— DTLib 是否有必要增加多维数组类?
答:没必要,因为多维数组的本质:数组的数组
如果你想得到一个二维数组对象,可以这样做:
来构造不规则的二维数组;;;
*/
int main()
{
DynamicArray< DynamicArray<int> > d; // d 是一个对象,具体类型是一个数组,保存的元素是另一个数组 《二维》
d.resize(3); // 第一维数组 3行
for(int i = 0; i < d.length();i++) // 确定每一行的元素
{
d[i].resize(i + 1); // 第0数组 有一个元素, 第2数组,又两个元素~~
}
for(int i = 0; i < d.length();i++)
{
for(int j = 0; j < d[i].length(); j++)
{
d[i][j] = i * j;
}
}
for(int i = 0; i < d.length();i++)
{
for(int j = 0; j < d[i].length(); j++)
{
cout << d[i][j] << " ";
}
cout << endl;
}
return 0;
}