博客主页:https://blog.csdn.net/2301_779549673
欢迎点赞 收藏 ⭐留言 如有错误敬请指正!
本文由 JohnKi 原创,首发于 CSDN
未来很长,值得我们全力奔赴更美好的生活
文章目录
一、前言
在 C++ 编程中,模拟实现标准模板库(STL)中的 list
具有重要意义和广泛的应用场景。list
作为一种常用的数据结构,其独特的特性使得在许多情况下能够提供高效和灵活的操作。
模拟实现 list
有助于深入理解其内部工作原理。通过亲手编写代码来模拟 list 的各种功能,如节点的创建、插入、删除、遍历等,可以清晰地掌握数据在链表中的存储和流动方式,这对于提升对数据结构的理解和编程能力至关重要。
在实际应用中,list 常用于需要频繁进行插入和删除操作的场景。例如,当处理动态变化的数据集合,且插入和删除操作的位置不固定时,list
的优势就凸显出来。相比其他线性数据结构如数组和向量,list
不需要移动大量元素来进行插入和删除,时间复杂度通常为常数级别。
此外,list
还适用于需要高效合并和分割数据的情况。由于其节点之间的链接关系相对独立,合并和分割操作可以相对轻松地实现。
总之,无论是为了深入学习数据结构和算法,还是为了在实际编程中灵活运用 list 来解决复杂问题,模拟实现 list
都具有不可忽视的价值。
️二、准备工作
list
中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为list中一些常见的重要接口。
因为list
是一个个节点组成的,所以对于他的每一个节点也要进行定义,代码如下:
template<class T>
struct list_node
{
T _data;
list_node<T>* _next;
list_node<T>* _prev;
// 缺省构造函数
list_node(const T& data = T())
:_data(data)
, _next(nullptr)
,_prev(nullptr)
{}
};
️三、节点结构体的实现
在模拟实现 C++ 中的 list
时,节点结构体的设计是基础。节点结构体通常包含以下几个部分:
首先是数据域,用于存储实际的数据。其数据类型可以根据具体需求灵活设定,比如整数、字符串、自定义对象等。
其次是前后指针域,分别指向链表中的前一个节点和后一个节点。这两个指针使得链表能够实现双向遍历和操作。
为了方便节点的创建和初始化,还会为节点结构体提供构造函数。构造函数可以接受数据作为参数,用于初始化数据域,并将前后指针域初始化为空指针,以确保节点在创建时处于正确的初始状态。
以下是一个简单的节点结构体示例代码:
template<typename T>
struct ListNode {
T data;
ListNode* prev;
ListNode* next;
ListNode(const T& val) : data(val), prev(nullptr), next(nullptr) {}
};
这样的节点结构体设计为后续实现 list
的各种复杂功能提供了基本的单元支持。
️四、迭代器结构体的实现
此处,大家可暂时将迭代器理解成一个指针,该指针指向Iist中的某个节点
我们可以通过下图理解其中的联系
️4.1 迭代器的模板参数
在模拟实现 list 的迭代器结构体中,通常会有多个模板参数。比如 <class T, class Ref, class Ptr>
,其中 T 表示迭代器所指向的节点中存储的数据类型。Ref 用于定义解引用操作返回的引用类型,以便在需要修改节点数据时能够进行修改。Ptr 则用于指定指针类型,例如在返回节点数据的指针时使用。
4.2 迭代器的成员变量
迭代器结构体的成员变量通常是一个指向节点的指针,比如 Node* _pnode
。这个指针用于在链表中定位当前迭代器所指向的节点,从而实现对链表的遍历和操作。
4.3 重载的运算符
4.3.1 解引用运算符
解引用运算符 operator*
用于获取迭代器所指向节点的数据。通常的实现方式是返回节点中的数据,例如 Ref operator*() { return _pnode->_val; }
,通过这种方式,可以像使用指针一样获取节点中的数据。
4.3.2 指针操作运算符
指针操作运算符 operator->
用于获取节点数据的地址。常见的实现是 Ptr operator->() { return &(_pnode->_val); }
,使得可以通过迭代器像使用指针一样访问节点数据的成员。
4.3.3 不等与相等判断运算符
不等运算符 operator!=
用于比较两个迭代器是否指向不同的节点,通常通过比较它们的指针值来实现,如 bool operator!=(const self& it) const { return _pnode!= it._pnode; }
。相等运算符 operator== 则相反,用于判断两个迭代器是否指向相同的节点。
下面是整个迭代器部分的代码:
// 迭代器 - 结构体
// 利用迭代器实现STL数据库
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*()
{
return _node->_data;
}
// 返回的是指针
Ptr operator->()
{
return &_node->_data;
}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
bool operator!=(const Self & s) const
{
return _node != s._node;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
};
️五、list 链表类的实现
首先针对链表类,我们需要定义一个头节点和一个用来计算长度的变量
private:
Node* _head;
size_t _size;
️5.1 成员变量说明头节点指针
在 list 链表类中,通常会有一个成员变量用于存储头节点的指针。这个头节点指针是整个链表的起始点,通过它可以访问到链表中的其他节点。
template<typename T>
class List {
ListNode<T>* head;
};
5.2 构造函数
- 默认构造函数会初始化头节点指针为空,表明链表初始时为空。
// 构造函数
void empty_init()
{
// 哨兵位
_head = new Node(T());
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
- 拷贝构造函数用于创建一个与现有链表完全相同的新链表。
list(const list <T>& it)
{
empty_init();
for (auto& e : it)
{
push_back(e);
}
}
- 析构函数用于释放链表中所有节点所占用的内存资源,以防止内存泄漏。在遍历链表时,依次删除每个节点。
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
auto it = begin();
while (it != end())
{
it = erase(it);
}
}
- 头插函数创建一个新节点,并将其置于链表头部。
void insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
newnode->_next = cur;
newnode->_prev = prev;
prev->_next = newnode;
cur->_prev = newnode;
}
void push_back(const T& x)
{
//Node* newnode = new Node(x);
//Node* tail = _head->_prev;
//tail->_next = newnode;
//newnode->_prev = tail;
//newnode->_next = _head;
//_head->_prev = newnode;
//++_size;
insert(end(), x);
}
- 头删函数删除链表头部的节点。
iterator erase(iterator pos)
{
assert(pos != end());
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
prev->_next = next;
next->_prev = prev;
delete pos._node;
return next;
}
void pop_back()
{
erase(end()--);
}
- 赋值将一个链表的内容赋给另一个链表。
list<T>& operator=(list<T> it)
{
swap(it);
return *this;
}
️六、注意事项
-
迭代器失效
在模拟实现 list 的过程中,需要特别注意迭代器失效的问题。当进行插入、删除等操作时,可能会导致原本指向特定节点的迭代器失效。例如,在插入节点后,原本指向插入位置之后节点的迭代器可能不再有效。为了处理迭代器失效,可以在进行可能导致失效的操作后,重新获取迭代器或者对相关迭代器进行更新。 -
构造函数细节
在构造函数中,尤其是按数量和类型构造以及迭代器构造,要确保正确地分配和初始化节点。对于内存分配,要处理可能的异常情况,以保证程序的健壮性。同时,要注意初始化节点之间的指针关系,避免出现悬空指针或循环引用等错误。 -
析构函数要点
析构函数用于释放链表中所有节点占用的内存。在实现时,要确保从链表头部开始,依次正确地删除每个节点,并将相关的指针置为空,以防止内存泄漏和野指针的出现。同时,要处理链表为空的特殊情况,避免出现非法的内存访问操作。
️整体代码
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace bit
{
// 单个节点
template<class T>
struct list_node
{
T _data;
list_node<T>* _next;
list_node<T>* _prev;
// 缺省构造函数
list_node(const T& data = T())
:_data(data)
, _next(nullptr)
,_prev(nullptr)
{}
};
// 迭代器 - 结构体
// 利用迭代器实现STL数据库
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*()
{
return _node->_data;
}
// 返回的是指针
Ptr operator->()
{
return &_node->_data;
}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
bool operator!=(const Self & s) const
{
return _node != s._node;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
};
/*template<class T>
struct list_const_iterator
{
typedef list_node<T> Node;
typedef list_const_iterator<T> Self;
Node* _node;
构造函数
list_const_iterator(Node* node)
:_node(node)
{}
const T& operator*()
{
return _node->_data;
}
返回的是指针
const T* operator->()
{
return &_node->_data;
}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
};*/
// 链表
template<class T>
class list
{
// 重命名
typedef list_node<T> Node;
public:
/*typedef list_iterator<T> iterator;
typedef list_const_iterator<T> const_iterator;*/
// 利用模板实现两个类(高度相似的两个类)
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
iterator begin()
{
return _head->_next;
}
iterator end()
{
return _head;
}
const_iterator begin() const
{
return _head->_next;
}
const_iterator end() const
{
return _head;
}
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
void empty_init()
{
// 哨兵位
_head = new Node(T());
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
list()
{
empty_init();
}
// lt2(lt1) 拷贝构造
list(const list <T>& it)
{
empty_init();
for (auto& e : it)
{
push_back(e);
}
}
// lt1 = lt3
list<T>& operator=(list<T> it)
{
swap(it);
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
auto it = begin();
while (it != end())
{
it = erase(it);
}
}
void insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
newnode->_next = cur;
newnode->_prev = prev;
prev->_next = newnode;
cur->_prev = newnode;
}
void push_back(const T& x)
{
//Node* newnode = new Node(x);
//Node* tail = _head->_prev;
//tail->_next = newnode;
//newnode->_prev = tail;
//newnode->_next = _head;
//_head->_prev = newnode;
//++_size;
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
iterator erase(iterator pos)
{
assert(pos != end());
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
prev->_next = next;
next->_prev = prev;
delete pos._node;
return next;
}
void pop_back()
{
erase(end()--);
}
void pop_front()
{
earse(begin());
}
size_t size() const
{
return _size;
}
bool empty() const
{
return _size == 0;
}
private:
Node* _head;
size_t _size;
};
struct AA
{
int _a1 = 1;
int _a2 = 1;
};
template<class Container>
void print_container(const Container& con)
{
// const iterator ->迭代器本身不能修改
// const_iterator -> 指向内容不能修改
// 如果没有typename,编译器可能会错误地将Container::const_iterator解释为一个静态成员变量或成员函数,而不是一个类型。
typename Container::const_iterator it = con.begin();
while (it != con.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : con)
{
cout << e << " ";;
}
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;
list<AA> lta;
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
lta.push_back(AA());
list<AA>::iterator ita = lta.begin();
while (ita != lta.end())
{
//cout << (*ita)._a1 << ":" << (*ita)._a2 << endl;
// 特殊处理,本来应该是两个->才合理,为了可读性,省略了一个->
cout << ita->_a1 << ":" << ita->_a2 << endl;
cout << ita.operator->()->_a1 << ":" << ita.operator->()->_a2 << endl;
++ita;
}
cout << endl;
}
void test_list2()
{
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();
lt.insert(it, 10);
*it += 100;
print_container(lt);
cout << endl;
// 删除所有的偶数
it = lt.begin();
while (it != lt.end())
{
if (*it % 2 == 0)
{
it = lt.erase(it);
}
else
++it;
}
print_container(lt);
}
void test_list3()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
list<int> lt2(lt1);
print_container(lt1);
print_container(lt2);
list<int> lt3;
lt3.push_back(9);
lt3.push_back(9);
lt3.push_back(9);
lt3.push_back(9);
lt1 = lt3;
print_container(lt1);
print_container(lt3);
}
}
总结
模拟实现 list 是一项具有挑战性但富有价值的工作。要点包括:
- 深入理解双向链表的结构和特性,确保节点结构体、迭代器结构体以及链表类的设计准确无误。
- 精心实现各种构造函数,满足不同的初始化需求,同时注意内存管理和指针操作的正确性。
- 准确编写基本操作函数,如插入、删除、遍历等,保证链表的功能完整且高效。
- 时刻关注迭代器失效问题,在相关操作后进行合理的处理和更新。
本篇博文对 list的模拟实现 做了一个较为详细的介绍,不知道对你有没有帮助呢
觉得博主写得还不错的三连支持下吧!会继续努力的~
本站资源均来自互联网,仅供研究学习,禁止违法使用和商用,产生法律纠纷本站概不负责!如果侵犯了您的权益请与我们联系!
转载请注明出处: 免费源码网-免费的源码资源网站 » 【C++】list的模拟实现
发表评论 取消回复