Table of contents
1. Introduction and use of list
1.2.1 Construction, destruction and assignment operator overloading
2. In-depth analysis and simulation implementation of list
2.1.1 Iterator and const iterator templating
2.2 Simulation implementation of list
2.3 Comparison between list and vector
1. Introduction and use of list
1.1 Introduction to list
Original English document
list - C++ Reference (cplusplus.com) https://legacy.cplusplus.com/reference/list/list/?kw=list Chinese introduction (the translation is not accurate, please understand it in conjunction with the document)
1. A list is a sequential container that can be inserted and deleted at any position within a constant range, and the container can be iterated in both directions. 2. The bottom layer of the list is a
doubly linked list structure. Each element in the doubly linked list is stored in an independent independent In the node, the pointer points to the previous element and the next element in the node (for details, please refer to other blogs)
3. List is very similar to forward_list: the main difference is that forward_list is a singly linked list and can only be unidirectional. Iteration is simpler and more efficient
4. Compared with other sequential containers (array, vector, deque), list usually has better execution efficiency for inserting and removing elements at any position. 5. Compared with other sequential
containers, list The biggest flaw with forward_list is that it does not support random access at any position. For example: to access the 6th element of the list, you must iterate from a known position (such as the head or tail) to that position. This process requires linear time overhead.
List of member types1.2 Use of list
1.2.1 Construction, destruction and assignment operator overloading
Constructor introduction:
(1) empty container constructor (default constructor) no parameter construction
Constructs an empty container, with no elements.
(2) fill constructor fill structure
Constructs a container with n elements. Each element is a copy of val.
(3) range constructor range construction
Constructs a container with as many elements as the range [first,last), with each element constructed from its corresponding element in that range, in the same order.
(4) copy constructor copy constructor
Constructs a container with a copy of each of the elements in x, in the same order.
注意:所有测试均在 VS2022 下进行 // constructing lists #include <iostream> #include <list> using namespace std; int main() { // constructors used in the same order as described above: list<int> lt1; // empty list of ints list<int> lt2(4, 100); // four ints with value 100 list<int> lt3(lt2.begin(), lt2.end()); // iterating through second list<int> lt4(lt3); // a copy of third // list<int> lt4 = lt3; int array[] = { 16,9,7,49,55,248 }; list<int> lt5(array, array + sizeof(array) / sizeof(int)); cout << "The contents of lt5 are: "; list<int>::iterator it = lt5.begin(); while (it != lt5.end()) { cout << *it << ' '; it++; } cout << endl; return 0; } output: The contents of lt5 are: 16 9 7 49 55 248
1.2.2 Iterators
1. begin and end are forward iterators, perform ++ operations on the iterator, and the iterator moves backward
2. rbegin(end) and rend(begin) are reverse iterators, perform ++ operations on the iterator, and iterate To move the iterator forward,
you just need to learn to use these four iterator functions.
1.2.3 Capacity
Old friends empty and size don’t need to be introduced too much.
1.2.4 Element access:
int main() { // list front list<int> list1; list1.push_back(13); list1.push_back(14); // now front equals 13, and back 14 list1.front() -= list1.back(); cout << "list1.front() is now " << list1.front() << endl; // // list back list<int> list2; list2.push_back(10); while (list2.back() != 0) { list2.push_back(list2.back() - 1); } cout << "list2 contains:"; list<int>::iterator it = list2.begin(); while (it != list2.end()) { cout << ' ' << *it; ++it; } cout << endl; return 0; } output: list1.front() is now -1 list2 contains: 10 9 8 7 6 5 4 3 2 1 0
1.2.5 Modifiers
int main() { list<int> lt1; lt1.push_back(1); lt1.push_back(2); lt1.push_back(3); lt1.push_front(4); lt1.push_front(5); lt1.push_front(6); list<int>::iterator it = lt1.begin(); it++; lt1.insert(it, 10); lt1.pop_back(); lt1.pop_front(); it++; lt1.erase(it); for (auto e : lt1) { cout << e << " "; } cout << endl; return 0; } output: 10 5 1 2
Just pay attention to a few functions with relatively complex function interfaces.
1.2.6 Operations
int main() { list<int> lt1; lt1.push_back(1); lt1.push_back(3); lt1.push_back(2); lt1.push_back(5); lt1.push_back(4); list<int> lt2; list<int>::iterator it2 = lt2.begin(); lt2.splice(it2, lt1); // 此时lt1已经为空了 for (auto e : lt2) { cout << e << " "; } cout << endl; lt2.reverse(); for (auto e : lt2) { cout << e << " "; } cout << endl; lt2.sort(); for (auto e : lt2) { cout << e << " "; } cout << endl; lt2.remove(5); for (auto e : lt2) { cout << e << " "; } cout << endl; lt2.push_back(4); lt2.push_back(4); lt2.push_back(4); lt2.unique(); for (auto e : lt2) { cout << e << " "; } cout << endl; list<int> lt3; lt3.push_back(9); lt3.push_back(8); lt3.push_back(7); lt3.push_back(6); lt2.sort(), lt3.sort(); // 注意先排序再合并 lt3.merge(lt2); // 此时lt2已经为空了 for (auto e : lt3) { cout << e << " "; } cout << endl; return 0; } output: 1 3 2 5 4 4 5 2 3 1 1 2 3 4 5 1 2 3 4 1 2 3 4 1 2 3 4 6 7 8 9
1.2.7 Non-member functions
2. In-depth analysis and simulation implementation of list
2.1 In-depth analysis of list
2.1.1 Iterator and const iterator templating
template<class T, class Ref, class Ptr> //普通正向迭代器 <T, T&, T*> //const正向迭代器 <T, const T&, const T*> struct __list_iterator { typedef list_node<T> Node; typedef __list_iterator<T, Ref, Ptr> itor; Node* _node; __list_iterator(Node* node) :_node(node) {} itor& operator++() { _node = _node->_next; return *this; } itor& operator--() { _node = _node->_prev; return *this; } itor operator++(int) // 前置++ { itor tmp(*this); _node = _node->_next; return tmp; } itor operator--(int) // 前置-- { itor tmp(*this); _node = _node->_prev; return tmp; } Ref operator*() // 引用 { return _node->_data; } Ptr operator->() // 指针 { return &_node->_data; } bool operator==(const itor& it) { return _node == it._node; } bool operator!=(const itor& it) { return _node != it._node; } };
2.1.2 Reverse iterator
template<class Iterator> class ReverseListIterator { // 注意:此处typename的作用是明确告诉编译器,Ref是Iterator类中的类型,而不是静态成员变量 // 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量 // 因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的 public: typedef typename Iterator::Ref Ref; typedef typename Iterator::Ptr Ptr; typedef ReverseListIterator<Iterator> Self; public: // // 构造 ReverseListIterator(Iterator it) : _it(it) {} // // 具有指针类似行为 Ref operator*() { Iterator temp(_it); --temp; return *temp; } Ptr operator->() { return &(operator*()); } // // 迭代器支持移动 Self& operator++() { --_it; return *this; } Self operator++(int) { Self temp(*this); --_it; return temp; } Self& operator--() { ++_it; return *this; } Self operator--(int) { Self temp(*this); ++_it; return temp; } // // 迭代器支持比较 bool operator!=(const Self& l)const { return _it != l._it; } bool operator==(const Self& l)const { return _it != l._it; } Iterator _it; };
2.2 Simulation implementation of list
namespace mylist { template<class T> struct list_node { T _data; list_node<T>* _prev; list_node<T>* _next; list_node(const T& x = T()) :_data(x) ,_prev(nullptr) ,_next(nullptr) {} }; // 迭代器 与 const迭代器 //template<class T> template<class T, class Ref, class Ptr> struct __list_iterator { typedef list_node<T> Node; typedef __list_iterator<T, Ref, Ptr> itor; Node* _node; __list_iterator(Node* node) :_node(node) {} itor& operator++() { _node = _node->_next; return *this; } itor& operator--() { _node = _node->_prev; return *this; } itor operator++(int) // 前置++ { itor tmp(*this); _node = _node->_next; return tmp; } itor operator--(int) // 前置-- { itor tmp(*this); _node = _node->_prev; return tmp; } Ref operator*() { return _node->_data; } Ptr operator->() { return &_node->_data; } bool operator==(const itor& it) { return _node == it._node; } bool operator!=(const itor& it) { return _node != it._node; } }; // 反向迭代器 template<class Iterator, class Ref, class Ptr> struct Reverse_iterator { Iterator _it; typedef Reverse_iterator<Iterator, Ref, Ptr> Self; Reverse_iterator(Iterator it) :_it(it) {} Ref operator*() { Iterator tmp = _it; return *(--tmp); } Ptr operator->() { return &(operator*()); } Self& operator++() { --_it; return *this; } Self& operator--() { ++_it; return *this; } bool operator!=(const Self& s) { return _it != s._it; } }; 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; typedef Reverse_iterator<iterator, T&, T*> reverse_iterator; typedef Reverse_iterator<const_iterator, const T&, const <T*>const_reverse_iterator; iterator begin() { return _head->_next; } iterator end() { return _head; } const_iterator begin() const { return const_iterator(_head->_next); } const_iterator end() const { return const_iterator(_head); } const_reverse_iterator rbegin() const { // list_node<int>* return const_reverse_iterator(end()); } const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } reverse_iterator rbegin() { return reverse_iterator(end()); } reverse_iterator rend() { return reverse_iterator(begin()); } void empty_init() { _head = new Node; _head->_next = _head; _head->_prev = _head; _size = 0; } list() { empty_init(); } // list(list<T>& lt) list(const list<T>& lt) { empty_init(); for (auto e : lt) { push_back(e); } } /*list<int>& operator=(const list<int>& lt) { if (this != <) { clear(); for (auto e : lt) { push_back(e); } } return *this; }*/ void swap(list<T>& lt) { std::swap(_head, lt._head); std::swap(_size, lt._size); } list<int>& operator=(list<int> lt) { swap(lt); return *this; } ~list() { clear(); delete _head; _head = nullptr; _size = 0; } void clear() { iterator it = begin(); while (it != end()) { it = erase(it); } } 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()); } T& front() { return _head->_next->_data; } const T& front()const { return _head->_next->_data; } T& back() { return _head->_prev->_data; } const T& back()const { return _head->_prev->_data; } size_t size() const { return _size; } bool empty() const { return _size == 0; } iterator insert(iterator pos, const T& x) { Node* cur = pos._node; Node* newnode = new Node(x); Node* prev = cur->_prev; prev->_next = newnode; newnode->_prev = prev; newnode->_next = cur; cur->_prev = newnode; ++_size; return iterator(newnode); } iterator erase(iterator pos) { Node* cur = pos._node; Node* prev = cur->_prev; Node* next = cur->_next; delete cur; prev->_next = next; next->_prev = prev; --_size; return iterator(next); } private: Node* _head; size_t _size; }; }
2.3 Comparison between list and vector
vector list
underlying structure Dynamic sequence list, a continuous space Bidirectional circular linked list with head node random access Supports random access, and the efficiency of accessing a certain element is O(1) Random access is not supported, and
the efficiency of accessing an element is O(N)Insertion and deletion Inserting and deleting at any position is inefficient and requires moving elements. The time complexity
is O(N). Capacity expansion may be required during insertion. Capacity expansion: open up new space
, copy elements, and release old space, resulting in lower efficiency.Insertion and deletion at any position is highly efficient, no
elements need to be moved, and the time complexity is
O(1)Space utilization The bottom layer is continuous space, which is not easy to cause memory fragmentation, has
high space utilization and high cache utilization.The underlying nodes are dynamically opened, and small nodes are prone
to memory fragmentation, low space utilization, and
low cache utilization.Iterator Original ecological pointer Encapsulate the original ecological pointer (node pointer) Iterator invalid When inserting elements, all iterators must be reassigned, because inserting
elements may cause re-expansion, causing the original iterators to become invalid.
When deleting, the current iterator needs to be reassigned, otherwise it will become invalid.Inserting an element will not cause the iterator to become invalid.
When an element is deleted, it will only cause the current iterator
to become invalid. Other iterators will not be affected.scenes to be used Need efficient storage, support random access, do not care about insertion and deletion efficiency Lots of insert and delete operations, don't care about random
access