The greatest advantage of hard work is that you can choose the life you want instead of being forced to let it be.
π list introduction
1. List is a sequential container that can be inserted and deleted anywhere in the * * constant range O(1) * *, and the container can iterate back and forth, but the list container is not suitable for sorting.
2. The bottom layer of list is a circular two-way linked list structure. Each element in the two-way linked list is stored in independent nodes that are not related to each other. In the node, the pointer points to the previous element and the latter element.
π Constructor
In C++98, the list constructor plus the copy constructor are the following four. Let's simulate and implement all the constructors here. Of course, we do not use the space configurator to create a space like the standard library. We use the new operator, but the principle is to create a new space.
π± π» non-parameter constructor
Here, we simulate the default constructor. The default value is T(), which means that the default value of this type is 0, and the char type is' \ 0 '. The user-defined type will call its constructor to initialize this anonymous object to get the default value.
list() //Parameterless default constructor { this->m_head = new node(T()); this->m_head->m_next = this->m_head; //Create a head node and connect the head and tail of the head node to form a circular structure this->m_head->m_prev = this->m_head; }
π Parameterized constructor
list(size_t size, const T& val=T()) //n element constructors { this->m_head = new node(T()); //Create a head node and connect the head and tail of the head node to form a circular structure this->m_head->m_next = this->m_head; this->m_head->m_prev = this->m_head; while (size--) { this->push_back(val); } }
be careful:
- 1. The constructor knows that it needs space for storing n data, so we use the new operator to open up the space, and then use push_ The back() function inserts the val value.
- 2. The constructor also needs to implement two overloaded functions. If the overloaded function is not implemented, the compiler will call the template interval constructor we want to simulate below when we originally want to call n element constructors.
list(long size, const T& val = T()) //n element constructors { assert(size > 0); this->m_head = new node(T()); //Create a head node and connect the head and tail of the head node to form a circular structure this->m_head->m_next = this->m_head; this->m_head->m_prev = this->m_head; while (size--) { this->push_back(val); } } list(int size, const T& val = T()) //n element constructors { assert(size > 0); this->m_head = new node(T()); //Create a head node and connect the head and tail of the head node to form a circular structure this->m_head->m_next = this->m_head; this->m_head->m_prev = this->m_head; while (size--) { this->push_back(val); } }
It can be seen that the difference between these two overloaded functions is that their parameter size types are different, but this is necessary. Otherwise, when we use the following code, the compiler will preferentially match the template interval constructor.
ZJ::list<int>L(2,1); //The overloaded version is not implemented and will call the template interval constructor
In addition, an error is reported because the constructor 2 refers to the parameters first and last (*) (but the int type cannot be dereferenced).
π Template interval constructor
Finally, the template interval constructor we have been talking about above. Because the iterator interval can be the iterator interval of other containers, and the type of iterator received by the function is uncertain, we need to design the constructor as a function template and insert the data of the iterator interval into the container one by one in the function body.
template <class InputIterator> list(InputIterator first, InputIterator last) //Interval structure { this->m_head = new node(T()); //Create header node this->m_head->m_next = this->m_head; this->m_head->m_prev = this->m_head; while (first != last) { node* newnode = new node(*first); node* tail = this->m_head->m_prev; tail->m_next = newnode; newnode->m_prev = tail; newnode->m_next = this->m_head; this->m_head->m_prev = newnode; ++first; } }
be careful:
- 1. The interval must be closed before open [obj.begin(), obj.end());
π copy constructor
The idea of copy construction is easy for us to think of. It is to traverse the obj linked list of the object we want to copy, give the value of each obj element to each corresponding node of this object, and link each node.
list(const list<T>& obj) //copy constructor { this->m_head = new node(T()); //Create a head node and connect the head and tail of the head node to form a circular structure this->m_head->m_next = this->m_head; this->m_head->m_prev = this->m_head; const_iterator it = obj.begin(); while (it != obj.end()) { node* newnode = new node(it.m_pnode->m_val); //Create a new node and assign a value to it node* tail = this->m_head->m_prev; tail->m_next = newnode; newnode->m_prev = tail; newnode->m_next = this->m_head; this->m_head->m_prev = newnode; ++it; } }
π Assignment operator overload
According to the history of our previous blog, here we use modern writing methods, call the copy constructor with obj to create a temporary object temp, and exchange the temp with the pointer of the object pointed to by our this pointer.
list<T>& operator=(const list<T>& obj) //Assignment operator overload { if (this != &obj) //Prevent self assignment { list<T>temp(obj); this->swap(temp); } return *this; //Return to yourself }
be careful:
- 1. When the above temp temporary object has an if statement, it will automatically call the destructor to release memory, so it will not cause memory leakage and other problems.
π Destructor
Destructor here, I'm a little lazy. I reuse the pop we want to simulate and implement below_ The general idea of the front () function is to delete nodes one by one from beginning to end, and delete the head nodes to empty.
~list() //Destructor { iterator it = this->begin(); while (it != this->end()) //Reuse the header deletion we write, delete one by one, of course, you can also reuse the tail deletion pop_back() and erase() { ++it; this->pop_front(); } delete this->m_head; this->m_head = nullptr; }
π iterator
In C + +, our iterators are as follows:
Next, I'll just simulate the begin() and end() iterators.
iterator begin() { return iterator(this->m_head->m_next); } const_iterator begin() const { return const_iterator(this->m_head->m_next); } iterator end() { return iterator(this->m_head); } const_iterator end() const { return const_iterator(this->m_head); }
Above, we simulated and implemented our iterator, and there are ordinary iterators and const iterators. But we also need to understand the principle of iterators. In our previous blog, we said that not every iterator is a native pointer, and our list encapsulates a pointer variable to achieve the result of implementing iterator.
typedef list_iterator<T,T&,T*> iterator; //Ordinary iterator typedef list_iterator<T,const T&,const T*> const_iterator; //Constant iterator
Some people may wonder that the above two iterators are not the same, but their names are different. Why not directly add const to the type, but specify the const attribute on the template parameter? We should know that const objects can only call constant functions, but can we support + +, -- when we use std::list? If it is a const object, it can only call constant functions. Once it becomes a const function, our const iterator cannot perform + +, –, '*' and so on. The effect we want to achieve is that we can perform + +, – and so on, but we can only not the value of its elements. So we encapsulate a template pointer here. We control the reading operation of the template through different parameters.
πΌ Iterator constructor
Is to pass a pointer and assign it to our member variable m_pnode.
list_iterator(node* pnode) :m_pnode(pnode) {}
π Iterator relational operator overloading
Because we want to implement the iterator related traversal operations, such as the following code:
ZJ::list<int>::iterator it = L1.begin();
it != L1.end()
bool operator!=(const myself&obj) const //Reload= number { return this->m_pnode != obj.m_pnode; } bool operator==(const myself& obj) const //Overload = = number { return this->m_pnode == obj.m_pnode; }
π§Έ Iterator + + -- operator overloading
The following returned myself type is actually the type of our iterator class, but we typedef it. The code is as follows:
typedef list_iterator<T, Ref, Ptr>myself;
Here, the pre and post overloads should be separated. The post needs to use an int placeholder to occupy the position, otherwise an error will occur.
myself& operator++() //Overload front++ { //Since our list is a two-way circular linked list, our + +, is to point to the next node this->m_pnode = this->m_pnode->m_next; return *this; } myself operator++(int) //Heavy load post++ { const myself temp(*this); this->m_pnode = this->m_pnode->m_next; return temp; } myself& operator--() //Overload front-- { //Because our list is a two-way circular linked list, our -- is to get the iterator of the previous node this->m_pnode = this->m_pnode->m_prev; return *this; } myself operator--(int) //Heavy load post-- { const myself temp(*this); this->m_pnode = this->m_pnode->m_prev; return temp; }
π½ Iterator * operator overload
Because we know that Ref is composed of two types, one is T &, and the other is const T &, when our object is const object, we can control it not to modify it.
Ref operator* () //Heavy load* { return this->m_pnode->m_val; }
πͺ Iterator - > operator overload
Why overload the - > operator? This is because if we store custom types in the list, our iterator cannot use - > to get its members.
Ptr operator->() //Overload - > { return &this->m_pnode->m_val; }
π± π summary
At this point, someone may ask why we don't write copy constructors and destructors of iterators?
A: This is because our iterator is used to traverse some or all of the elements in the container. Each iterator object represents the determined address in the container, and the decomposition and copy of these node elements are not under our control. The nodes should be under our list control, so the shallow copy provided by the compiler by default is enough.
π Capacity correlation function
π±βπempty()
- Function: used to obtain whether the container is empty.
- Return value: bool.
bool empty() const //Air judgment { return this->begin() == this->end(); //This is the case when begin() and end() point to the header node at the same time }
πsize()
- Function: it is used to obtain the number of elements.
- Return value: size_ T (unsigned integer).
- Note: however, this interface is not commonly used in linked lists. If the interface is called frequently, it will cause performance degradation.
//Traverse the linked list to get the number of elements. Why use traverse the linked list? //This is because we seldom know the number of elements when using the linked list, but if we call the interface frequently, the performance will be degraded //At this point, we should add a variable size to record the number of elements in the list class //If there are elements, the insertion will increase and the deletion will decrease size_t size() const //Get the number of elements { size_t size = 0; const_iterator it = this->begin(); while(it!=this->end()) { ++it; ++size; } return size; }
π Element access related functions
πback()
- Function: get the value of the last element.
- Return value: a reference to the stored type.
T& back() { assert(!this->empty()); return this->m_head->m_prev->m_val; } const T& back() const { assert(!this->empty()); return this->m_head->prev->m_val; }
πfront()
- Function: get the value of the first element.
- Return value: a reference to the stored type.
T& front() { assert(!this->empty()); return this->m_head->m_next->m_val; } const T& front() const { assert(!this->empty()); return this->m_head->m_next->m_val; }
π Modify correlation function
π₯push_back()
- Function: insert an element at the end.
- Return value: void (no return value).
void push_back(const T& val) //Tail insertion { node* tail = this->m_head->m_prev; //Point to tail node node* newnode = new node(val); //Create a new node newnode->m_next = tail->m_next; //M of the new node_ Next points to the header node newnode->m_prev = tail; //Add new node m_prev points to the tail node tail->m_next = newnode; //And the m of the original tail node_ Next points to newnode this->m_head->m_prev = newnode; //And the m of the head node_ Prev points to the new tail }
π±βπpush_front()
- Function: insert an element in the header.
- Return value: void (no return value).
void push_front(const T& val) //Head insert { node* newnode = new node(val); //Create a new node node* next = this->m_head->m_next; //Point to the first node newnode->m_next = next; //M of the new node to be created_ Next points to our original first element, next this->m_head->m_next = newnode; //M of our head node_ Next points to the newly created node newnode->m_prev = this->m_head; //M of the new node_ Prev points to the header node next->m_prev = newnode; //The original m of the first node element_ Prev points to the newly created node }
π©pop_back()
- Function: delete an element at the end.
- Return value: void (no return value).
void pop_back() //Tail deletion { assert(!empty()); //Assert that if the list is already empty, it cannot be deleted node* tail = this->m_head->m_prev; //First find the tail node we want to delete node* prev = tail->m_prev; //Then find the previous node of the tail node to be deleted this->m_head->m_prev = prev; //M of the head node_ Prev points to the new tail prev prev->m_next = this->m_head; //Then m of prev_ Next points to the header node tail->m_next = tail->m_prev = nullptr; //Add the member of the element to be deleted to nullptr tail->m_val = T(); //Set the value of the element to be deleted as the default value of the anonymous object delete tail; //Delete tail node }
π±βπpop_front()
- Function: delete an element in the header.
- Return value: void (no return value).
void pop_front() //Header deletion { assert(!empty()); //Assert that if the list is already empty, it cannot be deleted node* delnode = this->m_head->m_next; //First find the first node we want to delete node* next = delnode->m_next; //Find the location of the next node to delete the node this->m_head->m_next = next; //Then m of the head node_ Next points to our new first node next->m_prev = this->m_head; //And then we put the m of our new first node_ Prev points to the header node delnode->m_next = delnode->m_prev = nullptr; //Add the member of the element to be deleted to nullptr delnode->m_val = T(); //Set the value of the element to be deleted as the default value of the anonymous object delete delnode; //Delete the first node }
πinsert()
In c++98, our insert() function has the following three versions:
Let's simulate it:
Inserts an element at the specified location
- Function: insert an element at the specified position.
- Return value: iterator of the position of the inserted element.
//Insert the element to the specified location and return the iterator of the inserted element iterator insert(iterator pos, const T& val) { assert(pos.m_pnode != nullptr); //Error asserting whether iterator is a null pointer node* newnode = new node(val); //Create a new node node* cur = pos.m_pnode; //Record the pointer of the current node node* prev = cur->m_prev; //Record the pointer of the previous node of the current node newnode->m_next = cur; newnode->m_prev = prev; prev->m_next = newnode; cur->m_prev = newnode; return iterator(newnode); //Returns an iterator of an anonymous object constructed from the node of the currently inserted element }
Insert n identical elements at the specified location
- Function: insert an element at the specified position.
- Return value: void (no return value).
void insert(iterator pos, size_t n, const T& val) //Insert n Vals { assert(pos.m_pnode != nullptr); //Error asserting whether iterator is a null pointer while (n--) { node* newnode = new node(val); node* cur = pos.m_pnode; node* prev = cur->m_prev; newnode->m_prev = prev; prev->m_next = newnode; newnode->m_next = cur; cur->m_prev = newnode; } } void insert(iterator pos, int n, const T& val) //Insert n Vals { assert(pos.m_pnode != nullptr); //Error asserting whether iterator is a null pointer assert(n > 0); while (n--) { node* newnode = new node(val); node* cur = pos.m_pnode; node* prev = cur->m_prev; newnode->m_prev = prev; prev->m_next = newnode; newnode->m_next = cur; cur->m_prev = newnode; } } void insert(iterator pos, long n, const T& val) //Insert n Vals { assert(pos.m_pnode != nullptr); //Error asserting whether iterator is a null pointer assert(n > 0); while (n--) { node* newnode = new node(val); node* cur = pos.m_pnode; node* prev = cur->m_prev; newnode->m_prev = prev; prev->m_next = newnode; newnode->m_next = cur; cur->m_prev = newnode; } }
Note: the insert here is inserted at the specified position. Why do you want to implement three overloaded versions? This is the same type as the constructor problem above, which is related to the construction of template interval, so I won't go into details here.
Inserts an element in the interval at the specified position
- Function: insert elements in the interval at the specified position.
- Return value: void (no return value).
- be careful:
-
- 1. The section must be closed before opening;
template <class InputIterator> void insert(iterator pos, InputIterator first, InputIterator last) //Interval insertion { assert(pos.m_pnode != nullptr); //Error asserting whether iterator is a null pointer while (first != last) { node* newnode = new node(*first); node* cur = pos.m_pnode; node* prev = cur->m_prev; newnode->m_prev = prev; prev->m_next = newnode; newnode->m_next = cur; cur->m_prev = newnode; ++first; } }
πerase()
There are two versions of erase() in C++98. One is to delete the specified position, and the other is to delete the elements in the interval. As shown in the figure below:
Delete specified location
- Function: delete the element at the specified position.
- Return value: deletes the iterator of the next node of the element at the specified location.
//Delete the element at the specified position and return the iterator of the next element, but note that: //If the last element is deleted, the head node is returned, that is, the iterator at end() iterator erase(iterator pos) { assert(pos.m_pnode != nullptr); //Error asserting whether iterator is a null pointer assert(pos != end()); //Assert that the element in the list is empty and delete the header node node* next = pos.m_pnode->m_next; node* prev = pos.m_pnode->m_prev; prev->m_next = next; next->m_prev = prev; pos.m_pnode->m_next = pos.m_pnode->m_prev = nullptr; pos.m_pnode->m_val = T(); delete pos.m_pnode; return iterator(next); }
Delete elements within the interval
- Function: delete elements in the interval.
- Return value: the iterator that deletes the next node of the element in the interval. That is, return the last iterator.
- be careful:
-
- 1. The section must be closed before opening;
iterator erase(iterator first, iterator last) //Interval deletion { node* prev = first.m_pnode->m_prev; node* next = last.m_pnode; while (first != last) { node* cur = first.m_pnode; ++first; cur->m_next = cur->m_prev = nullptr; cur->m_val = T(); delete cur; cur = nullptr; } prev->m_next = next; next->m_prev = prev; return iterator(next); }
πΈclear()
- Function: used to clear the values of all elements in our list, but not to delete the list object.
- Return value: void (no return value).
void clear() //Empty elements instead of deleting the whole linked list { iterator it = this->begin(); while (it != this->end()) //Reuse the header deletion we write, delete one by one, of course, you can also reuse the tail deletion pop_back() and erase() { ++it; this->pop_front(); } }
π§¨swap()
- Function: swap, as the name suggests, means exchange. Here, we use this swap as our member function to exchange two list linked lists.
- Return value: void (no return value).
void swap(list<T>& obj) { node* temp = this->m_head; this->m_head = obj.m_head; obj.m_head = temp; }
π The complete code is as follows
#pragma once #define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<assert.h> using namespace std; namespace ZJ { template<class T> class list_node //node { public: list_node(T val=T()) //Constructor :m_val(val) ,m_prev(nullptr) ,m_next(nullptr) {} public: T m_val; //value list_node<T>* m_prev; //Pointer to previous list_node<T>* m_next; //Pointer to next }; template<class T,class Ref,class Ptr> //Encapsulate a node pointer and make it into an iterator class class list_iterator { public: typedef list_node<T> node; typedef list_iterator<T, Ref, Ptr>myself; list_iterator(node* pnode) :m_pnode(pnode) {} Ref operator* () //Heavy load* { return this->m_pnode->m_val; } Ptr operator->() //Overload - > { return &this->m_pnode->m_val; } bool operator!=(const myself&obj) const //Reload= number { return this->m_pnode != obj.m_pnode; } bool operator==(const myself& obj) const //Overload = = number { return this->m_pnode == obj.m_pnode; } myself& operator++() //Overload front++ { this->m_pnode = this->m_pnode->m_next; return *this; } myself operator++(int) //Heavy load post++ { const myself temp(*this); this->m_pnode = this->m_pnode->m_next; return temp; } myself& operator--() //Overload front-- { this->m_pnode = this->m_pnode->m_prev; return *this; } myself operator--(int) //Heavy load post-- { const myself temp(*this); this->m_pnode = this->m_pnode->m_prev; return temp; } public: node* m_pnode; }; template<class T> class list { public: typedef list_node<T> node; typedef list_iterator<T,T&,T*> iterator; //Ordinary iterator typedef list_iterator<T,const T&,const T*> const_iterator; //Constant iterator public: list() //Parameterless default constructor { this->m_head = new node(T()); this->m_head->m_next = this->m_head; this->m_head->m_prev = this->m_head; } list(size_t size, const T& val=T()) //n element constructors { this->m_head = new node(T()); this->m_head->m_next = this->m_head; this->m_head->m_prev = this->m_head; while (size--) { this->push_back(val); } } list(long size, const T& val = T()) //n element constructors { assert(size > 0); this->m_head = new node(T()); this->m_head->m_next = this->m_head; this->m_head->m_prev = this->m_head; while (size--) { this->push_back(val); } } list(int size, const T& val = T()) //n element constructors { assert(size > 0); this->m_head = new node(T()); this->m_head->m_next = this->m_head; this->m_head->m_prev = this->m_head; while (size--) { this->push_back(val); } } template <class InputIterator> list(InputIterator first, InputIterator last) //Interval structure { this->m_head = new node(T()); this->m_head->m_next = this->m_head; this->m_head->m_prev = this->m_head; while (first != last) { node* newnode = new node(*first); node* tail = this->m_head->m_prev; tail->m_next = newnode; newnode->m_prev = tail; newnode->m_next = this->m_head; this->m_head->m_prev = newnode; ++first; } } list(const list<T>& obj) //copy constructor { this->m_head = new node(T()); this->m_head->m_next = this->m_head; this->m_head->m_prev = this->m_head; const_iterator it = obj.begin(); while (it != obj.end()) { node* newnode = new node(it.m_pnode->m_val); node* tail = this->m_head->m_prev; tail->m_next = newnode; newnode->m_prev = tail; newnode->m_next = this->m_head; this->m_head->m_prev = newnode; ++it; } } list<T>& operator=(const list<T>& obj) //Assignment operator overload { if (this != &obj) { list<T>temp(obj); this->swap(temp); } return *this; } ~list() //Destructor { iterator it = this->begin(); while (it != this->end()) //Reuse the header deletion we write, delete one by one, of course, you can also reuse the tail deletion pop_back() and erase() { ++it; this->pop_front(); } delete this->m_head; this->m_head = nullptr; } iterator begin() { return iterator(this->m_head->m_next); } const_iterator begin() const { return const_iterator(this->m_head->m_next); } iterator end() { return iterator(this->m_head); } const_iterator end() const { return const_iterator(this->m_head); } bool empty() const //Air judgment { return this->begin() == this->end(); //This is the case when begin() and end() point to the header node at the same time } //Traverse the linked list to get the number of elements. Why use traverse the linked list? //This is because we seldom know the number of elements when using the linked list, but if we call the interface frequently, the performance will be degraded //At this point, we should add a variable size to record the number of elements in the list class //If there are elements, the insertion will increase and the deletion will decrease size_t size() const //Get the number of elements { size_t size = 0; const_iterator it = this->begin(); while(it!=this->end()) { ++it; ++size; } return size; } T& back() { assert(!this->empty()); return this->m_head->m_prev->m_val; } const T& back() const { assert(!this->empty()); return this->m_head->prev->m_val; } T& front() { assert(!this->empty()); return this->m_head->m_next->m_val; } const T& front() const { assert(!this->empty()); return this->m_head->m_next->m_val; } void push_front(const T& val) //Head insert { node* newnode = new node(val); node* next = this->m_head->m_next; newnode->m_next = next; this->m_head->m_next = newnode; newnode->m_prev = this->m_head; next->m_prev = newnode; } void push_back(const T& val) //Tail insertion { node* tail = this->m_head->m_prev; node* newnode = new node(val); newnode->m_next = tail->m_next; newnode->m_prev = tail; tail->m_next = newnode; this->m_head->m_prev = newnode; } //Insert the element to the specified location and return the iterator of the inserted element iterator insert(iterator pos, const T& val) { assert(pos.m_pnode != nullptr); //Error asserting whether iterator is a null pointer node* newnode = new node(val); //Create a new node node* cur = pos.m_pnode; //Record the pointer of the current node node* prev = cur->m_prev; //Record the pointer of the previous node of the current node newnode->m_next = cur; newnode->m_prev = prev; prev->m_next = newnode; cur->m_prev = newnode; return iterator(newnode); //Returns an iterator of an anonymous object constructed from the node of the currently inserted element } void insert(iterator pos, size_t n, const T& val) //Insert n Vals { assert(pos.m_pnode != nullptr); //Error asserting whether iterator is a null pointer while (n--) { node* newnode = new node(val); node* cur = pos.m_pnode; node* prev = cur->m_prev; newnode->m_prev = prev; prev->m_next = newnode; newnode->m_next = cur; cur->m_prev = newnode; } } void insert(iterator pos, int n, const T& val) //Insert n Vals { assert(pos.m_pnode != nullptr); //Error asserting whether iterator is a null pointer assert(n > 0); while (n--) { node* newnode = new node(val); node* cur = pos.m_pnode; node* prev = cur->m_prev; newnode->m_prev = prev; prev->m_next = newnode; newnode->m_next = cur; cur->m_prev = newnode; } } void insert(iterator pos, long n, const T& val) //Insert n Vals { assert(pos.m_pnode != nullptr); //Error asserting whether iterator is a null pointer assert(n > 0); while (n--) { node* newnode = new node(val); node* cur = pos.m_pnode; node* prev = cur->m_prev; newnode->m_prev = prev; prev->m_next = newnode; newnode->m_next = cur; cur->m_prev = newnode; } } template <class InputIterator> void insert(iterator pos, InputIterator first, InputIterator last) //Interval insertion { assert(pos.m_pnode != nullptr); //Error asserting whether iterator is a null pointer while (first != last) { node* newnode = new node(*first); node* cur = pos.m_pnode; node* prev = cur->m_prev; newnode->m_prev = prev; prev->m_next = newnode; newnode->m_next = cur; cur->m_prev = newnode; ++first; } } void pop_front() //Header deletion { assert(!empty()); //Assert that if the list is already empty, it cannot be deleted node* delnode = this->m_head->m_next; node* next = delnode->m_next; this->m_head->m_next = next; next->m_prev = this->m_head; delnode->m_next = delnode->m_prev = nullptr; delnode->m_val = T(); delete delnode; } void pop_back() //Tail deletion { assert(!empty()); //Assert that if the list is already empty, it cannot be deleted node* tail = this->m_head->m_prev; node* prev = tail->m_prev; this->m_head->m_prev = prev; prev->m_next = this->m_head; tail->m_next = tail->m_prev = nullptr; tail->m_val = T(); delete tail; } //Delete the element at the specified position and return the iterator of the next element, but note that: //If the last element is deleted, the head node is returned, that is, the iterator at end() iterator erase(iterator pos) { assert(pos.m_pnode != nullptr); //Error asserting whether iterator is a null pointer assert(pos != end()); //Assert that the element in the list is empty and delete the header node node* next = pos.m_pnode->m_next; node* prev = pos.m_pnode->m_prev; prev->m_next = next; next->m_prev = prev; pos.m_pnode->m_next = pos.m_pnode->m_prev = nullptr; pos.m_pnode->m_val = T(); delete pos.m_pnode; return iterator(next); } iterator erase(iterator first, iterator last) //Interval deletion { node* prev = first.m_pnode->m_prev; node* next = last.m_pnode; while (first != last) { node* cur = first.m_pnode; ++first; cur->m_next = cur->m_prev = nullptr; cur->m_val = T(); delete cur; cur = nullptr; } prev->m_next = next; next->m_prev = prev; return iterator(next); } void clear() //Empty elements instead of deleting the whole linked list { iterator it = this->begin(); while (it != this->end()) //Reuse the header deletion we write, delete one by one, of course, you can also reuse the tail deletion pop_back() and erase() { ++it; this->pop_front(); } } void swap(list<T>& obj) { node* temp = this->m_head; this->m_head = obj.m_head; obj.m_head = temp; } private: node* m_head; //Head pointer }; }
If there are any mistakes, please point them out. Thank you!!!
END...