目录
前言
一.常用接口展示
二.模拟常用接口
1.1 准备阶段
1.2 push_back 尾插
1.3 insert 插入
1.4 头插
1.5 erase 删除
1.6 clear 清理 + 析构
1.7 拷贝构造
1.8 赋值拷贝
1.9 反向迭代器
1.10 ->运算符重载
三.全部代码
前言
List其实就是我们前面数据结构学的带头双向循环链表并且List容器的使用跟前面的string以及vector是差不多的,所以我们直接跳过使用接口函数,直接模拟实现一些常用的接口。
建议可以看一下前面的文章了解一下接口功能,另外本章内容很‘杂’,很有必要复习前面的内容:
秒懂C++之vector(下)-CSDN博客
秒懂C++之string类(下)-CSDN博客
一.常用接口展示
二.模拟常用接口
1.1 准备阶段
namespace lj { template <class T> struct ListNode { ListNode<T>* _next; ListNode<T>* _prev; T _Data; //构造初始化 利用匿名对象调用该类型的构造函数 ListNode<T>(const T& x = T()) { _next = nullptr; _prev = nullptr; _Data = x; } }; template <class T> class list { typedef ListNode<T> Node; public: //构造函数 list() { init(); } void init() { _head = new Node;//创建带头节点 _head->_next = _head;//构成双向循序 _head->_prev = _head; } private: Node* _head;//带头节点:哨兵位 }; }
不过这样还不够,因为在链表中的物理空间是不连续的,这意味我们平时使用的迭代器将会失效~
迭代器可以看作是指针,而我们遍历链表的过程需要通过++指针来进行遍历,但是链表物理空间是不连续的,这意味我们需要重载运算符++,改变指针下一步的指向逻辑。
可是Node*是指针,代表内置类型是无法进行运行符重载的~所以我们需要特意对该指针进行封装~
封装到一个自定义的类中,然后就可以修改这个类的运算符重载,达到指针能指向下一个位置的目的。
为此我们在命名空间里又封装了一份关于指针运算符重载的结构体~
template <class T> struct _list_iterator { typedef ListNode<T> Node; typedef _list_iterator<T> self; //针对该类型的指针 Node* _node; //构造函数,初始化_node _list_iterator(Node* node) { _node = node; } //前置++ self& operator++() { //不再是单纯的自加1,而是指向下一位置 _node = _node->_next; return *this; } //后置++ self operator++(int) { self tmp(*this); _node = _node->_next; return tmp; } //前置-- self& operator--() { _node = _node->_prev; return *this; } //后置-- self operator--(int) { //拷贝一份--前的数据 self tmp(*this); _node = _node->_prev; return tmp; } //解引用 T& opeartor* () { return _node->_Data; } //指针与指针的对比!= 因为我们规定了这个指针的条件,所以对比的类型应该用self bool operator!=(const self& x) { return _node != x._node; } //指针与指针的对比== bool operator==(const self& x) { return _node == x._node; } };
1.2 push_back 尾插
//尾插 void push_back(const T& x) { //先创建新节点 Node* newnode = new Node(x); //先找到尾节点 Node* tail = _head->_prev; //开始链接 tail->_next = newnode; newnode->_prev = tail; newnode->_next = _head; _head->_prev = newnode; } //指向第一个有效节点 iterator begin() { //单参数隐式类型转换 //return iterator(_head->_next); return _head->_next; } //指向头节点:哨兵位 iterator end() { //return iterator(_head); return _head; }
1.3 insert 插入
iterator insert(iterator pos,const T& x) { //创建插入节点 Node* newnode = new Node(x); //记录插入位置 Node* node = pos._node; Node* nodeprev = node->_prev; //开始链接 nodeprev->_next = newnode; newnode->_prev = nodeprev; newnode->_next = node; node->_prev = newnode; //return iterator(newnode); return newnode; }
注意:虽然在list容器中insert不会迭代器失效,但为了代码的一致性,一般都要加上返回值~
写了insert我们就可以在尾插中进行复用啦~
1.4 头插
继续复用Insert
//头插 void push_front(const T& x) { insert(begin(), x); }
1.5 erase 删除
//erase 删除 iterator earse(iterator pos) { //记录位置 Node* node = pos._node; Node* nodenext = node->_next; Node* nodeprev = node->_prev; //开始链接 nodeprev->_next = nodenext; nodenext->_prev = nodeprev; delete node; return nodenext; }
有了erase我们就可以复用它写头删与尾删了
//pop_back 尾删 void pop_back() { erase(--end()); } //pop_front 头删 void pop_front() { erase(begin()); }
关于迭代器失效这一点,erase就会有失效的问题,当你把指向的节点删除的时候你已经是一个野指针了,所以必须得有返回值让你在下一次使用的时候能够有所指向~而下面的clear函数就很好地体现了这一点~
1.6 clear 清理 + 析构
void clear() { iterator it = begin(); while (it != end()) { it = erase(it); } } ~list() { clear(); delete _head; _head = nullptr; }
1.7 拷贝构造
//list l2(l1) 拷贝构造 list(list<T>& lt) { empty_init(); for (const auto& e : lt) { push_back(e); } }
如果不写拷贝构造的话编译器会调用默认的拷贝构造,但是该拷贝构造是浅拷贝,会调用两次析构函数导致报错~
1.8 赋值拷贝
//赋值拷贝 传统写法 list<T>& operator=(list<T>& lt) { if (*this != lt) { clear(); for (const auto& e : lt) { push_back(e); } } return *this; } void swap(list<T>& tmp) { std::swap(_head, tmp._head); } //赋值拷贝 现代写法 list<T>& operator=(list<T> lt) { swap(lt); return *this; }
1.9 反向迭代器
我们拿这个函数来举例,lt这个对象是被const所修饰的,那么便无法调用begin(),因为与隐藏this的类型对不上,权限放大。所以我们需要其对应的const成员函数~
那么问题就来了,我们的iterator都是自定义出来的,那么是否还得再自定义一个关于const_iterator的类出来?里面就是指针被const成员函数赋值的内容~原先的就是没有被const成员函数赋值的内容~
不需要,因为我们通过观察发现针对指针所重载的一系列运算符中只有解引用(*)运算符需要加上const成员函数,其他都是涉及到自身的修改是不需要加上const成员函数的。那么const_iterator的类与iterator的类就只有这一个函数的区别了~
所以在这里我们用类模板来让这两个类进行统一合并~ 如果it需要接收的是被const修饰过的对象,那么我们就通过const_iterator去获取被const成员函数修饰过的函数,如end,begin,* ~
非常之精彩的一步呢~
1.10 ->运算符重载
struct AA { int _a1; int _a2; AA(int a1 = 1, int a2 = 1) :_a1(a1) , _a2(a2) {} }; void test2() { list<AA> lt; AA aa1; lt.push_back(aa1); lt.push_back(AA()); AA aa2(2, 2); lt.push_back(aa2); lt.push_back(AA(2, 2)); list<AA>::iterator it = lt.begin(); while (it != lt.end()) { cout << (*it)._a1 << ":" << (*it)._a2 << endl; cout << it.operator*()._a1 << ":" << it.operator*()._a2 << endl; cout << it->_a1 << ":" << it->_a2 << endl; cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl; ++it; } cout << endl; }
我们来拿这段代码来演示->的必要性
当我们想要访问结构体内的成员_a1与_a2的时候,有两种方式:
- 一是让指向结构体的结构体指针进行解引用进入内部,然后用.访问成员
- 另外一个是让指向结构体的结构体指针直接用->访问成员
所以我们的目标很明确,首先得先得到一个能指向AA结构体的结构体指针!
*作用,直接让该指针获取list类里面的成员。怎么个获取法呢?直接让指向结构体的结构体指针直接用->访问成员,毕竟本质还是内置类型,只不过是为了重载++才进行封装的。然后访问的成员是一个结构体,那就直接用.访问结构体里面的成员_a1
->作用,返回一个指向该结构体的结构体指针,让指向结构体的结构体指针直接用->访问成员
巧了,->也可以被const成员函数修饰,毕竟不涉及到自身修改~所以我们再扩充一下我们的类模板~
三.全部代码
//list.h
#pragma once
#include <assert.h>
using namespace std;
namespace lj
{
template <class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _Data;
//构造初始化 利用匿名对象调用该类型的构造函数
ListNode<T>(const T& x = T())
{
_next = nullptr;
_prev = nullptr;
_Data = x;
}
};
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* node)
{
_node = node;
}
//前置++
self& operator++()
{
//不再是单纯的自加1,而是指向下一位置
_node = _node->_next;
return *this;
}
//后置++
self operator++(int)
{
self tmp(*this);
_node = _node->_next;
return tmp;
}
//前置--
self& operator--()
{
_node = _node->_prev;
return *this;
}
//前置--
self operator--(int)
{
//拷贝一份--前的数据
self tmp(*this);
_node = _node->_prev;
return tmp;
}
//解引用
Ref operator*()
{
return _node->_Data;
}
//指针与指针的对比!=
bool operator!=(const self& x)
{
return _node != x._node;
}
//指针与指针的对比==
bool operator==(const self& x)
{
return _node == x._node;
}
Ptr operator->()
{
return &_node->_data;
}
};
template <class T>
class list
{
typedef ListNode<T> Node;
public:
typedef _list_iterator<T,T&,T*> iterator;
typedef _list_iterator<T,const T&,T*> const_iterator;
//构造函数
list()
{
init();
}
void init()
{
_head = new Node;//创建带头节点
_head->_next = _head;//构成双向循序
_head->_prev = _head;
}
//尾插
void push_back(const T& x)
{
insert(end(), x);
先创建新节点
//Node* newnode = new Node(x);
先找到尾节点
//Node* tail = _head->_prev;
开始链接
//tail->_next = newnode;
//newnode->_prev = tail;
//newnode->_next = _head;
//_head->_prev = newnode;
}
//指向第一个有效节点
iterator begin()
{ //单参数隐式类型转换
//return iterator(_head->_next);
return _head->_next;
}
const_iterator begin()const
{ //单参数隐式类型转换
//return iterator(_head->_next);
return _head->_next;
}
//指向头节点:哨兵位
iterator end()
{
//return iterator(_head);
return _head;
}
//指向头节点:哨兵位
const_iterator end()const
{
//return iterator(_head);
return _head;
}
//插入
iterator insert(iterator pos,const T& x)
{
//创建插入节点
Node* newnode = new Node(x);
//记录插入位置
Node* node = pos._node;
Node* nodeprev = node->_prev;
nodeprev->_next = newnode;
newnode->_prev = nodeprev;
newnode->_next = node;
node->_prev = newnode;
//return iterator(newnode);
return newnode;
}
//头插
void push_front(const T& x)
{
insert(begin(), x);
}
//erase 删除
iterator erase(iterator pos)
{
//记录位置
Node* node = pos._node;
Node* nodenext = node->_next;
Node* nodeprev = node->_prev;
//开始链接
nodeprev->_next = nodenext;
nodenext->_prev = nodeprev;
delete node;
return nodenext;
}
//pop_back 尾删
void pop_back()
{
erase(--end());
}
//pop_front 头删
void pop_front()
{
erase(begin());
}
//clear 清理
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
//erase(it);
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
//list l2(l1) 拷贝构造
list(list<T>& lt)
{
init();
for (const auto& e : lt)
{
push_back(e);
}
}
//赋值拷贝 传统写法
/*list<T>& operator=(list<T>& lt)
{
if (*this != lt)
{
clear();
for (const auto& e : lt)
{
push_back(e);
}
}
return *this;
}*/
void swap(list<T>& tmp)
{
std::swap(_head, tmp._head);
}
//赋值拷贝 现代写法
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
private:
Node* _head;//带头节点:哨兵位
};
void test()
{
/*list<int> l1;
l1.push_back(1);
l1.push_back(2);
l1.push_back(3);
l1.push_back(4);
l1.push_back(5);
list<int>::iterator it = l1.begin();
while (it != l1.end())
{
cout << *it << " ";
it++;
}
cout << endl;
l1.insert(l1.begin(), 0);
l1.insert(l1.end(), 6);
for (auto e : l1)
{
cout << e << " ";
}
//cout << endl;*/
//list<int> l1;
//l1.push_front(1);
//l1.push_front(2);
//l1.push_front(3);
//l1.push_front(4);
//l1.push_front(5);
//for (auto e : l1)
//{
// cout << e << " ";
//}
//cout << endl;
//list<int>::iterator it = l1.begin();
/*l1.pop_front();
l1.pop_back();*/
//it = l1.erase(l1.begin());
//it = l1.erase(l1.begin());
//l1.erase(--l1.end());
//l1.erase(l1.end());
/*list<int> l1;
l1.push_front(1);
l1.push_front(2);
l1.push_front(3);
l1.push_front(4);
l1.push_front(5);
for (auto e : l1)
{
cout << e << " ";
}
cout << endl;
l1.clear();
for (auto e : l1)
{
cout << e << " ";
}
cout << endl;*/
/*list<int> l1;
l1.push_front(1);
l1.push_front(2);
l1.push_front(3);
l1.push_front(4);
l1.push_front(5);
list<int> l2;
l2.push_front(5);
l2.push_front(5);
l2.push_front(5);
l2.push_front(5);
l2 = l1;
for (auto e : l1)
{
cout << e << " ";
}
cout << endl;
for (auto e : l2)
{
cout << e << " ";
}
cout << endl;*/
//list<int> l1;
//l1.push_front(1);
//l1.push_front(2);
//l1.push_front(3);
//l1.push_front(4);
//l1.push_front(5);
//l1.erase(l1.begin());
//l1.erase(l1.begin());
//l1.erase(l1.begin());
//l1.erase(l1.begin());
//l1.erase(l1.begin());
//list<int>::iterator it = l1.begin();
//while (it != l1.end())
//{
// cout << *it << " ";
// it++;
//}
//cout << endl;
//for (auto e : l1)
//{
// cout << e << " ";
//}
//cout << endl;
//l1.clear();
//for (auto e : l1)
//{
// cout << e << " ";
//}
//cout << endl;
list<int> l1;
l1.push_front(1);
l1.push_front(2);
l1.push_front(3);
l1.push_front(4);
l1.push_front(5);
l1.clear();
for (auto e : l1)
{
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;
}
struct AA
{
int _a1;
int _a2;
AA(int a1 = 1, int a2 = 1)
:_a1(a1)
, _a2(a2)
{}
};
void test2()
{
list<AA> lt;
AA aa1;
lt.push_back(aa1);
lt.push_back(AA());
AA aa2(2, 2);
lt.push_back(aa2);
lt.push_back(AA(2, 2));
list<AA>::iterator it = lt.begin();
while (it != lt.end())
{
cout << (*it)._a1 << ":" << (*it)._a2 << endl;
cout << it.operator*()._a1 << ":" << it.operator*()._a2 << endl;
cout << it->_a1 << ":" << it->_a2 << endl;
cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;
++it;
}
cout << endl;
}
}
//test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include "List.h"
int main()
{
lj::test();
return 0;
}