> 作者简介:დ旧言~,目前大二,现在学习Java,c,c++,Python等
> 座右铭:松树千年终是朽,槿花一日自为荣。> 目标:能手撕list模拟
> 毒鸡汤:不为模糊不清的未来过分担忧,只为清清楚楚的现在奋发图强。
> 望小伙伴们点赞👍收藏✨加关注哟💕💕
🌟前言
前面我们已经学习了list的相关知识点,必然我们要简单的模拟一下,模拟list类比较复杂,里面掺杂了我们学习双链表的知识点,对模板的使用更加复杂,还有对c++类和对象的知识点使用起来更杂。对上面知识点忘了的小伙伴们大家先去巩固巩固,学习list那就更加轻松,那咱们走起。
⭐主体
这里我们就不分解成三文件啦,这里就创建两个文件List.h(头文件),Test.c(测试代码文件)
🌙list基本框架结构
list为任意位置插入删除的容器,底层为带头双向循环链表:
模拟实现list,要实现下列三个类:
①、模拟实现结点类
②、模拟实现迭代器的类
③、模拟list主要功能的类
🌙list的结点的实现
list为任意位置插入删除的容器,底层为带头双向循环链表,所以每个结点都需有以下成员:
- 前驱指针
- 后继指针
- data值存放数据
结点类本质上是一个初始化函数,在list类中只需要一个构造函数就行。
// 初始化(结点类) template<class T> // 模板 struct ListNode { // 定义两个指针 ListNode<T>* _next; // 前驱指针 ListNode<T>* _prev; // 后驱指针 // 存储节点值 T _data; // 初始化列表(构造函数) ListNode(const T& x = T()) :_next(nullptr) ,_prev(nullptr) ,_data(x) {} };
问题解剖:为什么是ListNode<T>???
首先,C++中用struct定义时可不加struct,重点是这里用了一个类模板,类模板的类名不是真正的类型且类模板不支持自动推类型,即ListNode不是真正的类型,定义变量时ListNode<T>这种才是真正的类型,也就是用类模板定义变量时必须 指定对应的类型 。 注:结构体模板或类模板在定义时可以不加 <T>,但使用时必须加 <T>。
🌙list的迭代器类的实现
- 链表的物理空间是不连续的,是通过结点的指针顺次链接。
- 不能像先前的string和vector一样直接解引用去访问其数据,
- 结点的指针解引用还是结点,结点指针++还是结点指针,
- 在string和vector的物理空间是连续的,所以这俩不需要实现迭代器类,可以直接使用。
为了能让list像vector一样解引用后访问对应节点中的值,++访问到下一个数据,我们需要单独写一个迭代器类的接口实现,在其内部进行封装补齐相应的功能,而这就要借助运算符重载来完成。
💫基本框架
框架结构:
template<class T,class Ref,class Ptr> struct __list_iterator { typedef __list_node<T> Node; typedef __list_iterator<T, Ref, Ptr> Self; Node* _node; }
①、迭代器类模板为什么有三个参数?
- 普通迭代器的话,一个class T参数就够了,由于const迭代器原因,需要加两个参数
- 两个参数名Ref(reference:引用)和Ptr(pointer:指针)
②、迭代器类是什么?
- 迭代器类就一个节点的指针变量_node,但是因为我们要运算符重载等一系列操作,不得不把list的迭代器写成类,完成那些操作,list的迭代器才能正确的++到下一位置,解引用访问节点的值
③、节点指针和迭代器的区别?
- Node* cur(节点指针) 和 iterator it(迭代器),它们指向同一个节点,它们的物理地址是一样的,但是它们的类型不同。
- *cur是一个指针的解引用,取到的值是节点。
- *it是去调用迭代器的operator*,返回值是节点中存的值。
- 类型决定了对空间的解释权。
💫构造函数
迭代器的构造函数只需要一个指针构造就行:
// 构造函数(初始化) _list_iterator(Node* x) :_node(x) {}
💫operator*
*it(调用的是函数,返回节点中的值):
// *it,返回节点的值 Ref operator*() { return _node->_data; }
问题解剖:返回值为什么是 Ref ???
- Ref是模板参数,因为迭代器类的模板参数Ref传入的要么是T&要么是const T&
- 为了const迭代器和普通迭代器的同时实现,意义就是一个只读,一个可读可写
注:比如之前讲的vector的迭代器,*it(假设it是迭代器变量)就是拿到对应的值,那么list的迭代器也要同理,解引用迭代器就是为了访问对应位置的值,那么list只要通过迭代器返回对应节点的值就好了(*it,我们是就想要对应的值)
💫operator->
// 返回节点值 Ptr operator->() { return &_node->data; }
问题解剖:为什么需要operator->?
- list存了个自定义类型的Date类,程序错误,因为我们并没有重载Date类的operator<<
若是内置类型的话才可以正常输出,那写一个operator<<重载吗?不,因为你无法确定要用哪些类,也不能每个类都写operator<<,那怎么办?我们访问Date类本质是想访问它内置类型(int)的_year、_month和_day吧,那我们不妨写个专属于自定义类型的operator->(因为内置类型只需要*it就可以直接输出了,但自定义类型不可以直接输出)
- 利用operator->直接访问类的成员变量,而内置类型可以直接输出
故从根源上解决问题: 在迭代器中实现个operator->:
(Ptr是迭代器的模板参数,我们用来作为T*或const T*的)
💫operator前置++--与后置++--
代码实现:
// ++it self& operator++() { _node = _node->_next; // 返回节点 return *this; } // it++ self operator++(int) { // 拷贝 self tmp(*this); // 向后走一个节点 _node = _node->_next; // 返回 return tmp; } // --it self& operator--() { _node = _node->_prev; // 返回节点 return *this; } // it-- self operator--(int) { // 拷贝 self tmp(*this); // 向前走一个节点 _node = _node->prev; // 返回 return tmp; }
问题解剖:迭代器++对于list是什么意思???
- 迭代器++的意思就是想让其指向下一个节点
- --正好相反,为了区分前置和后置++(--),
- 我们会用函数重载,也就是多一个'没用的'参数:int,这个参数没什么用,只是为了区分++与--
💫operator==和operator!=
代码实现:
// 判断 bool operator!=(const self& s) { return _node != s._node; } // 判断 bool operator==(const self& s) { return _node == s._node; }
问题解剖:两个迭代器怎么比较的???
- 迭代器中就一个成员变量_node,节点指针,只要比较当前的节点指针是否相同即可
- 这个操作在判断迭代器是否走到头有用(比如 it != s.end())
问题:迭代器的拷贝构造和赋值和析构函数需我们自己实现吗?
答:不需要
- 因为迭代器存的就是一个节点指针,而节点是属于链表list的,那么它的释放应由list来实现,那么迭代器的析构也无需我们自己写了。
- 拷贝构造和赋值就是节点指针的拷贝和赋值,即使指向同一节点了,迭代器也不会析构,而list的析构到时候只会释放这个节点一次,不存在什么问题,即我们无需深拷贝,使用系统提供的浅拷贝即可。
🌙list类的实现
在结点类和迭代器类都实现的前提下,就可实现list主要功能:增删等操作的实现。
💫基本框架
框架结构:
//3、链表类 template<class T> class list { typedef __list_node<T> Node; public: typedef __list_iterator<T,T&,T*> iterator; typedef __list_iterator<T,const T&,const T*> const_iterator; private: Node* _head;//头结点 }
问题解剖:const_iterator(const迭代器的介绍)
- 我们知道const_iterator在begin()和end()中的返回值是需要用到的
- 其主要作用就是当迭代器只读时使用
- 普通迭代器和const迭代器的实现区别仅仅在于内部成员函数的返回值不同
- 一个是引用class Ref(T&或const T&),一个是指针class Ptr(T*或const T*)
- 当Ref时const T&就是const迭代器的调用,当Ref时T& 时就是普通迭代器的调用
💫构造函数
框架结构:
// 初始化函数 void empty_init() { _head = new Node; _head->_next = _head; _head->_prev = _head; } // 构造函数 list() // 1. { empty_init(); }
解释:我们开辟一个头结点,这里就是一个循环双向链表。
💫begin和end
代码实现:
// 找头,这里有哨兵位 iterator begin() { return _head->_next; } // 找尾,哨兵位就是尾 iterator end() { return _head; } //const类 const_iterator begin() const { return _head->_next; } const_iterator end() const { return _head; }
💫拷贝构造
代码实现:
传统写法:
//拷贝构造:传统写法 list(const list<T>& lt) { _head = new Node;//开辟一样的头结点 _head->_next = _head; _head->_prev = _head; //1、用迭代器遍历 /*const_iterator it = lt.begin(); while (it != lt.end()) { push_back(*it); ++it; }*/ //2、范围for遍历 //遍历lt1,把lt1的元素push_back到lt2里头 for (auto e : lt) { push_back(e);//自动开辟新空间,完成深拷贝 } }
解释:list的拷贝构造跟vector不同,它的拷贝是拷贝一个个节点(因为不连续的物理空间), 那么我们可以用迭代器拿到list的一个个节点【上面是传统写法】
现代写法:
template<class InputIterator> list(InputIterator first, InputIterator last) { _head = new Node(); _head->_next = _head; _head->_prev = _head; while (first != last) { push_back(*first); first++; } } /* 拷贝构造(现代写法):L2(L1) */ list(const list<T>& L) { _head = new Node(); _head->_next = _head; _head->_prev = _head; list<T> tmp(L.begin(), L.end()); swap(_head, tmp._head); // 交换两个结点的指针 }
💫clear函数
代码实现:
// 销毁函数 void clear() { // 找头 iterator it = begin(); // 循环 while (it != end()) { it = erase(it); } }
问题解剖:it = erase(it)什么意思???
防止迭代器失效,因为erase返回的是被删除位置的下一位置,比如删除pos位置的,pos位置被删除后,这个位置的迭代器就失效了,那就无法通过++找到后面的位置了,所以我们通过erase返回值来更新一下迭代器位置,即更新到被删除位置的下一位置
💫operator=
代码实现:
//赋值运算符重载(传统写法) //lt1 = lt3 //list<T>& operator=(const list<T>& lt) //{ // if (this != <) // { // clear();//先释放lt1 // for (auto e : lt) // push_back(e); // } // return *this; //} //赋值运算符重载(现代写法) // 赋值运算重载 list<T>& operator=(list<T> lt) { swap(lt); return *this; }
注释:套用传值传参拷贝构造完成深拷贝
💫析构函数
代码实现:
// 析构函数 ~list() { clear(); delete _head;//删除除头结点以外的节点 _head = nullptr;//删去哨兵位头结点 }
💫insert函数
代码实现:
// insert插入函数 iterator insert(iterator pos, const T& x) { Node* cur = pos._node; // 迭代器pos处的结点指针 Node* prev = cur->_prev; Node* newnode = new Node(x); // 创建新的结点 // prev newnode cur prev->_next = newnode; newnode->_prev = prev; newnode->_next = cur; cur->_prev = newnode; //return iterator(newnode); return newnode; }
问题解剖:返回参数为什么是iterator???本质是为了防止迭代器失效问题
注:insert指的是插入到指定位置的前面
💫push_back 和 push_front
代码实现:
// 调用插入函数,尾插 void push_back(const T& x) { insert(end(), x); } // 调用插入函数,头插 void push_front(const T& x) { insert(begin(), x); }
这里的尾插和头插直接调用插入函数就可以了。
💫erase函数
代码实现:
//erase iterator erase(iterator pos) { assert(pos != end()); Node* cur = pos._node; Node* prev = cur->_prev; Node* next = cur->_next; //prev cur next //链接prev和next prev->_next = next; next->_prev = prev; //delete删去结点,因为每一个节点都是动态开辟出来的 delete cur; //返回被删除元素后一个元素的迭代器位置 //return next; return iterator(next); }
问题剖析:返回值问题???
erase的返回值,返回的是被删除位置的下一位置,库里面这么规定的,本质是因为删除元素一定会导致此位置的迭代器失效,故返回被删除位置的下一次位置可以更新迭代器。
💫pop_back 和 pop_front
代码实现:
// 调用删除函数,尾删 void pop_back() { erase(--end()); } // 调用删除函数,头删 void pop_front() { erase(begin()); }
🌙完整源代码
list.h:
#pragma once
#include<assert.h>
#include<iostream>
namespace lyk
{
// 初始化
template<class T> // 模板
struct ListNode
{
// 定义两个指针
ListNode<T>* _next; // 前驱指针
ListNode<T>* _prev; // 后驱指针
// 存储节点值
T _data;
// 初始化列表(构造函数)
ListNode(const T& x = T())
:_next(nullptr)
,_prev(nullptr)
,_data(x)
{}
};
// list迭代器
template<class T,class Ref,class Ptr> // 第二个参数是引用,第三个参数是指针,防止迭代器失效
struct _list_iterator
{
typedef ListNode<T> Node;
typedef _list_iterator<T, Ref, Ptr> self;
Node* _node; // 定义节点
// 构造函数(初始化)
_list_iterator(Node* x)
:_node(x)
{}
// ++it
self& operator++()
{
_node = _node->_next;
// 返回节点
return *this;
}
// it++
self operator++(int)
{
// 拷贝
self tmp(*this);
// 向后走一个节点
_node = _node->_next;
// 返回
return tmp;
}
// --it
self& operator--()
{
_node = _node->_prev;
// 返回节点
return *this;
}
// it--
self operator--(int)
{
// 拷贝
self tmp(*this);
// 向前走一个节点
_node = _node->prev;
// 返回
return tmp;
}
// *it,返回节点的值
Ref operator*()
{
return _node->_data;
}
// 返回节点值
Ptr operator->()
{
return &_node->data;
}
// 判断
bool operator!=(const self& s)
{
return _node != s._node;
}
// 判断
bool operator==(const self& s)
{
return _node == s._node;
}
};
// list的实现(包含各种函数实现)
template<class T>
class list
{
typedef ListNode<T> Node;
public:
typedef _list_iterator<T, T&, T*> iterator;
typedef _list_iterator<T, const T&, const T*> const_iterator;
// 找头,这里有哨兵位
iterator begin()
{
return _head->_next;
}
// 找尾,哨兵位就是尾
iterator end()
{
return _head;
}
//const类
const_iterator begin() const
{
return _head->_next;
}
const_iterator end() const
{
return _head;
}
// 初始化函数
void empty_init()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
// 构造函数
list() // 1.
{
empty_init();
}
list(list<T>& It) // 2.拷贝构造 传统写法
{
empty_init();
// 尾插
for (const auto& x : It)
{
push_back(x);
}
}
/* 拷贝构造(现代写法):L2(L1) */
list(const list<T>& L) {
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
list<T> tmp(L.begin(), L.end());
swap(_head, tmp._head); // 交换两个结点的指针
}
// 销毁函数
void clear()
{
// 找头
iterator it = begin();
// 循环
while (it != end())
{
it = erase(it);
}
}
// 析构函数
~list()
{
clear();
delete _head;
_head = nullptr;
}
// 交换
void swap(list<T>& tmp)
{
std::swap(_head, tmp._head);
}
// 赋值运算重载
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
// 调用插入函数,尾插
void push_back(const T& x)
{
insert(end(), x);
}
// 调用插入函数,头插
void push_front(const T& x)
{
insert(begin(), x);
}
// 调用删除函数,尾删
void pop_back()
{
erase(--end());
}
// 调用删除函数,头删
void pop_front()
{
erase(begin());
}
// insert插入函数
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
//return iterator(newnode);
return newnode;
}
// erase删除函数
iterator erase(iterator pos)
{
assert(pos != end());
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
return next;
}
private:
Node* _head; // 头节点
};
void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
//*it += 10;
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
void test_list2()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.push_back(5);
lt.push_front(0);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.pop_back();
lt.pop_front();
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.clear();
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.push_back(10);
lt.push_back(20);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
void test_list3()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
list<int> copy(lt);
for (auto e : copy)
{
cout << e << " ";
}
cout << endl;
list<int> lt1;
lt1.push_back(10);
lt1.push_back(20);
lt1.push_back(30);
lt1.push_back(40);
lt = lt1;
for (auto e : copy)
{
cout << e << " ";
}
cout << endl;
}
void print_list(const list<int>& lt)
{
list<int>::const_iterator it = lt.begin();
while (it != lt.end())
{
//*it += 10;
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
void test_list4()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
print_list(lt);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
*it += 10;
cout << *it << " ";
++it;
}
cout << endl;
}
}
test.cpp:
#define _CRT_SECURE_NO_WARNINGS 1
// 包含头文件
#include<iostream>
#include<list>
#include<vector>
#include<algorithm>
using namespace std;
#include"List.h"
int main()
{
lyk::test_list4();
return 0;
}
🌟结束语
今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。