Table of contents
Preface
In this article, we continue to learn about containers in STL. This article will explain lists.
1. Introduction and use of list
1.1 list introduction
listdocuments
The underlying implementation of list is the headed two-way circular linked list learned from the data structure:
1.2 List usage
Let’s take a look at some commonly used interfaces:
First, let’s take a look at the constructor:
here are the ones we are familiar with, including the default constructor, n val constructor, iterator interval constructor and copy constructor.
Let’s take a look at iterators again:
I believe the previous article has introduced iterators in detail so we won’t go into details here.
Let’s take a look at the modification operations:
The difference between list, string and vector here is that
list does not have overloading [], which means that if we want to traverse and access the list, we can only use iterators.Iterators are the universal method. All containers can use iterators, but they[]
are only special methods for specific containers.
Traverse
int main()
{
list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_back(3);
l.push_back(5);
for (auto it = l.begin(); it != l.end(); ++it)
cout << *it << " ";
cout << endl;
for (auto e : l)
cout << e << " ";
cout << endl;
for (auto rit = l.rbegin(); rit != l.rend(); ++rit)
cout << *rit << " ";
cout << endl;
return 0;
}
Let's take a look at a few interfaces that have not been used much before:
heresplice
, it can transfer part of the linked list to another linked list:
remove
that is, delete the specified element:
merge
you can merge two ordered linked lists:
2. List simulation implementation
2.1 Classification of iterator functions
1. One-way iterator: only possible
++
, not possible- -
. For example, singly linked list, hash table;
2. Bidirectional iterator:++
both can be used--
. For example, a doubly linked list;
3. Random access iterator: Yes++
- -
, it can also be+
summed-
. For example vector and string.
The iterator is a built-in type (inner class or defined in the class)
2.2 List iterator simulation implementation
2.2.1 Ordinary iterators
When simulating the implementation of string and vector, native pointers are used, and iterators are not encapsulated with classes. However, the STL standard library also encapsulates iterators with classes. When simulating the implementation of a list iterator, native pointers cannot be used because the node addresses of the list are discontinuous.
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 operator*()
{
return _node->_val;
}
Ptr operator->()
{
return &_node->_val;
}
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;
}
};
2.2.2 const iterator
Let’s take a look at the incorrect writing first:
在这里插入代码片typedef __list_iterator<T> iterator; const list<T>::iterator it=lt.begin();
In the STL library, this is achieved by giving one more parameter to the class template. In this way, the same class template can generate two different types of iterators.
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 _head->_next;
return iterator(_head->_next);
}
iterator end()
{
return _head;
//return iterator(_head);
}
const_iterator begin() const
{
//return _head->_next;
return const_iterator(_head->_next);
}
const_iterator end() const
{
return _head;
//return const_iterator(_head);
}
3. The difference 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 need to move elements, 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. Small nodes are prone to memory fragmentation, low space utilization, and low cache utilization. |
Iterator | Original ecological pointer | Encapsulate the original ecological 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. | Bidirectional circular linked list with head node |
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 |
4. Source code
list.h
#include<iostream>
using namespace std;
namespace w
{
template<class T>
struct list_node
{
list_node<T>* _next;
list_node<T>* _prev;
T _val;
list_node(const T& val = T())
:_next(nullptr)
, _prev(nullptr)
, _val(val)
{
}
};
// typedef __list_iterator<T, T&, T*> iterator;
// typedef __list_iterator<T, const T&, const T*> const_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 operator*()
{
return _node->_val;
}
Ptr operator->()
{
return &_node->_val;
}
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>
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 _head->_next;
return iterator(_head->_next);
}
iterator end()
{
return _head;
//return iterator(_head);
}
const_iterator begin() const
{
//return _head->_next;
return const_iterator(_head->_next);
}
const_iterator end() const
{
return _head;
//return const_iterator(_head);
}
void empty_init()
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
_size = 0;
}
list()
{
empty_init();
}
// lt2(lt1)
list(const list<T>& lt)
//list(const list& lt)
{
empty_init();
for (auto& e : lt)
{
push_back(e);
}
}
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
list<T>& operator=(list<T> lt)
//list& operator=(list lt)
{
swap(lt);
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
_size = 0;
}
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());
}
// pos位置之前插入
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
prev->_next = newnode;
newnode->_next = cur;
cur->_prev = newnode;
newnode->_prev = prev;
++_size;
return newnode;
}
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;
--_size;
return next;
}
size_t size()
{
return _size;
}
private:
Node* _head;
size_t _size;
};
void Print(const list<int>& lt)
{
list<int>::const_iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
}