LinkList.h
#ifndef LINKLIST_H
#define LINKLIST_H
#include "List.h"
#include "Exception.h"
namespace DTLib
{
template < typename T >
class LinkList : public List<T>
{
protected:
struct Node : public Object
{
T value; // 数据域,用来保存数据
Node* next; // 指针域, 用来保存下一个节点
};
//mutable Node m_header;
// 头节点存在隐患,进行代码优化 /// 此处不太理解,需要多看几遍
mutable struct
{
char reserved[sizeof(T)];
Node* next;
}m_header;
int m_length;
public:
LinkList()
{
m_header.next = NULL;
m_length = 0;
}
bool insert(const T& e) // 重载 最后一个位置插入
{
return insert(m_length, e);
}
bool insert(int i, const T& e)
{
bool ret = ((0 <= i) && (i <= m_length));
if(ret)
{
Node* node = new Node();
if( node != NULL )
{
//Node* current = &m_header; // 指针指向头节点
Node* current = reinterpret_cast<Node*>(&m_header);// 改变头节点以后的做法,为什么要这么做?
for(int p=0; p<i; p++)
{
current = current->next; // 目标位置为 i, 就移动 i 次
}
node->value = e;
node->next = current->next;
current->next = node;
m_length++;
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException, "No memery to insert new element...");
}
}
return ret;
}
bool remove(int i)
{
bool ret = ((0 <= i) && (i <= m_length));
if(ret)
{
//Node* current = &m_header;
Node* current = reinterpret_cast<Node*>(&m_header);// 改变头节点以后的做法
for(int p=0; p<i; p++)
{
current = current->next;
}
Node* toDel = current->next; // 指针指向要删除的位置
current->next = toDel->next; // 链接
delete toDel;
m_length--;
}
return ret;
}
bool set(int i, const T& e)
{
bool ret = ((0 <= i) && (i <= m_length));
if( ret )
{
// Node* current = &m_header;
Node* current = reinterpret_cast<Node*>(&m_header);// 改变头节点以后的做法
for(int p=0; p<i; p++)
{
current = current->next;
}
current->next->value = e;
}
return ret;
}
// get 函数使用不是很方便, 重新加载一个, 直接返回需要的值 ///
T get(int i) const
{
T ret;
if( get(i, ret) )
{
return ret;
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException, "Invalid parameter i to get element ...");
}
return ret;
}
bool get(int i, T& e) const
{
bool ret = ((0 <= i) && (i <= m_length));
if( ret )
{
//Node* current = &m_header; // 在const函数中不能修改任何成员变量的值,然而这里要取头节点的地址,
// 此时编译器会认为有可能会修改成员变量的值
// 解决方案: 将对应的成员加上 mutable 就可以了
Node* current = reinterpret_cast<Node*>(&m_header);// 改变头节点以后的做法
for(int p=0; p<i; p++)
{
current = current->next;
}
e = current->next->value;
}
return ret;
}
bool length() const
{
return m_length;
}
bool clear()
{
while( m_header.next ) // 通过第0号位置(头节点)的节点开始释放
{
Node* toDel = m_header.next; // 指向了第第0号位置(头节点)的节点,是即将被删除的节点
m_header.next = toDel->next; // 头节点的指针指向下一个节点
delete toDel; // 循环到头节点的指针为空时结束
}
m_length = 0;
}
~LinkList()
{
clear();
}
};
}
#endif // LINKLIST_H
main.cpp(测试一)
#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(int i = 0; i < list.length(); i++)
{
cout << list.get(i) << endl;
/* 未重载get() 函数
int v = 0;
list.get(i, v);
cout << v << endl;
*/
}
return 0;
}
main.cpp(测试二)
/*
* 此处需要做详细记录:
*
*/
using namespace std;
using namespace DTLib;
class Test
{
public:
Test()
{
throw 0;
}
};
int main()
{
LinkList<Test> list;
cout << "Hello World" << endl;
return 0;
}
LinkList.h(优化)
#ifndef LINKLIST_H
#define LINKLIST_H
#include "List.h"
#include "Exception.h"
/*
* 头节点的隐患: 自定义的头节点中仅仅使用了头节点的指针,数据与并没有使用
* 如果将泛指类型T 指定为自定义的类类型
class Test
{
public:
Test()
{
throw 0;
}
};
* 这个Test 是使用DTLib的程序员犯下的错误,和我们无关; 这样使用的结果: 报错
*
* 我们根本没有创建有问题的类的对象,我们创建的是DTLib里的单链表对象,
* 如果DTLib是一个商用的库,其他公司的程序员会打电话来抱怨库的质量问题;
* 只看代码,出问题的应该是用户编写的Test类,但是当前代码中并没有创建Test类对象,
* 那为什么会抛出这个异常呢?
*
* 查找后:
* //mutable Node m_header; 有这行代码,,,
* 主函数中创建LinkList对象,必然会调用构造函数,
* 先构造成员对象 : mutable Node m_header;
* 构造头节点的时候又会构造 value (是用户自定义的 Test 对象,是一个有问题的对象,所以就抛异常了)
* 技术支持肯定会说:是你代码的问题,是你代码导致这个异常出现,
* 其他程序员会说: 确实创建了有问题的类,但是我在main函数中并没有创建这个类对象,为什么会抛异常?
* 口水帐:::::::::::::
* 为了避免口水战,就要想方设法的使得在: 构造头节点的时候,不去构造泛指类型的构造函数;
* 这里必须定义一个内部的新类型了,并且这个类型是匿名的类型(类类型,没有具体的名字),这个类型的定义仅仅是为了头节点;
mutable struct
{
char reserved[sizeof(T)]; // 这个数组没有实际作用,仅仅为了占空间
Node* next;
}m_header;
构造头节点的时候,这里的实现当中不可能调用任何的构造函数了, 在内存布局上和之前没有差异,差异是:不管泛指类型是什么
都不会调用构造函数了
这就解决了刚刚的问题;;;;
*
*/
namespace DTLib
{
template < typename T >
class LinkList : public List<T>
{
protected:
struct Node : public Object
{
T value; // 数据域,用来保存数据
Node* next; // 指针域, 用来保存下一个节点
};
//mutable Node m_header;
// 头节点存在隐患,进行代码优化 , 此处改写为了避开泛指类型T 构造函数的调用, 匿名类和Node类应该内存相同,继承Object
mutable struct : public Object
{
char reserved[sizeof(T)];
Node* next;
}m_header;
int m_length;
// 代码优化->元素定位
Node* position(int i) const
{
Node* ret = reinterpret_cast<Node*>(&m_header); // 由于类型不同, 不能直接进行初始化
for(int p=0; p<i; p++)
{
ret = ret->next;
}
return ret;
}
public:
LinkList()
{
m_header.next = NULL;
m_length = 0;
}
bool insert(const T& e) // 重载 最后一个位置插入
{
return insert(m_length, e);
}
bool insert(int i, const T& e)
{
bool ret = ((0 <= i) && (i <= m_length));
if(ret)
{
Node* node = new Node();
if( node != NULL )
{
Node* current = position(i);
/*
//Node* current = &m_header; // 指针指向头节点
Node* current = reinterpret_cast<Node*>(&m_header);// 改变头节点以后的做法,为什么要这么做?
for(int p=0; p<i; p++)
{
current = current->next; // 目标位置为 i, 就移动 i 次
}
*/
node->value = e;
node->next = current->next;
current->next = node;
m_length++;
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException, "No memery to insert new element...");
}
}
return ret;
}
bool remove(int i)
{
bool ret = ((0 <= i) && (i <= m_length));
if(ret)
{
Node* current = position(i);
/*
//Node* current = &m_header;
Node* current = reinterpret_cast<Node*>(&m_header);// 改变头节点以后的做法
for(int p=0; p<i; p++)
{
current = current->next;
}
*/
Node* toDel = current->next; // 指针指向要删除的位置
current->next = toDel->next; // 链接
delete toDel;
m_length--;
}
return ret;
}
bool set(int i, const T& e)
{
bool ret = ((0 <= i) && (i <= m_length));
if( ret )
{
/*
Node* current = position(i);
// Node* current = &m_header;
Node* current = reinterpret_cast<Node*>(&m_header);// 改变头节点以后的做法
for(int p=0; p<i; p++)
{
current = current->next;
}
current->next->value = e;
*/
position(i)->next->value = e;
}
return ret;
}
// get 函数使用不是很方便, 重新加载一个, 直接返回需要的值 ///
T get(int i) const
{
T ret;
if( get(i, ret) )
{
return ret;
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException, "Invalid parameter i to get element ...");
}
return ret;
}
bool get(int i, T& e) const
{
bool ret = ((0 <= i) && (i <= m_length));
if( ret )
{
//Node* current = position(i);
/*
//Node* current = &m_header; // 在const函数中不能修改任何成员变量的值,然而这里要取头节点的地址,
// 此时编译器会认为有可能会修改成员变量的值
// 解决方案: 将对应的成员加上 mutable 就可以了
Node* current = reinterpret_cast<Node*>(&m_header);// 改变头节点以后的做法
for(int p=0; p<i; p++)
{
current = current->next;
}
*/
e = position(i)->next->value;
}
return ret;
}
bool length() const
{
return m_length;
}
bool clear()
{
while( m_header.next ) // 通过第0号位置(头节点)的节点开始释放
{
Node* toDel = m_header.next; // 指向了第第0号位置(头节点)的节点,是即将被删除的节点
m_header.next = toDel->next; // 头节点的指针指向下一个节点
delete toDel; // 循环到头节点的指针为空时结束
}
m_length = 0;
}
~LinkList()
{
clear();
}
};
}
#endif // LINKLIST_H
main.cpp(优化)
#include <iostream>
#include "LinkList.h"
using namespace std;
using namespace DTLib;
/* 测试一:
* 可以看到是先打印的Hello World然后报的异常,这就是用户定义Test类对象的问题了,这应该由用户自己负责了。
*
*
class Test
{
public:
Test()
{
throw 0;
}
};
int main()
{
LinkList<Test> list;
cout << "Hello World" << endl;
Test t;
list.insert(t);
return 0;
}
*/
/* 测试二:
这时只打印出了16,这不是我们想要的,可见重构代码出现了问题。
这时因为我们的匿名类型变量m_header没有继承自Object,
这样的话导致m_header和Node的内存布局有可能不一样,
*/
class Test
{
public:
Test()
{
throw 0;
}
};
int main()
{
LinkList<Test> lt;
LinkList<int> list;
for(int i = 0; i<5; i++)
{
list.insert(0,i);
list.set(0, i * i);
}
for(int i = 0; i < list.length(); i++)
{
cout << list.get(i) << endl;
}
list.clear();
for(int i=0; i<list.length(); i++)
{
cout << list.get(i) << endl;
}
return 0;
}