参考教程《数据结构与算法分析 C++描述 》第三版和第四版
这次写的过程中,发现的两个新的 问题:
第一:自己定义的数据结构UDT如果需要使用C++11中的范围for语句,需要定义begin和end函数。如果遍历的是常量对象,则要有相应的常量对象可用的begin和end函数。
声明形式如下:
iterator begin();
const_iterator begin() const;
第二个问题主要的问题集中在operator=函数的实现。有两个大类的实现:
1、使用自赋值检查->先创建临时的对象(防止申请内存过程中出现异常,便于处理异常,而且不损坏原对象)->释放原对象->接管临时对象
2、第二种是使用copy and swap,这种方法使用的时候(1)如果直接使用std::swap,则需要定义移动赋值函数,并且移动赋值函数中需要对类成员一一进行交换,而不能直接交换对象,否则会出现赋值操作符反复的调用,最后堆栈溢出。(2)使用自己定义的swap函数,在这个里面可以使用std::swap()函数,把类成员一一交换。
这个程序还有很多地方需要补充,主要体现在错误控制方面,比如没有对传入erase函数的迭代器进行有效性检查(如果传入的是指向另一个对象的迭代器呢?)等等。。。
记录一下今天的代码:
首先是mylist.h文件,定义了List模板
#ifndef MYLIST_H
#define MYLIST_H
#include // 在 赋值操作符重载的时候,可能会用到std::swap函数
/*
* 双向链表模板,包含表头节点(m_head指向)和尾节点(m_tail指向)
*/
template
class List
{
public:
typedef unsigned int size_t;
typedef Object value_type;
private:
// 节点通过struct定义,数据成员虽然默认都为public的访问级别,但是其本身是private的
struct Node{
value_type sNode_data; // 存储的内容
Node *sNode_pprev; // 前一个节点
Node *sNode_pnext; // 后一个节点
Node(const value_type & data = value_type(),
Node *prev = nullptr,
Node *next = nullptr)
: sNode_data(data), sNode_pprev(prev), sNode_pnext(next){}
};
public:
// 定义常迭代器和普通迭代器
class const_iterator{
public:
const_iterator() : p_current(nullptr) {}
const value_type & operator*() const {
return retrieve();
}
const_iterator & operator++(){
p_current = p_current->sNode_pnext;
return *this;
}
const_iterator & operator++(int){
const_iterator old = *this;
++(*this);
return old;
}
const_iterator & operator--(){
p_current = p_current->sNode_pprev;
return *this;
}
const_iterator & operator--(int){
const_iterator old = *this;
--(*this);
return old;
}
bool operator==(const const_iterator & rhs) const
{
return p_current == rhs.p_current;
}
bool operator!=(const const_iterator & rhs) const
{
return p_current != rhs.p_current;
}
protected:
Node *p_current;
value_type & retrieve() const{
return p_current->sNode_data;
}
const_iterator(Node *p) : p_current(p) {}
friend class List < value_type > ;
};
class iterator : public const_iterator {
public:
iterator() {}
value_type & operator*(){
return retrieve();
}
const value_type & operator*() const {
return retrieve();
}
iterator & operator++(){
p_current = p_current->sNode_pnext;
return *this;
}
iterator & operator++(int){
iterator old = *this;
++(*this);
return old;
}
iterator & operator--(){
p_current = p_current->sNode_pprev;
return *this;
}
iterator & operator--(int){
iterator old = *this;
--(*this);
return old;
}
protected:
iterator(Node *p) : const_iterator(p) {}
friend class List < value_type > ;
};
public:
// big-three
List() {
init();
}
List(const List & rhs) {
init();
for (const_iterator citr = rhs.cbegin();
citr != rhs.cend();
++citr)
push_back(*citr);
// 由于List内定义了begin和end函数,因此可以使用C++11中的范围for语句,如下:
#if 0
for ( auto & x : rhs)
push_back(x);
#endif
}
~List() {
clear();
delete m_head;
delete m_tail;
}
const List & operator=(const List & rhs) {
if (this == &rhs)
return *this;
List *tmp = new List(rhs); // 这里可以检查异常,防止从heap申请空间失败
// 下面三行释放当前对象空间
clear();
delete m_head;
delete m_tail;
// 下面三行接管新开辟的内存
m_theSize = tmp->m_theSize;
m_head = tmp->m_head;
m_tail = tmp->m_tail;
// 第二种实现方法,可以不用证同测试,也能实现异常安全
// 但是使用了STL 中的 std::swap 函数 (此时,必须定义移动赋值函数,不然会出现死循环)
// 因为std::swap中可能又使用了赋值操作符,如果没有定义移动赋值函数,则会出现反复调用自身
// 从而产生死循环,最后堆栈溢出
// 如果有的话,则调用了移动构造函数,避免的自身的调用
// 当然也可以吧移动构造函数中:
// std::swap(m_theSize, tmp.m_theSize);
// std::swap(m_head, tmp.m_head);
// std::swap(m_tail, tmp.m_tail);
// 直接替换 std::swap(*this, tmp);也可以完成赋值操作
// 当然,也可以自己为List定制swap,让每个成员对换,实现在后面private区域中
// 函数结束后,tmp会析构,但是tmp的内容和*this互换了,所以析够的是*this原来的内容
#if 0
List tmp(rhs);
swap(*this, tmp);
/*
std::swap(m_theSize, tmp.m_theSize);
std::swap(m_head, tmp.m_head);
std::swap(m_tail, tmp.m_tail);
*/
#endif
return *this;
}
// 移动赋值函数
List & operator= (List && rhs)
{
std::swap(m_theSize, rhs.m_theSize);
std::swap(m_head, rhs.m_head);
std::swap(m_tail, rhs.m_tail);
return *this;
}
// 返回普通迭代器的begin函数 和 返回常迭代器的begin函数
// 该迭代器指向第一个元素节点(m_head的下一个节点)
iterator begin() {
return iterator(m_head->sNode_pnext);
}
const_iterator begin() const {
return const_iterator(m_head->sNode_pnext);
}
const_iterator cbegin() const {
return const_iterator(m_head->sNode_pnext);
}
// 返回普通迭代器的end函数 和 返回常迭代器的end函数
// 该迭代器指向最后一个元素的下一个节点(即m_tail指向的位置)
iterator end() {
return iterator(m_tail);
}
const_iterator end() const {
return const_iterator(m_tail);
}
const_iterator cend() const {
return const_iterator(m_tail);
}
// 返回链表中元素的个数
int size() const {
return m_theSize;
}
// 判断链表是否为空
bool empty() const{
return 0 == m_theSize;
}
// 清空链表中的元素
void clear(){
while (!empty())
pop_front();
}
// 返回链表中第一个的元素
value_type & front() {
return *begin();
}
const value_type & front() const{
return *beign();
}
// 返回链表中最后一个元素
value_type & back(){
return *(--end());
}
const value_type & back() const{
return *(--end());
}
// 在链表的第一个元素之前插入x
void push_front(const value_type & x){
insert(begin(), x);
}
// 在链表最后一个元素之后插入x
void push_back(const value_type & x){
insert(end(), x);
}
// 删除链表中第一个元素
void pop_front(){
erase(begin());
}
// 删除链表中最后一个元素
void pop_back(){
erase(--end());
}
// 在itr之前插入元素x,返回新插入元素的位置
iterator insert(iterator itr, const value_type & x){
Node *p = itr.p_current;
++m_theSize;
Node *newNode = new Node(x, p->sNode_pprev, p);
p->sNode_pprev->sNode_pnext = newNode;
p->sNode_pprev = newNode;
return iterator(newNode);
}
// 删除itr指向的元素,返回被删除元素之后的那一项的位置
iterator erase(iterator itr){
Node *p = itr.p_current;
iterator retVal(p->sNode_pnext);
p->sNode_pprev->sNode_pnext = p->sNode_pnext;
p->sNode_pnext->sNode_pprev = p->sNode_pprev;
delete p;
--m_theSize;
return retVal;
}
iterator erase(iterator start, iterator end)
{
for (auto itr = start;
itr != end;
itr = erase(itr));
// 在STL使用中,遍历一个容器时,删除一个元素的方法也是如此。
// 要注意迭代器失效的问题。
// 对于List,由于其存储结构,for中的第三部分也可以写成 erase(itr++);
return end;
}
private:
// init函数用于生成一个空链表
void init() {
m_theSize = 0;
m_head = new Node;
m_tail = new Node;
m_head->sNode_pnext = m_tail;
m_tail->sNode_pprev = m_head;
}
// 把a和b交换
void swap(List & a, List &b)
{
std::swap(a.m_theSize, b.m_theSize);
std::swap(a.m_head, b.m_head);
std::swap(a.m_tail, b.m_tail);
}
private:
size_t m_theSize;
Node *m_head;
Node *m_tail;
};
#endif
demoTest.cpp
#include
#include"mylist.h"
#include
using namespace std;
int main()
{
cout << "*** ==== 测试无参构造函数" << endl;
List a;
if (a.empty())
cout << " ---- 链表已经调用无参构造函数,此时链表为空" << endl;
cout << endl;
//---------
cout << "*** ==== 测试push_back和push_front函数(相当于测试了insert函数)" << endl;
a.push_back(1);
a.push_back(2);
a.push_back(3);
a.push_front(10);
a.push_front(12);
cout << "*** ==== 测试begin和end函数,以及迭代器中的自增操作符" << endl;
cout << " ---- 链表元素为:";
for (List::const_iterator cit = a.begin();
cit != a.end();
++cit)
cout << *cit << " ";
cout << endl;
cout << endl;
//---------
cout << "*** ==== 测试pop_front 和 pop_back函数(相当于测试了erase函数)" << endl;
cout << "*** ==== 测试front和back函数" << endl;
a.pop_back();
a.pop_front();
cout << " ---- 此时第一个元素为 " << a.front()
<< " 最后一个元素为 " << a.back() << endl;
cout << endl;
//---------
cout << "*** ==== 测试拷贝构造函数" << endl;
List b(a);
cout << " ---- 链表元素为:";
for (auto & x : b)
cout << x << " ";
cout << endl;
cout << endl;
//----------
cout << "*** ==== 测试赋值函数" << endl;
List c;
c = a;
cout << " ---- 链表元素为:";
for (auto & x : c)
cout << x << " ";
cout << endl;
cout << endl;
return 0;
}
运行的结果如下
运行环境 window10+VS2013