1. 前言and框架
首先,我们要明白list的本质是什么,list的本质是带头双向循环链表
- 注意事项
- 类模板的声明:在类前面加一个模板
template<class T>
即可 - 需要创建一个类list,但list的底层是带头双向循环链表。链表则需要结点,那我们就还需要创建要给结点的类。【总结:就是在列表list中有一个头结点的指针。一个结点里面又会有数据,前一个,后一个结点指针】
- 这是自己模拟实现的,则需要有命名空间,否则编译器会调用库里的list
- 如果类里面的内容都是公有的,那么则可以使用struct。有公有和私有,那么使用class(这是惯例,但不是规定)。在struct中,如果没有对某个内容用限定符修饰,那么它就是私有。
- 一共两个类。
list_node
(结点)和list
(列表)
namespace hou
{
template<class T>
struct list_node
{
//在list_node中,存放这个结点的数据,前一个和后一个结点指针,
T _data;
list_node<T>* _next;
list_node<T>* _prev;
};
template<class T>
class list
{
typedef list_node<T> Node;
private:
Node* _head;
};
}
-
typedef list_node<T> Node;
这一步是为了防止大家忘记写模板参数< T>。
在class中,class没有用限定符修饰,是私有的。因此,只有在这个类里面才可以用Node,在这个类里面Node
才代表list_node<T>
. -
为什么
list_node
的所有成员全部对外开放?
因为链表list
在增删查改的时候需要高频地控制list_node
的成员变量。如果list_node
的成员都私有的话,则需要友元函数,比较麻烦。
但是全部对外开放,不害怕别人修改你的数据吗?
其实是很难修改的。我们在使用list的时候,是感知不到底层的结点,哨兵位之类的东西的。我们只是看到了它的各种接口,可以感知到它是双向的。想修改内容,需要知道变量的名称,命名空间的名字。所以,虽然开放,但是也安全。 -
list中大部分用迭代器。在此之前,有迭代器失效这个概念,这里也会出现。但string中也有迭代器失效,但比较少,因为大部分用下标访问,较少使用迭代器。vector也有迭代器失效。
2. 相对完整的框架
- 牢记牢记牢记:为了方便,我们已经将
list_node<T>
在list这个类里面typedef
为Node
。 - 给list构造(无参):
void empty_init()
{
_head = new Node(); //_head的类型是Node*(也就是list_node<T>)结点的指针
_head->_prev = _head;
_head->_next = _head;
}
list()
{
empty_init();
}
- list_node的构造
list_node(const T& x = T())
:_data(x)
,_next(nullptr)
,_prev(nullptr)
{
}
- 迭代器
面向对象的三大特征:封装,继承,多态。
封装可以用类和迭代器来实现。(它可以用来“屏蔽底层的实现细节,屏蔽个容器的结构差异,本质是封装底层细节和差异,提供同一的访问方式)
只需要提供一个结点即可
namespace hou
{
template<class T>
struct list_node
{
T _data;
list_node<T>* _next;
list_node<T>* _prev;
list_node(const T& x = T())
:_data(x)
,_next(nullptr)
,_prev(nullptr)
{
}
};
template<class T>
struct list_iterator
{
typedef list_node<T> Node;
Node* _node;
};
template<class T>
class list
{
typedef list_node<T> Node;
public:
typedef list_iterator<T> iterator;
void empty_init()
{
_head = new Node(); //_head的类型是Node*(也就是list_node<T>)结点的指针
_head->_prev = _head;
_head->_next = _head;
}
list()
{
empty_init();
}