1.list
1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)
2.list的使用
2.1 构造函数
void test1()
{//无参构造list<int> l1;//有参构造list<int> l2(10, 1);//10个1list<int> l3(10);//使用迭代器范围构造list<int> l4(l2.begin(), l2.end());//双向迭代器不支持加减法vector<int> v = { 1,2,3,4,5 };list<int> l5(v.begin() + 2, v.end());//拷贝构造list<int> l6(l5);//使用initializer_list初始化list<int> l7 = { 1,2,3,4,5,6,7,8 };
}
2.2operator=()
赋值运算符重载,进行深拷贝
//int类型
void PrintList(const list<int>& l)
{for (auto e : l){cout << e << ' ';}cout << endl;
}void test2()
{//operator=list<int> l1 = { 1,2,3 };list<int> l2;//深拷贝l2 = l1;PrintList(l1);PrintList(l2);
}
2.3begin() 和 end()
begin():返回第一个元素的双向迭代器,如果容器为空,则返回的迭代器值不能被解引用。
end():返回最后一个元素的下一位双向迭代器,如果容器为空,返回的迭代器与begin()返回的迭代器相同。
双向迭代器不支持加减法,支持 ++ 、 -- 、 * 、 == 、 != 、= 等操作。
void test3()
{//begin() and end()list<int> l = { 1,2,3,4,5,6,7,8 };list<int>::iterator it = l.begin();//返回双向迭代器while (it != l.end()){cout << *(it++) << ' ';}cout << endl;
}
2.4 rbegin() 和 rend()
rbegin():返回最后一个元素的反向双向迭代器
rend():返回第一个元素前一位的反向双向迭代器
使用++是向前迭代,--是向后迭代
void test4()
{list<int>l = { 1,2,3,4,5,6,7,8 };list<int>::reverse_iterator it = l.rbegin();while (it != l.rend()){cout << *(it++) << ' ';}cout << endl;
}
2.5 cbegin()、cend()、crbegin()、 crend()
无论调用对象是否const修饰,这四个函数返回const迭代器,不能通过迭代器修改指向内容,但迭代器本身可以改变。
2.6 empty()
判断容器是否为空,空返回true,非空返回false
void test5()
{list<int>l = { 1,2,3,4,5,6,7,8 };while (!l.empty()){cout << l.front() << ' ';l.pop_front();}
}
2.7 size()
用于返回有效元素个数
2.8 front()
返回容器第一个元素的引用,对空容器使用front是未定义行为
2.9 back()
返回容器最后一位元素的引用,对空容器使用back是未定义行为
2.10 assign()
将新内容替换原本的全部内容
void test8()
{//assignlist<int> l1 = { 1,2,3,4,5 };list<int> l2 = { 11,22 };vector<int> v = { 121,23,43,54,54,5,454 };//范围替换l1.assign(v.begin(), v.begin() + 3);l2.assign(l1.begin(), l1.end());PrintList(l1);PrintList(l2);//n个val替换l1.assign(10, 2);l2.assign(2, 0);PrintList(l1);PrintList(l2);
}
2.11 emplace_front
在开头构造并插入元素
在列表的开头插入一个新元素,就在它当前的第一个元素之前。这个新元素是使用args作为其构造的参数来就地构造的。
这有效地将容器大小增加了1。
该元素是通过调用allocator_traits::construct来就地构造的,并将参数转发。
存在一个类似的成员函数push_front,它复制或移动一个现有对象到容器中。
class A
{
public:A(int a, int b):_a(a), _b(b){cout << "构造" << endl;}A(const A& a){cout << "拷贝构造" << endl;}void Print(){cout << _a << ' ' << _b << endl;}
private:int _a;int _b;
};
void test10()
{list<A> l1;list<A> l2;l1.emplace_front(1,2);//直接构造并成为l1的元素//l2.push_front(1, 2);//errorl2.push_front({ 2,3 });//先隐式类型转换构造,再拷贝构造成l2的元素(*l1.begin()).Print();(*l2.begin()).Print();
}
2.12 push_front()
在容器第一个元素前插入一个元素, val的内容被拷贝(或移动)到插入的元素。
push_back()执行拷贝行为还是移动行为取决于传递给它的参数类型,传递左值触发拷贝构造函数执行拷贝行为,传递右值触发移动构造函数执行移动操作。
(在 C++ 中,左值(lvalue)和 右值(rvalue)是用来区分表达式的值在内存中的存在方式和生命周期的术语。)
移动构造函数是 C++11 引入的一种特殊构造函数,旨在高效地转移资源的所有权,而不是进行昂贵的复制。它通过移动语义来减少不必要的拷贝,提高程序性能。
移动构造函数通常在以下情况下被调用:
- 对象临时生命周期结束后:当你通过一个临时对象(右值)初始化另一个对象时,移动构造函数会被调用。
- 使用
std::move
:当你显式调用std::move
函数将一个对象转换为右值引用时,会触发移动构造函数。
2.13 pop_front()
用于删除第一个元素
2.14 emplace_back()
在末尾构造并插入元素
2.15 push_back()
在最后一个元素之后插入一个元素,val的内容被拷贝(或移动)到新元素。
2.16 pop_back()
删除最后一个元素
2.17 emplace()
在pos位置的元素之前构造并插入一个元素
2.18 insert()
在指定位置的元素之前插入新元素(一个或多个)
void test13()
{list<int> l1 = {1,2,3};vector<int> v = { 444,555,666,777 };auto it = l1.begin();//iterator insert (const_iterator position, const value_type& val);l1.insert(it, 0);PrintList(l1);//iterator insert (const_iterator position, size_type n, const value_type& val);it++;l1.insert(it, 3, 6);PrintList(l1);//template <class InputIterator>//iterator insert(const_iterator position, InputIterator first, InputIterator last);l1.insert(l1.begin(), v.begin() + 2, v.end());PrintList(l1);//iterator insert (const_iterator position, value_type&& val);//右值引用l1.insert(it, 9);PrintList(l1);//iterator insert(const_iterator position, initializer_list<value_type> il);l1.insert(it, { 11,12,13,14,15 });PrintList(l1);
}
2.19 erase()
从容器中删除一个或者范围内的元素
2.20 swap()
交换两个容器的内容
2.21 resize()
调整容器的有效元素的个数,使其包含n个元素
n小于当前个数,元素将减少到前n个,超出部分删除并销毁
n大于当前个数,元素将尾插至n个,如果指定了val,则新元素初始化为val的副本
class A
{
public:A(){cout << "无参构造" << endl;}A(int a, int b):_a(a), _b(b){cout << "有参构造" << endl;}A(const A& a){cout << "拷贝构造" << endl;}void Print(){cout << _a << ' ' << _b << endl;}
private:int _a;int _b;
};void test14()
{list<A> l1;l1.resize(2, {1,1});cout << "------" << endl;l1.resize(5);//3个无参构造
}
2.22 clear()
删除容器的所有内容
2.23 splice()
用于将元素从一个list转移到另一个list(转移:将元素从原来的list移除,原封不动转移到另一个list)
2.24 remove()
从容器中删除所有与val相同的元素
2.25 remove_if()
用于从容器中移除满足特定条件的元素
remove_if()传入一个谓语函数(返回bool值的函数)
2.26 unique()
删除相邻val值重复的元素
需要给整个容器去重:先排序,再unique
2.27 merge()
用于将两个已排序的列表合并成一个排序后的列表。需要注意的是,两个列表必须都是按顺序排列的。合并操作会通过移动节点来达成,而不需要额外分配内存。
两个列表的排序方式需要与合并成一个列表后的排序方式保持一致(即两个升序列表合并成一个升序列表,两个降序列表合并成一个降序列表)
2.28 sort()
sort() 函数会对列表中的元素进行升序排序。默认情况下,它使用元素的 < 运算符进行比较, 也可以提供一个自定义的比较函数。
list的sort()底层是基于归并排序实现的,归并排序在处理链表时的性能非常优越,因为它可以高效地操作节点而不需要额外的空间消耗。归并排序通过分割链表和合并已排序的部分,能够快速地处理链表的节点。
2.29 reverse()
用于反转list
2.30 关系运算符重载(非成员函数)
3.operator->()
为了可读性,强制剩下一个->
4. 部分模拟实现
#pragma once
#include<assert.h>namespace myList
{template<class T>struct ListNode{ListNode<T>* _next;ListNode<T>* _prev;T _data;ListNode(const T& data = T()):_next(nullptr),_prev(nullptr),_data(data){}};template<class T, class Ref, class Ptr>struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;Node* _node;ListIterator(Node* node):_node(node){}// ++it;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;}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& it){return _node != it._node;}bool operator==(const Self& it){return _node == it._node;}};//template<class T>//class ListConstIterator//{// typedef ListNode<T> Node;// typedef ListConstIterator<T> Self;// Node* _node;//public:// ListConstIterator(Node* node)// :_node(node)// {}// // ++it;// 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;// }// //*it// const T& operator*()// {// return _node->_data;// }// const T* operator->()// {// return &_node->_data;// }// bool operator!=(const Self& it)// {// return _node != it._node;// }// bool operator==(const Self& it)// {// return _node == it._node;// }//};template<class T>class list{typedef ListNode<T> Node;public:// 不符合迭代器的行为,无法遍历//typedef Node* iterator;//typedef ListIterator<T> iterator;//typedef ListConstIterator<T> const_iterator;typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T*> const_iterator;iterator begin(){//iterator it(_head->_next);//return it;return iterator(_head->_next);}const_iterator begin() const{return const_iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator end() const{return const_iterator(_head);}void empty_init(){_head = new Node();_head->_next = _head;_head->_prev = _head;}list(){empty_init();}list(initializer_list<T> il){empty_init();for (const auto& e : il){push_back(e);}}// lt2(lt1)list(const list<T>& lt){empty_init();for (const auto& e : lt){push_back(e);}}// lt1 = lt3list<T>& operator=(list<T> lt){swap(_head, lt._head);return *this;}~list(){clear();delete _head;_head = nullptr;}void clear(){auto 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 pop_back(){erase(--end());}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}// 没有iterator失效iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* newnode = new Node(x);Node* prev = cur->_prev;// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);}// erase 后 pos失效了,pos指向节点被释放了iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;return iterator(next);}private:Node* _head;};
}