全部代码
#pragma once
namespace HQJ
{
template<class T>
struct __list_node//节点类
{
T __data;
__list_node<T>* __prev;
__list_node<T>* __next;
__list_node(const T& x = T())//由于不知道要存储的数据类型,使用匿名对象进行初始化
:__data(x)
, __prev(nullptr)
, __next(nullptr)
{}
};
template<class T, class REF, class PTR>
//const迭代器和普通迭代器只是在*和->运算符重载的返回值不同
//我们添加REF作为T&和const T&,PTR作为T*和const T*
struct __iterator//迭代器类,核心是节点指针
{
typedef __list_node<T> Node;
typedef __iterator<T,REF,PTR> self;
Node* _node;
__iterator(Node* node)//指向头结点
:_node(node)
{}
self& operator++()
{
_node = _node->__next;
return *this;//不是返回_node
}
self& operator++(int)
{
self tmp(_node);
_node = _node->__next;
return tmp;//不是返回_node
}
self& operator--(int)
{
self tmp(_node);
_node = _node->__prev;
return tmp;//不是返回_node
}
self& operator--()
{
_node = _node->__prev;
return *this;
}
PTR operator->()
{
return &_node->__data;
}
REF operator*()
{
return _node->__data;
}
bool operator!=(const self& s)//这里也要是迭代器参数,不能是Node*
{
return _node != s._node;
}
};
template<class T>//这里也要是模板函数,实现的时候别忘记了
class list//list类
{
public:
typedef __list_node<T> Node;
typedef __iterator<T, T&, T*> iterator;
typedef __iterator<T, const T&, const T*> const_iterator;
template<class T>
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* newnode = new Node(x);
newnode->__next = cur;
newnode->__prev = cur->__prev;
cur->__prev->__next = newnode;
cur->__prev = newnode;
return newnode;
}
const_iterator begin()const
{
return const_iterator((_head->__next));//首元节点而不是头结点
}
const_iterator end()const
{
return const_iterator(_head);//不是_head->__next,因为迭代器是左闭右开
}
iterator begin()
{
return iterator((_head->__next));//首元节点而不是头结点
}
iterator end()
{
return iterator(_head);//不是_head->__next,因为迭代器是左闭右开
}
void empty_init()
{
_head = new Node;
_head->__next = _head;
_head->__prev = _head;
}
/* template<class T>
void push_back(const T& x)
{
Node* newnode = new Node(x);
newnode->__prev = _head->__prev;
_head->__prev->__next = newnode;
_head->__prev = newnode;
newnode->__next = _head;
}*/
template<class T>
void push_back(const T& x)
{
insert(end(), x);
}
template<class T>
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_back()
{
erase(end());
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
void pop_front()
{
erase(begin());
}
bool empty()
{
return (_head->__next) == _head;
}
void swap(list<T> lt)
{
std::swap(_head, lt._head);
}
iterator erase(iterator pos)
{
assert(!empty());
Node* cur = pos._node;
Node* next = cur->__next;
cur->__prev->__next = cur->__next;
cur->__next->__prev = cur->__prev;
delete cur;
return next;
}
list<T>& operator=(const list<T>& lt)
{
swap(lt);
return *this;
}
list()
{
empty_init();
}
list(const list<T>& lt)
{
empty_init();
for (auto e : lt)
{
push_back(e);
}
}
private:
Node* _head;
};
template<typename T>//告知编译器,这是个未实例化的模板,等实例化之后再去取
void print_list(const list<T>& lt)
{
typename list<T>::const_iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << endl;
it++;
}
}
template<typename Container>//告知编译器,这是个未实例化的模板,等实例化之后再去取
void print_list(const Container& con)
{
typename Container::const_iterator it = con.begin();
while (it != con.end())
{
cout << *it << endl;
it++;
}
}
}
节点类的模拟实现
-
这是一个双向链表的节点类,包含了数据成员__data(存储节点的数据)、__prev(指向前一个节点的指针)和__next(指向后一个节点的指针)。节点类还定义了一个构造函数,在创建节点时可以传入数据值进行初始化,如果没有传入数据值,则使用默认构造函数创建一个匿名对象进行初始化。这样设计的好处在于可以在创建节点时灵活地指定要存储的数据类型,并且可以通过指针链接起各个节点,形成一个双向链表的结构。
template<class T>
struct __list_node//节点类
{
T __data;
__list_node<T>* __prev;
__list_node<T>* __next;__list_node(const T& x = T())//由于不知道要存储的数据类型,使用匿名对象进行初始化 :__data(x) , __prev(nullptr) , __next(nullptr) {}
};
迭代器类的模拟实现
template<class T, class REF, class PTR>
//const迭代器和普通迭代器只是在*和->运算符重载的返回值不同
//我们添加REF作为T&和const T&,PTR作为T*和const T*
struct __iterator//迭代器类,核心是节点指针
{
typedef __list_node<T> Node;
typedef __iterator<T,REF,PTR> self;
Node* _node;
__iterator(Node* node)//指向头结点
:_node(node)
{}
self& operator++()
{
_node = _node->__next;
return *this;//不是返回_node
}
self& operator++(int)
{
self tmp(_node);
_node = _node->__next;
return tmp;//不是返回_node
}
self& operator--(int)
{
self tmp(_node);
_node = _node->__prev;
return tmp;//不是返回_node
}
self& operator--()
{
_node = _node->__prev;
return *this;
}
PTR operator->()
{
return &_node->__data;
}
REF operator*()
{
return _node->__data;
}
bool operator!=(const self& s)//这里也要是迭代器参数,不能是Node*
{
return _node != s._node;
}
};
类型重定义
-
typedef __list_node Node; 定义了一个类型别名 Node,它表示 __list_node 类型。这样做的目的是为了简化代码中使用 __list_node 的地方,可以直接使用 Node。
-
typedef __iterator<T,REF,PTR> self; 定义了一个类型别名 self,它表示 __iterator<T,REF,PTR> 类型。同样地,这样做的目的是为了简化代码中使用 __iterator<T,REF,PTR> 的地方,可以直接使用 self。
-
使用 typedef 关键字可以为已有的类型或者组合类型定义一个新的名称,方便在代码中使用并增加可读性。
typedef __list_node<T> Node;
typedef __iterator<T,REF,PTR> self;
构造函数
-
在该构造函数中,_node(节点指针)可以指向链表中的任意一个节点,但是这里是将其直接指向头结点。头结点是一个不存储具体数据的虚拟节点,它的作用主要是简化链表的各种操作,比如在头部插入一个元素或者删除一个元素时,无需特判链表为空的情况,只需要处理头结点和第一个实际节点即可。
__iterator(Node* node)//指向头结点 :_node(node) {}
++运算符重载
-
self& operator++() 是前置自增运算符的重载函数,它用于将迭代器指向链表中的下一个节点。在函数体内,首先将 _node 指针移动到下一个节点 _node->__next,然后返回自身的引用 *this。这样做是为了支持连续的自增操作,例如 ++i1; ++i1; 。
-
self& operator++(int) 是后置自增运算符的重载函数,它与前置自增运算符功能相同,但是通过传入一个额外的 int 参数来区分前置和后置自增运算符。为了保持后置自增运算符的语义,需要创建一个临时的迭代器 tmp 来保存当前迭代器的值,然后再将迭代器指向下一个节点 _node->__next,最后返回临时迭代器 tmp。这样做是为了实现 C++ 中后置自增运算符的规定行为,即返回自增前的值。
-
需要注意的是,这两个重载函数并不直接返回节点指针 _node,而是返回迭代器对象本身的引用或临时对象。这是为了保持迭代器的一致性和语义,在使用迭代器进行链表操作时更加直观和方便。
self& operator++() { _node = _node->__next; return *this;//不是返回_node } self& operator++(int) { self tmp(_node); _node = _node->__next; return tmp;//不是返回_node }
--运算符重载
-
self& operator--(int) 是后置自减运算符的重载函数,用于将迭代器指向链表中的前一个节点。在函数体内,首先创建一个临时的迭代器 tmp 来保存当前迭代器的值,然后将 _node 指针移动到前一个节点 _node->__prev,最后返回临时迭代器 tmp。这样做是为了实现 C++ 中后置自减运算符的规定行为,即返回自减前的值。
-
self& operator--() 是前置自减运算符的重载函数,与后置自减函数功能相同,但是不需要返回临时对象。在函数体内,直接将 _node 指针移动到前一个节点 _node->__prev,然后返回自身的引用 *this。这样做是为了支持连续的自减操作,例如 --i1; --i1; 。
self& operator--(int) { self tmp(_node); _node = _node->__prev; return tmp;//不是返回_node } self& operator--() { _node = _node->__prev; return *this; }
->运算符重载
-
在函数体内,首先获取 _node 指针所指向的节点中存储的数据成员的指针 _node->__data,然后将其取地址返回。由于 _node->__data 的类型为 T,因此返回的指针类型为 T*。
-
通过重载箭头运算符,可以使得迭代器对象在使用时像指针一样简洁明了,例如 iter->data_member; 可以直接访问节点存储的数据成员 data_member。而不需要通过 (*iter).data_member; 这样的方式来访问。
PTR operator->() { return &_node->__data; }
*(解引用)运算符重载
-
在函数体内,首先获取 _node 指针所指向的节点中存储的数据成员 _node->__data,然后直接返回其值。由于返回的是数据成员的引用,因此返回类型为 REF,可能是 T& 或者 const T&,取决于节点数据成员的类型和迭代器的常量性。
-
通过重载解引用运算符,可以使得迭代器对象在使用时可以直接获取节点存储的数据,并且可以修改数据(若不是 const 迭代器)。例如 *iter = new_data; 可以直接将新的数据 new_data 赋值给节点。而不需要通过 iter._node->__data = new_data; 这样的方式来修改。
REF operator*() { return _node->__data; }
!=运算符重载
-
在函数体内,将当前迭代器对象 _node 和参数传入的迭代器对象 s._node 进行比较。如果它们指向的节点地址不相等,则返回 true,表示两个迭代器不相等;否则返回 false,表示两个迭代器相等。
-
通过重载不等于运算符,我们可以直接使用 iter1 != iter2 来判断两个迭代器是否相等,而不需要显式地比较节点地址。这样可以提高代码的可读性和简洁性。
bool operator!=(const self& s)//这里也要是迭代器参数,不能是Node* { return _node != s._node; }
list类的模拟实现
template<class T>//这里也要是模板函数,实现的时候别忘记了 class list//list类 { public: typedef __list_node<T> Node; typedef __iterator<T, T&, T*> iterator; typedef __iterator<T, const T&, const T*> const_iterator; template<class T> iterator insert(iterator pos, const T& x) { Node* cur = pos._node; Node* newnode = new Node(x); newnode->__next = cur; newnode->__prev = cur->__prev; cur->__prev->__next = newnode; cur->__prev = newnode; return newnode; } const_iterator begin()const { return const_iterator((_head->__next));//首元节点而不是头结点 } const_iterator end()const { return const_iterator(_head);//不是_head->__next,因为迭代器是左闭右开 } iterator begin() { return iterator((_head->__next));//首元节点而不是头结点 } iterator end() { return iterator(_head);//不是_head->__next,因为迭代器是左闭右开 } void empty_init() { _head = new Node; _head->__next = _head; _head->__prev = _head; } /* template<class T> void push_back(const T& x) { Node* newnode = new Node(x); newnode->__prev = _head->__prev; _head->__prev->__next = newnode; _head->__prev = newnode; newnode->__next = _head; }*/ template<class T> void push_back(const T& x) { insert(end(), x); } template<class T> void push_front(const T& x) { insert(begin(), x); } void pop_back() { erase(end()); } void clear() { iterator it = begin(); while (it != end()) { it = erase(it); } } void pop_front() { erase(begin()); } bool empty() { return (_head->__next) == _head; } void swap(list<T> lt) { std::swap(_head, lt._head); } iterator erase(iterator pos) { assert(!empty()); Node* cur = pos._node; Node* next = cur->__next; cur->__prev->__next = cur->__next; cur->__next->__prev = cur->__prev; delete cur; return next; } list<T>& operator=(const list<T>& lt) { swap(lt); return *this; } list() { empty_init(); } list(const list<T>& lt) { empty_init(); for (auto e : lt) { push_back(e); } } private: Node* _head; };
类型重定义
-
Node 是 __list_node 类型的别名,其中 __list_node 是链表节点的实现类。通过定义 Node 别名,可以使得在模板类中定义节点指针时更加方便,例如 Node* _node;。
-
iterator 是 __iterator<T, T&, T*> 类型的别名,其中 __iterator 是迭代器类的实现类,T& 是解引用后返回的数据成员类型的引用,T* 是箭头运算符返回的数据成员类型的指针。通过定义 iterator 别名,可以使得在模板类中定义迭代器变量时更加简洁,例如 iterator iter;。
-
const_iterator 是 __iterator<T, const T&, const T*> 类型的别名,其中 const T& 是解引用后返回的数据成员类型的常量引用,const T* 是箭头运算符返回的数据成员类型的常量指针。通过定义 const_iterator 别名,可以使得在模板类中定义常量迭代器变量时更加方便,例如 const_iterator iter_c;。
typedef __list_node<T> Node; typedef __iterator<T, T&, T*> iterator; typedef __iterator<T, const T&, const T*> const_iterator;
私有成员
-
一个指向链表头节点的指针
Node* _head;
构造函数和拷贝构造函数
-
默认构造函数 list() 会调用 empty_init() 函数进行初始化。empty_init() 函数实际上是一个私有函数,用于初始化链表的头节点和尾指针,将头节点的前后指针都指向自身,表示链表为空。
-
拷贝构造函数 list(const list& lt) 接受一个常量引用类型的参数 lt,表示要进行拷贝的链表。
-
在函数内部,首先调用 empty_init() 函数初始化空链表;然后通过遍历参数链表 lt,逐一将其元素插入到新的链表中,这里使用了 push_back(e) 函数将每个元素插入到链表的尾部。具体实现时,可以使用 C++11 引入的 for-range 循环,对链表 lt 进行遍历。
list() { empty_init(); } list(const list<T>& lt) { empty_init(); for (auto e : lt) { push_back(e); } }
insert函数
+- 首先,通过 pos._node 获取插入位置 pos 对应的链表节点。然后,使用 new 运算符创建一个值为 x 的新节点 newnode。
-
接下来,将新节点 newnode 的指针指向当前位置 cur,即 newnode->__next = cur。
-
然后,将新节点 newnode 的前驱指针指向当前位置的前一个节点,即 newnode->__prev = cur->__prev。
-
随后,通过修改当前位置节点 cur 的前一个节点和后一个节点的指针,将新节点 newnode 插入到链表中。将当前位置节点的前一个节点的后继指针指向新节点 newnode,即 cur->__prev->__next = newnode,然后将当前位置节点的前驱指针指向新节点 newnode,即 cur->__prev = newnode。
-
最后,返回插入的新节点 newnode。
template<class T> iterator insert(iterator pos, const T& x) { Node* cur = pos._node; Node* newnode = new Node(x); newnode->__next = cur; newnode->__prev = cur->__prev; cur->__prev->__next = newnode; cur->__prev = newnode; return newnode; }
迭代器接口
-
对于 begin() 函数,它返回一个指向链表首元节点的迭代器。由于该函数是在常量函数中定义的,因此返回的是一个 const_iterator 类型的常量迭代器。它通过 _head->__next 获取首元节点的地址,然后将其传递给 const_iterator 类的构造函数,返回一个指向首元节点的常量迭代器。
-
对于 end() 函数,它返回一个指向链表尾后位置的迭代器。同样地,由于该函数是在常量函数中定义的,因此返回的是一个 const_iterator 类型的常量迭代器。它通过 _head 获取头结点的地址,并将其传递给 const_iterator 类的构造函数,返回一个指向尾后位置的常量迭代器。
-
对于非常量版本的 begin() 和 end() 函数,它们的实现方法与常量版本相似,只不过返回的是一个 iterator 类型的迭代器,用于修改链表元素的值。因此,它们的实现中也是通过 _head->__next 和 _head 获取首元节点和尾后位置的地址,并将其传递给 iterator 类的构造函数,返回一个指向首元节点和尾后位置的迭代器。
const_iterator begin()const { return const_iterator((_head->__next));//首元节点而不是头结点 } const_iterator end()const { return const_iterator(_head);//不是_head->__next,因为迭代器是左闭右开 } iterator begin() { return iterator((_head->__next));//首元节点而不是头结点 } iterator end() { return iterator(_head);//不是_head->__next,因为迭代器是左闭右开 }
empty_init函数
-
首先,代码通过创建一个新的节点 _head 来作为链表的头结点。然后,将 _head 的 __next 和 __prev 指针都指向自身,即 _head->__next = _head 和 _head->__prev = _head。这样就形成了一个循环链表,头结点的 __next 和 __prev 都指向自身,表示链表为空。
-
这样的初始化操作是为了确保在链表为空时,头结点的 __next 和 __prev 都指向自身,以方便后续对链表的插入、删除等操作进行统一处理。同时,由于链表为空,头结点既是首元节点又是尾节点,它们之间相互指向自身,构成一个闭合的循环链表结构。
void empty_init() { _head = new Node; _head->__next = _head; _head->__prev = _head; }
push_back和push_front函数·
- push_back() 函数通过调用 insert() 函数,在链表尾部插入元素。它接受一个常量引用 x,表示要插入的元素值。在具体实现中,通过调用 end() 函数获取指向链表尾后位置的迭代器,然后将该迭代器作为参数传递给 insert() 函数,同时传递要插入的元素值 x。这样就实现了将元素插入到链表尾部的功能。
-
push_front() 函数通过调用 insert() 函数,在链表头部插入元素。它接受一个常量引用 x,表示要插入的元素值。在具体实现中,通过调用 begin() 函数获取指向链表首元节点的迭代器,然后将该迭代器作为参数传递给 insert() 函数,同时传递要插入的元素值 x。这样就实现了将元素插入到链表头部的功能。
template<class T> void push_back(const T& x) { insert(end(), x); } template<class T> void push_front(const T& x) { insert(begin(), x); }
erase函数
-
首先,代码使用断言 assert(!empty()) 来确保链表不为空,即在删除元素之前必须保证链表中至少有一个元素。
-
接着,根据传入的参数 pos,获取要删除的节点 cur 和它的下一个节点 next。这里的 pos 是一个迭代器,指向要删除的节点。
-
将 cur 的前一个节点的 __next 指针指向 cur 的下一个节点,将 cur 的后一个节点的 __prev 指针指向 cur 的前一个节点。这样就将 cur 从链表中断开了。
-
使用 delete 关键字释放内存,删除节点 cur。
-
最后,返回下一个元素的迭代器 next,作为删除操作后的迭代位置。
iterator erase(iterator pos) { assert(!empty()); Node* cur = pos._node; Node* next = cur->__next; cur->__prev->__next = cur->__next; cur->__next->__prev = cur->__prev; delete cur; return next; }
pop_back和pop_front函数
-
在具体实现中,该函数通过调用 erase() 函数来删除链表中的最后一个元素。它首先调用 end() 函数获取指向链表尾部的迭代器,然后将该迭代器作为参数传递给 erase() 函数来删除其前一个节点,从而删除链表中的最后一个元素。
-
在具体实现中,该函数通过调用 erase() 函数来删除第一个元素。它首先调用 begin() 函数获取指向链表首部的迭代器,然后将该迭代器作为参数传递给 erase() 函数来删除链表的第一个元素。
此时,由于 erase() 函数返回的是被删除节点的下一个节点的迭代器,因此返回值可以忽略。删除链表的第一个元素后,若链表不为空,则其首部会被新的首元节点所取代。
void pop_front()
{
erase(begin());
}
void pop_back()
{
erase(end());
}
clear函数
-
在具体实现中,该函数通过迭代调用 erase() 函数来逐个删除链表中的元素。它首先通过调用 begin() 函数获取指向链表首部的迭代器 it,并与 end() 的迭代器进行比较,如果不相等就执行循环操作。
-
在每次循环中,它使用迭代器 it 作为参数调用 erase() 函数来删除当前节点,并将返回值赋给迭代器 it。由于 erase() 函数返回的是被删除节点的下一个节点的迭代器,所以在循环条件中不断判断 it 是否等于 end(),可以确保及时退出循环。
-
当函数执行完毕后,链表中的所有元素都已被删除,其大小为 0。
void clear() { iterator it = begin(); while (it != end()) { it = erase(it); } }
empty函数
-
首先,代码通过 _head->__next 来获取链表的头节点的下一个节点。
-
接着,将获取到的下一个节点与链表的头节点进行比较,如果相等,即 _head->__next == _head,则表示链表为空。
-
最后,将比较结果作为布尔值返回,以表示链表是否为空。
bool empty() { return (_head->__next) == _head; }
swap函数
-
swap() 函数接受一个 list 类型的参数 lt,表示另一个要进行交换的链表。
-
在函数内部,通过调用 std::swap() 函数来交换当前链表的 _head 与参数链表 lt 的 _head。std::swap() 是标准库中的函数,用于交换两个对象的值。
void swap(list<T> lt) { std::swap(_head, lt._head); }
=运算符重载
-
operator= 函数接受一个 const list& 类型的参数 lt,表示要进行赋值的链表。
-
在函数内部,调用 swap() 函数将当前链表与参数链表 lt 进行交换。由于 swap() 函数是以引用方式传递参数的,因此实际上是交换了两个链表的头节点;同时,函数的返回值是当前链表的引用。
-
最后,将当前链表的引用作为返回值返回,以支持链式赋值操作。
list<T>& operator=(const list<T>& lt) { swap(lt); return *this; }