目录
初步学习list的使用
如图为list的物理结构,由多个节点构成的双向带头循环,区别与vector的优势在于增删的时候,不涉及数据的挪动,在大型数据面前较为方便
list的迭代
list和vector一致也可通过iterator迭代器来遍历对象,但是本质上底层不同,这里体现了封装以及统一的思想,具体实现我们在后面的篇幅来具体讲解
void test_list1() {
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(2);
lt.push_back(1);
list<int>::iterator it = lt.begin();
while (it != lt.end()) {
cout << *it << " ";
it++;
}
cout << endl;
}
list中部分成员函数用法
我们知道了list与vector有着较大区别,也有一些新的成员函数
排序函数sort()
注意的是sort默认为升序排列,并且list中的sort效率太低
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
for (auto e : lt) {
cout << e << " ";
}
cout << endl;
lt.reverse(); // reverse为逆置函数
for (auto e : lt) {
cout << e << " ";
}
cout << endl;
lt.sort();
for (auto e : lt) {
cout << e << " ";
}
cout << endl;
lt.sort(greater<int>()); // sort默认为升序排列,通过匿名对象greater<int>()完成降序
// greater<int> gt; 并且list中的sort效率太低
// lt.sort(gt)
for (auto e : lt) {
cout << e << " ";
}
放一下控制台输出
去重函数unique()
unique需要先排序,才能达到去重的效果,因为底层是双指针
void test_list3() {
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(0);
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
// 去重 unique
lt.unique();
for (auto e : lt) {
cout << e << " ";
}
cout << endl;
// 实质上unique需要先排序,才能达到去重的效果,因为底层是双指针
lt.sort();
lt.unique();
for (auto e : lt) {
cout << e << " ";
}
cout << endl;
// 去除函数
lt.remove(0);
for (auto e : lt) {
cout << e << " ";
}
cout << endl;
lt.remove(100); // remove不存在的数据 不会有任何操作
for (auto e : lt) {
cout << e << " ";
}
cout << endl;
}
其他的就查阅cpp文档来对list的成员函数进行使用的学习 list - C++ Reference
list的简易模拟实现
在数据结构的学习中,我们学习过链表,链表的主体框架为先定义单个节点结构体,内存储节点值与节点的相关地址。接着就是定义链表类,里面来放一个个节点,在通过节点结构体来链接
list的框架
template<class T>
struct ListNode { // 这里用struct默认为共有,也可以换为class但是需要将class全部public
T _data;
ListNode<T>* _next;
ListNode<T>* _prev;
// 需要初始化
ListNode(const T& data = T()) // 通过匿名对象T()便于后续数据传入
:_data(data)
, _next(nullptr)
, _prev(nullptr)
{}
};
template<class T>
class my_list {
public:
// 空链表初始化
void empty_init() { // 单独对哨兵头结点操作
_head = new ListNode<T>;
_head->_next = _head;
_head->_prev = _head;
}
// 构造函数
my_list() { empty_init(); } // 这个格式简写,符合stl的代码风格
~my_list(){
_head->_next = nullptr;
_head->_prev = nullptr;
delete _head;
}
private:
ListNode<T>* _head; // 注意这是哨兵位
};
首先先定义一个节点
template<class T>
struct ListNode { // 这里用struct默认为共有,也可以换为class但是需要将class全部public
T _data;
ListNode<T>* _next;
ListNode<T>* _prev;
// 需要初始化
ListNode(const T& data = T()) // 通过匿名对象T()便于后续数据传入
:_data(data)
, _next(nullptr)
, _prev(nullptr)
{}
};
定义完节点之后,为了将节点连成链表,我们需要定义一个链表类,里面来实现链表的节点存储
template<class T>
class my_list {
public:
// 空链表初始化
void empty_init() { // 单独对哨兵头结点操作
_head = new ListNode<T>;
_head->_next = _head;
_head->_prev = _head;
}
// 构造函数
my_list() { empty_init(); } // 这个格式简写,符合stl的代码风格
~my_list(){
_head->_next = nullptr;
_head->_prev = nullptr;
delete _head;
}
private:
ListNode<T>* _head; // 注意这是哨兵位
};
从上面的代码,我们知道了在链表类中,我们一开始通过哨兵位置可以指向自己,接下来在通过插入一个新节点,就可以实现链表了
void push_back(const T& data) { // my_list为双向循环链表
ListNode<T>* tail = _head->_prev;
ListNode<T>* newNode = new ListNode<T>(data);
tail->_next = newNode;
newNode->_prev = tail;
_head->_prev = newNode;
newNode->_next = _head;
}
list的迭代器(遍历)
有了节点的增加,就可以初步形成一条链表,那么我们就来遍历一下这个链表,类似与vector以及string那样,并且通过stl库里面对list的用法也可以通过迭代器来遍历,那list的迭代器和vector,以及string是一样的吗?我们在“list的迭代”也讲过这个问题
所以我们想使用链表迭代器,又要为了跟string与vector迭代器的用法保持一致,那么我们就需要定制一个特别的迭代器,也就是封装好,让外面使用一致。那么如何操作呢?答案是通过调用运算符重载函数来实现,*it和it++。
STL_List源码中迭代器的基础框架(部分)
STL_List源码中迭代器的运算符重载(部分)
实现list的迭代器
结合着STL中list的源码初步实现
template<class T>
struct list_iterator {
typedef list_iterator iterator;
// 存放节点的地址 --- 迭代器就是通过地址来使用的,并且链表可以通过地址找到节点对象
ListNode<T>* _node;
// 默认构造函数,将迭代器当前节点地址传入
list_iterator(ListNode<T>* node_adress) :_node(node_adress){}
iterator& operator++() { // ++实现向后走
// 也可为 _node = _node->_next;
_node = (*_node)._next;
// 返回当前节点
return *this;
}
T& operator*() {
// 重载后也可以*it来去it这个节点对应的data
// return _node->_data;
return (*_node)._data;
}
bool operator==(const iterator& it) {
return _node == it._node;
}
bool operator!=(const iterator& it) {
return _node != it._node;
}
};
这里我们可以初步理解,迭代器就相当于平行于链表来进行遍历
通俗来讲迭代器和节点大致相似,不过通过迭代器找节点的数据就需要解引用找到节点然后再对节点进行操作,但是我们有知道迭代器当时和节点表示同一个地址,这样解释的话就是:在遍历的过程中,通过迭代器的地址找到节点然后节点找到自己带的数据,也可以找到节点的下一个地址给到迭代器来重复这个过程
核心思想:通过地址对应着对象(对象就是对应分配的地址形成的)
这里就是简易的可遍历的list
template<class T>
struct ListNode { // 这里用struct默认为共有,也可以换为class但是需要将class全部public
T _data;
ListNode<T>* _next;
ListNode<T>* _prev;
// 需要初始化
ListNode(const T& data = T()) // 通过匿名对象T()便于后续数据传入
:_data(data)
, _next(nullptr)
, _prev(nullptr)
{}
};
template<class T>
struct list_iterator {
typedef list_iterator self;
// 存放节点的地址 --- 迭代器就是通过地址来使用的,并且链表可以通过地址找到节点对象
ListNode<T>* _node;
// 默认构造函数,将迭代器当前节点地址传入
list_iterator(ListNode<T>* node_adress) :_node(node_adress){}
self& operator++() { // ++实现向后走
_node = (*_node)._next;
// 返回当前节点
return *this;
}
self& operator--() { // 前置--实现向前走
_node = _node->_prev;
return *this;
}
T& operator*() {
// 重载后也可以*it来去it这个节点对应的data
return (*_node)._data;
}
bool operator==(const self& s) {
return _node == s._node;
}
bool operator!=(const self& s) {
return _node != s._node;
}
};
template<class T>
class my_list {
public:
typedef list_iterator<T> iterator; // 在my_list这个类域中统一命名
iterator begin() {
// return iterator(_head->_next); 通过匿名对象
return _head->_next;
}
iterator end() {
return _head;
}
// 空链表初始化
void empty_init() { // 单独对哨兵头结点操作
_head = new ListNode<T>;
_head->_next = _head;
_head->_prev = _head;
}
// 构造函数
my_list() :_head(nullptr) { empty_init(); } // 这个格式简写,符合stl的代码风格
// 增
void push_back(const T& data) { // my_list为双向循环链表
ListNode<T>* tail = _head->_prev;
ListNode<T>* newNode = new ListNode<T>(data);
tail->_next = newNode;
newNode->_prev = tail;
_head->_prev = newNode;
newNode->_next = _head;
}
private:
ListNode<T>* _head; // 注意这是哨兵位
};
list主要成员函数实现
iterator begin() {
// return iterator(_head->_next); 通过匿名对象
return _head->_next;
}
iterator end() {
return _head;
}
// 空链表初始化
void empty_init() { // 单独对哨兵头结点操作
_head = new ListNode<T>;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
// 构造函数
my_list() :_head(nullptr) { empty_init(); } // 这个格式简写,符合stl的代码风格
// lt2(lt1)
// my_list(const my_list<T>& lt)
my_list(const my_list<T>& lt) {
empty_init();
ListNode<T>* current = lt._head->_next;
while (current != lt._head) {
push_back(current->_data);
current = current->_next;
}
// 偏记忆可以通过迭代器自动识别来完成
//for (auto e : lt) {
// 通过遍历不断插入给新链表
//push_back(e);
//}
}
// 析构函数
~my_list() {
clear();
delete _head;
_head = nullptr;
}
// 增
void push_back(const T& val) { // my_list为双向循环链表
// 未复用insert
/*
ListNode<T>* tail = _head->_prev;
ListNode<T>* newNode = new ListNode<T>(val);
tail->_next = newNode;
newNode->_prev = tail;
_head->_prev = newNode;
newNode->_next = _head;
*/
// 复用insert
insert(end(), val);
}
void push_front(const T& val) {
insert(begin(), val);
}
iterator insert(iterator pos, const T& val) {
ListNode<T>* current = pos._node;
ListNode<T>* prev = current->_prev;
ListNode<T>* newNode = new ListNode<T>(val);
newNode->_prev = prev;
prev->_next = newNode;
newNode->_next = current;
current->_prev = newNode;
++_size;
return iterator(newNode); // 返回指向该节点的迭代器
}
// 删
iterator erase(iterator pos) {
ListNode<T>* prev = pos._node->_prev;
ListNode<T>* next = pos._node->_next;
prev->_next = next;
next->_prev = prev;
pos._node->_prev = nullptr;
pos._node->_next = nullptr;
--_size;
return iterator(next);
}
void pop_front() {
erase(begin());
}
void pop_back() {
erase(end()._node->_prev);
}
void clear() { // 删除所有有效节点
iterator it = begin();
while (it != end()) {
it = erase(it);
}
}
// 返回链表长度
size_t size() {
return _size;
}
List迭代器(进阶)
上面我们讲了如何定制一个不同于vector与string的迭代器,这个篇幅是在原有的基础上来完善这个迭代器
对自定义类型重载 -> 运算符
在之前的学习中我们访问对象的成员主要有两种方式 . 操作符 以及 -> 操作符
在C语言中,访问对象成员的方式取决于对象的类型。如果对象是一个结构体(structure),可以使用点操作符(.)来访问成员。如果对象是一个指向结构体的指针,可以使用箭头操作符(->)来访问成员。
假定一个场景,我们定义一个AA结构体然后在迭代器中运行,通过迭代器来访问AA里面成员
重载->代码如下
// 自定义类型 -> 读取重载
T* operator->() {
return &(_node->_data);
}
问题可能出在于迭代器对象无法直接对应到AA对象
cout << it->a1 << " " << it->a2 << endl;
实际上是
// cout << (it.operator->())->a1 << " " << (it.operator->())->a2 << endl;
const迭代器的实现
对于我们上面实现的迭代器,具有可读可写的功能但是实际应用中,我们也有只读不写的场景,也就是需要const来限定住这个迭代器然后,防止在*it时,亦或是it++导致对应链表的数据改变
// const迭代器
template<class T>
struct list_const_iterator {
// 定义self为迭代器在自己结构体中的名字
typedef list_const_iterator self;
ListNode<T>* _node;
list_const_iterator(ListNode<T>* node_adress) :_node(node_adress) {}
// 迭代器的操作符重载
self& operator++() { // ++实现向后走
_node = (*_node)._next;
// 返回当前节点
return *this;
}
self& operator--() { // 前置--实现向前走
_node = _node->_prev;
return *this;
}
const T& operator*() const{
// 重载后也可以*it来去it这个节点对应的data
return (*_node)._data;
}
// 自定义类型 -> 读取重载
const T* operator->() const {
return &(_node->_data);
}
bool operator==(const self& s) const {
return _node == s._node;
}
bool operator!=(const self& s) const {
return _node != s._node;
}
};
与普通迭代器大致一致仅仅只是这两个涉及迭代器对应的链表对象,可能受影响的函数决定,也就是说,这一块const_iterator和iterator高度相似,代码有点冗余了
迭代器的优化
参考STL中list的源代码,发现是这样子来解决的,但是这是什么意思呢?答案是通过增加模版参数,直接让编译器来读取一个模版的迭代器进行相应的选择,本质上还是两个类,减少代码量
通过了将模版设定为3个模版参数:T对应传入的类型,Reference为引用,Pointer为指针,对应了T&,T*,const T& ,const T*,实现了模板化的迭代器
struct list_iterator {
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
typedef list_iterator<T, Reference, Pointer> self;
ListNode<T>* _node;
list_iterator(ListNode<T>* node_adress) :_node(node_adress) {}
self& operator++() { // ++实现向后走
_node = (*_node)._next;
// 返回当前节点
return *this;
}
self& operator--() { // 前置--实现向前走
_node = _node->_prev;
return *this;
}
Reference operator*() const{ return (*_node)._data; }
// 自定义类型 -> 读取重载
Pointer operator->() const{ return &(_node->_data); }
bool operator==(const self& s) { return _node == s._node; }
bool operator!=(const self& s) { return _node != s._node; }
};
因为存在模版所以在list这个类里面,而且我们也需要使用普通迭代器和const迭代器,我们也需要在类域中typedef一下两个迭代器便于提高代码的可读性
list中一些有意思的延伸内容
在list类外面我们可以定义一个printf函数来统一打印list的不同类型,如int char string......
那么这个时候我们想到了通过模版来实现多类型的统一,但是这里有一个语法,我们之前也知道typename和class都可以作为模版的变量,这里就体现typename的用法,表示是“对应类型”
但是也可以用auto来替代,所以auto这个语法真爽
template<typename T>
void prinf_list(const my_list<T>& list) {
// auto it = list.begin(); 可以直接auto代替
typename my_list<T>::const_iterator it = list.begin();
while (it != list.end()) {
cout << *it << " ";
++it;
}
cout << endl;
}
那么接着如果在不同的容器当中我们是否也可以实现呢,比如在vector list string这样呢?
list<string> lt_string;
printf_container(lt_string);
vector<string> v_string;
printf_container(v_string);
list的反向迭代器
在对容器的迭代中,我们除了可以借助正向迭代器,同时也可以通过反向迭代器,这个在“list的迭代”中也有反向迭代器的使用,反向迭代器顾名思义就是反着来迭代。反向迭代器的++就是往前走,同样相应的开始就是从尾巴开始,结束在头结束。不过反向迭代器的实现需要用容器适配器,这一块的知识主要在stack and queue中
所以我们看到operator* 和 operator-> 这两个成员函数内部写了实现这个迭代图的内容
template<class iterator, class Reference, class Pointer>
class Reverse_iterator {
public:
typedef Reverse_iterator<iterator, Reference, Pointer> self;
Reverse_iterator(iterator it) :_it(it) { }
self& operator++() {
--_it;
return *this;
}
self& operator--() {
++_it;
return *this;
}
Reference operator*() const {
iterator cur = _it;
return *(--cur);
}
Pointer operator->() const {
return &(operator*()) ;
}
bool operator==(const self& s) { return _it == s._it; }
bool operator!=(const self& s) { return _it != s._it; }
private:
iterator _it;
};
这里面各成员函数,实际上是通过了创建迭代器变量_it然后找到相应的迭代器中的成员函数,再进行调用
list初步收尾
在完成上述的学习之后,list也逐渐准备收尾了,但是也没有结束,下次再见的时候就是完完全全手撕STL中的list组件
先上初步list类代码
namespace zhong {
template<class T>
struct ListNode {
T _data;
ListNode<T>* _next;
ListNode<T>* _prev;
ListNode(const T& data = T())
:_data(data)
, _next(nullptr)
, _prev(nullptr)
{}
};
// 迭代器的具体实现通过容器的底层来决定的,可以通过类来封装,借助运算符重载
// T表示类型 Reference表示引用 Pointr而表示指针
template<class T, class Reference, class Pointer>
// 模板化的迭代器
struct list_iterator {
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
typedef list_iterator<T, Reference, Pointer> self;
ListNode<T>* _node;
list_iterator(ListNode<T>* node_adress) :_node(node_adress) {}
self& operator++() { // ++实现向后走
_node = (*_node)._next;
// 返回当前节点
return *this;
}
self& operator--() { // 前置--实现向前走
_node = _node->_prev;
return *this;
}
Reference operator*() const{ return (*_node)._data; }
// 自定义类型 -> 读取重载
Pointer operator->() const{ return &(_node->_data); }
bool operator==(const self& s) { return _node == s._node; }
bool operator!=(const self& s) { return _node != s._node; }
};
// 将反向迭代器视为适配器容器
template<class iterator, class Reference, class Pointer>
class Reverse_iterator {
public:
typedef Reverse_iterator<iterator, Reference, Pointer> self;
Reverse_iterator(iterator it) :_it(it) { }
self& operator++() {
--_it;
return *this;
}
self& operator--() {
++_it;
return *this;
}
Reference operator*() const {
iterator cur = _it;
return *(--cur);
}
Pointer operator->() const {
return &(operator*()) ;
}
bool operator==(const self& s) { return _it == s._it; }
bool operator!=(const self& s) { return _it != s._it; }
private:
iterator _it;
};
template<class T>
class my_list {
public:
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
typedef Reverse_iterator<iterator, T&, T*> reverse_iterator;
typedef Reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
// 迭代器位置
iterator begin() { return _head->_next; }
// return iterator(_head->_next); 通过匿名对象
iterator end() { return _head; }
const_iterator begin() const { return _head->_next; }
// return const_iterator(_head->next)
const_iterator end() const { return _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()); }
// 空链表初始化
void empty_init() { // 单独对哨兵头结点操作
_head = new ListNode<T>;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
// 构造函数
my_list() :_head(nullptr) { empty_init(); } // 这个格式简写,符合stl的代码风格
// lt2(lt1)
my_list(const my_list<T>& lt) {
empty_init();
// 偏记忆可以通过迭代器自动识别来完成
for (auto e : lt) {
// 通过遍历不断插入给新链表
push_back(e);
}
}
// 析构函数
~my_list() {
clear();
delete _head;
_head = nullptr;
}
// 增
void push_back(const T& val) { // my_list为双向循环链表
// 复用insert
insert(end(), val);
}
void push_front(const T& val) { insert(begin(), val); }
iterator insert(iterator pos, const T& val) {
ListNode<T>* current = pos._node;
ListNode<T>* prev = current->_prev;
ListNode<T>* newNode = new ListNode<T>(val);
newNode->_prev = prev;
prev->_next = newNode;
newNode->_next = current;
current->_prev = newNode;
++_size;
return iterator(newNode); // 返回指向该节点的迭代器
}
// 删
iterator erase(iterator pos) {
ListNode<T>* prev = pos._node->_prev;
ListNode<T>* next = pos._node->_next;
prev->_next = next;
next->_prev = prev;
pos._node->_prev = nullptr;
pos._node->_next = nullptr;
--_size;
return iterator(next);
}
void pop_front() { erase(begin()); }
void pop_back() { erase(--end()); } // erase(end()._node->_prev);
void clear() { // 删除所有有效节点
iterator it = begin();
while (it != end()) {
it = erase(it);
}
}
// 返回链表长度
size_t size() { return _size; }
void swap(my_list<T>& list) {
std::swap(_head, list._head);
std::swap(_size, list._size);
}
private:
ListNode<T>* _head; // 注意这是哨兵位
size_t _size;
};
template<typename T>
void prinf_list(const my_list<T>& list) { // 能够正向打印list容器中的各种类型
auto it = list.begin();
// typename my_list<T>::const_iterator it = list.begin();
while (it != list.end()) {
cout << *it << " ";
++it;
}
cout << endl;
}
}
测试函数
void test_list1() {
list::my_list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(2);
lt1.push_back(1);
list::my_list<int>::iterator it = lt1.begin();
while (it != lt1.end()) {
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : lt1) {
cout << e << " ";
}
cout << endl;
list::my_list<int> lt2(lt1);
for (auto e : lt2) {
cout << e << " ";
}
cout << endl;
lt2.pop_back();
for (auto e : lt2) {
cout << e << " ";
}
cout << endl;
}
// const迭代器的测试
void test_list3() {
list::my_list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(2);
lt.push_back(1);
for (auto e : lt) {
cout << e << " ";
}
cout << endl;
const list::my_list<int> lt1(lt);
list::my_list<int>::const_iterator const_it = lt1.begin();
while (const_it != lt1.end()) {
cout << *const_it << " ";
++const_it;
}
cout << endl;
}
// 测试多类型的list打印函数
void test_list4() {
list::my_list<string> lt_string;
lt_string.push_back("123");
lt_string.push_back("234");
lt_string.push_back("345");
lt_string.push_back("456");
list::my_list<string>::iterator it = lt_string.begin();
while (it != lt_string.end()) {
cout << *it << " ";
++it;
}
cout << endl;
list::my_list<string> lt_string1;
lt_string1.push_back("123");
lt_string1.push_back("234");
lt_string1.push_back("345");
lt_string1.push_back("456");
list::prinf_list(lt_string1);
}
// 测试list放弃其他自定义类型
void test_list5() {
list::my_list<string> lt_string;
lt_string.push_back("456");
lt_string.push_back("345");
lt_string.push_back("234");
lt_string.push_back("123");
list::my_list<string> lt_string1;
lt_string1.push_back("123");
lt_string1.push_back("234");
lt_string1.push_back("345");
lt_string1.push_back("456");
lt_string.swap(lt_string1);
for (auto e : lt_string) {
cout << e << " ";
}
cout << endl;
for (auto e : lt_string1) {
cout << e << " ";
}
cout << endl;
}
// 测试反向迭代器
void test_list6() {
zhong::my_list<int> lt;
lt.push_back(1);
lt.push_back(3);
lt.push_back(1);
lt.push_back(4);
for (auto e : lt) {
cout << e << " ";
}
cout << endl;
zhong::my_list<int>::iterator it = lt.begin();
zhong::my_list<int>::reverse_iterator rit = lt.rbegin();
while (it != lt.end()) {
cout << *it << " ";
++it;
}
cout << endl;
while (rit != lt.rend()) {
cout << *rit << " ";
++rit;
}
cout << endl;
}
啃下一块硬骨头,接下来我们继续重生之C++学习