【C++】list模拟实现


作者简介:一名在后端领域学习,并渴望能够学有所成的追梦人。
个人主页:不 良
系列专栏:C++  Linux
学习格言:博观而约取,厚积而薄发
欢迎进来的小伙伴,如果小伙伴们在学习的过程中,发现有需要纠正的地方,烦请指正,希望能够与诸君一同成长!


文章目录

  • 定义结点类
  • lis类的成员变量和构造函数
  • list的迭代器
  • list类常用接口模拟实现
    • insert函数
    • erase函数
    • clear函数
    • 构造函数
    • 拷贝构造函数
    • 赋值运算符重载
    • 析构函数
  • 对比vector和list
  • list的反向迭代器

定义结点类

list相当于带头节点的双向链表,我们定义节点时要用类模板参数,同时定义_next_prev指针和数据_data使用struct定义类,因为节点类要能够被访问,而struct的默认访问权限就是public;构造函数缺省值要使用匿名对象,保证无论是自定义类型还是内置类型都能够构造成功。

//定义节点
template<class T>
struct list_node {
    list_node<T>* _next;
    list_node<T>* _prev;
    T _data;
    //节点构造函数,缺省值使用匿名对象
    list_node(const T& x = T())
        :_next(nullptr)
        ,_prev(nullptr)
        ,_data(x)
        {
        }
};

lis类的成员变量和构造函数

list是带头节点的双向链表,所以成员变量我们只需要定义一个头结点_head,构造函数就是让头节点指向自己。

注意成员变量类型为类名 + 模板参数

template<class T>
class list {
    typedef list_node<T> node;
    public:
    list()
    {
        _head = new node;
        _head->_prev = _head;
        _head->_next = _head;
    }
    private:
    node* _head;
};

list的迭代器

vector和string的底层物理空间是连续的,我们可以通过指针的++或–来移动找到对应的元素,然而list的底层物理空间是不连续的,所以模拟实现list迭代器时,如果使用++--操作,++--的只是一个指针,并不能找到对应的位置。我们可以封装一个list迭代器类,实际上就是对结点指针进行封装,将各种运算符进行重载,使得在list类中能够像vector和string一样能够直接使用迭代器,而不用关心底层实现。

//迭代器类
template<class T>
struct __list_iterator {
    typedef list_node<T> node;
    typedef __list_iterator<T> self;
    node* _node;
    //构造一个迭代器,用结点的指针构造
    __list_iterator(node* n)
        :_node(n)
        {

        }
    T& operator*()
    {
        return _node->_data;
    }
    self& operator++()
    {
        _node = _node->_next;
        return *this;
    }
    //不相等比较的就是结点的指针
    bool operator!=(const self& s)
    {
        return _node != s._node;
    }
};

测试:

void test_list1()
{
    list<int> l1;
    l1.push_back(1);
    l1.push_back(1);
    l1.push_back(1);
    l1.push_back(1);
    list<int>::iterator it = l1.begin();
    while (it != l1.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
}

在上面的测试代码中list::iterator it = l1.begin();这句话调用了拷贝构造,因为我们没有实现所以调用的就是编译器自动生成的即浅拷贝,这里之所以能够使用浅拷贝是因为迭代器类中没有写析构函数,而且不需要释放结点(结点的指针不属于迭代器,仅仅是使用list结点,没有拥有list结点,没有权利释放list的对象,结点的析构交给链表。迭代器只是一个工具)。

完善一些后置++、后置–之后的代码:

//迭代器类
template<class T>
struct __list_iterator {
    typedef list_node<T> node;
    typedef __list_iterator<T> self;
    node* _node;
    //构造一个迭代器,用结点的指针构造
    __list_iterator(node* n)
        :_node(n)
        {

        }
    //重载解引用操作符*
    T& operator*()
    {
        return _node->_data;
    }
    //重载前置++
    self& operator++()
    {
        _node = _node->_next;
        return *this;
    }
    //重载后置++
    self operator++(int)
    {
        self tmp(*this);
        _node = _node->_next;
        return tmp;
    }
    //重载前置--
    self& operator--()
    {
        _node = _node->_prev;
        return *this;
    }
    //重载后置--
    self operator--(int)
    {
        self tmp(*this);
        _node = _node->_prev;
        return tmp;
    }
    //不相等比较的就是结点的指针
    bool operator!=(const self& s)
    {
        return _node != s._node;
    }
    bool operator==(const self& s)
    {
        return _node == s._node;
    }
};

当我们实现上述代码之后我们就可以在list类中实现begin和end函数:

iterator begin()
{
    return iterator(_head->_next);
}
iterator end()
{
    return iterator(_head);
}

当我们想要接收const对象时就需要先实现const_iterator:如下的代码

void print_list(const list<int>& l1)
{
    list<int>::iterator it = l1.begin();
    while (it != l1.end())
    {
        (*it) *= 2;
        cout << *it << " ";
        ++it;
    }
    cout << endl;
}

我们在list类中实现下面的代码中编译能够通过:

iterator begin() const
{
    return iterator(_head->_next);
}
iterator end() const
{
    return iterator(_head);
}

但是为什么使用const修饰之后还能构造普通迭代器?这里的const修饰的是*this,也就是this指针指向的内容,但是这里this指针指向的内容还是一个指针,修饰的是指针本身即修饰的_head不能被改变,并不是_head指向的内容,指针本身不能改变,但是可以拷贝给别人,也就是说可以将这个指针传到迭代器类中。

虽然上述代码可以通过编译了,但是并不能达到我们想要的效果,仍然能够改变list中的元素值,为什么呢?因为构造出来的迭代器是普通迭代器,可读可修改。

库中const对象调用的是const函数,返回的是const迭代器。

库中函数声明:

     	iterator begin();
const_iterator begin() const;

const迭代器和普通迭代器的区别是:const迭代器本身可以修改,const迭代器指向的内容不可以被修改。

那我们可不可以用下面这种方式定义const迭代器呢?

typedef __list_iterator<T> iterator;   //T*
typedef const iterator const_iterator;  // T* const,而我们想要的是迭代器内容不能被修改,const T*

不可以,普通迭代器对标的是指针即T*,而const加在iterator前面保护迭代器本身不能被修改相当于保护指针不被修改即 T* const,而我们想要的是迭代器指向的内容不能被修改即const T*

那我们想实现const迭代器应该怎么实现呢?很简单,只需要将普通迭代器代码稍加修改:

//const迭代器类
template<class T>
struct __list_const_iterator {
    typedef list_node<T> node;
    typedef __list_const_iterator<T> self;
    node* _node;
    //构造一个迭代器,用结点的指针构造
    __list_const_iterator(node* n)
        :_node(n)
        {

        }
    //重载解引用操作符*
    const T& operator*()
    {
        return _node->_data;
    }
    //重载前置++
    self& operator++()
    {
        _node = _node->_next;
        return *this;
    }
    //重载后置++
    self operator++(int)
    {
        self tmp(*this);
        _node = _node->_next;
        return tmp;
    }
    //重载前置--
    self& operator--()
    {
        _node = _node->_prev;
        return *this;
    }
    //重载后置--
    self operator--(int)
    {
        self tmp(*this);
        _node = _node->_prev;
        return tmp;
    }
    //不相等比较的就是结点的指针
    bool operator!=(const self& s)
    {
        return _node != s._node;
    }
    bool operator==(const self& s)
    {
        return _node == s._node;
    }
};

再将list类中的begin函数和end函数重载:

const_iterator begin() const
{
    return const_iterator(_head->_next);
}
const_iterator end() const
{
    return const_iterator(_head);
}

此时我们再将print_list代码修改如下即可实现我们想要的功能:

void print_list(const list<int>& l1)
{
    list<int>::const_iterator it = l1.begin();
    while (it != l1.end())
    {
        //(*it) *= 2;
        cout << *it << " ";
        ++it;
    }
    cout << endl;
}

观察发现,const迭代器类和普通迭代器类中只有*重载运算符函数的返回值不一样,我们可以通过增加一个模板参数来控制返回值

//迭代器类
template<class T,class Ref> //增加一个模板参数
struct __list_iterator {
    typedef list_node<T> node;
    typedef __list_iterator<T,Ref> self;
    node* _node;
    //构造一个迭代器,用结点的指针构造
    __list_iterator(node* n)
        :_node(n)
        {
        }
    //重载解引用操作符*
    Ref operator*()
    {
        return _node->_data;
    }
};

然后在list类中通过模板参数控制返回值

template<class T>
	class list {
		//将节点类型重命名为node
		typedef list_node<T> node;
	public:
		//将迭代器类重命名为iterator
		typedef __list_iterator<T,T&> iterator;
		typedef __list_iterator<T,const T&> const_iterator;
	private:
		node* _head;
	}; 

【C++】list模拟实现_第1张图片

模板参数传引用或者传指针都可以,模板参数不同类型也就不同。

库里面的list迭代器类中一共有3个模板参数:

template<class T,class Ref,class Ptr>

我们要知道:

1.迭代器要么就是原生指针;

2.迭代器要么就是自定义类型对原生指针的封装,模拟指针的行为(因为node*本身的++不符合需求,不是连续的空间)。

我们还需要重载一个->,自定义类型的指针要用->,内置类型用解引用*就可以。

如下面的测试代码:一般的类最好都将默认构造加上,并且给上缺省值。

struct AA {
    int _a1;
    int _a2;
    AA(int a1 = 0, int a2 = 0)
        :_a1(a1)
        ,_a2(a2)
        {

        }
}; 
void test_list2()
{
    list<AA> l1;
    l1.push_back(AA(1, 2));
    l1.push_back(AA(1, 2));
    l1.push_back(AA(1, 2));
    list<AA>::iterator it = l1.begin();
    while (it != l1.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
}

运行结果报错信息如下:

image-20230704160949768

我们可以通过重载流插入<<和流提取>>运算符解决问题,也可以通过下面的方式访问:

struct AA {
    int _a1;
    int _a2;
    AA(int a1 = 0, int a2 = 0)
        :_a1(a1)
        ,_a2(a2)
        {

        }
}; 
void test_list2()
{
    list<AA> l1;
    l1.push_back(AA(1, 2));
    l1.push_back(AA(1, 2));
    l1.push_back(AA(1, 2));
    list<AA>::iterator it = l1.begin();
    while (it != l1.end())
    {
        cout << (*it)._a1 << " " << (*it)._a2 << endl;
        ++it;
    }
    cout << endl;
}

但是这种方法有点麻烦,当我们有一个AA* ptr类型的指针时,我们通常使用->进行解引用:

AA* ptr = new AA(3,4);
cout << ptr->_a1 << " " << ptr->_a2 << endl;

所以迭代器也要去重载->,因为模拟的是指针的行为,所以也要支持使用->解引用。

在迭代器类中实现重载->

//重载->,这里返回的相当于AA*
T* operator->()
{
    return &_node->_data;
}

然后测试代码就可以使用->:

void test_list2()
{
    list<AA> l1;
    l1.push_back(AA(1, 2));
    l1.push_back(AA(1, 2));
    l1.push_back(AA(1, 2));
    list<AA>::iterator it = l1.begin();
    while (it != l1.end())
    {
        //cout << it.operator->()->_a1  << it.operator->()->_a2 << endl;
        //可优化为下面这样:
        cout << it->_a1 << " " << it->_a2 << endl;
        ++it;
    }
    cout << endl;
}

【C++】list模拟实现_第2张图片

而当是const迭代器的时候返回值就是const T*,所以我们可以在迭代器类中再加一个模板参数Ptr用来表示->返回值:

//迭代器类
template<class T,class Ref,class Ptr>
struct __list_iterator {
    typedef list_node<T> node;
    typedef __list_iterator<T,Ref,Ptr> self;
    //重载->,这里返回的相当于AA*
    Ptr operator->()
    {
        return &_node->_data;
    }
}

在list类中通过模板参数控制返回值:

template<class T>
class list {
    //将节点类型重命名为node
    typedef list_node<T> node;
    public:
    //将迭代器类重命名为iterator
    typedef __list_iterator<T,T&,T*> iterator;
    typedef __list_iterator<T,const T&,const T*> const_iterator;
}

list类常用接口模拟实现

链表中只要实现了insert和erase,头插头删和尾插尾删都可以实现,所以我们先实现insert和erase函数。

insert函数

在pos位置之前插入一个新结点。

//在pos位置之前插入一个新结点
void insert(iterator pos,const T& val)
{
    node* cur = pos._node;
    node* prev = cur->_prev;
    node* new_node = new node(val);

    prev->_next = new_node;
    new_node->_prev = prev;
    new_node->_next = cur;
    cur->_prev = new_node;
}

list的迭代器不会失效,因为pos的指向不会改变,且它们的位置关系没有改变。

测试代码:迭代器pos的指向都是同一个位置。

void test_list1()
{
    list<int> l1;
    l1.push_back(1);
    list<int>::iterator it = l1.begin();
    while (it != l1.end())
    {
        cout << *it << " ";
        ++it;
    }
    //输出1
    cout << endl;
    list<int>::iterator pos = l1.begin();
    l1.insert(pos, 5);
    l1.insert(pos, 6);
    l1.insert(pos, 7);

    it = l1.begin();
    while (it != l1.end())
    {
        cout << *it << " ";
        ++it;
    }
    //输出5 6 7 1
    cout << endl;
}

有了插入之后头插和尾插就可以复用insert:

//尾插
void push_back(const T& x )
{
    //node* tail = _head->_prev;//这就是尾
    //node* new_node = new node(x);

    //tail->_next = new_node;
    //new_node->_prev = tail;
    //new_node->_next = _head;
    //_head->_prev = new_node;
    insert(end(), x);
}
//头插
void push_front(const T& x)
{
    insert(begin(), x);
}

erase函数

删除pos位置的结点。

void erase(iterator pos)
{
    //头结点不能被删除
    assert(pos != _head);
	//记录pos位置结点的前后结点
    node* prev = pos._node->_prev;
    node* next = pos._node->_next;

    //将pos结点移出list
    prev->_next = next;
    next->_prev = prev;
	
    //释放结点
    delete pos._node;
}

注意erase删除之后迭代器会失效。

迭代器失效是指迭代器所指向的节点失效,list中即节点被删除了,erase函数执行后,it所指向的节点被删除,因此it无效,在下一次使用it时,必须先给it赋值。

复用erase实现头删和尾删:

//尾删
void pop_back()
{
    erase(--end());
}
//头删
void pop_front()
{
    erase(begin());
}

注意引入模板参数之后类名已经不能做类型了,类名加模板参数才是类型。

**迭代器不需要关心模板参数是什么,直接使用就可以。vector的迭代器不一定是原生指针,有可能是被封装的。**我们可以通过typeid().name()函数查看类型。

【C++】list模拟实现_第3张图片

重载运算符可以由我们自己实现,关于迭代器失效问题具体问题具体对待。

list<int> l1;
//使用迭代器
list<int>::iterator it = l1.begin();
while (it != l1.end())
{
    cout << *it << " ";
    ++it;
}

//直接使用结点来定义
list_node<int>* pnode = l1._head->_next;

上面代码中分别使用迭代器和结点来直接定义,如果没有访问限定符的限制,那么上面两个一样吗?从物理空间上看一样,但是我们不能使用pnode遍历,因为++的结果不一样,*pnode是原生指针的解引用,pnode++只是加了一个指针,因为空间不连续并不一定能找到下一个结点。

既然erase之后迭代器失效,那么就要对erase进行修改,要有返回值:返回当前位置的下一个元素。

iterator erase(iterator pos)
{
    //头结点不能被删除
    assert(pos != _head);
	//记录pos位置结点的前后结点
    node* prev = pos._node->_prev;
    node* next = pos._node->_next;

    //将pos结点移出list
    prev->_next = next;
    next->_prev = prev;
	
    //释放结点
    delete pos._node;
    //返回下一个结点
    return iterator(next);
}

clear函数

清空list

void clear()
{
    iterator it = begin();
    while (it != end())
    {
        //it = erase(it);//erase会返回当前元素的下一个位置。
        erase(it++);
    }
}

我们也可以使用erase(it++),因为后置++返回的是++之前的值,erase的不是it,是返回的it的拷贝。

迭代器中后置++:

//重载后置++
self operator++(int)
{
    self tmp(*this);
    _node = _node->_next;
    return tmp;
}

构造函数

默认构造函数

要push_back的前提要有哨兵位的头结点,所以要先建立一个头结点。我们可以学库里直接建立一个empty_init函数,用来建立头节点,用来初始化,构造函数调用就可以了。

void empty_init()
{
    _head = new node;
    _head->_next = _head;
    _head->_prev = _head;
}
list()
{
    empty_init();
}

有迭代器区间的构造函数

template<class Iterator>
list(Iterator first, Iterator last)
{
    empty_init();
    while (first != last)
    {
        push_back(*first);
        ++first;
    }
}

const对象可不可以调用构造函数?

可以,const对象定义的时候没有const属性,如:const int n = 2,否则const对象怎么初始化。

拷贝构造函数

注意也要给定头节点,先进行初始化,调用empty_init函数,再通过尾插将被拷贝list内容拷贝到新容器中。

传统写法:

void empty_init()
{
	_head = new node;
	_head->_next = _head;
	_head->_prev = _head;
}
 
list(const list<T>& lt)
{
	empty_init();
	for ( auto e : lt)
	{
		push_back(e);
	}
}

现代写法:

//先实现交换函数,交换两个链表的头节点
void swap(list<T>& tmp)
{
    std::swap(_head, tmp._head);
}

list(const list<int>& lt)
{
    empty_init();//初始化
    list<T> tmp(lt.begin(), lt.end());//使用迭代器区间构造
    swap(tmp);//调用swap函数交换头节点
}

赋值运算符重载

void swap(list<T>& tmp)
{
    std::swap(_head, tmp._head);
}
//l1 = l2

list<T>& operator=(list<T> lt)
{
    swap(lt);
    return *this;
}

通过传值传参调用拷贝构造函数构造出一个临时对象,然后通过交换头节点实现赋值。不能使用引用,使用引用就相当于将l3和l1的头节点交换,就改变了l3的值,相当于两者交换了并不能正确完成拷贝。

析构函数

~list()
{
    //先将list中数据清空
    clear();
    //再将头结点删除
    delete _head;
    //指向空
    _head = nullptr;
}

对比vector和list

vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

vector list
底层结构 动态顺序表,一段连续的空间 带头结点的双向循环链表
随机访问 支持随机访问,访问任意元素的效率O(1) 不支持随机访问,访问某个元素的效率O(N)
插入和删除 任意位置插入和删除效率低,需要移动元素,时间复杂度为O(N),插入时可能需要增容(开辟新空间,拷贝元素,释放旧空间),导致效率更低 任意位置插入和删除效率高,不需要移动元素,时间复杂度为O(1)
空间利用率 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低, 缓存利用率低
迭代器 原生态指针 对原生态指针(节点指针)进行封装
迭代器失效 在插入元素时,要给所有的迭代器重新赋值,因为插入 元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 插入元素不会导致迭代器失效, 删除元素时,只会导致当前迭代 器失效,其他迭代器不受影响
使用场景 需要高效存储,支持随机访问,不关心插入删除效率 大量插入和删除操作,不关心随机访问

list的反向迭代器

正向迭代器的++运算符重载是向后走,即向着_next指针走;而反向迭代器的++运算符重载是沿着_prev方向走,我们可以通过将list迭代器的代码改造一下:修改的地方主要就是将正向迭代器的重载++运算符中改成_prev,重载--运算符中改为_next

list的反向迭代器代码如下:

//反向迭代器
template<class T, class Ref, class Ptr>
struct __list_reverse_iterator {
    typedef list_node<T> node;
    typedef __list_reverse_iterator<T, Ref, Ptr> self;
    node* _node;

    //构造一个迭代器,用结点的指针构造
    __list_reverse_iterator(node* n)
        :_node(n)
        {

        }
    Ref operator*()
    {
        return _node->_data;
    }
    Ptr operator->()
    {
        return &_node->_data;
    }
    self& operator++()
    {
        _node = _node->_prev;
        return *this;
    }
    //后置++
    self operator++(int)
    {
        self tmp(*this);
        _node = _node->_prev;
        return tmp;
    }

    self& operator--()
    {
        _node = _node->_next;
        return *this;
    }

    self operator--(int)
    {
        self tmp(*this);
        _node = _node->_next;
        return tmp;
    }
    //不相等比较的就是结点的指针
    bool operator!=(const self& s)
    {
        return _node != s._node;
    }
    bool operator==(const self& s)
    {
        return _node == s._node;
    }
};

然后在list类中将反向迭代器类重命名以及rbegin和rend函数:

template<class T>
class list {
    //将节点类型重命名为node
    typedef list_node<T> node;
public:
    void empty_init()
    {
        _head = new node;
        _head->_next = _head;
        _head->_prev = _head;
    }

    //将迭代器类重命名为iterator
    typedef __list_iterator<T,T&,T*> iterator;
    typedef __list_iterator<T,const T&,const T*> const_iterator;
    //将反向迭代器类重命名为 reverse_iterator
    typedef __list_reverse_iterator<T, T&, T*> reverse_iterator;
    typedef __list_reverse_iterator<T, const T&, const T*> const_reverse_iterator;
    list()
    {
        empty_init();
    }
    list(const list<T>& lt)
    {
        empty_init();
        for (auto e : lt)
        {
            push_back(e);
        }
    }
    iterator begin()
    {
        return iterator(_head->_next);
    }
    iterator end()
    {
        return iterator(_head);
    }

    const_iterator begin() const
    {
        return const_iterator(_head->_next);
    }
    const_iterator end() const
    {
        return const_iterator(_head);
    }
    
    //反向迭代器对应函数
    reverse_iterator rbegin()
    {
        return reverse_iterator(_head->_prev);
    }
    reverse_iterator rend()
    {
        return reverse_iterator(_head);
    }
    const_reverse_iterator rbegin() const
    {
        return const_reverse_iterator(_head->_prev);
    }
    const_reverse_iterator rend() const
    {
        return const_reverse_iterator(_head); 
    }
private:
    node* _head;
}; 

测试:

void test_list()
{
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
    lt.push_back(5);
    list<int>::iterator it = lt.begin();
    while (it != lt.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    //反向迭代器
    list<int>::reverse_iterator rit = lt.rbegin();
    while (rit != lt.rend())
    {
        cout << (*rit) << " ";
        ++rit;
    }
    cout << endl;
}

输出结果:

image-20230704211417861

上面begin和end、rbegin和rend的指向刚开始的位置如下:

【C++】list模拟实现_第4张图片

但是库中的反向迭代器是通过正向迭代器完成的, 库中这种方法使反向迭代器作为一种正向迭代器的适配器模式,可以生产出任何容器的反向迭代器,只要正向迭代器可以正常工作,那么反向迭代器就能够正常的工作。库中begin和end、rbegin和rend的指向刚开始的位置如下:

【C++】list模拟实现_第5张图片

如果是上面这样刚开始头结点的位置不能解引用即rbegin位置不能解引用,此时我们如果想要实现倒序遍历就要对*重新进行重载,在重载*函数内让其对上一个位置进行解引用。

重载解引用返回的时候,返回值是个问题,我们可以再定义两个模板参数解决。此时我们就不用再用上面那种反向迭代器实现,可以在新的头文件中定义:

#pragma once
namespace Niu {
	template<class Iterator, class Ref, class Ptr>
    struct ReverseIterator {
        typedef ReverseIterator<Iterator, Ref, Ptr> Self;
        Iterator _cur;

        //我去用正向迭代器去构造一个方向迭代器
        ReverseIterator(Iterator it)
            :_cur(it)
            {

            }
        //重载*
        Ref operator*()
        {
            Iterator tmp = _cur;//拷贝构造
            --tmp;
            return *tmp;//返回上一个结点
        }
        Self& operator++()
        {
            --_cur;
            return *this;
        }
        Self& operator--()
        {
            ++_cur;
            return *this;
        }
        bool operator!=(const Self& s)
        {
            //反向迭代器和正向迭代器都是指针可以直接判断
            return  _cur != s._cur;
        }
    };
}

在类list中定义:

typedef ReverseIterator<iterator, T&,  T*> reverse_iterator;
typedef ReverseIterator<iterator, const T&, const T*> const_reverse_iterator;

此时list类中的rbegin和rend函数也要修改:

reverse_iterator rbegin()
{
    return reverse_iterator(end());
}
reverse_iterator rend()
{
    return reverse_iterator(begin());
}

此时再测试:

void test_list()
{
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
    lt.push_back(5);
    list<int>::iterator it = lt.begin();
    while (it != lt.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    //反向迭代器
    list<int>::reverse_iterator rit = lt.rbegin();
    while (rit != lt.rend())
    {
        cout << (*rit) << " ";
        ++rit;
    }
    cout << endl;
}

测试结果:

image-20230704234036262

我们使用模板,只要实现了一个迭代器,所有的迭代器都出来了,但是要求必须是双向迭代器,支持--才能使用,如list和vector。

list可以拷贝一份正向迭代器改造成反向迭代器,但是vector不能解决,因为vector本身就是内置类型。此时我们通过模板给vector的正向迭代器可以得到vector的反向迭代器:

namespace Niu {
	template<class T>
	class vector {
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
		typedef ReverseIterator<iterator, T&, T*> reverse_iterator;
		typedef ReverseIterator<iterator, const T&, const T*> const_reverse_iterator;
		reverse_iterator rbegin()
		{
			return reverse_iterator(end());
		}
		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}
        ……
    };
}

测试:

void test_vector2()
{
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);
    //正向遍历
    vector<int>::iterator it = v.begin();
    while (it != v.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    //反向迭代器
    vector<int>::reverse_iterator rit = v.rbegin();
    while (rit != v.rend())
    {
        cout << (*rit) << " ";
        ++rit;
    }
    cout << endl;
}

测试结果:

image-20230704233816526

你可能感兴趣的:(C++,c++,list,后端)