文章目录
一、 list的介绍
- 在 C++ 中,list 是一种标准库容器(container),定义在 < list > 头文件中,属于标准模板库(STL)。list 是一个双向链表(doubly linked list),与数组(如 vector)相比,list 的存储方式和操作方式有一些不同。
- 双向链表结构:list 使用双向链表实现,这意味着每个元素都存储一个指向前一个和后一个元素的指针。与单向链表不同,双向链表可以从任意节点向前或向后遍历。
二、list的使用
1. list的构造
- list (size_type n, const value_type& val = value_type()) 构造的list中包含n个值为val的元素
- list() 构造空的list
- list (const list& x) 拷贝构造函数
- list (InputIterator first, InputIterator last) 用[first, last)区间中的元素构造list
#include <iostream>
#include <list>
int main()
{
// 定义一个名为 first 的空列表,类型为 int
std::list<int> first;
// 定义一个名为 second 的列表,包含四个元素,每个元素的值为 100
std::list<int> second(4, 100);
// 定义一个名为 third 的列表,通过迭代 second 列表中的所有元素来初始化 third
std::list<int> third(second.begin(), second.end());
// 定义一个名为 fourth 的列表,使用 third 列表进行拷贝构造
std::list<int> fourth(third);
// 迭代器构造函数也可以用于从数组构造列表:
int myints[] = {16, 2, 77, 29};
// 定义一个名为 fifth 的列表,使用 myints 数组的元素进行初始化
std::list<int> fifth(myints, myints + sizeof(myints) / sizeof(int));
std::cout << "The contents of fifth are: ";
for (std::list<int>::iterator it = fifth.begin(); it != fifth.end(); it++)
std::cout << *it << ' ';
std::cout << '\n';
return 0;
}
2.list iterator的使用
- begin/end
// list::begin
#include <iostream>
#include <list>
int main ()
{
int myints[] = {75,23,65,42,13};
std::list<int> mylist (myints,myints+5);
std::cout << "mylist contains:";
for (std::list<int>::iterator it=mylist.begin(); it != mylist.end(); ++it)
std::cout << ' ' << *it;
std::cout << '\n';
return 0;
}
- rbegin/rend
// list::rbegin/rend
#include <iostream>
#include <list>
int main()
{
std::list<int> mylist;
for (int i = 1; i <= 5; ++i)
mylist.push_back(i);
std::cout << "mylist backwards:";
for (std::list<int>::reverse_iterator rit = mylist.rbegin(); rit != mylist.rend(); ++rit)
std::cout << ' ' << *rit;
std::cout << '\n';
return 0;
}
3. list capacity
- empty 检测list是否为空,是返回true,否则返回false
#include <iostream>
#include <list>
int main()
{
std::list<int> mylist;
int sum(0);
for (int i = 1; i <= 10; ++i)
mylist.push_back(i);
while (!mylist.empty())
{
sum += mylist.front(); // 将 mylist 列表的第一个元素的值加到 sum 中
mylist.pop_front(); // 删除 mylist 列表的第一个元素
}
std::cout << "total: " << sum << '\n';
return 0;
}
- size 返回list中有效节点的个数
#include <iostream>
#include <list>
int main()
{
std::list<int> myints;
std::cout << "0. size: " << myints.size() << '\n'; // 输出字符串 "0. size: " 和 myints 列表的大小,初始大小为 0
for (int i = 0; i < 10; i++)
myints.push_back(i);
std::cout << "1. size: " << myints.size() << '\n';
// 输出字符串 "1. size: " 和 myints 列表的大小,此时大小为 10
myints.insert(myints.begin(), 10, 100); // 在 myints 列表的开头插入 10 个值为 100 的元素
std::cout << "2. size: " << myints.size() << '\n';
// 输出字符串 "2. size: " 和 myints 列表的大小,此时大小为 20
myints.pop_back(); // 删除 myints 列表的最后一个元素
std::cout << "3. size: " << myints.size() << '\n';
// 输出字符串 "3. size: " 和 myints 列表的大小,此时大小为 19
return 0;
}
4. list element access
- front 返回list的第一个节点中 值的引用
// list::front
#include <iostream>
#include <list>
int main ()
{
std::list<int> mylist;
mylist.push_back(77);
mylist.push_back(22);
// now front equals 77, and back 22
mylist.front() -= mylist.back();
std::cout << "mylist.front() is now " << mylist.front() << '\n';
return 0;
}
- back 返回list的最后一个节点中值的引用
// list::back
#include <iostream>
#include <list>
int main ()
{
std::list<int> mylist;
mylist.push_back(10);
while (mylist.back() != 0)
{
mylist.push_back ( mylist.back() -1 );
}
std::cout << "mylist contains:";
for (std::list<int>::iterator it=mylist.begin(); it!=mylist.end() ; ++it)
std::cout << ' ' << *it;
std::cout << '\n';
return 0;
}
5.list modifiers
- push_front 在list首元素前插入值为val的元素
// list::push_front
#include <iostream>
#include <list>
int main ()
{
std::list<int> mylist (2,100); // two ints with a value of 100
mylist.push_front (200);
mylist.push_front (300);
std::cout << "mylist contains:";
for (std::list<int>::iterator it=mylist.begin(); it!=mylist.end(); ++it)
std::cout << ' ' << *it;
std::cout << '\n';
return 0;
}
- pop_front 删除list中第一个元素
// list::pop_front
#include <iostream>
#include <list>
int main ()
{
std::list<int> mylist;
mylist.push_back (100);
mylist.push_back (200);
mylist.push_back (300);
std::cout << "Popping out the elements in mylist:";
while (!mylist.empty())
{
std::cout << ' ' << mylist.front();
mylist.pop_front();
}
std::cout << "\nFinal size of mylist is " << mylist.size() << '\n';
return 0;
}
- push_back 在list尾部插入值为val的元素
// list::push_back
#include <iostream>
#include <list>
int main ()
{
std::list<int> mylist;
int myint;
std::cout << "Please enter some integers (enter 0 to end):\n";
do {
std::cin >> myint;
mylist.push_back (myint);
} while (myint);
std::cout << "mylist stores " << mylist.size() << " numbers.\n";
return 0;
}
- pop_back 删除list中最后一个元素
// list::pop_back
#include <iostream>
#include <list>
int main ()
{
std::list<int> mylist;
int sum (0);
mylist.push_back (100);
mylist.push_back (200);
mylist.push_back (300);
while (!mylist.empty())
{
sum+=mylist.back();
mylist.pop_back();
}
std::cout << "The elements of mylist summed " << sum << '\n';
return 0;
}
- insert 在list position 位置中插入值为val的元素
mylist.insert(it, 10);insert 不会改变 it 的位置;it 仍然指向原来的元素。
it = mylist.insert(it, 10); 将 insert 的返回值赋给了 it。insert 的返回值是一个指向新插入的元素的迭代器。
所以,it 将被更新为指向新插入的元素 10。
#include <iostream>
#include <list>
#include <vector>
int main() // 主函数开始
{
std::list<int> mylist;
std::list<int>::iterator it;
// 设置一些初始值:
for (int i = 1; i <= 5; ++i)
mylist.push_back(i);
it = mylist.begin(); // 将迭代器 it 指向 mylist 列表的第一个元素
++it; // 将迭代器 it 向前移动一个位置,现在它指向数字 2
mylist.insert(it, 10); // 在迭代器 it 指向的位置之前插入数字 10
// 此时 mylist 的内容为: 1 10 2 3 4 5
// ^
// "it" 仍然指向数字 2
mylist.insert(it, 2, 20); // 在迭代器 it 指向的位置之前插入两个值为 20 的元素
// 此时 mylist 的内容为: 1 10 20 20 2 3 4 5
// ^
--it; // 将迭代器 it 向后移动一个位置,现在它指向前一个 20
std::vector<int> myvector(2, 30);
// 定义一个名为 myvector 的向量,类型为 int,包含两个元素,每个元素的值为 30
mylist.insert(it, myvector.begin(), myvector.end()); // 在迭代器 it 指向的位置之前插入 myvector 向量中的所有元素
// 此时 mylist 的内容为: 1 10 20 30 30 20 2 3 4 5
// ^
std::cout << "mylist contains:";
for (it = mylist.begin(); it != mylist.end(); ++it)
std::cout << ' ' << *it;
std::cout << '\n';
return 0;
}
这段代码的输出将会是:
mylist contains: 1 10 20 30 30 20 2 3 4 5
- erase 删除list position位置的元素
在 C++ 中,调用 mylist.erase(it1); 之后,it1 会被更新为指向下一个有效元素。如果这个下一个有效元素存在,it1 就指向该元素;如果 it1 指向的是最后一个元素,那么 erase(it1) 操作会让 it1 指向 mylist.end(),这表示一个超出容器末尾的迭代器位置。
#include <iostream>
#include <list>
int main()
{
std::list<int> mylist;
std::list<int>::iterator it1, it2;
for (int i = 1; i < 10; ++i)
mylist.push_back(i * 10);
// 此时 mylist 的内容为: 10 20 30 40 50 60 70 80 90
it1 = it2 = mylist.begin();
advance(it2, 6); // 使用 advance 函数将迭代器 it2 向前移动 6 个位置
// 此时 it2 指向第七个元素(70),it1 仍指向第一个元素(10)
++it1; // 将迭代器 it1 向前移动一个位置
// 现在 it1 指向第二个元素(20),it2 仍指向第七个元素(70)
it1 = mylist.erase(it1); // 删除迭代器 it1 指向的元素(20),并将 it1 移动到下一个有效元素
// 此时 mylist 的内容为: 10 30 40 50 60 70 80 90
// it1 现在指向元素 30,it2 仍指向 70
it2 = mylist.erase(it2); // 删除迭代器 it2 指向的元素(70),并将 it2 移动到下一个有效元素
// 此时 mylist 的内容为: 10 30 40 50 60 80 90
// it1 仍指向 30,it2 现在指向元素 80
++it1; // 将迭代器 it1 向前移动一个位置
// 现在 it1 指向元素 40,it2 仍指向 80
--it2; // 将迭代器 it2 向后移动一个位置
// 现在 it1 指向 40,it2 指向 60
mylist.erase(it1, it2); // 删除从迭代器 it1 到 it2(不包括 it2)之间的所有元素
// 即删除 40 和 50,mylist 的内容为: 10 30 60 80 90
// it1 和 it2 都失效,指向无效区域
std::cout << "mylist contains:";
for (it1 = mylist.begin(); it1 != mylist.end(); ++it1)
std::cout << ' ' << *it1;
std::cout << '\n';
return 0;
}
- swap 交换两个list中的元素
// swap lists
#include <iostream>
#include <list>
int main ()
{
std::list<int> first (3,100); // three ints with a value of 100
std::list<int> second (5,200); // five ints with a value of 200
first.swap(second);
std::cout << "first contains:";
for (std::list<int>::iterator it=first.begin(); it!=first.end(); it++)
std::cout << ' ' << *it;
std::cout << '\n';
std::cout << "second contains:";
for (std::list<int>::iterator it=second.begin(); it!=second.end(); it++)
std::cout << ' ' << *it;
std::cout << '\n';
return 0;
}
- clear 清空list中的有效元素
// clearing lists
#include <iostream>
#include <list>
int main ()
{
std::list<int> mylist;
std::list<int>::iterator it;
mylist.push_back (100);
mylist.push_back (200);
mylist.push_back (300);
std::cout << "mylist contains:";
for (it=mylist.begin(); it!=mylist.end(); ++it)
std::cout << ' ' << *it;
std::cout << '\n';
mylist.clear();
mylist.push_back (1101);
mylist.push_back (2202);
std::cout << "mylist contains:";
for (it=mylist.begin(); it!=mylist.end(); ++it)
std::cout << ' ' << *it;
std::cout << '\n';
return 0;
}
6.list的迭代器失效
迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
- 删除元素:
- 使用list::erase()或list::pop_back()、list::pop_front()方法删除元素时,会导致被删除的元素的迭代器失效。
- 删除操作仅会使被删除的那个元素的迭代器失效,不会影响其他元素的迭代器。
- 如果在遍历list时使用迭代器指向的元素被删除了,则该迭代器将失效,后续对该迭代器的使用可能会导致未定义行为。
- 移动语义与迭代器失效
- 在C++11及以后的版本中,list支持移动语义,这意味着通过std::move来移动一个list容器。移动一个容器后,源容器的迭代器和引用会失效,而目标容器的迭代器则保持有效。
使用建议:
- 在遍历list时,如果需要删除元素,建议使用list::erase()的返回值更新当前迭代器,确保安全。
- 遍历时使用auto it = list.begin();并在删除时it = list.erase(it);来避免失效问题。
#include <iostream>
#include <list>
#include <vector>
using namespace std;
void TestListIterator1()
{
int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
list<int> l(array, array + sizeof(array) / sizeof(array[0]));
auto it = l.begin();
while (it != l.end())
{
// l.erase(it);
// ++it;
// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
it = l.erase(it);
}
}
三.、list的模拟实现
1.模拟实现list
#pragma once
#include <iostream>
using namespace std;
#include <assert.h>
namespace simulate
{
// List的节点类
template <class T>
struct ListNode
{
ListNode(const T &val = T())
: _prev(nullptr), _next(nullptr), _val(val)
{
}
ListNode<T> *_prev;
ListNode<T> *_next;
T _val;
};
/*
List 的迭代器
迭代器有两种实现方式,具体应根据容器底层数据结构实现:
1. 原生态指针,比如:vector
2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:
1. 指针可以解引用,迭代器的类中必须重载operator*()
2. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()
3. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前 移动,所以需要重载,如果是forward_list就不需要重载--
4. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()
*/
template <class T, class Ref, class Ptr>
class ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T, Ref, Ptr> Self;
// Ref 和 Ptr 类型需要重定义下,实现反向迭代器时需要用到
public:
typedef Ref Ref;
typedef Ptr Ptr;
public:
// 构造
ListIterator(Node *node = nullptr)
: _node(node)
{
}
// 具有指针类似行为
Ref operator*()
{
return _node->_val;
}
Ptr operator->()
{
return &(operator*());
}
// 迭代器支持移动
Self &operator++()
{
_node = _node->_next;
return *this;
}
Self operator++(int)
{
Self temp(*this);
_node = _node->_next;
return temp;
}
Self &operator--()
{
_node = _node->_prev;
return *this;
}
Self operator--(int)
{
Self temp(*this);
_node = _node->_prev;
return temp;
}
// 迭代器支持比较
bool operator!=(const Self &l) const
{
return _node != l._node;
}
bool operator==(const Self &l) const
{
return _node != l._node;
}
Node *_node;
};
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;
};
template <class T>
class list
{
typedef ListNode<T> Node;
public:
// 正向迭代器
typedef ListIterator<T, T &, T *> iterator;
typedef ListIterator<T, const T &, const T &> const_iterator;
// 反向迭代器
typedef ReverseListIterator<iterator> reverse_iterator;
typedef ReverseListIterator<const_iterator> const_reverse_iterator;
public:
// List的构造
list()
{
CreateHead();
}
list(int n, const T &value = T())
{
CreateHead();
for (int i = 0; i < n; ++i)
push_back(value);
}
template <class Iterator>
list(Iterator first, Iterator last)
{
CreateHead();
while (first != last)
{
push_back(*first);
++first;
}
}
list(const list<T> &l)
{
CreateHead();
// 用l中的元素构造临时的temp,然后与当前对象交换
list<T> temp(l.begin(), l.end());
this->swap(temp);
}
list<T> &operator=(list<T> l)
{
this->swap(l);
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
// List的迭代器
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
const_reverse_iterator rbegin() const
{
return const_reverse_iterator(end());
}
const_reverse_iterator rend() const
{
return const_reverse_iterator(begin());
}
// List的容量相关
size_t size() const
{
Node *cur = _head->_next;
size_t count = 0;
while (cur != _head)
{
count++;
cur = cur->_next;
}
return count;
}
bool empty() const
{
return _head->_next == _head;
}
void resize(size_t newsize, const T &data = T())
{
size_t oldsize = size();
if (newsize <= oldsize)
{
// 有效元素个数减少到newsize
while (newsize < oldsize)
{
pop_back();
oldsize--;
}
}
else
{
while (oldsize < newsize)
{
push_back(data);
oldsize++;
}
}
}
// List的元素访问操作
// 注意:List不支持operator[]
T &front()
{
return _head->_next->_val;
}
const T &front() const
{
return _head->_next->_val;
}
T &back()
{
return _head->_prev->_val;
}
const T &back() const
{
return _head->_prev->_val;
}
// List的插入和删除
void push_back(const T &val)
{
insert(end(), val);
}
void pop_back()
{
erase(--end());
}
void push_front(const T &val)
{
insert(begin(), val);
}
void pop_front()
{
erase(begin());
}
// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T &val)
{
Node *pNewNode = new Node(val);
Node *pCur = pos._node;
// 先将新节点插入
pNewNode->_prev = pCur->_prev;
pNewNode->_next = pCur;
pNewNode->_prev->_next = pNewNode;
pCur->_prev = pNewNode;
return iterator(pNewNode);
}
// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{
// 找到待删除的节点
Node *pDel = pos._node;
Node *pRet = pDel->_next;
// 将该节点从链表中拆下来并删除
pDel->_prev->_next = pDel->_next;
pDel->_next->_prev = pDel->_prev;
delete pDel;
return iterator(pRet);
}
void clear()
{
Node *cur = _head->_next;
// 采用头删除删除
while (cur != _head)
{
_head->_next = cur->_next;
delete cur;
cur = _head->_next;
}
_head->_next = _head->_prev = _head;
}
void swap(simulate::list<T> &l)
{
std::swap(_head, l._head);
}
private:
void CreateHead()
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
}
private:
Node *_head;
};
}
// 对模拟实现的list进行测试
// 正向打印链表
template <class T>
void PrintList(const simulate::list<T> &l)
{
auto it = l.begin();
while (it != l.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
2.list的反向迭代器
反向迭代器内部可以包含一个正向迭代器,对正向迭代器的接口进行包装即可
template <class Iterator>
class ReverseListIterator
{
// 注意:此处typename的作用是明确告诉编译器,Ref是Iterator类中的类型,而不是静态成员变量
// 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量
// 因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的
public:
// Ref表示引用类型,Ptr表示指针类型
typedef typename Iterator::Ref Ref;
typedef typename Iterator::Ptr Ptr;
typedef ReverseListIterator<Iterator> Self;
public:
// 构造
ReverseListIterator(Iterator it) : _it(it) {}
// 重载解引用操作符*,返回迭代器当前指向的元素。
// 反向迭代器的解引用实际上是对正向迭代器进行递减操作后,再进行解引用。
// 创建一个临时迭代器temp,指向当前的_it位置。
// 将temp递减一个位置,然后返回该位置的值。
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;
};
四、list与vector
vector | list | |
---|---|---|
底层结构 | 动态顺序表 | 带头结点的双向循环链表 |
随机访问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素效率O(N) |
插入删除 | 任意位置插入和删除效率低,时间复杂度O(N) | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度O(1) |
空间利用率 | 连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 |
迭代器 | 指针 | 对节点指针进行封装 |
迭代器失效问题 | 插入所有的迭代器重新赋值、删除当前迭代器需要重新赋值 | 删除元素时,只会导致当前迭代器失效 |
使用 | 高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |