目录
序言
本篇文章主要是用C++模拟实现list类,关键是了解迭代器除原生指针外的另一种实现,并且进一步
巩固之前学过的类和对象的相关知识.
1.基本构型
C++的list类是一个双向带头链表,我们曾经用C语言实现过.
简单回忆C语言时候的结构.
每一个结点node都是一个结构体,结构体中包含三个元素,一个是前驱指针prev,一个是后驱指
针next,最后是数据域data.(由于data类型不确定,为了方便起见,我们typedef进行重命名,那需
要修改的时候,直接在typedef处修改即可.)
typedef int LTDataType;
typedef struct __list_node{
LTDataType data;
struct __list_node* next;
struct __list_node* prev;
}Node;
不同于C语言,C++有以下两大区别
1.不需要对类进行重命名,即__list_node本身就是类名.
2.不需要再typedef数据域的类型,可以用模板,进行全部不同类型的统一.
同时我们还知道,C++的类,支持函数放置在内部.
因此,在实现C++的结点类(__liet_node)时,就可以直接完成构造函数,也就是我们之前实现的
BuylistNode函数.
//之前C实现的结点构造函数
SLTNode* BuySLTNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
//判断申请是否失败
if (newnode == NULL)
{
//申请失败,报错,并直接返回
perror("malloc fail");
exit(-1);
}
//申请成功,数值域赋为x,指针域赋为空
newnode->data = x;
newnode->next = NULL;
return newnode;
}
对比来看,C++的实现更为简单,而且直接明了.
采用初始化列表初始化,并且还支持数据为自定义类型.(用匿名对象作为缺省参数.)
//C++实现
template <class T>
struct __list_node {
__list_node<T>* _next;
__list_node<T>* _prev;
T _data;
//节点初始化,初始化列表初始化即可
__list_node(const T& x = T())
:_next(nullptr)
,_prev(nullptr)
,_data(x)
{}
};
PS:这里采用Struct来实现类,即是公有开放,这样list类就可以访问里面的成员.
在C++类和模板最开始我们就说过,C++更关注不同对象之间的交互,是面向对象编程.
因此,我们现在关注的就是list类和node类之间的交互.
具体list类的成员是什么呢?我们同样可以参考源码进行模拟实现.
我们在list类可以找到相应的成员变量,但link_type我们并不熟悉其是什么类型.
我们可以采取Ctrl+F在vs下直接对其搜索,或者在notepad下Alt+g跳转找到link_type的相应定义.
可以看到link_type实际上就是指向头节点的指针,不过对其进行重命名而已.
当然在模拟实现的时候,为了使程序更为清晰,我们就直接用_head作为头节点的名称了,而不采
用_node.
//PS:这里只抽取了list类的成员变量代码
typedef __list_node<T> node;
private:
node* _head;
2.构造函数
和C语言一样,初始化list链表,实际上就是对头节点进行初始化.
由于后续要实现尾插操作,会有类似node* prev = _head->_prev的操作,假如头节点的_prev成员
赋值为空指针,则程序就会直接崩溃.
C语言实现
因此和C语言实现的时候保持一致,列表初始化,都让头节点的_prev和_next指针都指向自己.
C++实现则直接放在构造函数完成,由于初始化操作后面将会频繁使用,因此我们把它单独分装成
一个函数实现.
void empty_initialize()
{
_head = new node;
_head->_next = _head;
_head->_prev = _head;
}
list()
{
empty_initialize();
}
源码实现也是类似的方式.
3.迭代器构造(1)
接下来,就到了list类最为核心的部分,迭代器的实现.
我们知道,如果只是单纯用原生指针来实现迭代器,由于链表存储空间并不是连续的,因此对指针
进行++,--等操作,得到的并不是我们想要的结果.
我们想要实现像vector,string类迭代器的操作,对指针++,就可以得到访问下一个元素的指针.
这时候就体现处类的魅力,用类来封装原生指针,构造相应函数来模拟实现++等操作.
迭代器类仅有一个成员,就是结点指针
typedef __list_iterator<T> self;
typedef __list_node<T> node;
typedef __list_node<T>* link_type;
link_type _node;
不过不能把它就理解为一个指针,它是一个类,它有相应的成员函数
下面两行代码,代表的含义并不相同,第一行只是单纯的指针,但第二行是一个迭代器类,可以在
里面实现++等函数.
__list_node<T>* pnode = lt._head->_next;
list<int>::iterator it = lt.begin();
迭代器的构造函数实现起来也很简单,就是将相应结点的指针赋给类里面的_node指针变量即可.
typedef __list_iterator<T> self;
typedef __list_node<T> node;
typedef __list_node<T>* link_type;
link_type _node;
__list_iterator(link_type x)
:_node(x)
{}
我们先来简单实现前置++和后置++的运算符重载.
++运算符重载,实际上想要的就是下一个结点的指针.
PS:为了书写简便,我们重命名iterator迭代器类为self
简单回顾:前置++直接返回当前对象即可,后置++实现需要存储没移动之前的对象.
//++运算符重载
self& operator++()
{
_node = _node->_next;
return *this;
}
self operator++(int)
{
self tmp(*this);
++* this;
return tmp;
}
源码实现类似.
类似地,--等运算符重载也是同样实现.
//解引用返回
T& operator*()
{
return _node->_data;
}
//--运算符重载
self& operator--()
{
_node = _node->_prev;
return *this;
}
self operator--(int)
{
self tmp(*this);
--* this;
return tmp;
}
//!=和==运算符重载
bool operator!=(const self& x)
{
return _node != x._node;
}
bool operator==(const self& x)
{
return _node == x._node;
}
源码如下:
4.迭代器构造(2)—Ref参数
接下来,我们实现const迭代器.
const迭代器不允许我们修改指向结点的data,只能访问.
一个常见的写法错误就是在list类里直接重命名.
typedef const iterator const_iterator
但这个显然是错误的写法,const修饰迭代器,反而是迭代器不能被修改,即指针不能被修改,反而
意味着指针不能随意++,--移动,这显然是错误的.
比较正确但挫的写法就是重新写一个类,但是返回数据的时候,返回的是const val.
template<class T>
struct __list_const_iterator
{
typedef list_node<T> node;
typedef __list_const_iterator<T> self;
node* _node;
__list_const_iterator(node* n)
:_node(n)
{}
const T& operator*()
{
return _node->_data;
}
self& operator++()
{
_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;
}
bool operator!=(const self& s)
{
return _node != s._node;
}
bool operator==(const self& s)
{
return _node == s._node;
}
};
整段代码,仅仅在*运算符重载有不同,其余和非const修饰的迭代器类完全一样,代码十分冗余
此时模板的作用就发挥出来了,模板支持传引用,指针等等.
我们在模板处加上第二个参数Ref就可以将两个迭代器类合并成一个.
template <class T,class Refr>
struct __list_iterator
{
typedef __list_iterator<T,Ref> self;
typedef __list_node<T> node;
typedef __list_node<T>* link_type;
link_type _node;
__list_iterator(link_type x)
:_node(x)
{}
//*运算符重载
Ref operator*()
{
return _node->_data;
}
//++运算符重载
self& operator++()
{
_node = _node->_next;
return *this;
}
self operator++(int)
{
self tmp(*this);
++* this;
return tmp;
}
//--运算符重载
self& operator--()
{
_node = _node->_prev;
return *this;
}
self operator--(int)
{
self tmp(*this);
--* this;
return tmp;
}
//!=和==运算符重载
bool operator!=(const self& x)
{
return _node != x._node;
}
bool operator==(const self& x)
{
return _node == x._node;
}
};
在list类重命名时,只需要传const T引用,或者普通引用即可.
typedef __list_iterator<T,T&> iterator;
typedef __list_iterator<T,const T&> const_iterator;
5.迭代器构造(3)—Ptr参数
struct AA
{
int _a1;
int _a2;
AA(int a1 = 0, int a2 = 0)
:_a1(a1)
, _a2(a2)
{}
};
假如list里面的data数据是一个自定义类型,那我们用迭代器去访问就没有这么方便了.
it是我们的迭代器对象,*it可以获取迭代器指向的结点中的data,它是一个自定义类型,或者说在
这个例子里,它是一个结构体,那就还需用.进行配合访问.
(*it)._a1
但我们想要的却并不这样的效果,假如是自定义类型,此时迭代器的成员变量就应该是一个自定义
类型指针.
即上述例子来说,是AA*类型的.
结构体指针访问,应该采取->来访问对应元素.
因此我们需要对->进行运算符重载.
T* operator->()
{
return &_node->_data;
}
当然,实际上正确的写法应该有两个箭头,因为一个箭头得到相应的自定义类型指针,再用一个箭
头来访问自定义类型的数据,不过编译器会进行相应优化,直接用一个箭头,便可以取到对应数据.
//实际的正确写法
it.operator->()->_a1
it->->_al
由于指针,同样需要有const版本,即不能通过->来修改data.
因此,我们给迭代器添加第三个参数——Ptr.
于是完整版迭代器基本构造如下
// 1、迭代器要么就是原生指针
// 2、迭代器要么就是自定义类型对原生指针的封装,模拟指针的行为
template <class T,class Ref,class Ptr>
struct __list_iterator
{
typedef __list_iterator<T,Ref,Ptr> self;
typedef __list_node<T> node;
typedef __list_node<T>* link_type;
link_type _node;
__list_iterator(link_type x)
:_node(x)
{}
//*运算符重载
Ref operator*()
{
return _node->_data;
}
//->运算符重载
Ptr operator->()
{
return &_node->_data;
}
//++运算符重载
self& operator++()
{
_node = _node->_next;
return *this;
}
self operator++(int)
{
self tmp(*this);
++* this;
return tmp;
}
//--运算符重载
self& operator--()
{
_node = _node->_prev;
return *this;
}
self operator--(int)
{
self tmp(*this);
--* this;
return tmp;
}
//!=和==运算符重载
bool operator!=(const self& x)
{
return _node != x._node;
}
bool operator==(const self& x)
{
return _node == x._node;
}
};
同步的,list类相应位置也进行修改,const迭代器,就传const T*指针;普通迭代器,就传T*指针.
typedef __list_iterator<T,T&,T*> iterator;
typedef __list_iterator<T,const T&,const T*> const_iterator;
//迭代器实现
iterator begin()
{
return iterator(_head->_next);
}
const_iterator begin()const
{
return const_iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator end()const
{
return const_iterator(_head);
}
总结:
1.迭代器要么是原生指针
2.要么是自定义类型对原生指针的封装,模拟指针的行为
6.增删查改实现
1.insert函数
优先实现insert函数,后面头插,尾插就可以直接赋用.
分为两步:1.创建新结点 2.调节指针位置即可.
//insert实现
void insert(iterator pos, const T& x)
{
node* cur = pos._node;
node* prev = cur->_prev;
node* new_node = new node(x);
prev->_next = new_node;
new_node->_prev = prev;
new_node->_next = cur;
cur->_prev = new_node;
}
2.erase函数
优先实现erase函数,后面头删,尾删就可以直接赋用.
分为两步:1.找到前后结点指针 2.调节指针位置即可.
PS:注意判断头结点不能删除
//erase实现,存在迭代器失效问题
iterator erase(iterator pos)
{
//头节点不能删除
assert(pos != end());
node* prev = pos._node->_prev;
node* next = pos._node->_next;
prev->_next = next;
next->_prev = prev;
delete pos._node;
return iterator(next);
}
PS:链表的erase函数同样存在迭代器失效问题,因此需要返回被删除结点的下一个结点位置作为迭代器返回.
3.尾插,尾删,头插,头删操作
//尾插实现
void push_back(const T& val)
{
insert(end(), val);
}
//头插实现
void push_front(const T& val)
{
insert(begin(), val);
}
//尾删实现
void pop_back()
{
erase(--end());
}
//头删实现
void pop_front()
{
erase(begin());
}
7.迭代器构造
实现尾插后,我们可以进一步着手迭代器构造实现.
这时候,我们就可以看出单独将头节点初始化(链表初始化)分装为一个函数的优势在哪,我们不需
要再重复写相应代码.
//迭代器构造
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
empty_initialize();
while (first != last)
{
push_back(*first);
++first;
}
}
8.析构和拷贝构造实现
1.析构函数
由于链表和顺序表空间不同,其不是一块连续的空间,因此不能直接delete掉这块空间.
一种常见的作法,是像我们C语言一样,边遍历链表,边释放相应的结点.
另一种就是C++写法,用迭代器实现释放全部结点的函数.
析构函数,就是释放所有结点+头结点.
//析构函数和clear
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while (it != end())
{
erase(it++);
}
}
2.拷贝构造函数
拷贝构造和赋值运算符重载我们这里就统一都采取现代写法,即用Swap函数进行实现.
//拷贝构造
void swap(list<T>& tmp)
{
std::swap(_head,tmp._head);
}
list(const list<T>& lt)
{
empty_initialize();
list<T> tmp(lt.begin(), lt.end());
swap(tmp);
}
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
由于进了函数,构造的tmp,lt都是临时对象,出了函数作用域会被销毁,所以可以大胆放心swap,
夺取成果.
但不要传引用作为参数,否则就真的成为两个对象互换名字而已.
9.测试代码
//迭代器检验
void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
// int*
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
(*it) *= 2;
cout << *it << " ";
++it;
}
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;
auto pos = lt.begin();
++pos;
lt.insert(pos, 20);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.push_back(100);
lt.push_front(1000);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.pop_back();
lt.pop_front();
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);
lt.push_back(5);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
list<int> lt2(lt);
for (auto e : lt2)
{
cout << e << " ";
}
cout << endl;
list<int> lt3;
lt3.push_back(10);
lt3.push_back(20);
lt3.push_back(30);
for (auto e : lt3)
{
cout << e << " ";
}
cout << endl;
lt2 = lt3;
for (auto e : lt2)
{
cout << e << " ";
}
cout << endl;
for (auto e : lt3)
{
cout << e << " ";
}
cout << endl;
}
10.总体代码展示
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
namespace zzq {
//结点类
template <class T>
struct __list_node {
__list_node<T>* _next;
__list_node<T>* _prev;
T _data;
//节点初始化,初始化列表初始化即可
__list_node(const T& x = T())
:_next(nullptr)
,_prev(nullptr)
,_data(x)
{}
};
// 1、迭代器要么就是原生指针
// 2、迭代器要么就是自定义类型对原生指针的封装,模拟指针的行为
//迭代器类
template <class T,class Ref,class Ptr>
struct __list_iterator
{
typedef __list_iterator<T,Ref,Ptr> self;
//typedef __list_node<T> node;
typedef __list_node<T>* link_type;
link_type _node;
__list_iterator(link_type x)
:_node(x)
{}
//*运算符重载
Ref operator*()
{
return _node->_data;
}
//->运算符重载
Ptr operator->()
{
return &_node->_data;
}
//++运算符重载
self& operator++()
{
_node = _node->_next;
return *this;
}
self operator++(int)
{
self tmp(*this);
++* this;
return tmp;
}
//--运算符重载
self& operator--()
{
_node = _node->_prev;
return *this;
}
self operator--(int)
{
self tmp(*this);
--* this;
return tmp;
}
//!=和==运算符重载
bool operator!=(const self& x)
{
return _node != x._node;
}
bool operator==(const self& x)
{
return _node == x._node;
}
};
//list类
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;
void empty_initialize() {
_head = new node;
_head->_next = _head;
_head->_prev = _head;
}
list()
{
empty_initialize();
}
//迭代器构造
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
empty_initialize();
while (first != last)
{
push_back(*first);
++first;
}
}
//拷贝构造
void swap(list<T>& tmp)
{
std::swap(_head,tmp._head);
}
list(const list<T>& lt)
{
empty_initialize();
list<T> tmp(lt.begin(), lt.end());
swap(tmp);
}
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
//析构函数和clear
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while (it != end())
{
erase(it++);
}
}
//迭代器实现
iterator begin()
{
return iterator(_head->_next);
}
const_iterator begin()const
{
return const_iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator end()const
{
return const_iterator(_head);
}
//push_back实现
void push_back(const T& val)
{
insert(end(), val);
}
//push_front实现
void push_front(const T& val)
{
insert(begin(), val);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
//insert实现
void insert(iterator pos, const T& x)
{
node* cur = pos._node;
node* prev = cur->_prev;
node* new_node = new node(x);
prev->_next = new_node;
new_node->_prev = prev;
new_node->_next = cur;
cur->_prev = new_node;
}
//erase实现,存在迭代器失效问题
iterator erase(iterator pos)
{
//头节点不能删除
assert(pos != end());
node* prev = pos._node->_prev;
node* next = pos._node->_next;
prev->_next = next;
next->_prev = prev;
delete pos._node;
return iterator(next);
}
private:
node* _head;
};
}