
🦌云深麋鹿
专栏 :C++ | 用C语言学数据结构 | Java

回顾:上一篇我们结束了容器vector,接下来这篇文章让我们进入到新的容器list,体会新的设计思路吧~
放个目录
- [一 使用](#一 使用)
-
- [1.1 sort](#1.1 sort)
- [1.2 splice](#1.2 splice)
-
- [1.2.1 两个链表之间使用](#1.2.1 两个链表之间使用)
- [1.2.2 一个链表中结点调整](#1.2.2 一个链表中结点调整)
- [二 模拟](#二 模拟)
-
- [2.1 定义list_node](#2.1 定义list_node)
-
- [2.1.1 成员变量](#2.1.1 成员变量)
- [2.1.2 构造函数](#2.1.2 构造函数)
- [2.2 构造函数](#2.2 构造函数)
-
- [2.2.1 无参构造](#2.2.1 无参构造)
- [2.2.2 拷贝构造](#2.2.2 拷贝构造)
- [2.2.3 initializer_list](#2.2.3 initializer_list)
- [2.3 改变list](#2.3 改变list)
-
- [2.3.1 insert](#2.3.1 insert)
- [2.3.2 erase](#2.3.2 erase)
- [2.3.3 push_back](#2.3.3 push_back)
- [2.3.4 pop_back](#2.3.4 pop_back)
- [2.3.5 push_front](#2.3.5 push_front)
- [2.3.6 pop_front](#2.3.6 pop_front)
- [2.3.7 swap](#2.3.7 swap)
- [2.4 封装一个list_iterator](#2.4 封装一个list_iterator)
-
- [2.4.1 成员变量](#2.4.1 成员变量)
- [2.4.2 构造函数](#2.4.2 构造函数)
- [2.4.3 重载操作符](#2.4.3 重载操作符)
- [2.4.4 const_iterator](#2.4.4 const_iterator)
- [2.5 迭代器遍历](#2.5 迭代器遍历)
-
- [2.5.1 begin](#2.5.1 begin)
- [2.5.2 end](#2.5.2 end)
- [2.5.3 print](#2.5.3 print)
- [2.6 容量相关](#2.6 容量相关)
-
- [2.6.1 size](#2.6.1 size)
- [2.6.2 clear](#2.6.2 clear)
- [2.7 重载操作符](#2.7 重载操作符)
-
- [2.7.1 =](#2.7.1 =)
一 使用
1.1 sort
数据量大的话,用list这个容器来sort效率很低。
cpp
srand(time(0));
const int N = 10000000;
list<int> lt1;
list<int> lt2;
for (size_t i = 0;i < N;++i) {
auto e = rand() + i;
lt1.push_back(e);
lt2.push_back(e);
}
// sort lt1
int begin1 = clock();
lt1.sort();
int end1 = clock();
// sort lt2
int begin2 = clock();
vector<int> vt(lt2.begin(), lt2.end());
sort(vt.begin(), vt.end());
// copy to lt2
lt2.assign(vt.begin(), vt.end());
int end2 = clock();
// output
cout << "sort lt1: " << end1 - begin1 << endl;
cout << "sort lt2: " << end2 - begin2 << endl;
运行:

结果显示:把 lt2 的数据放到 vector 里去排,再拷贝回原 lt2,都比单纯用 list 的 sort 排 lt1 来的效率高。
1.2 splice
1.2.1 两个链表之间使用
cpp
list<int> lt1 = { 1,2,3,4,5 };
list<int> lt2 = { 6,7,8,9,10 };
lt1.splice(lt1.end(),lt2);
输出:
cpp
cout << "lt1: ";
for (auto e : lt1) {
cout << e << " ";
}
cout << endl << "lt2: ";
for (auto e : lt2) {
cout << e << " ";
}
cout << endl;
运行:

1.2.2 一个链表中结点调整
cpp
list<int> lt1 = { 1,2,3,4,5 };
auto it = --lt1.end();
lt1.splice(lt1.begin(), lt1, it);
输出:
cpp
for (auto e : lt1) {
cout << e << " ";
}
cout << endl;
运行:

二 模拟
这是一个双向带头链表。
2.1 定义list_node
2.1.1 成员变量
cpp
T _data;
node* _pre = nullptr;
node* _next = nullptr;
- _data存储数据。
- _pre存储上一个结点的地址。
- _next存储下一个结点的地址。
2.1.2 构造函数
支持无参构造:
cpp
list_node(const T& data = T())
:_data(data)
{}
2.2 构造函数
2.2.1 无参构造
cpp
list(){
list_init();
}
封装一个函数:生成一个 _pre 和 _next 都指向 自己 的_head。
cpp
void list_init() {
_head = new node();
_head->_next = _head;
_head->_pre = _head;
}
测试:
cpp
list<int> l;
调试:

2.2.2 拷贝构造
我们先写一个不完善版本:
cpp
list(list<T>& l1) {
list_init();
for (auto& e : l1) {
push_back(e);
}
}
复用push_back。
测试:
cpp
list<int> l1 = {1,2,3,4,5,6};
list<int> l2(l1);
调试:

2.2.3 initializer_list
cpp
list(initializer_list<T> il) {
list_init();
for (auto& e : il) {
push_back(e);
}
}
依旧复用。
测试:
cpp
list<int> l = {1,2,3,4,5,6};
调试:

2.3 改变list
2.3.1 insert
cpp
iterator insert(iterator pos, T& data) {
if (pos == nullptr) {
return nullptr;
}
node* posNode = pos._ptr;
node* pre = posNode->_pre;
node* newNode = new node(data);
newNode->_pre = pre;
newNode->_next = posNode;
pre->_next = newNode;
posNode->_pre = newNode;
++_size;
return newNode;
}
- 检查参数合法性。
- 根据参数data造一个新结点。

- 处理相邻结点的链接关系。

2.3.2 erase
cpp
iterator erase(iterator pos) {
if (pos == nullptr) {
return nullptr;
}
node* posNode = pos._ptr;
node* pre = posNode->_pre;
node* next = posNode->_next;
pre->_next = next;
next->_pre = pre;
delete posNode;
--_size;
return iterator(next);
}
- 依旧先检查参数合法性。
- 记录pre,next结点。
- 处理链接关系,可以据 insert 的图倒推 erase 的过程。
- 释放 pos 结点空间。
- 返回值为更新后的iterator,以防止迭代器失效。
测试:
cpp
list<int> l = {1,2,3,4,5,6};
// ...
list_iterator it = l.begin();
while(it != l.end()){
if ((*it) % 2 == 0) {
it = l.erase(it);
}
else {
++it;
}
}
//...
输出部分:
cpp
for (auto e : l) {
cout << e << " ";
}
cout << endl;
// ...
cout << "--- after erase ---" << endl;
for (auto e:l) {
cout << e << " ";
}
cout << endl;
运行:

2.3.3 push_back
cpp
iterator push_back(const T& data) {
return insert(end(), data);
}
直接复用。
测试:
cpp
list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_back(4);
调试:

2.3.4 pop_back
cpp
iterator pop_back() {
return erase(--end());
}
直接复用。
测试:
cpp
list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_back(4);
l.pop_back();
调试:

2.3.5 push_front
cpp
iterator push_front(const T& data) {
return insert(begin(), data);
}
直接复用。
测试:
cpp
list<int> l;
l.push_front(1);
l.push_front(2);
l.push_front(3);
l.push_front(4);
输出:
cpp
for (auto e:l) {
cout << e << " ";
}
cout << endl;
运行:

2.3.6 pop_front
cpp
iterator pop_front() {
return erase(begin());
}
直接复用。
测试:
cpp
l.push_front(1);
l.push_front(2);
l.push_front(3);
l.push_front(4);
l.pop_front();
运行输出:

2.3.7 swap
cpp
void swap(list<T>& l2) {
if (this == &l2) {
return;
}
std::swap(_head, l2._head);
std::swap(_size, l2._size);
}
测试:
cpp
list<int> l1 = { 1,2,3,4,5,6 };
list<int> l2 = { 6,5,4,3,2,1 };
l1.swap(l2);
输出:
cpp
cout << "l1:";
for (auto e : l1) {
cout << e << " ";
}
cout << endl;
cout << "l2:";
for (auto e : l2) {
cout << e << " ";
}
cout << endl;
l1.swap(l2);
cout << "--- after swap ---" << endl;
cout << "l1:";
for (auto e : l1) {
cout << e << " ";
}
cout << endl;
cout << "l2:";
for (auto e : l2) {
cout << e << " ";
}
cout << endl;
运行:

2.4 封装一个list_iterator
不写析构和拷贝构造函数(默认浅拷贝就够用了)。
2.4.1 成员变量
cpp
node* _ptr;
2.4.2 构造函数
cpp
list_iterator(node* ptr)
:_ptr(ptr)
{}
2.4.3 重载操作符
(1)operator*
cpp
T& operator*() {
return _ptr->_data;
}
(2)operator++
①前置
cpp
Self& operator++() {
_ptr = _ptr->_next;
return *this;
}
- 往后走,遍历到下一个结点。
- 返回下一个结点的指针。
这里的 Self 是我们typedef出来的:
cpp
typedef list_iterator<T> Self;
②后置
cpp
Self operator++(int) {
Self temp(*this);
_ptr = _ptr->_next;
return temp;
}
- 遍历到下一个结点。
- 返回原指针(事先存储)。
(3)operator--
①前置
cpp
Self& operator--() {
_ptr = _ptr->_pre;
return *this;
}
②后置
cpp
Self operator--(int) {
Self temp(*this);
_ptr = _ptr->_pre;
return temp;
}
(4)operator!=
cpp
bool operator!=(const Self& it) const{
return _ptr != it._ptr;
}
(5)operator==
cpp
bool operator==(const Self& it) const {
return _ptr == it._ptr;
}
(6)operator->
cpp
T* operator->() {
return &_ptr->_data;
}
①使用场景
我们有一个自定义类型Aa:
cpp
struct Aa {
int _A;
int _a;
Aa(int A = 0,int a = 0)
:_A(A),_a(a)
{}
};
造一个list< Aa >:
cpp
list<Aa> l;
l.push_back({ 1,2 });
l.push_back({ 3,4 });
l.push_back({ 5,6 });
输出需要这么写:
cpp
list_iterator it = l.begin();
while (it != l.end()) {
cout << (*it)._A << ":" << (*it)._a << " ";
++it;
}
cout << endl;
(*it)._A 这种写法显得很复杂,我们可以重载 操作符-> ,使得写法更加简便。
②重载后用法
cpp
while (it != l.end()) {
cout << it.operator->()->_A << ":" << it.operator->()->_a << " ";
++it;
}
可以简便写成:
cpp
cout << it->_A << ":" << it->_a << " ";
2.4.4 const_iterator
(1)第一版
我们发现 iterator 跟 const_iterator 两个 struct 有一个函数不一样:
cpp
const T& operator*() {
return _ptr->_data;
}
返回值需要加上修饰符 const。
(2)第二版
所以我们直接加个模板参数,让编译器自己生成 const_iterator。
cpp
template<typename T,typename Ref>
struct list_iterator {
//...
Ref operator*() {
return _ptr->_data;
}
//...
}
这个函数的返回值改为我们的模板参数。
list类里就可以这样玩:
cpp
typedef list_iterator<T, T&> iterator;
typedef list_iterator<T, const T&> const_iterator;
我们上面写的拷贝构造的参数就可以加上 const 修饰了:
cpp
list(const list<T>& l1) {
list_init();
for (auto& e : l1) {
push_back(e);
}
}
(3)第三版
还有一个函数 iterator 和 const_iterator 两个版本不一样:
cpp
const T* operator->() {
return &_ptr->_data;
}
我们再加一个模板参数Ptr:
cpp
template<typename T,typename Ref,typename Ptr>
list类里这么搞:
cpp
typedef list_iterator<T, T&,T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
2.5 迭代器遍历
2.5.1 begin
(1)非const版本
cpp
iterator begin() {
return iterator(_head->_next);
}
(2)const版本
cpp
const_iterator begin() const {
return const_iterator(_head->_next);
}
2.5.2 end
(1)非const版本
cpp
iterator end() {
return iterator(_head);
}
(2)const版本
cpp
const_iterator end() const {
return const_iterator(_head);
}
2.5.3 print
这里需要用const_iterator。
cpp
void print() const {
const_iterator it = begin();
while (it != end()) {
cout << (*it) << " ";
++it;
}
cout << endl;
}
测试:
cpp
list<int> l1 = { 1,2,3,4,5,6 };
l1.print();
运行:

2.6 容量相关
2.6.1 size
cpp
size_t size() const {
return _size;
}
直接返回成员变量。
2.6.2 clear
cpp
void clear() {
iterator it = begin();
while (it != end()) {
it = erase(it);
}
}
复用+1。
测试:
cpp
list<int> l = { 1,2,3,4,5,6 };
l.clear();
调试:

2.7 重载操作符
2.7.1 =
cpp
list<T>& operator=(list<T> l2) {
swap(l2);
return *this;
}
复用+1。
测试:
cpp
list<int> l1 = { 1,2,3,4,5,6 };
list<int> l2;
//...
l2 = l1;
//...
输出:
cpp
//...
cout << "l1:";
for (auto e : l1) {
cout << e << " ";
}
cout << endl;
cout << "l2:";
for (auto e : l2) {
cout << e << " ";
}
cout << endl;
//...
cout << "--- after ---" << endl;
cout << "l1:";
for (auto e : l1) {
cout << e << " ";
}
cout << endl;
cout << "l2:";
for (auto e : l2) {
cout << e << " ";
}
cout << endl;
运行:

容器vector的学习就到这里啦,下一篇 容器stack&queue 也是今天会更出来~

