list的使用,及部分功能的模拟实现(C++)
目录(文章中"节点"和"结点"是同一个意思)
1. list的介绍及使用
1.1 list的介绍
1.2 list的使用
1.2.1 list的构造
1.2.2 list iterator的使用
1.2.3 list capacity
1.2.4 list element access
1.2.5 list modifiers
1.2.6 list的迭代器失效
2. list的模拟实现
2.1 list_node结点
2.2 list_iterator迭代器
2.2.1 运算符重载!=
2.2.2 运算符重载==
2.2.3 运算符重载前置++
2.2.4 运算符重载后置++
2.2.5 运算符重载前置--
2.2.6 运算符重载后置--
2.2.7 运算符重载*
2.2.8 运算符重载->
2.3 list类
2.3.1 构造函数
2.3.1.1默认构造函数list
2.3.1.2 拷贝构造
2.3.1.3 list(std::initializer_list lt)
2.3.2 析构函数
2.3.3 赋值运算符重载
2.3.4 front和back
2.3.5 size和begin和end
2.3.6 insert插入和erase删除
2.3.6.1 insert插入
2.3.6.2 erase删除
2.3.7 头插push_front,头删pop_front,尾插push_back,尾删pop_back
2.4 整体代码
1. list的介绍及使用
1.1 list的介绍
std::list是 C++ 标准模板库(STL)中的一个容器类,底层实现为双向链表,适用于需要频繁插入和删除操作的场景。
1.2 list的使用
以下为list中一些常见的重要接口。
1.2.1 list的构造
构造函数( (constructor)) | 接口说明 |
list (size_type n, const value_type& val = value_type()) | 构造的list中包含n个值为val的元素 |
list() | 构造空的list |
list (const list& x) | 拷贝构造函数 |
list (InputIterator first, InputIterator last) | 用[first, last)区间中的元素构造 list |
使用演示:
void test1() { list<int> l1(10, 5); list<int> l2; list<int> l3(l1); list<int> l4(l1.begin(),l1.end()); for (auto& e : l1) { cout << e << " "; } cout << endl; for (auto& e : l2) { cout << e << " "; } cout << endl; for (auto& e : l3) { cout << e << " "; } cout << endl; for (auto& e : l4) { cout << e << " "; } cout << endl; }
结果:
1.2.2 list iterator的使用
这里可以暂时将迭代器理解成一个指针,该指针指向list中的某个节点。(其实底层实现时,迭代器是一个封装的对象)
函数声明 | 接口说明 |
begin + end | 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器 |
rbegin + rend | 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位 置的reverse_iterator,即begin位置 |
1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动
使用演示:
void test2() { list<int> l; l.push_back(1); l.push_back(2); l.push_back(3); l.push_back(4); l.push_back(5); list<int>::reverse_iterator rit = l.rbegin(); while (rit != l.rend()) { cout << *rit << " "; rit++; } cout << endl; list<int>::iterator it = l.begin(); while (it != l.end()) { cout << *it << " "; it++; } cout << endl; }
结果:
1.2.3 list capacity
函数声明 | 接口说明 |
empty | 检测list是否为空,是返回true,否则返回false |
size | 返回list中有效节点的个数 |
使用演示:
void test3() { list<int> l; l.push_back(1); l.push_back(2); l.push_back(3); l.push_back(4); l.push_back(5); if(!l.empty()) { cout << "该链表非空" << endl; } cout << "有效节点个数:" << l.size() << endl; }
结果:
1.2.4 list element access
函数声明 | 接口说明 |
front | 返回list的第一个节点中值的引用 |
back | 返回list的最后一个节点中值的引用 |
使用演示:
void test4() { list<int> l; l.push_back(1); l.push_back(2); l.push_back(3); l.push_back(4); l.push_back(5); cout << l.front() << endl; cout << l.back() << endl; }
结果:
1.2.5 list modifiers
函数声明 | 接口说明 |
push_front | 在list首元素前插入值为val的元素 |
pop_front | 删除list中第一个元素 |
push_back | 在list尾部插入值为val的元素 |
pop_back | 删除list中最后一个元素 |
insert | 在list position 位置中插入值为val的元素 |
erase | 删除list position位置的元素 |
swap | 交换两个list中的元素 |
clear | 清空list中的有效元素 |
使用演示:
push_front和pop_front使用:
void test5() { list<int> l; l.push_back(1); l.push_back(2); l.push_back(3); l.push_back(4); l.push_back(5); for (auto& e : l) { cout << e << " "; } cout << endl; l.push_front(10); cout << "头插后:"; for (auto& e : l) { cout << e << " "; } cout << endl; l.pop_front(); cout << "头删后:"; for (auto& e : l) { cout << e << " "; } cout << endl; }
结果:
push_back和pop_back:
void test6() { list<int> l; l.push_back(1); l.push_back(2); l.push_back(3); l.push_back(4); l.push_back(5); for (auto& e : l) { cout << e << " "; } cout << endl; l.push_back(6); cout << "尾插后:"; for (auto& e : l) { cout << e << " "; } cout << endl; l.pop_back(); cout << "尾删后:"; for (auto& e : l) { cout << e << " "; } cout << endl; }
结果:
insert和erase:
void test7() { list<int> l; l.push_back(1); l.push_back(2); l.push_back(3); l.push_back(4); l.push_back(5); for (auto& e : l) { cout << e << " "; } cout << endl; l.insert(++l.begin(), 30); cout << "插入后:"; for (auto& e : l) { cout << e << " "; } cout << endl; l.erase(--l.end()); cout << "删除后:"; for (auto& e : l) { cout << e << " "; } cout << endl; }
结果:
swap:
void test8() { list<int> l1; l1.push_back(1); l1.push_back(2); l1.push_back(3); l1.push_back(4); l1.push_back(5); list<int> l2(5, 5); cout << "交换前" << endl; cout << "l1:"; for (auto& e : l1) { cout << e << " "; } cout << endl; cout << "l2:"; for (auto& e : l2) { cout << e << " "; } cout << endl; cout << "交换后" << endl; l1.swap(l2); cout << "l1:"; for (auto& e : l1) { cout << e << " "; } cout << endl; cout << "l2:"; for (auto& e : l2) { cout << e << " "; } cout << endl; }
结果:
clear:
void test9() { list<int> l1; l1.push_back(1); l1.push_back(2); l1.push_back(3); l1.push_back(4); l1.push_back(5); cout << "清空前" << endl; cout << "有效元素个数:" << l1.size() << endl; for (auto& e : l1) { cout << e << " "; } cout << endl; l1.clear(); cout << "清空后" << endl; cout << "有效元素个数:" << l1.size() << endl; for (auto& e : l1) { cout << e << " "; } cout << endl; }
结果:
1.2.6 list的迭代器失效
小编前面文章里面“vector迭代器失效”,提到只要使用erase或者insert之后迭代器就直接失效,但是对于list有所不同,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
举例(删除所有结点):
void test10() { int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; list<int> l(array, array + sizeof(array) / sizeof(array[0])); auto it = l.begin(); while (it != l.end()) { // erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值 l.erase(it); ++it; } }
结果:
可以看到这里报段错误,迭代器失效了。
改进后:
void test10() { int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; list<int> l(array, array + sizeof(array) / sizeof(array[0])); auto it = l.begin(); while (it != l.end()) { it=l.erase(it); } }
因为erase函数删除后,会返回删除结点的下一个节点迭代器,it接收返回值,相当于更新了迭代器。
2. list的模拟实现
这里利用双向带头链表实现,因为链表的物理空间并不是连续的,所以这里的迭代器不能使用指针,这里对迭代器进行了封装,实现一个对象。还有这里为了和原本库里list区分,将自己写的list封装在命名空间里(作者的是iu)
2.1 list_node结点
template<class T>
struct list_node
{
list_node<T>* _prev;
list_node<T>* _next;
T _value;
list_node(const T& x = T())
:_prev(nullptr)
,_next(nullptr)
,_value(x)
{}
};
双向带头链表,所以成员有前驱结点,和后继结点,和元素对象。
2.2 list_iterator迭代器
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)
{}
};
这里模版为什么会有REF和PTR?其实是解决,范围for迭代器访问时,会有const修饰的对象,如果直接确定的写,只有普通对象可以,所以这里多给两个参数模版,让编译器自己去判断是const修饰的对象,还是普通对象。
2.2.1 运算符重载!=
bool operator!=(const Self& it)
{
return _node!=it._node;
}
2.2.2 运算符重载==
bool operator==(const Self& it)
{
return _node == it._node;
}
2.2.3 运算符重载前置++
Self& operator++()
{
_node = _node->_next;
return *this;
}
结点的指向直接向后移动一位。
2.2.4 运算符重载后置++
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
创建一个临时对象,节点的指向向后移动一位,返回临时对象。
2.2.5 运算符重载前置--
Self& operator--()
{
_node = _node->_prev;
return *this;
}
结点的指向直接向前移动一位。
2.2.6 运算符重载后置--
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
创建一个临时对象,节点的指向向前移动一位,返回临时对象。
2.2.7 运算符重载*
REF operator*()
{
return _node->_value;
}
解引用,就是返回对象的值。
2.2.8 运算符重载->
PTR operator->()
{
return &_node->_value;
}
这里为什么是取值的地址?通过下面的例子来讲解:
void test2() { struct A { A(int a = 0,int b=0) :_a(a) ,_b(b) {} int _a; int _b; }; iu::list<A>l2; l2.push_back({1,1}); l2.push_back({2,2}); l2.push_back({3,3}); l2.push_back({4,4}); iu::list<A>::iterator it = l2.begin(); while (it != l2.end()) { cout << (*it)._a << ":" << it->_b << endl; it++; } }
这里可以利用解引用在去访问,A类中的成员,这是一种访问方式;而这个it->b是如何访问的?前面实现的是返回A的地址,拿到A对象的地址和成员_b之间也没有运算符,那有怎么样才能访问到_b呢?其实这里省略了一个->,这里其实应该是it->->_b,所以先拿到A对象的地址,再利用结构体->这种方式。解引用访问里面的成员。
结果:
2.3 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;
private:
Node* _head;
size_t _size;
};
}
成员有头结点,和元素个数。
2.3.1 构造函数
2.3.1.1默认构造函数list
void empty_init()
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
_size = 0;
}
list()
{
empty_init();
}
这里利用empty_init函数,进行初始化,是为了方便后面几个构造函数,先初始化,再尾插的操作。
2.3.1.2 拷贝构造
list(const list<T>& lt)
{
empty_init();
for (auto& e : lt)
{
push_back(e);
}
}
初始化,再遍历一遍lt,进行尾插依次插入。
2.3.1.3 list(std::initializer_list<T> lt)
list(std::initializer_list<T> lt)
{
empty_init();
for (auto& e : lt)
{
push_back(e);
}
}
和上面同理。
这里举一个例子:
void test8() { iu::list<int>l1 = { 1,2,3,4,5,6,7 }; for (auto& e : l1) { cout << e << " "; } cout << endl; }
结果:
这个initializer_list<T>类型是一个类似于数组的类型,可以理解成一种类似数组初始化的方式。
2.3.2 析构函数
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
先在clear函数里面将数据全部清除,再将头结点释放掉,置为空。
2.3.3 赋值运算符重载
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;
}
利用std中的交换函数,将临时对象与this指向的对象进行交换,临时对象出了作用域会自然销毁,也不会影响实参,这样就相当于实现了赋值操作。
2.3.4 front和back
T& front()
{
return _head->_next->_value;
}
const T& front()const
{
return _head->_next->_value;
}
T& back()
{
return _head->_prev->_value;
}
const T& back()const
{
return _head->_prev->_value;
}
这里利用的是双向带头链表,所以头部front元素就是头结点的下一个,尾结点back就是头结点的前一个。这里还重载了const修饰的对象,当对象元素是不可改变的const时,也可以使用。
2.3.5 size和begin和end
size_t size()
{
return _size;
}
iterator begin()
{
return _head->_next;
}
iterator end()
{
return _head;
}
const_iterator begin() const
{
return _head->_next;
}
const_iterator end() const
{
return _head;
}
size函数,直接返回成员_size大小即可。
begin()返回头结点的下一个结点的迭代器,end()返回最后一个元素的下一个结点的迭代器,所以就是头结点head,这里也是重载了const修饰的对象,当对象元素是不可改变的const时,也可以使用。
2.3.6 insert插入和erase删除
2.3.6.1 insert插入
void insert(iterator pos,const T& x)
{
Node* newnode = new Node(x);
Node* cur = pos._node;
Node* prev = cur->_prev;
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
++_size;
}
首先创建一个新的结点,再进行插入操作,插入时,先找到pos位置的结点,和pos位置的前一个结点,再改变前后指针指向即可,最后_size再加1。
2.3.6.2 erase删除
iterator erase(iterator pos)
{
assert(pos != end());
Node* del = pos._node;
Node* prev = del->_prev;
Node* next = del->_next;
prev->_next = next;
next->_prev = prev;
delete del;
--_size;
return iterator(next);
}
首先找到pos位置的结点,再确定pos前一个结点,和后一个结点的位置,最后改变指针指向,再删除pos位置的节点,_size再减1,最后还要返回删除节点的下一个位置的结点,返回匿名对象即可。
2.3.7 头插push_front,头删pop_front,尾插push_back,尾删pop_back
void push_back(const T& x)
{
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
这里直接复用insert和erase函数实现,只是要注意尾结点的位置是end()的前一个位置。
例子:
void test4() { iu::list<int>l1; l1.push_back(1); l1.push_back(2); l1.push_back(3); l1.push_back(4); l1.insert(l1.begin(), 10); l1.insert(l1.begin(), 20); for (auto& e : l1) { cout << e << " "; } cout << endl; l1.pop_back(); l1.pop_back(); l1.pop_front(); l1.pop_front(); for (auto& e : l1) { cout << e << " "; } cout << endl; }
结果:
2.4 整体代码
#include<assert.h>
#include<algorithm>
#include <initializer_list>
namespace iu
{
template<class T>
struct list_node
{
list_node<T>* _prev;
list_node<T>* _next;
T _value;
list_node(const T& x = T())
:_prev(nullptr)
,_next(nullptr)
,_value(x)
{}
};
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)
{}
bool operator!=(const Self& it)
{
return _node!=it._node;
}
bool operator==(const Self& it)
{
return _node == it._node;
}
REF operator*()
{
return _node->_value;
}
PTR operator->()
{
return &_node->_value;
}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
Self operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
};
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_init()
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
_size = 0;
}
list()
{
empty_init();
}
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(std::initializer_list<T> lt)
{
empty_init();
for (auto& e : lt)
{
push_back(e);
}
}
list(const list<T>& lt)//拷贝构造
{
empty_init();
for (auto& e : lt)
{
push_back(e);
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
T& front()
{
return _head->_next->_value;
}
const T& front()const
{
return _head->_next->_value;
}
T& back()
{
return _head->_prev->_value;
}
const T& back()const
{
return _head->_prev->_value;
}
size_t size()
{
return _size;
}
iterator begin()
{
return _head->_next;
}
iterator end()
{
return _head;
}
const_iterator begin() const
{
return _head->_next;
}
const_iterator end() const
{
return _head;
}
void push_back(const T& x)
{
//Node* newnode = new Node(x);
//Node* tail = _head->_prev;
//newnode->_prev = tail;
//tail->_next = newnode;
//newnode->_next = _head;
//_head->_prev = newnode;
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
void insert(iterator pos,const T& x)
{
Node* newnode = new Node(x);
Node* cur = pos._node;
Node* prev = cur->_prev;
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
++_size;
}
iterator erase(iterator pos)
{
assert(pos != end());
Node* del = pos._node;
Node* prev = del->_prev;
Node* next = del->_next;
prev->_next = next;
next->_prev = prev;
delete del;
--_size;
return iterator(next);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
private:
Node* _head;
size_t _size;
};
}