C++打怪升级(十一)- STL之list
~~~~
- 前言
- 1. list是什么
- 2. list接口函数的使用
- 1. 构造相关
- 默认构造
- n个val构造
- 迭代器范围构造
- 拷贝构造
- 2 赋值运算符重载函数
- 2 析构函数
- 3 迭代器相关
- begin 和 end
- rbegin 和rend
- 4 容量相关
- empty
- size
- 5 元素访问相关
- front
- back
- 6 修改相关
- push_back
- pop_back
- push_front
- pop_front
- insert - 插入新值
- erase
- resize
- assign
- swap
- clear
- 7 其他操作相关
- reverse - 逆置
- sort - 排序
- unique - 去重
- merge - 合并
- remove - 删除值为val的元素
- remove_if - 删除满足条件的元素
- splice - 从list对象转移元素到另一个list对象
- 8 非成员函数
- swap
- 3. list的模拟实现
- 基本框架
- 定义节点结构
- 定义链表结构
- 定义迭代器结构 - 类的封装
- 迭代器分类
- 模板类的类型与内部类型的特殊使用方式
- 版本1:只支持非const对象
- 基本结构
- 构造函数
- *运算符重载
- ->运算符重载
- 前置++运算符重载
- 后置++运算符重载
- 前置--运算符重载
- 后置--运算符重载
- !=运算符重载
- ==运算符重载
- 代码汇总
- 版本2:支持const对象和非const对象
- 基本结构
- *运算符重载
- ->运算符重载
- 代码汇总
- 版本3:支持const对象和非const对象且简化写法
- 基本结构
- *运算符重载
- ->运算符重载
- 前置++
- 后置++
- 前置--
- 后置--
- !=运算符重载
- ==运算符重载
- 代码汇总
- 构造函数
- 初始化链表 - 代码复用
- 无参构造
- 迭代器范围构造
- 拷贝构造函数
- 传统写法 - 自己实现
- 现代写法 - 复用
- 赋值运算符重载函数
- 传统写法 - 自己实现
- 现代写法 - 复用
- 析构函数
- 正向迭代器相关
- begin
- end
- 增删
- insert
- erase
- push_back
- pop_back
- push_front
- pop_front
- clear
- swap
- resize
- size
- 关系运算符重载
- ==
- !=
- list与vector比较
- list与vector排序效率简要分析
- list与vector迭代器差异再次分析 - 调试
- 结语
前言
本节将介绍list
的使用,之后模拟实现list
的基本功能,最后将分析与连续顺序容器(vector
)的差异。
1. list是什么
list
是逻辑上的顺序容器,实际list
的储存中是不连续的,相邻节点之间通过指针进行链接。
list
是带头节点的双向循环链表,在所有的链表中结构最复杂,使用也最方便。
list
在头部和尾部的插入和删除操作、中间部分的插入删除操作都是常数时间,效率很高;与连续顺序容器相比的优势是中间位置的插入和删除操作时间复杂度是常数时间 O ( 1 ) O(1) O(1),而像vector
在中间插入或删除元素往往涉及到元素的移动,时间复杂度是 O ( N ) O(N) O(N)。劣势是list作为不连续存储的顺序容器不能实现随机访问元素 O ( 1 ) O(1) O(1),需要从第一个元素开始依次查找,直到找到或到最后一个元素为止,时间复杂度是 O ( N ) O(N) O(N)。
2. list接口函数的使用
1. 构造相关
初始化list对象
默认构造
声明
list ();
eg
list<int> l1;
n个val构造
声明
list (size_type n, const value_type& val = value_type());
eg
list<int> l1(5, 7); // l1: 7 7 7 7 7
迭代器范围构造
声明
template <class InputIterator>
list (InputIterator first, InputIterator last);
eg
vector<int> v{1, 2, 3, 4, 5};
list<int> l1(v.begin() + 1, v.end()); // l1: 2 3 4 5
拷贝构造
声明
list (const list& x);
eg
list<int> l1(5, 6); // 6 6 6 6 6
list<int> l2(l1); // l2: 6 6 6 6 6
2 赋值运算符重载函数
声明
list& operator= (const list& x);
eg
list<int> l1(4, 3); // l1: 3 3 3 3
list<int> l2(3, 6); // l2: 6 6 6
l2 = l1; // l2: 3 3 3 3
2 析构函数
释放对象申请的所有资源,为对象销毁做好准备。
~list();
3 迭代器相关
迭代器是像指针一样的类型。
begin 和 end
声明
begin
返回第一个对象的所在位置的迭代器。
iterator begin();
const_iterator begin() const;
end
返回最后一个对象所在位置的下一个位置的迭代器。
iterator end();
const_iterator end() const;
eg
list<int> l1{1,2,3,4,5};
list<int>::iterator it = l1.begin();
while (it != l1.end()) {cout << *it << " ";it++;
}
cout << endl << endl; // 结果为 1 2 3 4 5
rbegin 和rend
声明
rbegin
返回第一个对象的前一个位置的迭代器。
reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
rend
返回最后一个对象的迭代器。
reverse_iterator rend();
const_reverse_iterator rend() const;
eg
list<int> l1{1,2,3,4,5};list<int>::reverse_iterator rit = l1.rbegin();
while (rit != l1.rend()) {cout << *rit << " ";rit++;
}
cout << endl << endl; // 结果为 5 4 3 2 1
4 容量相关
empty
声明
判断list对象是否为空,是返回true;反之返回false。
bool empty() const;
eg
list<int> l1;
l1.empty(); // true
l1.push_back(10);
l1.empty(); // false
size
声明
返回list对象的元素个数
size_type size() const;
5 元素访问相关
front
声明
返回第一个有效元素的引用。
当list为空时调用front函数会产生未定义的行为。
reference front();
const_reference front() const;
eg
list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
l1.front(); // 1
back
声明
返回最后一个元素的引用。
当list为空时调用back函数会产生未定义的行为。
reference back();
const_reference back() const;
eg
list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
l1.back(); // 5
6 修改相关
push_back
声明
尾插一个新元素
void push_back (const value_type& val);
eg
list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
l1.push_back(10); // l1: 1 2 3 4 5 10
l1.size() // 6
pop_back
声明
尾删一个元素。
当list对象为空时调用该函数程序将会出错。
void pop_back();
eg
list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
l1.pop_back(); // l1: 1 2 3 4
l1.size() // 4
push_front
声明
头插一个新元素
void push_front (const value_type& val);
eg
list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
l1.push_front(9); // l1: 9 1 2 3 4 5
l1.size() // 6
pop_front
声明
头删一个元素
当list对象为空时调用该函数程序将会出错。
void pop_front();
eg
list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
l1.pop_front(); // l1: 2 3 4 5
l1.size() // 4
insert - 插入新值
声明
在迭代器pos指向的元素之前插入一个值为val的元素。
iterator insert (iterator pos, const value_type& val);
eg
list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
list<int>::iterator pos = find(l1.begin(), l1.end(), 3);l1.insert(pos, 9); // l1: 1 2 9 3 4 5
声明
在迭代器pos指向的元素之前插入n个值为val的元素。
void insert (iterator pos, size_type n, const value_type& val);
eg
list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
list<int>::iterator pos = find(l1.begin(), l1.end(), 3);l1.insert(pos, 3, 6); // l1: 1 2 6 6 6 3 4 5
声明
在迭代器pos指向的元素之前插入指定迭代器范围内的所有元素。
指定的迭代器范围是左闭右开区间。
template <class InputIterator>
void insert (iterator pos, InputIterator first, InputIterator last);
eg
list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
vector<int> v{7, 8, 9, 0};
list<int>::iterator pos = find(l1.begin(), l1.end(), 3);l1.insert(pos, 3, 6); // l1: 1 2 7 8 3 4 5
erase
声明
删除迭代器pos指向的元素
iterator erase (iterator position);
eg
list<int> l1{1, 2, 3, 4, 5}; // l1: 1 2 3 4 5
list<int>::iterator pos = find(l1.begin(), l1.end(), 3);
if (pos != l1.end()) {l1.erase(pos); // l1: 1 2 4 5a
}
声明
删除迭代器范围内的所有元素。
iterator erase (iterator first, iterator last);
eg
list<int> l1{1, 2, 3, 4, 5}; // l1: 1 2 3 4 5
list<int>::iterator pos = find(l1.begin(), l1.end(), 3);
l1.erase(l1.begin(), l1.end()); // l1: 空
resize
声明
设置list对象的大小为n,每个元素都使用val初始化;
如果 n 大于list对象的大小,就尾插新元素直到list对象大小恰好为n;
如果 n 小于list对象的大小,就尾删元素直到list对象大小恰好为n;
如果 n 等于list对象大小,就什么也不做。
void resize (size_type n, value_type val = value_type());
eg
list<int> l1{1,2,3,4,5}; // l1: 1 2 3 4 5
l1.size(); // 5l1.resize(8, 9); // l1: 1 2 3 4 5 8 8 8
l1.size(); // 8l1.resize(3, 1); // l1: 1 2 3
l1.size(); // 3
assign
用新值替换就值
声明
使用n个val赋值给list对象。
list对象的大小可能发生变化。
void assign (size_type n, const value_type& val);
eg
list<int> l1{1, 2, 3, 4, 5}; // l1: 1 2 3 4 5
l1.size(); // 5
vector<int> v{2, 2, 3, 3};
l1.assign(4, 6); // l1: 6 6 6 6
l1.size(); // 4
声明
使用指定迭代器范围内的所有元素赋值给list对象。
template <class InputIterator>
void assign (InputIterator first, InputIterator last);
eg
list<int> l1{1, 2, 3, 4, 5}; // l1: 1 2 3 4 5
l1.size(); // 5
vector<int> v{2, 2, 3, 3};// 4
l1.assign(v.begin(), v.end()); // l1: 2 2 3 3
l1.size(); // 4
swap
声明
交换两个list对象的内容。
void swap (list& x);
eg
list<int> l1{1, 2, 3, 4, 5};
l1.size();
list<int> l2{2, 2, 3 ,3};
l2.size();l1.swap(l2);
// l1: 2 2 3 3 size: 4
// l2: 1 2 3 4 5 size: 5
clear
声明
删除list对象的所有元素,并使list对象的大小为0。
void clear();
eg
list<int> l1{1, 2, 3, 4, 5};// l1: 1 2 3 4 5
l1.size(); // 5l1.clear(); // 清空l1
l1.size(); // 0
7 其他操作相关
reverse - 逆置
声明
逆置list对象的所有元素。
void reverse();
eg
list<int> l1{1, 2, 3, 4, 5}; // l1: 1 2 3 4 5
l1.reverse(); // l1: 5 4 3 2 1
reverse(l1.begin(), l1.end()); // l1: 1 2 3 4 5
sort - 排序
声明
对list对象的所有元素进行排序,无参时采用默认排序规则;有参时传入函数指针或仿函数,使用函数或仿函数定义的规则。
排序方法是归并排序。
void sort();template <class Compare>
void sort (Compare comp);
eg
list<int> l1{1, 3, 5, 2, 4, 6}; // l1: 1 3 5 2 4 6
l1.sort(); // l1: 1 2 3 4 5 6// 传入函数指针
list<int> l2{ 1, 3, 5, 2, 4, 6 }; // l2: 1 3 5 2 4 6
l2.sort(compare<int>); // l2: 6 5 4 3 2 1// 传入函数对象
list<int> l3{ 1, 3, 5, 2, 4, 6 }; // l3: 1 3 5 2 4 6
l3.sort(com<int>()); // l3: 6 5 4 3 2 1
从大到小排序
template<class T>
bool compare(const T& t1, const T& t2) {return t1 > t2;
}
从大到小排序
template<class T>
struct com {bool operator()(const T& t1, const T& t2) {return t1 > t2;}
};
unique - 去重
声明
无参数时,删除相邻的重复元素;
有参数时,参数是一个函数指针或函数对象,删除的不再是重复的相邻元素,而是满足传入参数所设置的条件的元素;
unique函数不对list对象所有参数进行排序,所以在使用该函数之前需要额外调用排序函数进行排序,以保证重复的元素是相邻的。
void unique();template <class BinaryPredicate>
void unique (BinaryPredicate binary_pred);
eg
list<int> l1{3, 2, 3, 1, 1, 2, 2}; // l1: 3, 2, 3, 1, 1, 2, 2// 不排序直接去重
l1.unique(); // l1: 3, 2, 3, 1, 2list<int> l2{3, 2, 3, 1, 1, 2, 2}; // l2: 3, 2, 3, 1, 1, 2, 2// 先排序在去重
l2.sort(); // l2: 1, 1, 2, 2, 2, 3, 3
l2.unique(); // l2: 1, 2, 3
merge - 合并
声明
合并两个已经排好序的list对象;
无参时,按默认规则把x中的所有元素依次转移到本list对象中,转移的操作为把x的元素的值依次与本list对象元素的值进行比较,如果x的元素的值大于比较的值则把x的元素插入到比较的元素之后;反之就继续比较直到最后一个元素。转移完成后,x对象就为空了。
有参时,区别是传入的函数指针或函数对象定义了比较的规则,转移时不在根据默认默认的规则进行。
void merge (list& x);template <class Compare>
void merge (list& x, Compare comp);
eg
list<int> l1{1, 9, 7, 6, 4}; // l1: 1 9 7 6 4 size: 5
list<int> l2{3, 2, 8}; // l2: 3 2 8 size: 3l1.sort(); // l1: 1 4 6 7 9
l2.sort(); // l2: 2 3 8l1.merge(l2); // l1: 1 2 3 4 6 7 8 9 size: 8// l2: size: 0
remove - 删除值为val的元素
声明
void remove (const value_type& val);
eg
list<int> l1{0, 1, 2, 3, 3, 4, 3}; // l1: 0, 1, 2, 3, 3, 4, 3 size: 7
l1.remove(3); // l1: 0, 1, 2, 4 size: 4
remove_if - 删除满足条件的元素
声明
template <class Predicate>
void remove_if (Predicate pred);
eg
list<int> l2{0, 1, 2, 3, 4}; // l1: 0, 1, 2, 4 size: 4
l2.remove_if(isOdd()); // l1: 1, 3 size: 2
是偶数就返回true
struct isOdd {
bool operator()(const int& t) {return t % 2 == 0;
}
};
splice - 从list对象转移元素到另一个list对象
声明
把list对象x的元素转移到另一个list对象的pos迭代器位置之前;
// 转移x所有的元素
void splice (iterator pos, list& x);
// 转移x迭代器i指向的元素
void splice (iterator pos, list& x, iterator i);
// 转移x中指定迭代器范围的元素
void splice (iterator pos, list& x, iterator first, iterator last);
eg
list<int> l1{1, 2, 3, 4, 5}; // l1: 1, 2, 3, 4, 5
list<int> l2{6, 7, 8}; // l2: 6, 7, 8
auto pos = find(l1.begin(), l1.end(), 3);
l1.splice(pos, l2); // l1: 1, 2, 6, 7, 8, 3, 4, 5 size: 8// l2: size: 0
8 非成员函数
swap
声明
template <class T>
void swap (list<T>& x, list<T>& y);
eg
list<int> l1{1, 2, 3}; // l1: 1, 2, 3 size: 3
list<int> l2{6, 7, 8, 9}; // l2: 6, 7, 8, 9 size: 4
swap(l1, l2); // 交换 // l1: 6, 7, 8, 9 size: 4// l2: 1, 2, 3 size: 3
3. list的模拟实现
本次list
实现是简化版,主要以学习为主,着重关注的是list
的基本结构和其迭代器的具体实现。特别是const
的迭代器与连续顺序容器的迭代器(以Linux下G++采用的SGI版本为例,VS2019下vector的迭代器被封装为了一个类,不再是原生指针)有着巨大的差异:如vector
的迭代器是由原生指针typedef
而来的,而list
迭代器必须封装为一个类才能达到与vector
迭代器类似的行为。
基本框架
list是带头双向循环链表,本次实现参考的是Linux中g++中采用的STL的SGI版。
定义节点结构
由于list可以存放多种类型的数据,需要采用类模板的方式。
包含三个成员变量:
节点指针_prev: 指向前一个节点
节点指针_next: 指向后一个节点
_val: 节点储存该类型对应的元素
struct和class作用基本相同,只是默认成员变量和成员函数是public的。
下文定义的list类模板需要在类内访问节点的成员变量和成员函数,直接使用struct定义节点结构就可以方便实现需求。
template<class T>
struct __list_node {__list_node(const T& val):_val(val),_prev(nullptr),_next(nullptr){ }__list_node* _prev;__list_node* _next;T _val;
};
定义链表结构
链表结构list也是一个类模板,因为其节点储存的元素类型不确定;
list的成员变量有两个:
节点指针_phead: 指向头结点,头结点是整个链表的开始但不存放有效元素
_size: 表示当前链表的有效节点个数,不包括头结点
template<class T>
class list {typedef __list_node<T> node; // 对节点类实例化重定义以简化写法
public:typedef __list_iterator<T> iterator; // 迭代器类型typedef __list_const_iterator<T> const_iterator;
private:node* _phead;size_t _size;
};
定义迭代器结构 - 类的封装
迭代器分类
原生指针实现;
类的封装实现;
模板类的类型与内部类型的特殊使用方式
类名与类型的区别:
普通类:类名就是类型;
类模板:类名+模板参数才是类型,
例外是:在类内可以省略模板参数,只写类名表示类型;
但是并不推荐这种写法,应该写出具体完整的类型,防止以后给自己或他人造成困扰;
例如标准库中list的构造实现:
版本1:只支持非const对象
迭代器对于链表来说,是一个行为像指针一样的类型。
指针支持的操作有:
解引用*、->、前置++、后置++、前置–、后置–、指针比较操作
原生的节点指针:
- 不支持++、–操作;
- 解引用操作*得到的是整个节点而不是预期的节点的值;
- ->操作得到的是整个节点的地址,而不是节点值的地址;
- 比较操作是符合的,可以满足比较操作;
如string、vector可以直接借助原生指针实现迭代器类型(当然,不是所有版本的string、vector的迭代器是原生指针,可能是被封装为了一个复杂的类),这依赖于它们天生的物理结构优势-连续存储。
原生指针的迭代器基本不能满足list对迭代器的需求,所以需要把迭代器定义为一个单独的类,依赖运算符重载功能对**运算符*、->、前置++、后置++、前置–、后置–**进行重载以便于满足我们对迭代器的预期。
对迭代器的预期:
- *得到节点内部的值;
- ->得到节点内部值的地址;
- 支持++:迭代器由当前节点指向下一个节点;
- 支持–:迭代器由当前节点指向前一个节点;
基本结构
迭代器只包含一个节点指针成员变量
template<class T>
struct __list_iterator {typedef __list_node<T> node;typedef __list_iterator<T> iterator;node* _pnode;
};
构造函数
实现基本的构造函数,接受节点地址进行初始化;
由于迭代器类本身不涉及资源的申请和释放,故析构、拷贝构造、赋值运算符重载等函数都不需要显式实现,由编译器生成默认的即可。
__list_iterator(node* pnode):_pnode(pnode){ }
*运算符重载
得到节点内部的值并传引用返回;
由于这个迭代器是非const的,所以迭代器指向的节点也是非const的,故返回类型是非const的:T&。
T& operator*() {return _pnode->_val;
}
->运算符重载
返回节点内部值的地址;
由于这是非const迭代器,所以迭代器指向的节点也是非const的,故·返回类型是非const的:T*
T* operator->() {return &(_pnode->_val);
}
前置++运算符重载
迭代器指向下一个位置,返回下一个位置的迭代器;
即前置++返回++之前的值;
iterator operator++() {_pnode = _pnode->_next;return *this;
}
后置++运算符重载
迭代器指向下一个位置,返回当前位置的迭代器;
即后置++返回++之后的值
iterator operator++(int) {iterator tmp(*this);_pnode = _pnode->_next;return tmp;
}
前置–运算符重载
迭代器指向前一个位置,返回前一个位置的迭代器;
iterator operator--() {_pnode = _pnode->_prev;return *this;
}
后置–运算符重载
迭代器指向前一个位置,返回当前位置的迭代器;
iterator operator--(int) {iterator tmp(*this);_pnode = _pnode->_prev;return tmp;
}
!=运算符重载
两个迭代器不相等即两个迭代器指向的节点不相等;
bool operator!=(iterator it) {return _pnode != it._pnode;
}
==运算符重载
两个迭代器相等即两个迭代器指向的节点相等;
bool operator==(iterator it){return !(*this != it);
}
代码汇总
template<class T>
struct __list_iterator {typedef __list_node<T> node;typedef __list_iterator<T> iterator;node* _pnode;__list_iterator(node* pnode):_pnode(pnode){ }T& operator*() {return _pnode->_val;}iterator operator++() {_pnode = _pnode->_next;return *this;}iterator operator++(int) {iterator tmp(*this);_pnode = _pnode->_next;return tmp;}iterator operator--() {_pnode = _pnode->_prev;return *this;}iterator operator--(int) {iterator tmp(*this);_pnode = _pnode->_prev;return tmp;}bool operator!=(iterator it) {return _pnode != it._pnode;}bool operator==(iterator it){return !(*this != it);}
};
版本2:支持const对象和非const对象
版本1只实现了非const的迭代器,对于const对象无法使用非const迭代器,需要实现const迭代器;
也许你有疑问,为什么不是普通迭代器前加上const就是const迭代器呢?
比如:typedef const __list_iterator const_iterator;
首先清楚const迭代器的const实际上修饰的谁?
肯定不是修饰的迭代器本身,因为不管迭代器是const还是非const的都应该能满足++、–操作,而++、–迭代器一定会改变迭代器,故迭代器本身是非const的;而简单的在普通迭代器之前加上const修饰而成的迭代器将会导致迭代器本身是const的而不能进行++、–操作,故其不是const迭代器。
那const修饰的是谁呢?
const迭代器的const,修饰的其实是迭代器指向的节点,特别是包括节点内的值;
const迭代器与普通迭代器的不同在于operator()和->operator返回类型分别是const T&和const T;
至于++、–、比较大小操作同普通迭代器相同;
需要一个新的类模板作为const迭代器类,以满足于不同于普通迭代器的*和->操作;
基本结构
template<class T>
struct __list_const_iterator {typedef __list_node<T> node;typedef __list_const_iterator<T> const_iterator;node* _pnode;
};
*运算符重载
返回迭代器指向节点内的值的引用;
由于是const迭代器,所以指向的节点是const的,故返回类型是const T&;
const T& operator*() {return _pnode->_val;
}
->运算符重载
返回迭代器指向节点内的值的地址;
由于是const迭代器,所以指向的节点是const的,故返回类型是const T*;
const T* operator->(){return &(_pnode->_val);
}
代码汇总
template<class T>
struct __list_const_iterator {typedef __list_node<T> node;typedef __list_const_iterator<T> const_iterator;node* _pnode;__list_const_iterator(node* pnode):_pnode(pnode) { }const T& operator*() {return _pnode->_val;}const_iterator operator++() {_pnode = _pnode->_next;return *this;}const_iterator operator++(int) {const_iterator tmp(*this);_pnode = _pnode->_next;return tmp;}const_iterator operator--() {_pnode = _pnode->_prev;return *this;}const_iterator operator--(int) {const_iterator tmp(*this);_pnode = _pnode->_prev;return tmp;}bool operator!=(const_iterator it) {return _pnode != it._pnode;}bool operator==(const_iterator it){return !(*this != it);}
};
版本3:支持const对象和非const对象且简化写法
版本1和版本2分别是普通迭代器的实现和const迭代器的实现,仔细分析发现二者的代码基本上很相似,主要区别是*和->操作返回的类型,普通迭代器返回非const的类型,const迭代器返回const的类型;
为了简化代码书写,选择合并这两个类模板,把两个不同之处分别多增加参数进行表示:
即操作的返回类型分为普通类型和const类型,用参数Ref表示;
->操作的返回类型分为普通类型和const*类型,用参数Ptr表示;
本质仍然是会生成两个迭代器类型,只是不再是由我们自己显式写出,而是由编译器根据类模板及其参数进行推导然后生成两个迭代器类型,所做的工作没有减少,只是交给类编译器承担。
基本结构
template<class T, class Ref, class Ptr>
struct __list_iterator {typedef __list_node<T> node;typedef __list_iterator<T, Ref, Ptr> Self;node* _pnode;
};
typedef __list_iterator<T, Ref, Ptr> Self;
在一开始,重定义了迭代器类型本身,简化后续书写;
如果还需要对迭代器模板参数进行改动只需要在此处进行修改即可,不需要对后续涉及到的代码进行修改,很便利。
*运算符重载
Ref operator*() {return _pnode->_val;
}
->运算符重载
Ptr operator->() {return &(_pnode->Val);
}
前置++
Self& operator++() {_pnode = _pnode->_next;return *this;
}
后置++
Self operator++(int) {Self tmp = *this;++*this;return tmp;
}
前置–
Self& operator--() {_pnode = _pnode->_prev;return *this;
}
后置–
Self operator--(int) {Self tmp(*this);_pnode = _pnode->_prev;return tmp;
}
!=运算符重载
bool operator!=(const Self& it) const{return _pnode != it._pnode;
}
==运算符重载
bool operator==(const Self& it) const {return !(*this != it);
}
代码汇总
template<class T, class Ref, class Ptr>
struct __list_iterator {typedef __list_node<T> node;typedef __list_iterator<T, Ref, Ptr> Self;node* _pnode;__list_iterator(node* pnode):_pnode(pnode) { }Ref operator*() {return _pnode->_val;}Ptr operator->() {return &(_pnode->Val);}Self& operator++() {_pnode = _pnode->_next;return *this;}Self operator++(int) {Self tmp = *this;++*this;return tmp;}Self& operator--() {_pnode = _pnode->_prev;return *this;}Self operator--(int) {Self tmp(*this);_pnode = _pnode->_prev;return tmp;}bool operator!=(const Self& it) const{return _pnode != it._pnode;}bool operator==(const Self& it) const {return !(*this != it);}
};
构造函数
初始化链表 - 代码复用
当定义一个list对象时,将会调用构造函数对该对象进行初始化:申请一个节点作为头结点,并使节点指针均指向自己。
除了无参的构造函数需要这个功能,迭代器范围做参数的构造函数、拷贝构造函数也需要该功能,为了减少代码冗余,便把申请头结点并处理的功能代码单独抽出来作为一个函数供其他需要的构造函数调用。
void initialize() {_phead = new node(T());_phead->_next = _phead;_phead->_prev = _phead;_size = 0;
}
无参构造
list() {initialize();
}
迭代器范围构造
范围: [ f i r s t , l a s t ) [first, last) [first,last)
template<class InputIterator>
list(InputIterator first, InputIterator last) {initialize();while (first != last) {push_back(*first);++first;}
}
拷贝构造函数
传统写法 - 自己实现
申请并初始化头结点
遍历链表lt,依次取节点中的元素尾插到本链表this中
list(const list<T>& lt) {initialize();node* cur = lt._phead->_next;while (cur != lt._phead) {push_back(cur->_val);cur = cur->_next;}
}
现代写法 - 复用
复用迭代器范围的构造函数先构造一个局部list对象tmp
交换tmp内指针_phead和this内指针_phead,之后除了构造函数作用域tmp对象将自动销毁
list(const list<T>& lt) {initialize();list<T> tmp(lt.begin(), lt.end());swap(tmp);
}
赋值运算符重载函数
传统写法 - 自己实现
避免自己给自己赋值,特殊判断一下,比较两个list对象用到了!=的运算符重载
赋值之前先依次delete本身的所有节点
list<T>& operator=(const list<T>& lt) {if (*this != lt) {clear();node* cur = lt._phead->_next;while (cur != lt._phead) {push_back(cur->_val);cur = cur->_next;}}return *this;
}
现代写法 - 复用
由传引用传参变为传值传参构造局部list对象lt
交换本对象this和lt内部的_phead指针
list<T>& operator=(list<T> lt) {swap(lt);return *this;
}
析构函数
delete释放所有申请的节点,包括头节点
复用了clear函数实现出头节点之外的所有节点的delete释放
~list() {clear();delete _phead;_phead = nullptr;
}
正向迭代器相关
begin
头结点不是储存数据节点,第一个储存数据的有效节点是头结点的下一个节点
iterator begin() {return iterator(_phead->_next);
}
const_iterator begin() const {return const_iterator(_phead->_next);
}
end
循环链表,最后一个节点的下一个节点是头结点
iterator end() {return iterator(_phead);
}
const_iterator end() const {return const_iterator(_phead);
}
增删
insert
在pos迭代器指向的节点之前插入一个新节点
iterator insert(iterator pos, T val) {// prev cur newnodenode* newnode = new node(val);node* cur = pos._pnode;node* prev = cur->_prev;newnode->_prev = prev;prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);
}
erase
iterator erase(iterator pos) {assert(pos != end());// prev cur nextnode* cur = pos._pnode;node* prev = cur->_prev;node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(prev);
}
push_back
void push_back(const T& val){// phead tail newnodeinsert(end(), val);
}
pop_back
void pop_back() {erase(--end());
}
push_front
void push_front(const T& val) {insert(begin(), val);
}
pop_front
void pop_front() {erase(begin());
}
clear
void clear() {iterator it = begin();while (it != end()) {it = erase(it);}
}
swap
void swap(list<T>& lt) {std::swap(_phead, lt._phead);
}
resize
void resize(size_t n, const T& val = T()) {size_t sizes = size();if (n > sizes) {while (sizes < n) {push_back(val);++sizes;}}else if (n < sizes) {while (n < sizes) {pop_back();--sizes;}}
}
size
size_t size() const {return _size;
}
关系运算符重载
==
bool operator==(const list<T>& lt) const {return _phead == lt._phead;
}
!=
bool operator!=(const list<T>& lt) const {return !(*this == lt);
}
list与vector比较
list与vector排序效率简要分析
算法库algorithm
中的sort
函数底层使用快速排序实现,不支持对list
进行排序。
list
内部实现了自己的排序函数sort
,该函数底层使用归并排序实现。
快速排序和归并排序的时间复杂度都是 O ( N ∗ l n N ) O(N*lnN) O(N∗lnN),但是实际上比较算法库中的sort
函数和list
内部的sort
函数效率时,对同一组数,list
内部实现的排序函数运行效率明显低于算法库中的排序函数。
使用同一组数不同情况下效率的比较:
- 对vector进行排序;
- list的元素先拷贝vector中,对vector排序再拷贝回list中;
- 对list进行排序;
void testSort() {int n = 100000;vector<int> v1;vector<int> v2;list<int> l1;list<int>l2;srand(time(0));for (int i = 0; i < n; ++i) {int val = rand();l1.push_back(val);l2.push_back(val);}v1.reserve(n);v2.reserve(n);for (auto& e : l1) {v1.push_back(e);}// vector 对vector进行排序int begin1 = clock();sort(v1.begin(), v1.end());int end1 = clock();// list->vector->list list的元素先拷贝vector中,对vector排序再拷贝回list中int begin2 = clock();for (auto& e : l1) {v2.push_back(e);}sort(v2.begin(), v2.end());int i = 0;for (auto& e : l1) {e = v2[i++];}int end2 = clock();// list 对list进行排序int begin3 = clock();l2.sort();int end3 = clock();cout << "vector: " << end1 - begin1 << endl;cout << "list->vector->list: " << end2 - begin2 << endl;cout << "list: " << end3 - begin3 << endl;
}
list与vector迭代器差异再次分析 - 调试
相同点:
- 迭代器的行为相似,都支持*、->、++、–、比较大小、范围for等操作;
- 都不支持迭代器相加操作;
不同点:
- vector迭代器是一个原生指针实现(不排除不同版本STL的vector迭代器也是类的封装实现),list迭代器是类的封装实现;
- vector的数据连续存储,支持迭代器+1、-1、迭代器之间的相减操作,++后指向相邻的下一个位置,–后指向相邻的前一个位置;list的节点在堆上随机存储,节点之间在储存上没有先后顺序,通过节点内的指针逻辑上链接相邻的节点,不支持+1、-1、迭代器相减操作,++后指向不相邻的下一个位置,–后指向不相邻的前一个位置。
结语
本节简要介绍了list的接口函数,重点实现了list的主要接口函数,特别是封装的迭代器。list由于是节点链接,insert之后直接插入了一个新的节点,原迭代器还指向原节点,并不会导致迭代器失效问题,与连续容器不同;erase之后,删除了原有节点,原迭代器指向了被删除的节点,迭代器是失效的,与连续容器相同。
T h e The The E n d End End
相关文章:

C++打怪升级(十一)- STL之list
~~~~ 前言1. list是什么2. list接口函数的使用1. 构造相关默认构造n个val构造迭代器范围构造拷贝构造 2 赋值运算符重载函数2 析构函数3 迭代器相关begin 和 endrbegin 和rend 4 容量相关emptysize 5 元素访问相关frontback 6 修改相关push_backpop_backpush_frontpop_frontins…...

Python编程陷阱(七)
陷阱26:不要使用list.reverse方法来反转列表 列表是Python中最常用的数据结构之一,它可以存储任意类型的元素,并且可以动态地增加或删除元素。有时候,我们需要将列表中的元素反转,比如打印或排序它们的值,就需要使用list.reverse方法或[::-1]切片来反转列表。但是,如果我…...

Python如何调用ixchariot进行吞吐量测试
Python如何调用ixchariot进行吞吐量测试 要使用Python调用IxChariot进行吞吐量测试,您可以使用 subprocess 模块来执行IxChariot的TCL命令行。下面是一个简单的示例代码: import subprocess# 定义IxChariot的安装路径和测试脚本路径 ixchariot_path &q…...

51单片机应用从零开始(五)·加减乘除运算
51单片机应用从零开始(一)-CSDN博客 51单片机应用从零开始(二)-CSDN博客 51单片机应用从零开始(三)-CSDN博客 51单片机应用从零开始(四)-CSDN博客 详解 KEIL C51 软件的使用建立工程…...

Meta降本增效大招之:弃用产品
今晚无意间进入去哪儿技术沙龙的直播间,听到他们要删除50%的代码和停掉50%的服务。我就想起Meta公司最近写的这篇博客:Automating product deprecation。 这篇博客对于效能平台的建设非常具有指导意义。文章最后有原文链接和我个人的总结。 这是一个系列…...

Adobe Illustrator——原创设计的宝藏软件
今天,我们来谈谈一款在Adobe系列中曾经多次给大家都提到的原创性极强的设计理念丰富的矢量图形编辑软件——Adobe Illustrator。 Adobe Illustrator,其定位是一款与Photoshop相类似对矢量图形进行编辑的软件。 Adobe Illustrator,作为全球最著…...

LEEDCODE 220 存在重复元素3
class Solution { public:int getId(int a, int valuediff){// 值// return a/(valuediff1);return a < 0 ? (a ) -) / (valuediff 1) - 1 : a / (valuediff 1);}public: unordered_map<int, int> bucket;bool containsNearbyAlmostDuplicate(vector<int>&am…...

从内网到公网:使用Axure RP和内网穿透技术发布静态web页面的完整指南
文章目录 前言1.在AxureRP中生成HTML文件2.配置IIS服务3.添加防火墙安全策略4.使用cpolar内网穿透实现公网访问4.1 登录cpolar web ui管理界面4.2 启动website隧道4.3 获取公网URL地址4.4. 公网远程访问内网web站点4.5 配置固定二级子域名公网访问内网web站点4.5.1创建一条固定…...

第三天课程 RabbitMQ
RabbitMQ 1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式: 同步通讯:就像打电话,需要实时响应。 异步通讯:就像发邮件,不需要马上回复。 两种方式各有优劣,打电话可以立即得到响应&am…...

Ubuntu18.04编译OpenCV时遇到无法下载ADE的问题
安装OpenCV过程中编译时出现下载ADE失败的问题 报错如下: -- ADE: Downloading v0.1.2a.zip from https://github.com/opencv/ade/archive/v0.1.2a.zip -- Try 1 failed CMake Warning at cmake/OpenCVDownload.cmake:248 (message):ADE: Download failed: 28;&quo…...

基于JavaWeb+SSM+社区居家养老服务平台—颐养者端微信小程序系统的设计和实现
基于JavaWebSSM社区居家养老服务平台—颐养者端微信小程序系统的设计和实现 源码获取入口前言主要技术系统设计功能截图Lun文目录订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 前言 在复杂社会化网络中,灵活运用社会生活产生的大数据&am…...

算法实战:亲自写红黑树之五 删除erase的平衡
本文承接自: 算法实战:亲自写红黑树之一-CSDN博客 算法实战:亲自写红黑树之二 完整代码-CSDN博客 算法实战:亲自写红黑树之三 算法详解-CSDN博客 算法实战:亲自写红黑树之四 插入insert的平衡-CSDN博客 目录 一、入口…...

春秋云境靶场CVE-2021-41402漏洞复现(任意代码执行漏洞)
文章目录 前言一、CVE-2021-41402描述二、CVE-2021-41402漏洞复现1、信息收集1、方法一弱口令bp爆破2、方法二7kb扫路径,后弱口令爆破 2、找可能可以进行任意php代码执行的地方3、漏洞利用找flag 总结 前言 此文章只用于学习和反思巩固渗透测试知识,禁止…...

12 Go的接口
概述 在上一节的内容中,我们介绍了Go的作用域,包括:局部作用域、全局作用域、命名空间作用域等。在本节中,我们将介绍Go的接口。Go语言中的接口是一种类型,它定义了一组函数的集合。接口是一种抽象的描述,它…...

Python编程-----并行处理应用程序
目录 一.进程 二.线程 三.Python标准库中并行处理的相关模块 Threading模块 (1)使用Thread对象创建线程 (2)自定义派生于Thread的对象 (3)线程加入join() (4)用户线程和daemon线程 (5)Timer线程 线…...

kubernetes集群编排——istio
官网:https://istio.io/latest/zh/about/service-mesh/ 部署 [rootk8s2 ~]# tar zxf istio-1.19.3-linux-amd64.tar.gz [rootk8s2 ~]# cd istio-1.19.3/[rootk8s2 istio-1.19.3]# export PATH$PWD/bin:$PATH demo专为测试准备的功能集合 [rootk8s2 istio-1.19.3]# i…...

mfc140u.dll丢失的解决方法,以及mfc140u.dll解决方法的优缺点
在使用电脑过程中,有时会遇到一些与动态链接库文件(DLL)相关的错误。其中,mfc140u.dll丢失的错误是较为常见的一种。当这个关键的mfc140u.dll文件丢失或损坏时,可能会导致某些应用程序无法正常运行。在本文中ÿ…...

2源码安装网络协议
2.2源码安装/网络协议 一、源码包应用场景 有时我们所用的内核版本太旧,系统自带的库(如libstdc.so.6)版本低或者依赖的其他软件版 本较低,导致无法安装目标软件。 软件/库其实是对机器汇编指令集的封装,在X86体系下…...

未来服务器操作系统的趋势与展望
摘要: 随着云计算、大数据和人工智能不断的发展,服务器操作系统也需要随之进行新一轮的升级。本文通过分析当前服务器操作系统的现状,探讨了未来服务器操作系统的趋势和展望,并针对一些关键问题提出了解决方案。 一、引言 服务器…...

VB.net WebBrowser网页元素抓取分析方法
在用WebBrowser编程实现网页操作自动化时,常要分析网页Html,例如网页在加载数据时,常会显示“系统处理中,请稍候..”,我们需要在数据加载完成后才能继续下一步操作,如何抓取这个信息的网页html元素变化&…...

自建ES6.2.4切阿里云商业版ES(7.10)整体方案
一、切换目的&阿里云商业版ES版本选择 1.1 升级切换阿里云商业版7.10目的 自建的Elasticsearch服务运维难度高,操作复杂,需要手动调整资源,遇到性能瓶颈时优化难度相对云上Elasticsearch较大。使用阿里云提供的ES服务,提高系统稳定性使用云服务es,易于备份,数据恢复…...

Vue实现封装自定义指令
目录 一、什么是自定义指令? 二、自定义指令的使用 Vue中的自定义指令使用Vue.directive函数进行定义。该函数接受两个参数,第一个是指令名称,第二个是指令选项对象。 上述代码中,我们定义了一个名为my-directive的自定义指令…...
<MySQL> 查询数据进阶操作 -- 聚合查询
目录 一、聚合查询概述 二、聚合函数查询 2.1 常用函数 2.2 使用函数演示 2.3 聚合函数参数为*或列名的查询区别 2.4 字符串不能参与数学运算 2.5 具有误导性的结果集 三、分组查询 group by 四、分组后条件表达式查询 五、MySQL 中各个关键字的执行顺序 一、聚合查询…...

arm开发板
一个简单的hello world程序 minicom用来和开发板之间交互并且可以向开发板传输文件。打印hello world字符串。在linux虚拟机上编译我的代码,使用的交叉编译工具是arm-linux-gnueabihf-gcc (hard float) 可以使用 readelf -h libc.so.6 查看开发板是不是(…...

nodejs+vue教室管理系统的设计与实现-微信小程序-安卓-python-PHP-计算机毕业设计
用户 用户管理:查看,修改自己的个人信息 教室预约:可以预约今天明天的教室,按着时间段预约(可多选),如果当前时间超过预约时间段不能预约该时间段的教室 预约教室的时候要有个预约用途ÿ…...

rabbitMQ的Topic模式的生产者与消费者使用案例
topic模式 RoutingKey 按照英文单词点号多拼接规则填充。其中消费者匹配规则时候 * 代表一个单词,#表示多个单词 消费者C1的RoutingKey 规则按照*.orange.* 匹配 绑定队列Q1 package com.esint.rabbitmq.work05;import com.esint.rabbitmq.RabbitMQUtils; import …...

【软考篇】中级软件设计师 第五部分
中级软件设计师 第五部分 三十六. 下午题变动题型参考答案例题一 如何保持数据流图平衡例题二 结构化语言例题三 关系模式例题四 用例关系内涵例题五 观察者模式 三十七:下午题第四题往年算法部分参考答案 读前须知: 【软考篇】中级软件设计师 学前须知 …...

论文阅读——RetNet
transformer的问题:计算量大,占用内存大,不好部署。 所以大家在找能解决办法,既能和transformer表现一样好,又能在推理阶段计算复杂度很低。 这些方法大概分类三类:一是代替transformer非线性注意力机制的…...

【Proteus仿真】【51单片机】锂电池管理系统
文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真51单片机控制器,使用LCD1602显示模块、DS18B20温度传感器、PCF8691 ADC模块、按键、LED蜂鸣器模块等。 主要功能: 系统运行后,LCD1602显示温度…...

【工具使用-VScode】设置 VSCode 的自动保存功能
要设置 VSCode 的自动保存功能,请按照以下步骤进行操作: 打开 VSCode 编辑器。在顶部菜单中选择 “文件(File)”。选择 “首选项(Preferences)”。在下拉菜单中选择 “设置(Settings࿰…...