1 项目设计
在考虑设计时,我们需要提供如下4个类:
1、DoubleLinkedList类本身,它包含连接到表两端的链、表的大小,以及一些方法。
2、Node类,它很有可能是一个私有的内嵌类。一个节点包含数据和指向前后两个节点的两个指针,以及一些适当的构造函数。
3、const_iterator类,它抽象了位置的概念,而且是一个公有的内嵌类。存储一个指向“当前”节点的指针,并提供基本迭代器操作的实现,所有的操作,像=、==、!=和++等,均以重载运算符的形式出现。
4、iterator类,它抽象了位置的概念,而且是一个公有的内嵌类。有着与const_iterator相同的功能,但operator*返回的是所指项的引用,而不是对该项的常量引用。iterator可以用在任何需要const_iterator的例程中,但反之不真。换句话说,itr是一个const_iterator。
1.1 DoubleLinkedList类
template <typename DT>
class DoubleLinkedList
{
private:
struct Node //内嵌Node类
{
//见1.2节
};
public:
class const_iterator
{
//见1.3节
};
class iterator : public const_iterator //继承,iterator具有和const_iterator完全相同的功能,继承了const_iterator的所有数据和方法,并且可以用在任何需要const_iterator的地方。
{
//见1.4节
};
public:
DoubleLinkedList( ) //创建一个空的List
{ //见1.5节 }
~DoubleLinkedList( ) //析构函数回收头节点和尾节点
{
//见1.5节
}
DoubleLinkedList( const DoubleLinkedList & rhs ) //拷贝构造函数
{
//见1.5节
}
DoubleLinkedList & operator= ( const DoubleLinkedList & rhs ) //拷贝赋值
{
//见1.5节
}
DoubleLinkedList( DoubleLinkedList && rhs ) //移动构造函数
: theSize{ rhs.theSize }, head{ rhs.head }, tail{ rhs.tail }
{
//见1.5节
}
DoubleLinkedList & operator= ( DoubleLinkedList && rhs ) //移动赋值
{
//见1.5节
}
iterator begin( ) //返回一个适当的迭代器,表示容器中的第一项
{ return iterator( head->next ); }
const_iterator begin( ) const
{ return const_iterator( head->next ); }
iterator end( ) //返回一个适当的迭代器,表示容器中的尾端(终端)标记(即容器中最后一项之后的位置)
{ return iterator( tail ); }
const_iterator end( ) const
{ return const_iterator( tail ); }
int size( ) const //返回当前表中的元素数目
{ return theSize; }
bool empty( ) const //如果表为空返回true,反之返回false
{ return size( ) == 0; }
void clear( ) //通过反复删除成员项直至List为空来完成清除工作。
{
while( !empty( ) )
pop_front( );
}
DT & front( ) //读取最前端元素
{ return *begin( ); }
const DT & front( ) const
{ return *begin( ); }
DT & back( ) //读取最后端元素
{ return *--end( ); }
const DT & back( ) const
{ return *--end( ); }
void push_front( const DT & x ) //在表最前端插入元素
{ insert( begin( ), x ); }
void push_back( const DT & x ) //在表最尾端插入元素
{ insert( end( ), x ); }
void push_front( DT && x )
{ insert( begin( ), std::move( x ) ); }
void push_back( DT && x )
{ insert( end( ), std::move( x ) ); }
void pop_front( ) //删除表最前端的元素
{ erase( begin( ) ); }
void pop_back( ) //删除表最尾端的元素
{ erase( --end( ) ); }
iterator insert( iterator itr, const DT & x ) //在itr前插入x
{
//见1.6节
}
iterator insert( iterator itr, DT && x )
{
//见1.6节
}
iterator erase( iterator itr ) //删除itr处的元素
{
//见1.7节
}
iterator erase( iterator from, iterator to ) //删除迭代器from和to所指位置之间的元素
{
//见1.7节
}
private:
int theSize; //跟踪一个数据成员的大小,以便size方法能够以常数时间被实现。
Node *head; //指向头节点的指针
Node *tail; //指向尾节点的指针
void init( ) //创建一个空的表
{
theSize = 0;
head = new Node;
tail = new Node;
head->next = tail;
tail->prev = head;
}
};
观察第5行私有嵌套的Node类的声明的开始部分,可以看到这里使用了struct而不是使用class关键字。C++中的struct在本质上来说就是其成员默认为公有的class。在声明一个大部分数据需要直接访问而不是使用方法来访问的类型时可以使用struct。在我们的例子中,令Node类中的成员为public并不会成为什么问题,因为Node类本身是私有的并且不允许来自List类之外的访问。
观察第10行的公有嵌套的const_iterator类的声明的开始部分和第16行公有嵌套的iterator类的声明的开始部分,可以看到这里使用了继承。iterator具有与const_iterator完全相同(有可能会多一些)的功能,并且iterator与const_iterator的数据类型是完全兼容的。所有需要使用const_iterator的地方都可使用iterator。
第22行到第49行是构造函数、五大函数和一些方法。
begin 和end返回适当的迭代器;第52行的调用是一个典型的实现,在实现中返回一个已构造的迭代器,这样iterator和const_iterator类每一个都有自己的构造函数,该构造函数使用指向Node的指针作为参数。
第63行的size方法返回当前表中的元素数目。
第66行的empty方法可判断表是否为空。
第69行的clear方法执行的时候重复地执行删除操作来删除每一项,直到List变为空的为止。使用这种策略可以避免clear染指结点空间的回收。现在节点空间的回收已经归入pop_front来执行。
第75~103行的方法都是通过巧妙地包含和使用恰当的迭代器来工作的。
第105行insert方法在某个位置之前插入,因此,在需要的时候,push_back在末尾标记之前插入。对于pop_back,erase(–end())生成了一个对应末尾标记的临时迭代器,后移这个临时迭代器,然后使用这个迭代器来执行erase。back也使用相似的工作原理。在pop_front和pop_back操作时,我们再一次避免使用结点回收。
第140~142行是List的数据成员,命名了指向表头结点和尾结点的指针。我们也将记录数据成员的大小,这样一来size方法就可以在常量的时间内实现。
1.2 Node类
struct Node //内嵌Node类
{
DT data; //所存储的项
Node *prev; //指向前一节点的指针
Node *next; //指向下一节点的指针
Node( const DT & d = DT{ }, Node * p = nullptr, Node * n = nullptr ) //拷贝构造函数
: data{ d }, prev{ p }, next{ n } { }
Node( DT && d, Node * p = nullptr, Node * n = nullptr ) //移动构造函数
: data{ std::move( d ) }, prev{ p }, next{ n } { }
};
该类包括所存储的项、指向Node之前及之后的指针和一个构造函数。所有的数据成员都是公有的。
1.3 const_iterator类
class const_iterator
{
public:
const_iterator( ) : current{ nullptr } //零参数构造函数
{ }
const DT & operator* ( ) const //重载指针运算符operator*
{ return retrieve( ); }
const_iterator & operator++ ( ) //重载前缀自增运算符operator++
{
current = current->next;
return *this;
}
const_iterator operator++ ( int ) //重载后缀自增运算符operator++
{
const_iterator old = *this;
++( *this );
return old;
}
const_iterator & operator-- ( ) //重载前缀自减运算符operator--
{
current = current->prev;
return *this;
}
const_iterator operator-- ( int ) //重载后缀自减运算符operator--
{
const_iterator old = *this;
--( *this );
return old;
}
bool operator== ( const const_iterator & rhs ) const //重载相等运算符operator==
{ return current == rhs.current; }
bool operator!= ( const const_iterator & rhs ) const //重载不等运算符operator!=
{ return !( *this == rhs ); }
protected:
Node *current; //指向"当前“节点的指针
DT & retrieve( ) const //获取当前节点的数据值
{ return current->data; }
const_iterator( Node *p ) : current{ p } //有参构造函数
{ }
friend class DoubleLinkedList<DT>; //使用friend声明,赋予List类访问const_iterator的非公有成员的权利
};
在第43和44行,const_iterator像存储它的单一数据成员一样存储指向“当前”结点的指针。一般地,这个指针都是私有的,但是如果是私有的,那么iterator将不能访问这个指针。令const_iterator的成员为protected的,将允许从const_iterator继承的类具有访问这些成员的权限,但是不允许其他的类访问。
在第48和49行是const_iterator的构造函数。该构造函数在List类的begin和end的实现中用到。我们不希望所有的类都能访问这个构造函数(假定迭代器不能从指针变量显式构造),因此该构造函数不可以是公有的,但是我们又希望iterator类可以访问它,因此,逻辑上说,这个构造函数是被保护的。然而,这个保护没有提供List类访问这个构造函数的权限。解决的方案是第52行的友元声明(friend declaration)。该声明允许List类访问const_iterator的非公有成员。
const_iterator的公有方法都使用操作符重载。operator==、operator!=和operator*都获得实现。在第11~22行,可以看到operator++的实现。在语法上前缀和后缀版本的operator++是完全不同的。因此,需要对不同的形式分别来编写例程。它们拥有相同的名字,因此必须用不同的符号来区分。需要通过给前缀形式指定空参数表,给后缀形式指定一个(匿名的)int参数来赋予前缀和后缀形式以不同的标识。然后,++itr调用零参数operator++;而itr++调用单参数operator++。这个int参数永远也不使用,其存在的意义仅仅在于给出一个不同的标识。
1.4 iterator类
class iterator : public const_iterator //继承,iterator具有和const_iterator完全相同的功能,继承了const_iterator的所有数据和方法,并且可以用在任何需要const_iterator的地方。
{
public:
iterator( ) //零参构造函数
{ }
DT & operator* ( ) //返回当前节点的值
{ return const_iterator::retrieve( ); }
const DT & operator* ( ) const //访问函数operator*直接使用与在const_iterator中相同的实现,显示地在iterator中实现
{ return const_iterator::operator*( ); }
iterator & operator++ ( ) //重载前缀自增运算符operator++
{
this->current = this->current->next;
return *this;
}
iterator operator++ ( int ) //重载后缀自增运算符operator++
{
iterator old = *this;
++( *this );
return old;
}
iterator & operator-- ( ) //重载前缀自减运算符operator--
{
this->current = this->current->prev;
return *this;
}
iterator operator-- ( int ) //重载后缀自减运算符operator--
{
iterator old = *this;
--( *this );
return old;
}
protected:
iterator( Node *p ) : const_iterator{ p } //保护型构造函数用到一个初始化表列来初始化继承来的当前节点
{ }
friend class DoubleLinkedList<DT>;
};
在iterator类中,受保护的构造函数(在第40行)使用一个初始化表来初始化继承来的当前的结点。我们不需要重新实现operator==和operator!=,因为它们都是未经任何改变就继承下来的。我们提供了一对新的operator++的实现(因为改变了返回类型),该新的实现隐藏了const_interator的原始实现,同时也提供了operator的一个访问函数/修改函数对。第10~11行显示的访问函数operator简单地使用了与const_iterator完全相同的实现。在iterator中,修改函数必须显式实现,因为不这样的话原始的实现就会被新加的修改函数隐藏。
1.5 DoubleLinkedList类的构造函数、五大函数和私有init例程
DoubleLinkedList( ) //创建一个空的List
{ init( ); }
~DoubleLinkedList( ) //析构函数回收头节点和尾节点
{
clear( );
delete head;
delete tail;
}
DoubleLinkedList( const DoubleLinkedList & rhs ) //拷贝构造函数
{
init( );
for( auto & x : rhs )
push_back( x );
}
DoubleLinkedList & operator= ( const DoubleLinkedList & rhs ) //拷贝赋值
{
DoubleLinkedList copy = rhs;
std::swap( *this, copy );
return *this;
}
DoubleLinkedList( DoubleLinkedList && rhs ) //移动构造函数
: theSize{ rhs.theSize }, head{ rhs.head }, tail{ rhs.tail }
{
rhs.theSize = 0;
rhs.head = nullptr;
rhs.tail = nullptr;
}
DoubleLinkedList & operator= ( DoubleLinkedList && rhs ) //移动赋值
{
std::swap( theSize, rhs.theSize );
std::swap( head, rhs.head );
std::swap( tail, rhs.tail );
return *this;
}
iterator begin( ) //返回一个适当的迭代器,表示容器中的第一项
{ return iterator( head->next ); }
const_iterator begin( ) const
{ return const_iterator( head->next ); }
iterator end( ) //返回一个适当的迭代器,表示容器中的尾端(终端)标记(即容器中最后一项之后的位置)
{ return iterator( tail ); }
const_iterator end( ) const
{ return const_iterator( tail ); }
int size( ) const //返回当前表中的元素数目
{ return theSize; }
bool empty( ) const //如果表为空返回true,反之返回false
{ return size( ) == 0; }
void clear( ) //通过反复删除成员项直至List为空来完成清除工作。
{
while( !empty( ) )
pop_front( );
}
DT & front( ) //读取最前端元素
{ return *begin( ); }
const DT & front( ) const
{ return *begin( ); }
DT & back( ) //读取最后端元素
{ return *--end( ); }
const DT & back( ) const
{ return *--end( ); }
void push_front( const DT & x ) //在表最前端插入元素
{ insert( begin( ), x ); }
void push_back( const DT & x ) //在表最尾端插入元素
{ insert( end( ), x ); }
void push_front( DT && x )
{ insert( begin( ), std::move( x ) ); }
void push_back( DT && x )
{ insert( end( ), std::move( x ) ); }
void pop_front( ) //删除表最前端的元素
{ erase( begin( ) ); }
void pop_back( ) //删除表最尾端的元素
{ erase( --end( ) ); }
iterator insert( iterator itr, const DT & x ) //在itr前插入x
{
Node *p = itr.current;
++theSize;
return iterator( p->prev = p->prev->next = new Node{ x, p->prev, p } );
}
iterator insert( iterator itr, DT && x )
{
Node *p = itr.current;
++theSize;
return iterator( p->prev = p->prev->next = new Node{ std::move( x ), p->prev, p } );
}
iterator erase( iterator itr ) //删除itr处的元素
{
Node *p = itr.current;
iterator retVal( p->next );
p->prev->next = p->next;
p->next->prev = p->prev;
delete p;
--theSize;
return retVal;
}
iterator erase( iterator from, iterator to ) //删除迭代器from和to所指位置之间的元素
{
for( iterator itr = from; itr != to; )
itr = erase( itr );
return to;
}
private:
int theSize; //跟踪一个数据成员的大小,以便size方法能够以常数时间被实现。
Node *head; //指向头节点的指针
Node *tail; //指向尾节点的指针
void init( ) //创建一个空的表
{
theSize = 0;
head = new Node;
tail = new Node;
head->next = tail;
tail->prev = head;
}
};
因为零参数构造函数和复制构造函数两者都必须分配表头结点和尾结点,我们给出了一个私有的init例程。init生成了一个空List。析构函数回收表头结点和尾结点;所有的其他结点在析构函数调用clear时回收。相似地,operator=是通过调用公有方法来实现的,而没有试图使用低级的指针操作。
1.6 插入新元素(insert)
下图阐释包含x的新节点是如何拼接到由p所指向的节点和节点p.prev之间的。对节点指针的赋值可以描述如下。
![image.png]([object Object]&name=image.png&originHeight=361&originWidth=1033&originalType=binary&ratio=1&rotation=0&showTitle=false&size=39220&status=done&style=none&taskId=uc3b88563-5949-40b3-87b5-0fcb2dafd01&title=&width=688.6666666666666)
Node* newNode = new Node{x, p -> prev, p};//第1步和第2步
p -> prev -> next = newNode;//第3步
p -> prev = newNode;//第4步
可以将第3步和第4步合并,只得到如下两行
Node* newNode = new Node{x, p -> prev, p};//第1步和第2步
p -> prev = p -> prev -> next = newNode;//第3步和第4步
不过,此时这两行还可以合并,得到
p -> prev = p -> prev -> next = new Node{x, p -> prev, p};
这就缩短了下面的insert例程
// Insert x before itr.
iterator insert( iterator itr, const Object & x )
{
Node *p = itr.current;
theSize++;
return { p->prev = p->prev->next = new Node{ x, p->prev, p } };
}
// Insert x before itr.
iterator insert( iterator itr, Object && x )
{
Node *p = itr.current;
theSize++;
return { p->prev = p->prev->next = new Node{ std::move( x ), p->prev, p } };
}
1.7 删除元素(erase)
下图显示删除一个节点的思路。如果p指向要被删除的节点,则只要改变两个指针就可以回收这个节点
p -> prev -> next = p -> next;
p -> next -> prev = p -> prev;
delete p;
![image.png]([object Object]&name=image.png&originHeight=304&originWidth=825&originalType=binary&ratio=1&rotation=0&showTitle=false&size=27121&status=done&style=none&taskId=u7d6a11d3-fd37-45b1-bec5-3523aef6266&title=&width=550)
下面显示的是一对erase例程。
// Erase item at itr.
iterator erase( iterator itr )
{
Node *p = itr.current;
iterator retVal{ p->next }; //代表被删除元素的后面的项
p->prev->next = p->next;
p->next->prev = p->prev;
delete p;
theSize--;
return retVal;
}
iterator erase( iterator from, iterator to )
{
for( iterator itr = from; itr != to; )
itr = erase( itr ); //已经返回指向下一个位置的iterator,for循环不用再itr++
return to;
}
第一个版本的erase例程包含上述的三行代码,并且返回一个指向删除元素后面的项的iterator。和insert一样,erase必须更新theSize。第二个版本的erase仅仅简单使用iterator来调用第一个版本的erase。在第16行的for循环中不可以简单地使用itr++,也不能忽略第17行中erase的返回值。itr的值在调用erase后立刻就失效了,这也是erase返回一个iterator的原因。
2 测试说明
2.1 查找函数(find)
template <typename DT>
typename DoubleLinkedList <DT>:: iterator find ( DoubleLinkedList <DT>& _list, DT& _val ) //查找函数,如果找到元素值返回当前迭代器,若找不到返回尾端标记
{
typename DoubleLinkedList <DT>:: iterator itr = _list.begin();
while (itr != _list.end())
{
if (*itr == _val)
{
return itr;
}
itr++;
}
return _list.end();
}
利用while循环遍历每一个元素的值,若找到有值与_val相等,则返回当前迭代器,若找不到,则返回_list的尾端标记。
2.2 main函数
int main()
{
DoubleLinkedList <int> L; //创建链表 L
int a = 3;
for (int i = 1; i <= 5; i++) //用 push_back 依次插入 1, 2, 3, 4, 5
{
L.push_back(i);
}
cout << "输出L:" << endl;
for (DoubleLinkedList <int>:: iterator itr = L.begin(); itr != L.end(); ++itr) //输出 L
{
cout << *itr << endl;
}
DoubleLinkedList <int>:: iterator iter;
iter = find(L, a); //执行find操作,如果iter等于L.end(),表明未找到元素值,打印can't find,如果iter等于L.end(),表明未找到元素值,打印can't find,若找到返回当前迭代器所指位置的值
cout << "执行find操作:" << endl;
if (iter == L.end()){
cout << "can't find" << endl;
}else{
cout << *iter << endl;
}
L.erase(iter); //删除iter所指位置的值
cout << "erase后输出L:" << endl;
for (DoubleLinkedList <int>:: iterator itr = L.begin(); itr != L.end(); ++itr)
{
cout << *itr << endl;
}
iter = find(L, a); //执行find操作
cout << "erase后执行find操作:" << endl;
if (iter == L.end()){
cout << "can't find" << endl;
}else{
cout << *iter << endl;
}
return 0;
}
(1)第3行DoubleLinkedList L; 创建链表 L;。
(2)借助for循环,用 push_back 依次向L表插入值为1, 2, 3, 4, 5的节点。
(3)输出 L,输出所有元素的值,运行后所有元素值成功打印,证明push_back成功执行。
(4)执行find操作,如果iter等于L.end(),表明未找到元素值,打印can’t find,如果iter等于L.end(),表明未找到元素值,打印can’t find,若找到返回当前迭代器所指位置的值。运行后,打印出元素3,证明find函数在表中能找到值与目标值相等的元素。
(5)执行erase(iter); 删除iter所指位置的值 。
(6)再次输出 L,运行后,只有3未打印,其他元素均被打印出,证明erase成功执行。
(7)erase后再次执行find函数,运行后打印出can’t find,证明erase函数成功执行,并且find函数能够发现表中是否存在值与目标值相等的元素。
运行结果如图:
![image.png]([object Object]&name=image.png&originHeight=754&originWidth=1489&originalType=binary&ratio=1&rotation=0&showTitle=false&size=184317&status=done&style=none&taskId=u7e383ee1-b0db-4cc8-acf0-8e78602285b&title=&width=992.6666666666666)
2.3 内存泄漏检查
//在Linux系统中安装valgrind。安装完成后,在终端进入源文件所在文件夹,输入下列指令进行编译
gcc main.cpp -g -o main
//下图为编译成功结果
![image.png]([object Object]&name=image.png&originHeight=726&originWidth=1420&originalType=binary&ratio=1&rotation=0&showTitle=false&size=212262&status=done&style=none&taskId=u678f120d-113a-40fb-9636-109ba94ba00&title=&width=946.6666666666666)
//编译通过后,输入下列指令进行检测
valgrind --tool=memcheck --leak-check=full ./main
![image.png]([object Object]&name=image.png&originHeight=438&originWidth=1135&originalType=binary&ratio=1&rotation=0&showTitle=false&size=98191&status=done&style=none&taskId=u64fac650-d723-42b6-82a8-a6c69455aee&title=&width=756.6666666666666)
根据HEAP SUMMARY,有73728个字节的空间获得申请,并且程序运行后均获得释放。因此,该程序无内存泄漏问题。
3 完整代码
3.1 DoubleLinkedList.h
#ifndef DOUBLELINKEDLIST_H
#define DOUBLELINKEDLIST_H
#include <algorithm>
using namespace std;
template <typename DT>
class DoubleLinkedList
{
private:
struct Node //内嵌Node类
{
DT data; //所存储的项
Node *prev; //指向前一节点的指针
Node *next; //指向下一节点的指针
Node( const DT & d = DT{ }, Node * p = nullptr, Node * n = nullptr ) //拷贝构造函数
: data{ d }, prev{ p }, next{ n } { }
Node( DT && d, Node * p = nullptr, Node * n = nullptr ) //移动构造函数
: data{ std::move( d ) }, prev{ p }, next{ n } { }
};
public:
class const_iterator
{
public:
const_iterator( ) : current{ nullptr } //零参数构造函数
{ }
const DT & operator* ( ) const //重载指针运算符operator*
{ return retrieve( ); }
const_iterator & operator++ ( ) //重载前缀自增运算符operator++
{
current = current->next;
return *this;
}
const_iterator operator++ ( int ) //重载后缀自增运算符operator++
{
const_iterator old = *this;
++( *this );
return old;
}
const_iterator & operator-- ( ) //重载前缀自减运算符operator--
{
current = current->prev;
return *this;
}
const_iterator operator-- ( int ) //重载后缀自减运算符operator--
{
const_iterator old = *this;
--( *this );
return old;
}
bool operator== ( const const_iterator & rhs ) const //重载相等运算符operator==
{ return current == rhs.current; }
bool operator!= ( const const_iterator & rhs ) const //重载不等运算符operator!=
{ return !( *this == rhs ); }
protected:
Node *current; //指向"当前“节点的指针
DT & retrieve( ) const //获取当前节点的数据值
{ return current->data; }
const_iterator( Node *p ) : current{ p } //有参构造函数
{ }
friend class DoubleLinkedList<DT>; //使用friend声明,赋予List类访问const_iterator的非公有成员的权利
};
class iterator : public const_iterator //继承,iterator具有和const_iterator完全相同的功能,继承了const_iterator的所有数据和方法,并且可以用在任何需要const_iterator的地方。
{
public:
iterator( ) //零参构造函数
{ }
DT & operator* ( ) //返回当前节点的值
{ return const_iterator::retrieve( ); }
const DT & operator* ( ) const //访问函数operator*直接使用与在const_iterator中相同的实现,显示地在iterator中实现
{ return const_iterator::operator*( ); }
iterator & operator++ ( ) //重载前缀自增运算符operator++
{
this->current = this->current->next;
return *this;
}
iterator operator++ ( int ) //重载后缀自增运算符operator++
{
iterator old = *this;
++( *this );
return old;
}
iterator & operator-- ( ) //重载前缀自减运算符operator--
{
this->current = this->current->prev;
return *this;
}
iterator operator-- ( int ) //重载后缀自减运算符operator--
{
iterator old = *this;
--( *this );
return old;
}
protected:
iterator( Node *p ) : const_iterator{ p } //保护型构造函数用到一个初始化表列来初始化继承来的当前节点
{ }
friend class DoubleLinkedList<DT>;
};
public:
DoubleLinkedList( ) //创建一个空的List
{ init( ); }
~DoubleLinkedList( ) //析构函数回收头节点和尾节点
{
clear( );
delete head;
delete tail;
}
DoubleLinkedList( const DoubleLinkedList & rhs ) //拷贝构造函数
{
init( );
for( auto & x : rhs )
push_back( x );
}
DoubleLinkedList & operator= ( const DoubleLinkedList & rhs ) //拷贝赋值
{
DoubleLinkedList copy = rhs;
std::swap( *this, copy );
return *this;
}
DoubleLinkedList( DoubleLinkedList && rhs ) //移动构造函数
: theSize{ rhs.theSize }, head{ rhs.head }, tail{ rhs.tail }
{
rhs.theSize = 0;
rhs.head = nullptr;
rhs.tail = nullptr;
}
DoubleLinkedList & operator= ( DoubleLinkedList && rhs ) //移动赋值
{
std::swap( theSize, rhs.theSize );
std::swap( head, rhs.head );
std::swap( tail, rhs.tail );
return *this;
}
iterator begin( ) //返回一个适当的迭代器,表示容器中的第一项
{ return iterator( head->next ); }
const_iterator begin( ) const
{ return const_iterator( head->next ); }
iterator end( ) //返回一个适当的迭代器,表示容器中的尾端(终端)标记(即容器中最后一项之后的位置)
{ return iterator( tail ); }
const_iterator end( ) const
{ return const_iterator( tail ); }
int size( ) const //返回当前表中的元素数目
{ return theSize; }
bool empty( ) const //如果表为空返回true,反之返回false
{ return size( ) == 0; }
void clear( ) //通过反复删除成员项直至List为空来完成清除工作。
{
while( !empty( ) )
pop_front( );
}
DT & front( ) //读取最前端元素
{ return *begin( ); }
const DT & front( ) const
{ return *begin( ); }
DT & back( ) //读取最后端元素
{ return *--end( ); }
const DT & back( ) const
{ return *--end( ); }
void push_front( const DT & x ) //在表最前端插入元素
{ insert( begin( ), x ); }
void push_back( const DT & x ) //在表最尾端插入元素
{ insert( end( ), x ); }
void push_front( DT && x )
{ insert( begin( ), std::move( x ) ); }
void push_back( DT && x )
{ insert( end( ), std::move( x ) ); }
void pop_front( ) //删除表最前端的元素
{ erase( begin( ) ); }
void pop_back( ) //删除表最尾端的元素
{ erase( --end( ) ); }
iterator insert( iterator itr, const DT & x ) //在itr前插入x
{
Node *p = itr.current;
++theSize;
return iterator( p->prev = p->prev->next = new Node{ x, p->prev, p } );
}
iterator insert( iterator itr, DT && x )
{
Node *p = itr.current;
++theSize;
return iterator( p->prev = p->prev->next = new Node{ std::move( x ), p->prev, p } );
}
iterator erase( iterator itr ) //删除itr处的元素
{
Node *p = itr.current;
iterator retVal( p->next );
p->prev->next = p->next;
p->next->prev = p->prev;
delete p;
--theSize;
return retVal;
}
iterator erase( iterator from, iterator to ) //删除迭代器from和to所指位置之间的元素
{
for( iterator itr = from; itr != to; )
itr = erase( itr );
return to;
}
private:
int theSize; //跟踪一个数据成员的大小,以便size方法能够以常数时间被实现。
Node *head; //指向头节点的指针
Node *tail; //指向尾节点的指针
void init( ) //创建一个空的表
{
theSize = 0;
head = new Node;
tail = new Node;
head->next = tail;
tail->prev = head;
}
};
#endif
3.2 main.cpp
#include<iostream>
#include <stdlib.h>
#include "DoubleLinkedList.h"
using namespace std;
template <typename DT>
typename DoubleLinkedList <DT>:: iterator find ( DoubleLinkedList <DT>& _list, DT& _val ) //查找函数,如果找到元素值返回当前迭代器,若找不到返回尾端标记
{
typename DoubleLinkedList <DT>:: iterator itr = _list.begin();
while (itr != _list.end())
{
if (*itr == _val)
{
return itr;
}
itr++;
}
return _list.end();
}
int main()
{
DoubleLinkedList <int> L; //创建链表 L
int a = 3;
for (int i = 1; i <= 5; i++) //用 push_back 依次插入 1, 2, 3, 4, 5
{
L.push_back(i);
}
cout << "输出L:" << endl;
for (DoubleLinkedList <int>:: iterator itr = L.begin(); itr != L.end(); ++itr) //输出 L
{
cout << *itr << endl;
}
DoubleLinkedList <int>:: iterator iter;
iter = find(L, a); //执行find操作,如果iter等于L.end(),表明未找到元素值,打印can't find,如果iter等于L.end(),表明未找到元素值,打印can't find,若找到返回当前迭代器所指位置的值
cout << "执行find操作:" << endl;
if (iter == L.end()){
cout << "can't find" << endl;
}else{
cout << *iter << endl;
}
L.erase(iter); //删除iter所指位置的值
cout << "erase后输出L:" << endl;
for (DoubleLinkedList <int>:: iterator itr = L.begin(); itr != L.end(); ++itr)
{
cout << *itr << endl;
}
iter = find(L, a); //执行find操作
cout << "erase后执行find操作:" << endl;
if (iter == L.end()){
cout << "can't find" << endl;
}else{
cout << *iter << endl;
}
return 0;
}