list容器不像之前的string、vector,不属于顺序存储的容器,是经由链表实现的,而且还是双链表
目录
一、准备工作:构建结点+连接节点
为了实现双链表,我们需要先通过结构体表示出每一个结点,然后使用类来连接这些结点
为了和原生的list区分开,我们把自定义的list放在自定义命名空间xing中
1、构建结点
每一个结点的数据类型是不确定的,所以这里加入了模板
定义结构体模板或者类模板时,无需加 <T>,除此之外,使用结构体模板或者类模板都需要加 <T>,因为类模板不会自动推导数据类型
C++中的结构体和类一样,在创建结构体对象的时候,会调用构造函数,方便我们在创建对象的时候就给_data赋值,赋予缺省值的目的是,创建哨兵位结点时,不会放入数据
namespace xing
{
template<class T>
struct ListNode //定义一个结构体模板
{
T _data; //每个结点中保存的数据
ListNode<T>* _next; //下一个结点的地址
ListNode<T>* _prev; //上一个结点的地址
ListNode(const T& data = T()) //方便下面在构建结点的时候就直接初始化
:_data(data),
_next(nullptr),
_prev(nullptr)
{
}
};
}
2、连接结点
每一个小结点有了,现在只需要将结点连接起来
结构体类型ListNode<T> 比较长,而且可读性差,我们使用typedef 给他起一个别名叫做 Node,代表每一个结点
这个类中只需要存储 头结点( 哨兵位 )的地址,因为是双链表,头结点的上一个结点就是链表的尾结点
list类创建对象的时候,会自动调用构造函数,构造函数中需要做的工作是,创建一个哨兵位结点,一开始没有其他结点,所以哨兵位结点的 prev 和 next 都是指向自己
namespace xing
{
//构建结点
template<class T>
struct ListNode
{
T _data;
ListNode<T>* _next;
ListNode<T>* _prev;
ListNode(const T& data = T()) //方便下面在构建结点的时候就直接初始化
:_data(data),
_next(nullptr),
_prev(nullptr)
{
}
};
//连接结点
template<class T>
class list
{
typedef ListNode<T> Node;
public:
list(){
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
private:
Node* _head; //头结点的地址
};
}
二、尾插添加数据
(1) 创建一个新结点 newNode
(2) 处理左边部分的和 尾结点的联系
(3) 处理右边部分和 头结点的联系
void push_back(const T& val)
{
Node* tail = _head->_prev; //获取尾结点
Node* newNode = new Node(val); //创建新的结点
//处理新节点newNode的左边部分
tail->_next = newNode;
newNode->_prev = tail;
//处理新节点newNode的右边部分
newNode->_next = _head;
_head->_prev = newNode;
}
a. 测试代码一:测试内置类型
int main()
{
xing::list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
return 0;
}
b. 测试代码二:测试自定义类型: