目录
3.iterator和const_iterator的区别实现
一.前言
本文将通过模拟实现c++中的list模板中一些常用的函数功能,具体包含push_back,pop_back等常用函数的模拟,但并不考虑到所有的成员函数,仅通过模拟以达到学习的目的。
本文共四部分,一部分用来构建list,了解其底层实现原理;第二部分是自己构建迭代器,也是本文的重点和难点,涉及到封装和模板类的问题;第三部分则是一切就绪后对具体功能的实现;最后一部分则为源码的分享(编译器环境为VS2019)。
二.list构建
链表的构建和C语言的链表很相似,不过多了封装和类模板的概念,对于其构建,C++中设置的是双向带头链表。
对此,我们需要一个链表,里面放一个头节点,用来指向链表的头和尾;另一部分则是对节点设置,其成员函数除了需要本身存储的数据外,还要指向下一个和上一个节点的指针。具体实现如下:
//节点
template<class T>
struct List_Node
{
List_Node<T>* _next;
List_Node<T>* _prev;
T _val;
// 使用new初始化节点
List_Node(const T& val = T())
:_next(nullptr)
, _prev(nullptr)
, _val(val)
{}
};
//链表
template<class T>
class list
{
typedef List_Node<T> Node;
// 此为迭代器
typedef __list_iterator<T> iterator;
private:
Node* _head;
// 链表的大小可以通过遍历链表获取
// 此处为了方便直接设置
size_t _sz;
};
三.迭代器设置
重新对迭代器设置因为迭代器本身是指针,由于链表并不能像string和vector那样指向一段连续的数据块,不能直接地对迭代器++或--达到修改和遍历的目的,因此我们需要重新设置将其正确的指向每块不连续空间的节点。
1.迭代器模板
迭代器作为指针要指向开辟的数据块,因此成员仅需要一个节点指针即可。(节点中有双向指针,可以找到下个或上个节点,达到双向遍历的目的)
//迭代器
template<class T>
struct __list_iterator
{
typedef List_Node<T> Node;
typedef __list_iterator<T> iterator;
Node* _node;
};
2.迭代器功能实现
①构造函数
迭代器构造函数的设置有两种,一个为默认构造函数(含初始化),另一个为拷贝构造函数,这两个是使用迭代器时常见的两种构造。构造后的迭代器就是给的节点的指针。
//初始化构造
__list_iterator(Node* node = nullptr)
:_node(node)
{}
//拷贝构造
__list_iterator(const iterator& l)
{
_node = l._node;
}
你可能会疑惑为什么迭代器没有析构函数,因为迭代器仅仅相当于用于访问数据的工具,具体的销毁工作还是得靠链表本身来完成。如果设置了析构函数销毁节点,可不仅仅只会出现析构两次这样糟糕的情况了。
②重载++、--
此处的重载有一点需要注意:返回值,注意到前置的返回值为迭代器的引用,而后置的迭代器返回的是值,因为如果后置中返回的值是临时变量,如果传引用返回该引用会在函数栈帧销毁时一起销毁。++与--的区别不过将成员指针指向下一个和上一个。
// 前置++
iterator& operator++()
{
_node = _node->_next;
return *this;
}
// 后置++
iterator operator++(int)
{
iterator tmp = *this;
_node = _node->_next;
return tmp;
}
// 前置--
iterator& operator--()
{
_node = _node->_prev;
return *this;
}
//后置--
iterator operator--(int)
{
iterator tmp = *this;
_node = _node->_prev;
return tmp;
}
③重载==、!=
bool operator==(const iterator& l) const
{
return _node == l._node;
}
bool operator!=(const iterator& l) const
{
return !(*this == l);
}
④重载*
解指针取出保存的数据,再用.访问保存的类中的内容。
// 解指针
T& operator*()
{
// 返回节点内容即可
return _node->_val;
}
⑤重载->
此处有一点非常值得注意,->重载后取出的数据为保存类的指针,如果要继续访问其中的内容,需要连续使用两个->,也就是->->。但并不是,我们平常使用指针时,也不过只使用了一次->,其实编译器为了运算符重载的可读性,对此就进行了特殊处理,省略了其中一个。
T* operator->()
{
// 返回节点地址
return &_node->_val;
}
3.iterator和const_iterator的区别实现
const_iterator的实现与iterator大有区别,也许你会像如下设置const迭代器:
//迭代器
template<class T>
struct __list_iterator
{
typedef List_Node<T> Node;
typedef __list_iterator<T> iterator;
typedef const __list_iterator<T> const_iterator;
Node* _node;
};
如果仍然像string或const那样设置const迭代器——在前面直接加const,就大错特错了。因为
迭代器需要有迭代的功能,如果给迭代器加上const,那么++和--都将出错,有两种解决办法:
第一种:重新构建一个const模板类
template<class T>
struct __list_const_iterator
{
typedef list_node<T> Node;
//...各种模板函数
Node* _node;
};
这样设计起来实在是过于冗余,完全没有必要,因此我们一般使用第二种方式设置const迭代器,其本质和上面一样,不过更简便。
第二种:增加类模板参数
// Ref 为T&,或const T&
// Ptr 为T*,或const T*
template<class T, class Ref, class Ptr>
struct __list_iterator
{
typedef List_Node<T> Node;
typedef __list_iterator<T, Ref, Ptr> iterator;
//T&
Ref operator*()
{
return _node->_val;
}
//T*
Ptr operator->()
{
return &_node->_val;
}
//...其他不变
Node* _node;
};
在链表类中也应该这样定义:
//链表
template<class T>
class list
{
typedef List_Node<T> Node;
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
private:
Node* _head;
size_t _sz;
};
四.模板函数功能实现
1.构造函数和析构函数
①构造函数
带头双向链表不论在何种情况下都需要构建头节点,我们可以先构建一个函数以达到初始化头节点的目的。
void init()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_sz = 0;
}
再实现构造函数,共实现了以下四种构造函数,要点已在代码中提到,其中的尾插函数将在后面实现:
//默认构造函数
list()
{
init();
}
//此处n的参数若为size_t型,则会在使用int时与使用迭代器的构造函数出现分歧
//list(size_t n, const T& value = T())
list(int n, const T& value = T())
{
init();
size_t i = 0;
while (i < n)
{
push_back(value);
i++;
}
}
template <class Iterator>
list(Iterator first, Iterator last)
{
init();
while (first != last)
{
push_back(*first);
first++;
}
}
//拷贝构造
list(const list<T>& l)
{
init();
for (auto& i : l)
{
push_back(i);
}
}
②析构函数
析构函数需要遍历链表将每个节点删除,最后删除头节点。
//析构函数
~list()
{
iterator i = begin();
while (i != end())
{
//使用erase需要传递新的迭代器防止迭代器失效
i = erase(i);
i++;
}
_sz = 0;
// 以上代码和clear函数一致,可直接调用clear函数
delete _head;
}
2.设置迭代器
头节点的下一个就是第一个数据块,自己本身则为最后一个节点。其中返回_head也不会出错,系统会自动调用构造函数返回参数为_head的迭代器。
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return _head;
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return _head;
}
3.operator=(重载赋值)
//赋值
list<T>& operator=(list<T> l) //此处不使用常量引用方便拷贝构造后直接交换
//list<T>& operator=(const list<T>& l)
{
swap(l);
return *this;
}
void swap(list<T> l)
{
std::swap(_head, l._head);
std::swap(_sz, l._sz);
}
4.size函数(获取大小)
size_t size() const
{
return _sz;
}
5.empty函数(判断空)
bool empty() const
{
return _sz == 0;
}
6.clear函数(清空)
使用迭代器遍历清空,不删除头节点。
void clear()
{
iterator& i = begin();
while (i != end())
{
i = erase(i);
i++;
}
_sz = 0;
}
8.insert函数(插入)和erase函数(删除)
插入或删除指定迭代器位置的数据,和带头双向链表一样,先创建一个新节点,再将前一个和当前节点的内容修改即可。需要注意的是二者都会导致迭代器失效,如果要继续使用迭代器,请使用返回更新后的迭代器。
iterator insert(iterator pos, const T& x)
{
Node* newnode = new Node(x);
Node* cur = pos._node;
Node* prev = cur->_prev;
newnode->_next = cur;
newnode->_prev = prev;
cur->_prev = newnode;
prev->_next = newnode;
_sz++;
return newnode;
}
iterator erase(iterator pos)
{
assert(pos != end());
Node* cur = pos._node;
Node* next = cur->_next;
Node* prev = cur->_prev;
prev->_next = next;
next->_prev = prev;
delete cur;
--_sz;
return next;
}
7.push函数(头插、尾插)和pop函数(头删、尾删)
可以按照往常一样,手搓出来各种功能,不过我们前面实现了insert和erase,为什么不直接调用呢。(懒)
void push_back(const T& x)
{
/*Node* newnode = new Node(x);
newnode->_next = _head;
newnode->_prev = _head->_prev;
_head->_prev->_next = newnode;
_head->_prev = newnode;*/
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
五.源码(含部分测试用例)
使用了命名空间封装了自己模拟的list类。
#pragma once
#include <iostream>
#include <assert.h>
namespace List
{
//节点
template<class T>
struct List_Node
{
List_Node<T>* _next;
List_Node<T>* _prev;
T _val;
//使用new初始化节点
List_Node(const T& val = T())
:_next(nullptr)
, _prev(nullptr)
, _val(val)
{}
};
//迭代器
template<class T, class Ref, class Ptr>
struct __list_iterator
{
typedef List_Node<T> Node;
typedef __list_iterator<T, Ref, Ptr> iterator;
//初始化构造
__list_iterator(Node* node = nullptr)
:_node(node)
{}
//拷贝构造
__list_iterator(const iterator& l)
{
_node = l._node;
}
Ref operator*()
{
return _node->_val;
}
Ptr operator->()
{
return &_node->_val;
}
iterator& operator++()
{
_node = _node->_next;
return *this;
}
iterator operator++(int)
{
iterator tmp = *this;
_node = _node->_next;
return tmp;
}
iterator& operator--()
{
_node = _node->_prev;
return *this;
}
iterator operator--(int)
{
iterator tmp = *this;
_node = _node->_prev;
return tmp;
}
bool operator==(const iterator& l) const
{
return _node == l._node;
}
bool operator!=(const iterator& l) const
{
return !(*this == l);
}
Node* _node;
};
//链表
template<class T>
class list
{
typedef List_Node<T> Node;
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
public:
//构造函数
list()
{
init();
}
//此处n的参数若为size_t型,则会在使用int时与使用迭代器的构造函数出现分歧
//list(size_t n, const T& value = T())
list(int n, const T& value = T())
{
init();
size_t i = 0;
while (i < n)
{
push_back(value);
i++;
}
}
template <class Iterator>
list(Iterator first, Iterator last)
{
init();
while (first != last)
{
push_back(*first);
first++;
}
}
//拷贝构造
list(const list<T>& l)
{
init();
for (auto& i : l)
{
push_back(i);
}
}
void init()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_sz = 0;
}
//析构函数
~list()
{
iterator i = begin();
while (i != end())
{
//使用erase需要传递新的迭代器防止迭代器失效
i = erase(i);
i++;
}
delete _head;
_sz = 0;
}
//赋值
list<T>& operator=(list<T> l) //此处不使用常量引用方便拷贝构造后直接交换
//list<T>& operator=(const list<T>& l)
{
swap(l);
return *this;
}
void swap(list<T> l)
{
std::swap(_head,l._head);
std::swap(_sz, l._sz);
}
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return _head;
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return _head;
}
void push_back(const T& x)
{
/*Node* newnode = new Node(x);
newnode->_next = _head;
newnode->_prev = _head->_prev;
_head->_prev->_next = newnode;
_head->_prev = newnode;*/
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
iterator insert(iterator pos, const T& x)
{
Node* newnode = new Node(x);
Node* cur = pos._node;
Node* prev = cur->_prev;
newnode->_next = cur;
newnode->_prev = prev;
cur->_prev = newnode;
prev->_next = newnode;
_sz++;
return newnode;
}
iterator erase(iterator pos)
{
assert(pos != end());
Node* cur = pos._node;
Node* next = cur->_next;
Node* prev = cur->_prev;
prev->_next = next;
next->_prev = prev;
delete cur;
--_sz;
return next;
}
size_t size() const
{
return _sz;
}
bool empty() const
{
return _sz == 0;
}
void clear()
{
iterator& i = begin();
while (i != end())
{
i = erase(i);
i++;
}
_sz = 0;
}
void test_list1()
{
list<T> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
for (auto& i : lt)
{
std::cout << i << ' ';
}
std::cout << std::endl;
list<T> New(lt);
for (auto& i : New)
{
std::cout << i << ' ';
}
std::cout << std::endl;
lt.pop_back();
lt.pop_back();
for (auto& i : lt)
{
std::cout << i << ' ';
}
std::cout << std::endl;
}
void test_list2()
{
list<T> lt;
for (size_t i = 0; i < 5; i++)
lt.push_front(i);
for (auto& i : lt)
{
std::cout << i << ' ';
}
std::cout << std::endl;
for (size_t i = 0; i < 2; i++)
lt.pop_front();
lt.pop_back();
for (auto& i : lt)
{
std::cout << i << ' ';
}
std::cout << std::endl;
}
void test_list3()
{
list<T> lt(10, 5);
std::cout << lt.size() << std::endl;
for (auto& i : lt)
{
std::cout << i << ' ';
}
std::cout << std::endl;
list<T> New1(lt);
New1 = *this;
for (auto& i : New1)
{
std::cout << i << ' ';
}
std::cout << std::endl;
list<T> New2(lt.begin(), lt.end());
for (auto& i : New2)
{
std::cout << i << ' ';
}
std::cout << std::endl;
}
private:
Node* _head;
size_t _sz;
};
};