本文是小编巩固自身而作,如有错误,欢迎指出!
一.list简介
1.list特点
C++ 中的 std::list
是标准模板库(STL)提供的一个容器,是一个双向带头循环链表。
2.list的常见用法
list与string和vector的常见接口很类似,但唯独有一个不太一样,就是排序接口sort,像string和vector都是以顺序表的方式实现,而string以链表的方式实现,导致其排序的底层实现与string和vector不同,在排序时更加复杂,导致耗费时间更多,因此,如果在元素个数非常多的情况下,我们一般可以将list的元素先拷贝到vector中后,进行排序
#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
int main() {
std::list<int> myList = {4,2,1,5,6};
// 将元素从list拷贝到vector
std::vector<int> myVector(myList.begin(), myList.end());
// 在vector中排序
std::sort(myVector.begin(), myVector.end());
// 将排序后的元素从vector拷贝回list
myList.assign(myVector.begin(), myVector.end());
for (auto e : myList) {
std::cout << e << " ";
}
std::cout << std::endl;
return 0;
}
二.list一些常见接口的模拟实现
1.类list的框架
我们回想一下,一个双向带头链表由什么组成?其就是由很多个节点组成,那么我们首先就能确定需要两个类,一个类是list这个链表,一个类是每一个节点
namespace yiming
{
template<class T>
struct list_node//当不考虑用访问限定符做限制,所有成员公有的情况下,可以使用struct
{
T _data;
list_node<T>* _next;
list_node<T>* _prev;
};
template<class T>
class list
{
typedef list_node<T> Node;
public:
Node* _head;
};
}
而我们要对链表进行操作,毫无疑问,我们需要用到指针,也就是迭代器,这里我们创建第三个类,迭代器
struct list_iterator
{
typedef list_node<T> Node;
typedef list_iterator<T, Ref, Ptr> Self;
Node* _node;
};
至于为什么这个模版有三个参数,我们后续详细讲
2.构造与析构
上面我们已经看了上述三个类的成员
我们现在看看其构造是怎么实现的
list_node(const T& x = T())
:_data(x)
, _next(nullptr)
, _prev(nullptr)
{
}
list_iterator(Node* node)
:_node(node)
{}
list()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
大家可以看到,为什么只写了list的析构呢?其原因在于我们对链表操作时,使用迭代器,节点,希望其创造后仍可使用,而不是销毁,故其不需要析构函数。而我们后续对链表的操作,都是在list类的实现的,我们现在看看list类的其他构造函数
void empty_init()//初始化节点
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
list()
{
empty_init();
}
list(const list<T>& lt)
{
//先让新创建的对象拥有自己的头尾节点
empty_init();
//遍历将后续节点接入
for (const auto& e : lt)
{
push_back(e);
}
}
list(initializer_list<T> il)//实现花括号进行初始化
{
empty_init();
for (const auto& e : il)
{
push_back(e);
}
}
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
list<T>& operator=( list<T> lt)//不使用引用,会调用拷贝构造,不用担心右侧值被修改
{
swap(lt);
return *this;
}
3.迭代器操作
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin()const
{
return const_iterator(_head->_next);
}
const_iterator end()const
{
return const_iterator(_head);
}
上述是一些简单的头尾迭代器,而且应该大家也会发现我们上述的迭代器其模版有三个参数
typedef list_iterator<T, Ref, Ptr> Self;
typedef list_iterator<T,T&,T*> iterator;
typedef list_iterator<T,const T&,const T*> const_iterator;
其主要原因就是,首先我们在进行解引用的操作时,我们希望得到其拷贝的值,故其使用Ref(reference),而我们使用时,时常会遇到const不匹配的情况,如下述代码
void print(const list<T><)//如何实现指向的内容不能修该但是能对it进行操作?
{
typename list<T>::const_iterator it = lt.begin();//类模版未实例化,不能去类模版中找后面的东西,typename告诉编译器是类型而非静态成员变量
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
其lt内的节点需要能够遍历而不能对其内的内容修改,也就是我们不能直接使用const进行修饰,那样会导致迭代器无法修改,导致无法 遍历,那我们按照直接的方法只能够选择一个新的类,其中全都是const类型的如下
template<class T>
struct list_const_iterator
{
typedef list_node<T> Node;
Node* _node;
list_const_iterator(Node* node)
:_node(node)
{
}
~list_const_iterator()//迭代器不需要析构,由链表负责
{
}
const T& operator*()
{
return _node->_data;
}
list_const_iterator<T>& operator++()
{
_node = _node->_next;
return *this;
}
list_const_iterator<T>& operator++(int)
{
list_const_iterator<T> tmp(*this);
_node = _node->_next;
return tmp;
}
list_const_iterator<T>& operator--()
{
_node = _node->_prev;
return *this;
}
list_const_iterator<T>& operator--(int)
{
list_const_iterator<T> tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator==(const list_const_iterator<T>& it)const
{
return _node == it._node;
}
bool operator!=(const list_const_iterator<T>& it)const
{
return _node != it._node;
}
};
很明显这样就太麻烦了,所以我们选择直接将模版给予多个参数,直接模版化,那么后续的迭代器实现就可以不需要多写一遍了
Ref operator*()
{
return _node->_data;
}
Ptr 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& it)const
{
return _node == it._node;
}
bool operator!=(const Self& it)const
{
return _node != it._node;
}
};
4.增删插改操作
其所有的头插,尾插头删尾删都是基于,insert插入,erase删除完成的
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;
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
iterator insert(iterator pos, const T& val)
{
Node* cur = pos._node;
Node* prev = cur->_prev; // 保存原始前驱节点
Node* newnode = new Node(val);
// 建立新节点连接
newnode->_prev = prev; // ← 先指向原始前驱
newnode->_next = cur; // → 指向当前节点
// 更新相邻节点
prev->_next = newnode; // 原始前驱→新节点
cur->_prev = newnode; // 当前节点←新节点
++_size;
return iterator(newnode);
}
iterator erase(iterator pos)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
cur->_next = next;
cur->_prev = prev;
delete cur;
--_size;
return iterator(next);
}
而其原理,我们之前在学习链表时已经详细讲述过了,不清楚的同学可以看看以下链接
5.容量操作
size_t size()const
{
//int n = 0;
//for (auto e : *this)
//{
// ++n;
//}
//return n;
return _size;
}
可以看到我们可以使用遍历的方式来实现对链表个数的统计,但是我们也可以直接添加一个成员变量size,在insert后++,erase后--;
三.完整代码实现
头文件
#pragma once
#include<iostream>
#include<list>
using namespace std;
namespace yiming
{
template<class T>
struct list_node//当不考虑用访问限定符做限制,所有成员公有的情况下,可以使用struct
{
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,class Ref,class Ptr>
struct list_iterator
{
typedef list_node<T> Node;
typedef list_iterator<T, Ref, Ptr> Self;
Node* _node;
list_iterator(Node* node)
:_node(node)
{
}
~list_iterator()//迭代器不需要析构,由链表负责
{
}
Ref operator*()
{
return _node->_data;
}
Ptr 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& it)const
{
return _node == it._node;
}
bool operator!=(const Self& it)const
{
return _node != it._node;
}
};
//template<class T>
//struct list_const_iterator
//{
// typedef list_node<T> Node;
// Node* _node;
// list_const_iterator(Node* node)
// :_node(node)
// {
// }
// ~list_const_iterator()//迭代器不需要析构,由链表负责
// {
// }
// const T& operator*()
// {
// return _node->_data;
// }
// list_const_iterator<T>& operator++()
// {
// _node = _node->_next;
// return *this;
// }
// list_const_iterator<T>& operator++(int)
// {
// list_const_iterator<T> tmp(*this);
// _node = _node->_next;
// return tmp;
// }
// list_const_iterator<T>& operator--()
// {
// _node = _node->_prev;
// return *this;
// }
// list_const_iterator<T>& operator--(int)
// {
// list_const_iterator<T> tmp(*this);
// _node = _node->_prev;
// return tmp;
// }
// bool operator==(const list_const_iterator<T>& it)const
// {
// return _node == it._node;
// }
// bool operator!=(const list_const_iterator<T>& it)const
// {
// return _node != it._node;
// }
//};
//
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;
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin()const
{
return const_iterator(_head->_next);
}
const_iterator end()const
{
return const_iterator(_head);
}
void empty_init()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
list()
{
empty_init();
}
list(const list<T>& lt)
{
//先让新创建的对象拥有自己的头尾节点
empty_init();
//遍历将后续节点接入
for (const auto& e : lt)
{
push_back(e);
}
}
list(initializer_list<T> il)
{
empty_init();
for (const auto& e : il)
{
push_back(e);
}
}
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
list<T>& operator=( list<T> lt)//不使用引用,会调用拷贝构造,不用担心右侧值被修改
{
swap(lt);
return *this;
}
//list<T>& operator=(const list<T>& lt)
//{
// if (this != <)//不是同一个节点时
// {
// clear();
// for (const auto& e : lt)
// {
// push_back(e);
// }
// }
// return *this;
//}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
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;
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
iterator insert(iterator pos, const T& val)
{
Node* cur = pos._node;
Node* prev = cur->_prev; // 保存原始前驱节点
Node* newnode = new Node(val);
// 建立新节点连接
newnode->_prev = prev; // ← 先指向原始前驱
newnode->_next = cur; // → 指向当前节点
// 更新相邻节点
prev->_next = newnode; // 原始前驱→新节点
cur->_prev = newnode; // 当前节点←新节点
++_size;
return iterator(newnode);
}
iterator erase(iterator pos)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
cur->_next = next;
cur->_prev = prev;
delete cur;
--_size;
return iterator(next);
}
size_t size()const
{
//int n = 0;
//for (auto e : *this)
//{
// ++n;
//}
//return n;
return _size;
}
private:
Node* _head;
size_t _size=0;
};
}
测试文件
#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
namespace yiming
{
template<class T>
void print(const list<T><)//如何实现指向的内容不能修该但是能对it进行操作?
{
typename list<T>::const_iterator it = lt.begin();//类模版未实例化,不能去类模版中找后面的东西,typename告诉编译器是类型而非静态成员变量
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
void test01()
{
yiming::list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
list<int>::iterator it = lt.begin();
lt.insert(it, 10);
lt.push_back(6);
lt.push_front(3);
print(lt);
yiming::list<int> lt2(lt);
print(lt2);
yiming::list<int> lt3={ 1,2,3,4,5 };
print(lt3);
}
}
int main()
{
yiming::test01();
return 0;
}
测试结果
本次分享就到这里结束了,后续会继续更新,感谢阅读!