🌏博客主页:PH_modest的博客主页
🚩当前专栏:C++跬步积累
💌其他专栏:
🔴 每日一题
🟡 Linux跬步积累
🟢 C语言跬步积累
🌈座右铭:广积粮,缓称王!
一、总揽(三个类和相关接口)
节点类(list_node)
template<calss T>
class list_node
{
//成员对象
list_node<T>* _pre;
list_node<T>* _next;
T _val;
//成员函数
//默认构造
list_node(const T& val=T());
};
迭代器(list_iterator)
template<class T, class Ref, class Ptr>
class list_iterator
{
public:
//重命名
typedef list_node<T> node;
typedef list_iterator<T, Ref, Ptr> iterator;
//成员变量
node* _node;
//构造函数
list_iterator(node* x);
//运算符重载
iterator& operator++();
iterator operator++(int);
iterator& operator--();
iterator operator--(int);
bool operator==(const iterator& lt) const;
bool operator!=(const iterator& lt) const;
Ref operator*();
Ptr operator->();
};
list类
//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;
//默认成员函数
list();
list(const list<T>& lt);
~list();
list<T>& operator=(const list<T>& lt);
//迭代器相关函数
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
//访问容器相关函数
T& front();
T& back();
const T& front() const ;
const T& back() const ;
//插入、删除函数
void push_back(const T& x);
void push_front(const T& x);
void pop_back();
void pop_front();
iterator erase(iterator& it);
iterator insert(iterator pos, const T& x);
//其他函数
void clear();
void swap(list<T>& lt);
private:
node* _head;//指向链表头结点的指针
};
二、节点类的模拟实现
list底层的实现就是一个带头双向循环链表。
因此在实现list类之前需要先实现节点类。一个节点类需要存储三个信息:数据、前一个节点的地址、后一个节点的地址。所以成员函数就知道了:数据(_val)、前驱指针(_pre)、后继指针(_next)。
对于节点类的成员函数来说,我们只需要实现一个构造函数即可,因为这个节点类的作用就是根据数据来创建一个节点即可,而节点的释放则由list的析构函数来完成。
构造函数
list_node(const T& x = T())
:_pre(nullptr)
, _next(nullptr)
, _val(x)
{
}
构造函数主要是存储数据,当没有传递参数时,会调用所存储类型的默认构造所构造出来的值作为参数。
例如:数据类型为int时,会调用int类型的默认构造,会将_val初始化为0;
节点类总结
template<class T>
struct list_node
{
//成员对象
list_node<T>* _pre;
list_node<T>* _next;
T _val;
//成员函数
list_node(const T& x = T())
:_pre(nullptr)
, _next(nullptr)
, _val(x)
{
}
};
三、迭代器类的模拟实现
迭代器类的意义
与vector和string不同,他们可以直接使用原生指针,因为他们的数据是存储在一块连续的内存空间,我们可以直接通过指针进行自增、自减、解引用等操作来进行相关操作。
而对于list,各个节点在内存中的位置是随机的,不是连续的,所以我们不能直接通过节点指针的自增、自减。解引用等操作对相应节点的数据进行操作。
迭代器的意义: 让使用者可以不用关心容器的底层实现,可以用简单统一的方式对容器内部的数据进行访问,只需要将迭代器进行相应的封装即可。
迭代器的本质就是指针,既然这个原生指针不能满足我们的要求,那么我们就可以自己封装一个,对相关的操作符进行重载,让其满足相关的操作。(例如:当你使用list迭代器进行自增时,其实执行了ptr = ptr -> next;指向了下一个节点,也就是我们所希望的自增)
如果还觉得抽象,我再举个例子,原生指针就可以看成老虎,他们吃东西的时候是直接吃生肉;而迭代器封装就可以看成是人,我们吃东西的时候,需要将食物煮熟了再吃。老虎想要吃肉就可以直接吃生肉(原生指针进行相关操作时可以直接进行++、- -等操作),而我们人类吃东西时,需要将食物进行相关处理(list迭代器的相关操作符需要进行封装,以此来达到对应的操作)
总结: list迭代器类,实际上就是对节点指针进行了封装,对相关运算符进行了重载,使得节点指针的各种行为看起来和普通指针一样。
迭代器模板参数说明
我们这里使用了三个模版参数,这是为什么呢?
template<class T, class Ref, class Ptr>
在list的模拟实现中,需要有两种迭代器:普通迭代器和const迭代器。
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
不难看出,迭代器类的模版参数列表中的Ref和Ptr分别表示引用类型和指针类型。
通过下图对比一下不难看出,const和非const的类型只有三个不同,因此通过设置三个模版参数就可以将两种状态包含了。