【C++】list用法简单模拟实现
创始人
2024-05-10 17:20:21
0

在这里插入图片描述

文章目录

  • 1. list的介绍及使用
    • 1.1 list基本概念
    • 1.2 list的构造
    • 1.3 list的迭代器使用
    • 1.4 list 赋值和交换
    • 1.5 list 插入和删除
    • 1.6 list容量大小操作
    • 1.7 list 数据存取
  • 2. list的模拟实现
    • 这次要模拟实现的类及其成员函数接口总览
    • 2.1 结点类的实现
    • 2.2 迭代器的模拟实现
    • 2.3 反向迭代器模拟实现
    • 2.4 list的模拟实现
      • 构造函数
      • 拷贝构造
      • 赋值运算符重载函数
      • 析构函数
      • 迭代器相关函数
      • 插入、删除函数
      • resize
      • clear
      • swap
    • 完整的实现代码


1. list的介绍及使用

【list的文档介绍】

1.1 list基本概念

功能:将数据进行链式存储

链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的

链表的组成:链表由一系列结点组成

结点的组成:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域

STL中的链表是一个双向循环链表

image-20230106100428871

由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器

list的优点:

  • 采用动态存储分配,不会造成内存浪费和溢出
  • 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素

list的缺点:

  • 链表灵活,但是空间(指针域) 和 时间(遍历)额外耗费较大

List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的。

总结:STL中List和vector是两个最常被使用的容器,各有优缺点


1.2 list的构造

功能描述:

  • 创建list容器
构造函数( (constructor))接口说明
list (size_type n, const value_type& val = value_type())构造的list中包含n个值为val的元素
list()构造空的list
list (const list& x)拷贝构造函数
list (InputIterator first, InputIterator last)用[first, last)区间中的元素构造list

示例

// list的构造
void TestList1()
{list l1;                         // 构造空的l1list l2(4, 100);                 // l2中放4个值为100的元素list l3(l2.begin(), l2.end());  // 用l2的[begin(), end())左闭右开的区间构造l3list l4(l3);                    // 用l3拷贝构造l4// 以数组为迭代器区间构造l5int array[] = { 16, 2, 77, 29 };list l5(array, array + sizeof(array) / sizeof(int));// 列表格式初始化C++11list l6{ 1, 2, 3, 4, 5 };// 用迭代器方式打印l5中的元素list::iterator it = l5.begin();while (it != l5.end()){cout << *it << " ";++it;}cout << endl;// C++11范围for的方式遍历for (auto& e : l5)cout << e << " ";cout << endl;
}
image-20230106101258360

提醒:list构造方式同其他几个STL常用容器,熟练掌握其中几个常用的即可


1.3 list的迭代器使用

此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点

list的迭代器跟vector的迭代器并非一样是原生指针,具体是什么,后面模拟实现就清楚了

函数声明接口说明
begin + end返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin + rend返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的 reverse_iterator,即begin位置

【注意】

  1. beginend为正向迭代器,对迭代器执行**++**操作,迭代器向后移动

  2. **rbegin(end)rend(begin)为反向迭代器,对迭代器执行++**操作,迭代器向前移动

示例

// list迭代器的使用
// 注意:遍历链表只能用迭代器和范围for
void PrintList(const list& l)
{// 注意这里调用的是list的 begin() const,返回list的const_iterator对象for (list::const_iterator it = l.begin(); it != l.end(); ++it){cout << *it << " ";// *it = 10; 编译不通过}cout << endl;
}void TestList2()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list l(array, array + sizeof(array) / sizeof(array[0]));// 使用正向迭代器正向list中的元素// list::iterator it = l.begin();   // C++98中语法auto it = l.begin();                     // C++11之后推荐写法while (it != l.end()){cout << *it << " ";++it;}cout << endl;// 使用反向迭代器逆向打印list中的元素// list::reverse_iterator rit = l.rbegin();auto rit = l.rbegin();while (rit != l.rend()){cout << *rit << " ";++rit;}cout << endl;
}
image-20230106101735070

1.4 list 赋值和交换

功能描述:

  • 给list容器进行赋值,以及交换list容器
函数原型接口说明
assign(beg, end)将[beg, end)区间中的数据拷贝赋值给本身
assign(n, elem)将n个elem拷贝赋值给本身
list& operator=(const list &lst)重载等号操作符
swap(lst)将lst与本身的元素互换。

示例

// 注意:遍历链表只能用迭代器和范围for
void PrintList(const list& l)
{// 注意这里调用的是list的 begin() const,返回list的const_iterator对象for (list::const_iterator it = l.begin(); it != l.end(); ++it){cout << *it << " ";// *it = 10; 编译不通过}cout << endl;
}//赋值和交换
void TestList3()
{listL1;L1.push_back(10);L1.push_back(20);L1.push_back(30);L1.push_back(40);PrintList(L1);//赋值listL2;L2 = L1;PrintList(L2);listL3;L3.assign(L2.begin(), L2.end());PrintList(L3);listL4;L4.assign(10, 100);PrintList(L4);}//交换
void TestList4()
{listL1;L1.push_back(10);L1.push_back(20);L1.push_back(30);L1.push_back(40);listL2;L2.assign(10, 100);cout << "交换前: " << endl;PrintList(L1);PrintList(L2);cout << endl;L1.swap(L2);cout << "交换后: " << endl;PrintList(L1);PrintList(L2);}
image-20230106102612931 image-20230106102629648

1.5 list 插入和删除

功能描述:

  • 对list容器进行数据的插入和删除
函数声明接口说明
push_front在list首元素前插入值为val的元素
pop_front删除list中第一个元素
push_back在list尾部插入值为val的元素
pop_back删除list中最后一个元素
insert在list position 位置中插入值为val的元素
erase删除list position位置的元素
swap交换两个list中的元素
clear清空list中的有效元素

示例

// list插入和删除
// push_back/pop_back/push_front/pop_front
void TestList5()
{int array[] = { 1, 2, 3 };list L(array, array + sizeof(array) / sizeof(array[0]));// 在list的尾部插入4,头部插入0L.push_back(4);L.push_front(0);PrintList(L);// 删除list尾部节点和头部节点L.pop_back();L.pop_front();PrintList(L);
}
image-20230106103518520
// insert /erase 
void TestList6()
{int array1[] = { 1, 2, 3 };list L(array1, array1 + sizeof(array1) / sizeof(array1[0]));// 获取链表中第二个节点auto pos = ++L.begin();cout << *pos << endl;// 在pos前插入值为4的元素L.insert(pos, 4);PrintList(L);// 在pos前插入5个值为5的元素L.insert(pos, 5, 5);PrintList(L);// 在pos前插入[v.begin(), v.end)区间中的元素vector v{ 7, 8, 9 };L.insert(pos, v.begin(), v.end());PrintList(L);// 删除pos位置上的元素L.erase(pos);PrintList(L);// 删除list中[begin, end)区间中的元素,即删除list中的所有元素L.erase(L.begin(), L.end());PrintList(L);
}
image-20230106103538833
// resize/swap/clear
void TestList7()
{// 用数组来构造listint array1[] = { 1, 2, 3 };list l1(array1, array1 + sizeof(array1) / sizeof(array1[0]));PrintList(l1);// 交换l1和l2中的元素list l2;l1.swap(l2);PrintList(l1);PrintList(l2);// 将l2中的元素清空l2.clear();cout << l2.size() << endl;
}
image-20230106103556897

1.6 list容量大小操作

功能描述:

  • 对list容器的大小进行操作
函数声明接口说明
empty检测list是否为空,是返回true,否则返回false
size返回list中有效节点的个数
resize(num,elem)重新指定容器的长度为num,若容器变长,则以elem值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除

示例

//大小操作
void TestList8()
{listL1;L1.push_back(10);L1.push_back(20);L1.push_back(30);L1.push_back(40);if (L1.empty()){cout << "L1为空" << endl;}else{cout << "L1不为空" << endl;cout << "L1的大小为: " << L1.size() << endl;}//重新指定大小L1.resize(10);PrintList(L1);L1.resize(2);PrintList(L1);
}
image-20230106104259886

1.7 list 数据存取

功能描述:

  • 对list容器中数据进行存取
函数声明接口说明
front返回list的第一个节点中值的引用
back返回list的最后一个节点中值的引用

示例

//数据存取
void TestList9()
{listL1;L1.push_back(10);L1.push_back(20);L1.push_back(30);L1.push_back(40);//cout << L1.at(0) << endl;//错误 不支持at访问数据//cout << L1[0] << endl; //错误  不支持[]方式访问数据cout << "第一个元素为: " << L1.front() << endl;cout << "最后一个元素为: " << L1.back() << endl;//list容器的迭代器是双向迭代器,不支持随机访问list::iterator it = L1.begin();//it = it + 1;//错误,不可以跳跃访问,即使是+1
}
image-20230106104439866

总结:

  • list容器中不可以通过[]或者at方式访问数据
  • 返回第一个元素 — front
  • 返回最后一个元素 — back

2. list的模拟实现

这次要模拟实现的类及其成员函数接口总览

namespace hdm
{//模拟实现list当中的结点类templatestruct ListNode{//成员函数ListNode(const T& val = T()); //构造函数//成员变量T _val;                 //数据域ListNode* _next;   //后继指针ListNode* _prev;   //前驱指针};//模拟实现list迭代器templatestruct ListIterator{typedef ListIterator Node;typedef ListIterator Self;ListIterator(node* pnode);  //构造函数//各种运算符重载函数Self operator++();Self operator--();Self operator++(int);Self operator--(int);bool operator==(const Self& s) const;bool operator!=(const Self& s) const;Ref operator*();Ptr operator->();//成员变量Node* _node; //一个指向结点的指针};//模拟实现listtemplateclass list{public:typedef ListNode Node;// 正向迭代器typedef ListIterator iterator;typedef ListIterator const_iterator;// 反向迭代器typedef ReverseIterator reverse_iterator;typedef ReverseIterator const_reverse_iterator;//默认成员函数list();list(const list& lt);list& operator=(const list& lt);~list();//迭代器相关函数iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//反向迭代器reserve_iterator rbegin();reserve_iterator rend();const_reserve_iterator rbegin()const;const_reserve_iterator rend();const;//访问容器相关函数T& front();T& back();const T& front() const;const T& back() const;//插入、删除函数void insert(iterator pos, const T& x);iterator erase(iterator pos);void push_back(const T& x);void pop_back();void push_front(const T& x);void pop_front();//其他函数size_t size() const;void resize(size_t n, const T& val = T());void clear();bool empty() const;void swap(list& lt);private:Node* _head; //指向链表头结点的指针};
}

2.1 结点类的实现

我们在之前的章节中曾经讲述过并且也模拟实现过纯C语言版本,C++中STL标准库中的list用的是双向循环链表,它的结构看似复杂,功能却是最好,最容易实现的

image-20230109151213943

我们若要实现list,则首先需要实现一个结点类。而一个结点需要存储的信息有:数据、前一个结点的地址、后一个结点的地址(跟我们之前实现的C语言版本差不多,只不过在C语言叫结构体,而且它没有自动初始化的功能,而C++有构造函数可以解决这一问题)

而对于该结点类的成员函数来说,我们只需实现一个构造函数即可,它的节点释放可用list这个大类来实现

list的节点类

// List的节点类template struct ListNode{ListNode(const T& val=T()):_val(val), _next(nullptr), _prev(nullptr){}T _val;ListNode* _next;ListNode* _prev;};

2.2 迭代器的模拟实现

迭代器就是把不同的数据结构 "相同功能 "的函数装到一个名字相同的函数里,这样的话你在写算法的时候就可以不管你要操作的数据结构的逻辑结构了。
比如不管是链表,数组还是别的什么,统一都用迭代器进行访问的话可能都是 Next()表示下一个元素 Pre()表示上一个元素等等

至于迭代器怎么能够做到这样的功能呢,首先就是我们要实现这样的功能,必须要对对应的数据结构有一定的理解,比如vector,

我们要实现++就是下一个元素,–就是上一个元素,对于vector来说这是非常简单的,因为vector的定义是什么呀,它就是在一段

连续的空间中存储数据,因为地址的连续原生的指针就天然的就形成了所谓的迭代器了

image-20230109153522791

那么对于list呢?

很不幸,list的结构导致它的迭代器并没有vector的迭代器这么简单,因为list的空间并不连续,它的节点都是按需申请的,是用一个指针把它串联起来,这就导致了我们仅仅对它的原生指针进行++,–这样后操作后,得到的结果并不一定是我们想象的下一个节点。

所以我们需要对它的迭代器进行重新封装,并不能认为它的原生指针就可以当迭代器!!!

image-20230109153652762

​ 那么我们怎么实现这个迭代器呢?

其实我们熟悉这个结构,就可以很容易知道,我们要实现++找到下一个元素,其实对于list的操作就是它节点的next指针对应的地址就是它下一个元素,–找到上一个元素,就是prev指针对应的地址,所以我们只需要对++,–等操作符进行操作符重载即可!

/*List 的迭代器迭代器有两种实现方式,具体应根据容器底层数据结构实现:1. 原生态指针,比如:vector2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:1. 指针可以解引用,迭代器的类中必须重载operator*()2. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()3. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载4. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()*/template class ListIterator{public:typedef ListNode Node;typedef ListIterator Self;//Self是当前迭代器对象的类型:public:// Ref 和 Ptr 类型需要重定义下,实现反向迭代器时需要用到typedef Ref Ref;typedef Ptr Ptr;ListIterator(Node* node=nullptr):_node(node){}//// 具有指针类似行为//返回当前结点指针所指结点的数据Ref operator*(){return _node->_val;}//对于->运算符的重载,我们直接返回结点当中所存储数据的地址即可Ptr operator->(){return &(_node->_val);}//// 迭代器支持移动//前置++:先让结点指针指向后一个结点,然后再返回“自增”后的结点指针Self operator++(){_node = _node->_next;return *this;}//后置++:先记录当前结点指针的指向,然后让结点指针指向后一个结点,最后返回“自增”前的结点指针Self operator++(int){Self tem(*this);_node = _node->_next;return tem;}Self operator--(){_node = _node->_prev;return *this;}Self operator--(int){Self tem(*this);_node = _node->_prev;return tem;}//// 迭代器支持比较//判断这两个迭代器当中的结点指针的指向是否不同bool operator!=(const Self& lt)const{return _node != lt._node;}//判断这两个迭代器当中的结点指针的指向是否相同bool operator==(const Self& lt)const{return _node == lt._node;}Node* _node;};

迭代器类的模板参数说明
这里我们所实现的迭代器类的模板参数列表当中为什么有三个模板参数?

template

在list的模拟实现当中,我们typedef了两个迭代器类型,普通迭代器和const迭代器。

// 正向迭代器
typedef ListIterator iterator;
typedef ListIterator const_iterator;

这里我们就可以看出,迭代器类的模板参数列表当中的Ref和Ptr分别代表的是引用类型和指针类型

当我们使用普通迭代器时,编译器就会实例化出一个普通迭代器对象;当我们使用const迭代器时,编译器就会实例化出一个const迭代器对象。

若该迭代器类不设计三个模板参数,那么就不能很好的区分普通迭代器和const迭代器。

->运算符的重载说明

//对于->运算符的重载,我们直接返回结点当中所存储数据的地址即可
Ptr operator->()
{return &(_node->_val);
}

对于->运算符的重载,我们直接返回结点当中所存储数据的地址

可能你会觉得不对,按照这种重载方式的话,这里使用迭代器访问日期类当中的成员变量时不是应该用两个->吗?

image-20230109161749009


2.3 反向迭代器模拟实现

实现反向迭代器前首先要知道一个名词“适配器

所谓的适配器就是:通过限制模型的功能以让它满足另一个模型的功能,相当于改变了接口,但实现不变。

我们要实现反向迭代器其实很简单,就在原来的迭代器基础上改接口即可

image-20230109155345060

但是对于operator*()我们要特殊处理一下,因为反向迭代器的rbegin对应的是原迭代器的end()

也就是说它指向了最后一个元素的后一个位置,所以要取这个rbegin的元素的时候,我们要对end()–后再取元素

Ref operator*()
{iterator tmp = _it;return *(--tmp);
}

反向迭代器的适配器实现

//适配器
// 给我不同容器的正向迭代器,适配出对应的这个容器需要的反向迭代器
template class ReverseIterator{// 注意:此处typename的作用是明确告诉编译器,Ref是Iterator类中的一个类型,而不是静态成员变量// 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量// 因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的public:typedef typename iterator::Ref Ref;typedef typename iterator::Ptr Ptr;typedef ReverseIterator Self;ReverseIterator(iterator it):_it(it){}//前置++Self operator++(){--_it;return *this;}//后置++Self operator++(int){Self tmp(*this);--_it;return tmp;}//前置--Self operator--(){++_it;return *this;}//后置--Self operator--(int){Self tmp(*this);++_it;return tmp;}Ref operator*(){iterator tmp = _it;return *(--tmp);}Ptr operator->(){return &(operator*());}bool operator!=(const Self & l) const{return _it != l._it;}private:iterator _it;};

2.4 list的模拟实现

构造函数

list是一个带头双向循环链表,在构造一个list对象时,直接申请一个头结点,并让其前驱指针和后继指针都指向自己即可

image-20230109162251228

无参的简单构造

list()
{CreatNode();
}
void CreatNode()
{_head = new Node;_head->_next = _head;_head->_prev = _head;
}

迭代器构造

这里的迭代器不一定是list的迭代器,也可能是用的其他容器来构造list,所以需要模板实现

//迭代器构造
template
list(InputIterator first, InputIterator last)
{CreatNode();while (first != last){push_back(*first);++first;}
}

拷贝构造

拷贝构造函数就是根据所给list容器,拷贝构造出一个对象

对于拷贝构造,这里提供两种写法:

  1. 传统写法

    先申请一个头结点,并让其前驱指针和后继指针都指向自己,然后将所给容器当中的数据,通过遍历的方式一个个尾插到新构造的容器后面

//拷贝构造函数
list(const list& lt)
{_head = new node; //申请一个头结点_head->_next = _head; //头结点的后继指针指向自己_head->_prev = _head; //头结点的前驱指针指向自己for (const auto& e : lt){push_back(e); //将容器lt当中的数据一个个尾插到新构造的容器后面}
}
  1. 现代写法

    利用迭代器构造函数,构造一个一模一样的tmp,然后再跟它交换

//拷贝构造
list(const list& l)
{CreatNode();list tmp(l.begin(), l.end());swap(tmp);
}

赋值运算符重载函数

对于赋值运算符的重载,这里也提供两种写法:

  1. 传统写法
    这是比较容易理解的一种写法,先调用clear函数将原容器清空,然后将容器lt当中的数据,通过遍历的方式一个个尾插到清空后的容器当中
//传统写法
list& operator=(const list& lt)
{if (this != <) //避免自己给自己赋值{clear(); //清空容器for (const auto& e : lt){push_back(e); //将容器lt当中的数据一个个尾插到链表后面}}return *this; //支持连续赋值
}
  1. 现代写法

    首先利用编译器机制,故意不使用引用接收参数,通过编译器自动调用list的拷贝构造函数构造出来一个list对象,然后调用swap函数将原容器与该list对象进行交换

//现代写法
list& operator=(list tmp)
{swap(tmp);	return *this;
}

析构函数

先调用clear函数清理容器当中的数据,然后将头结点释放,最后将头指针置空

~list()
{clear();delete _head;_head = nullptr;
}

迭代器相关函数

begin:返回的是第一个有效数据的迭代器

end:最后一个有效数据的下一个位置的迭代器

对于list这个带头双向循环链表来说,第一个有效数据的迭代器就是头结点后的第一个结点的地址构造出来的迭代器,而其最后一个有效数据的下一个位置的迭代器就是头结点的地址构造出来的迭代器

// List的迭代器
iterator begin()
{return iterator(_head->_next);
}
iterator end()
{return iterator(_head);
}//const迭代器也必不可少
const_iterator begin() const 
{return const_iterator(_head->_next);
}const_iterator end()const
{return const_iterator(_head);
}

对于反向迭代器

rbegin:就是正向迭代器的end()

rend: 就是正向迭代器的begin()

reverse_iterator rbegin()
{return reverse_iterator(end());
}reverse_iterator rend()
{return reverse_iterator(begin());
}//const迭代器
const_reverse_iterator rbegin() const 
{return reverse_iterator(end());
}const_reverse_iterator rend() const 
{return reverse_iterator(begin());
}

插入、删除函数

插入函数算是老生常谈了,跟以往不同的就是这次要返回新节点位置的迭代器

在这里插入图片描述

// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& x)
{Node* newNode = new Node(x);Node* cur = pos._node;//将新节点插入cur->_prev->_next = newNode;newNode->_prev = cur->_prev;cur->_prev = newNode;newNode->_next = cur;return	iterator(newNode);
}

还有一些头插,尾插的接口,其实都可以复用上面的插入函数

void push_back(const T& val)
{insert(iterator(_head), val);
}void push_front(const T&val)
{insert(iterator(_head->_next), val);
}

erase函数可以删除所给迭代器位置的结点。同时还要返回该位置下一个节点的迭代器,可以有效防止迭代器失效的问题。

// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{Node* del = pos._node;Node* Ret = del->_next;//删除节点del->_prev->_next = Ret;Ret->_prev = del->_prev;delete del;return iterator(Ret);
}

当然还有一些头删,尾删,也是可以复用上面的接口

void pop_back()
{erase(_head->_prev);
}void pop_front()
{erase(_head->_next);
}

resize

resize函数的规则:

  1. 若当前容器的size小于所给n,则尾插结点,直到size等于n为止。
  2. 若当前容器的size大于所给n,则只保留前n个有效数据。
void resize(size_t newsize, const T& val = T())
{size_t oldsize = size();//newsizewhile (newsize < oldsize){pop_back();--oldsize;}}else //newsize>oldsize,用val扩充元素到newsize{while (newsize > oldsize){push_back(val);oldsize++;}}
}

clear

clear函数用于清空容器,我们通过遍历的方式,逐个删除结点,只保留头结点

void clear()
{Node*cur = _head->_next;while (cur != _head){Node* del = cur;cur = cur->_next;delete del;}
}

swap

swap函数用于交换两个容器,list容器当中存储的实际上就只有链表的头指针,我们将这两个容器当中的头指针交换

void swap(list& l)
{std::swap(_head, l._head);
}

这里我们复用标准库里面的交换函数swap即可,不过要主要的是要加上访问限定符,没加std::就有可以导致无限递归


完整的实现代码

#pragma once 
#include 
using namespace std;namespace hdm
{// List的节点类template struct ListNode{ListNode(const T& val=T()):_val(val), _next(nullptr), _prev(nullptr){}T _val;ListNode* _next;ListNode* _prev;};/*List 的迭代器迭代器有两种实现方式,具体应根据容器底层数据结构实现:1. 原生态指针,比如:vector2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:1. 指针可以解引用,迭代器的类中必须重载operator*()2. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()3. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是forward_list就不需要重载--4. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()*/template class ListIterator{public:typedef ListNode Node;typedef ListIterator Self;public:// Ref 和 Ptr 类型需要重定义下,实现反向迭代器时需要用到typedef Ref Ref;typedef Ptr Ptr;ListIterator(Node* node=nullptr):_node(node){}//// 具有指针类似行为Ref operator*(){return _node->_val;}Ptr operator->(){return &(_node->_val);}//// 迭代器支持移动Self operator++(){_node = _node->_next;return *this;}Self operator++(int){Self tem(*this);_node = _node->_next;return tem;}Self operator--(){_node = _node->_prev;return *this;}Self operator--(int){Self tem(*this);_node = _node->_prev;return tem;}//// 迭代器支持比较bool operator!=(const Self& lt)const{return _node != lt._node;}bool operator==(const Self& lt)const{return _node == lt._node;}Node* _node;};//适配器// 给我不同容器的正向迭代器,适配出对应的这个容器需要的反向迭代器template class ReverseIterator{// 注意:此处typename的作用是明确告诉编译器,Ref是Iterator类中的一个类型,而不是静态成员变量// 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量// 因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的public:typedef typename iterator::Ref Ref;typedef typename iterator::Ptr Ptr;typedef ReverseIterator Self;ReverseIterator(iterator it):_it(it){}Self operator++(){--_it;return *this;}Self operator--(){++_it;return *this;}Ref operator*(){iterator tmp = _it;return *(--tmp);}Ptr operator->(){return &(operator*());}bool operator!=(const Self & l) const{return _it != l._it;}private:iterator _it;};template class list{public:typedef ListNode Node;// 正向迭代器typedef ListIterator iterator;typedef ListIterator const_iterator;// 反向迭代器typedef ReverseIterator reverse_iterator;typedef ReverseIterator const_reverse_iterator;list(){CreatNode();}list(int n, const T& val = T()){CreatNode();while (n--){push_back(val);}}//迭代器构造templatelist(InputIterator first, InputIterator last){CreatNode();while (first != last){push_back(*first);++first;}}//拷贝构造list(const list& l){CreatNode();list tmp(l.begin(), l.end());swap(tmp);}list& operator=(list tmp){swap(tmp);	return *this;}~list(){clear();delete _head;_head = nullptr;}///// List的迭代器iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin() const {return const_iterator(_head->_next);}const_iterator end()const{return const_iterator(_head);}reverse_iterator rbegin(){return reverse_iterator(end());}const_reverse_iterator rbegin() const {return reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}const_reverse_iterator rend() const {return reverse_iterator(begin());}///// List的容量相关size_t size() const{size_t count = 0;Node*cur = _head->_next;while (cur != _head){count++;cur = cur->_next;}return count;}bool empty()const{return _head->_next == _head;	}void resize(size_t newsize, const T& val = T()){size_t oldsize = size();//newsizewhile (newsize < oldsize){pop_back();--oldsize;}}else{while (newsize > oldsize){push_back(val);oldsize++;}}}// List的元素访问操作// 注意:List不支持operator[]T& front(){return _head->_next -> val;}const T& front()const {return _head->_next->val;}T& back(){return _head->_prev->_val;}const T& back() const {return _head->_prev->_val;}// List的插入和删除// 在pos位置前插入值为val的节点iterator insert(iterator pos, const T& x){Node* newNode = new Node(x);Node* cur = pos._node;//将新节点插入cur->_prev->_next = newNode;newNode->_prev = cur->_prev;cur->_prev = newNode;newNode->_next = cur;return	iterator(newNode);}void push_back(const T& val){insert(iterator(_head), val);}void push_front(const T&val){insert(iterator(_head->_next), val);}// 删除pos位置的节点,返回该节点的下一个位置iterator erase(iterator pos){Node* del = pos._node;Node* Ret = del->_next;//删除节点del->_prev->_next = Ret;Ret->_prev = del->_prev;delete del;return iterator(Ret);}void pop_back(){erase(_head->_prev);}void pop_front(){erase(_head->_next);}void clear(){Node*cur = _head->_next;while (cur != _head){Node* del = cur;cur = cur->_next;delete del;}}void swap(list& l){std::swap(_head, l._head);}private:void CreatNode(){_head = new Node;_head->_next = _head;_head->_prev = _head;}private:Node* _head;};///// 对模拟实现的list进行测试// 正向打印链表templatevoid PrintList(const hdm::list& l){auto it = l.begin();while (it != l.end()){cout << *it << " ";++it;}cout << endl;}// 测试List的构造void TesthdmList1(){hdm::list l1;hdm::list l2(10, 5);PrintList(l2);int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };hdm::list l3(array, array + sizeof(array) / sizeof(array[0]));PrintList(l3);hdm::list l4(l3);PrintList(l4);l1 = l4;PrintList(l1);}// PushBack()/PopBack()/PushFront()/PopFront()void TesthdmList2(){// 测试PushBack与PopBackhdm::list l;l.push_back(1);l.push_back(2);l.push_back(3);PrintList(l);l.pop_back();l.pop_back();PrintList(l);l.pop_back();cout << l.size() << endl;// 测试PushFront与PopFrontl.push_front(1);l.push_front(2);l.push_front(3);PrintList(l);l.pop_front();l.pop_front();PrintList(l);l.pop_front();cout << l.size() << endl;}// 测试insert和erasevoid TesthdmList3(){int array[] = { 1, 2, 3, 4, 5 };hdm::list l(array, array + sizeof(array) / sizeof(array[0]));auto pos = l.begin();l.insert(l.begin(), 0);PrintList(l);++pos;l.insert(pos, 2);PrintList(l);l.erase(l.begin());l.erase(pos);PrintList(l);// pos指向的节点已经被删除,pos迭代器失效cout << *pos << endl;auto it = l.begin();while (it != l.end()){it = l.erase(it);}cout << l.size() << endl;}// 测试反向迭代器void TesthdmList4(){int array[] = { 1, 2, 3, 4, 5 };hdm::list l(array, array + sizeof(array) / sizeof(array[0]));auto rit = l.rbegin();while (rit != l.rend()){cout << *rit << " ";++rit;}cout << endl;const hdm::list cl(l);auto crit = l.rbegin();while (crit != l.rend()){cout << *crit << " ";++crit;}cout << endl;}
}

相关内容

热门资讯

【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
ASM贪吃蛇游戏-解决错误的问... 要解决ASM贪吃蛇游戏中的错误问题,你可以按照以下步骤进行:首先,确定错误的具体表现和问题所在。在贪...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...