List是一种序列容器,允许在其中的任何位置进行固定时间复杂度的插入、删除操作,以及双向迭代器。它被实现为双链表,可以将它其中包含的每个元素存储在不同且不相关的存储位置。每个元素之间的顺序是通过与每个元素的关联在内部保持的,该关联是指向其前面元素的链接和指向其后面元素的链接。它们与forward_list非常相似:主要区别在于forward_list对象是单链表,因此它们只能向前迭代,以换取更小、更高效。
与其他基本标准序列容器(array,、vector、deque)相比,list插入、提取和挪动容器中任何位置的元素,通常表现得更好,因此在密集地使用与这些元素相关的算法(如排序算法)时也表现得更好。
与其他序列容器相比,list和forward_list的主要缺点是它们无法通过位置对元素的直接访问。例如,要访问列表中的第六个元素,必须从已知位置(如开头或结尾)迭代到该位置,这需要耗费这些位置之间的线性时间。此外,它们还需要消耗一些额外的内存来保持与每个元素相关联的链接信息(这可能是数据量小却需要大链表的重要因素)。
#include<list> // list涉及的头文件
list<int> lt1; // 声明一个int类型的容器lt1,但未初始化,容器lt1是空的
list<int> lt2(3, 2); // 声明一个int类型的容器lt2,并初始化为3个2
list<int> lt3(lt2); // 声明一个int类型的容器lt3,并通过容器lt2初始化(拷贝构造)
//string s("hello world");
list<char> lt4(s.begin(), s.end()); // 声明一个char类型的容器lt4,并通过string类s的迭代器来初始化
本篇博客主要梳理了C++98下 list 的基本用法,搭配对 list 重要功能及其迭代器的模拟实现,旨在更好地帮助读者理解容器 list。 (获取更具体的内容,请参考list - C++ Reference)
一、list的基本用法
情景一:
void test_list1()
{
list<int>lt;
lt.push_back(1); //在容器lt尾部尾插1
lt.push_back(2); //在容器lt尾部尾插2
lt.push_back(3); //在容器lt尾部尾插3
lt.push_back(4); //在容器lt尾部尾插4
lt.push_front(10); //在容器lt头部头插10
lt.push_front(20); //在容器lt头部头插20
//通过范围for可以遍历lt
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
auto it = lt.begin();
for (size_t i = 0; i < 5; i++)
{
it++;
}
lt.insert(it, 100); //insert()可以在指定位置插入指定数据
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
it = find(lt.begin(), lt.end(), 3); //find()可以在指定范围内查找指定数据,并返回指定数据首次出现的位置的迭代器
if (it != lt.end())
{
lt.insert(it, 30);
*it *= 100; // insert以后,list的迭代器不失效
}
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
it = find(lt.begin(), lt.end(), 2);
if (it != lt.end())
{
lt.erase(it); //erase()可以在指定位置删除一个数据,并返回删除位置的下一个位置的迭代器
// *it *= 100; //但erase以后,list的迭代器会失效
}
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
//可以通过迭代器来遍历lt
it = lt.begin();
while (it != lt.end())
{
//删除lt中2的倍数
if (*it % 2 == 0)
{
it = lt.erase(it);
}
else
{
++it;
}
}
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
情景二:
void test_list2()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_front(10);
lt.push_front(20);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
reverse(lt.begin(), lt.end()); //reverse()是一个逆置算法,此处它逆置了lt中的所有数据
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.reverse(); //reverse()可以通过迭代器指定逆置的范围,也可以默认整体逆置
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
//sort(lt.begin(), lt.end());
lt.sort(); //sort()排序算法在list中主要是这样使用的
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
情景三:
void test_list3()
{
std::list<int> mylist1, mylist2;
std::list<int>::iterator it;
for (int i = 1; i <= 4; ++i)
mylist1.push_back(i);
for (int i = 1; i <= 3; ++i)
mylist2.push_back(i * 10);
for (auto e : mylist1)
{
cout << e << " "; // mylist1: 1 2 3 4
}
cout << endl;
for (auto e : mylist2)
{
cout << e << " "; // mylist2: 10 20 30
}
cout << endl << endl;
it = mylist1.begin();
++it; // points to 2
//splice()是一个转移算法,可以将一个容器中的数据转移到另一个容器的指定位置处
//void splice (iterator position, list& x);
//void splice (iterator position, list& x, iterator i);
//void splice (iterator position, list& x, iterator first, iterator last);
// 全部转移
//mylist1.splice(it, mylist2);
// 部分转移
//mylist1.splice(it, mylist2, ++mylist2.begin());
//mylist1.splice(it, mylist2, ++mylist2.begin(), mylist2.end());
// 原地转移
//mylist1.splice(mylist1.begin(), mylist1, ++mylist1.begin(), mylist1.end());
for (auto e : mylist1)
{
cout << e << " ";
}
cout << endl;
for (auto e : mylist2)
{
cout << e << " ";
}
cout << endl;
}
情景四:
//sort性能测试
void test_op()
{
srand(time(0));
const int N = 100000;
vector<int> v;
v.reserve(N);
list<int> lt1;
list<int> lt2;
for (int i = 0; i < N; ++i)
{
auto e = rand();
lt2.push_back(e);
lt1.push_back(e);
}
// 拷贝到vector排序,排完以后再拷贝回来
int begin1 = clock();
// 先拷贝到vector
for (auto e : lt1)
{
v.push_back(e);
}
// 排序
sort(v.begin(), v.end());
// 拷贝回list
size_t i = 0;
for (auto& e : lt1)
{
e = v[i++];
}
int end1 = clock();
int begin2 = clock();
lt2.sort();
int end2 = clock();
printf("vector sort:%d\n", end1 - begin1);
printf("list sort:%d\n", end2 - begin2);
}
//打印结果:
//vector sort:45
//list sort:58
【Tips】
- list的声明必须指定容器中数据的类型,可以指定大小和数据内容来初始化,也可以通过其他容器及其迭代器来初始化;
- list主要通过迭代器和范围for进行遍历;
- list涉及的增删改查一般有:push_back()、push_front()、insert()、erase()、find()、splice()等;
- list涉及的算法一般有sort()、reverse()等。
二、list的模拟实现
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace bit
{
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)
{}
};
//旧迭代器设计
//template<class T>
//struct __list_iterator
//{
// typedef list_node<T> Node;
// Node* _node;
//
// __list_iterator(Node* node)
// :_node(node)
// {}
//
// const T& operator*()
// {
// return _node->_val;
// }
//
// __list_iterator<T>& operator++()
// {
// _node = _node -> _next;
// return *this;
// }
// __list_iterator<T> operator++(int)
// {
// __list_iterator<T> tmp(*this);
//
// _node = _node->_next;
//
// return tmp;
// }
//
// bool operator!=(const __list_iterator<T>& it)
// {
// return _node != it._node;
// }
//
// bool operator==(const __list_iterator<T>& it)
// {
// return _node == it._node;
// }
//};
//template<class T>
//struct __list_const_iterator
//{
// typedef list_node<T> Node;
// Node* _node;
//
// __list_const_iterator(Node* node)
// :_node(node)
// {}
//
// const T& operator*()
// {
// return _node->_val;
// }
//
// __list_const_iterator<T>& operator++()
// {
// _node = _node->_next;
// return *this;
// }
//
// __list_const_iterator<T> operator++(int)
// {
// __list_const_iterator<T> tmp(*this);
//
// _node = _node->_next;
//
// return tmp;
// }
//
// bool operator!=(const __list_const_iterator<T>& it)
// {
// return _node != it._node;
// }
//
// bool operator==(const __list_const_iterator<T>& it)
// {
// return _node == it._node;
// }
//};
//
//
// 如何设计const迭代器?
// 这样设计const迭代器是不行的,因为const迭代器期望指向内容不能修改
// 这样设计是迭代器本身不能修改
//typedef __list_const_iterator<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*() //返回类型Ref,模板参数
{
return _node->_val;
}
Ptr operator->()//返回类型Ptr,模板参数
{
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> iterator;
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迭代器是不行的,因为const迭代器期望指向内容不能修改
// 这样设计是迭代器本身不能修改
//typedef __list_const_iterator<T> const_iterator;
//
const_iterator begin() const
{
//return _head->_next;
return const_iterator(_head->_next);
}
const_iterator end() const
{
return _head;
//return const_iterator(_head);
}
template<class T>
struct __list_const_iterator
{
typedef list_node<T> Node;
Node* _node;
__list_const_iterator(Node* node)
:_node(node)
{}
const T& operator*()
{
return _node->_val;
}
__list_const_iterator<T>& operator++()
{
_node = _node->_next;
return *this;
}
__list_const_iterator<T> operator++(int)
{
__list_const_iterator<T> tmp(*this);
_node = _node->_next;
return tmp;
}
bool operator!=(const __list_const_iterator<T>& it)
{
return _node != it._node;
}
bool operator==(const __list_const_iterator<T>& it)
{
return _node == it._node;
}
};
//但以上这种方式过于冗余
void empty_init()
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
_size = 0;
}
list()
{
empty_init();
}
//lt2(lt1)
list(const list<T>& 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)
{
swap(lt);
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
/* void push_back(const T& x)
{
Node* tail = _head->_prev;
Node* newnode = new Node(x);
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
}*/
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());
}
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;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
_size = 0;
}
size_t size()
{
/*size_t sz = 0;
iterator it = begin();
while (it != end())
{
++sz;
++it;
}
return sz;*/
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())
{
// (*it) += 1;
cout << *it << " ";
++it;
}
cout << endl;
}
void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
list<int>::iterator it = lt.begin();//拷贝构造
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
struct A
{
A(int a1=0, int a2=0)
:_a1(a1)
,_a2(a2)
{}
int _a1 = 0;
int _a2 = 1;
};
void test_list2()
{
list<A> lt;
lt.push_back(A(1, 1));
lt.push_back(A(2, 2));
lt.push_back(A(3, 3));
lt.push_back(A(4, 4));
list<A>::iterator it = lt.begin();
while (it != lt.end())
{
//cout << (*it)._a1 << " " << (*it)._a2 << endl;
cout << it->_a1 << " " << it->_a2 << endl;
//严格来说,it->重载后为it.operator->(),等价于A*
//故it->->_a1才是符合语法的
//但因为运算符重载要求可读性,于是编译器做了优化
//省略了一个 ->
++it;
}
cout << endl;
}
void test_list3()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_front(5);
lt.push_front(6);
lt.push_front(7);
lt.push_front(8);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.pop_front();
lt.pop_back();
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.clear();
lt.push_back(10);
lt.push_back(20);
lt.push_back(30);
lt.push_back(40);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
cout << lt.size() << endl;
}
void test_list4()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
list<int> lt1(lt);
for (auto e : lt1)
{
cout << e << " ";
}
cout << endl;
list<int> lt2;
lt2.push_back(10);
lt2.push_back(20);
lt2.push_back(30);
lt2.push_back(40);
for (auto e : lt2)
{
cout << e << " ";
}
cout << endl;
lt1 = lt2;
for (auto e : lt1)
{
cout << e << " ";
}
cout << endl;
}
}