【C++从入门到踹门】 第九篇:list的实现

本文详细介绍了如何从头开始实现C++链表,包括构造函数、迭代器的设计与重载,以及const_iterator和反向迭代器的实现。通过版本升级展示了如何逐步完善链表类,使其易于使用和适应不同场景,如const对象遍历和通用迭代器支持。
摘要由CSDN通过智能技术生成


在这里插入图片描述


list的实现

简单造个轮子(版本:1.0)

list在STL中的结构是一个带头双向链表,首先创建结点结构体作为链表单元:

template<T>
struct Listnode
{
    T _val;
    Listnode<T>* _next;
    Listnode<T>* _prev;

    //构造函数
    Listnode(const T& val=T()):_val(val),_next(nullptr),_prev(nullptr)
    {}
} 

构建链表类:

template<T>
class mylist
{
public:
    typedef Listnode<T> node;

    //构造函数
    mylist()
    {
        _head=new node;//头结点开辟结点空间
        _head->_next=_head;//尾指向自己
        _head->_prev=_head;//头指向自己
    }

    //尾插结点
    push_back(const T& x)
    {
        node* newnode=new node(x);//用结点的构造函数new一个新结点
        node* tail=_head->_prev;
        tail->_next=newnode;
        head->_prev=newnode;
        newnode->_next=_head;
        newnode->_prev=tail;
    }

private;
    node* _head;//成员变量为哨兵结点指针
};

调试看下效果:

这个刚做的轮子可以跑,但是收不回来(没有析构函数),别人也不能复制(没有拷贝和赋值重载)。。。缺的还很多,慢慢来。

为了更好的控制这个轮子,我决定先为其建立一个迭代器。

🚩迭代器

🚩list的迭代器 iterator

vector和string都是连续空间的,所以他们的迭代器可以是原生指针,其进行++和–操作可以直观的理解为地址空间上的前进和后退。

但是list的各个结点大部分情况下都不连续,其原指针的++和–操作并不会让其自主的指向另一个结点处,而是越界访问了相邻的无关空间。

所以我们需要自己创建一个迭代器类,重载其操作符,比如使用–和++操作能让结点变为自身的前后结点。

template <T>
class list_iterator
{
public:
    typedef Listnode<T> node;
    typedef list_iterator<T> self;//改个名字方便调用

    //这里依旧使用链表结点指针作为我们的成员变量
    node* _pnode;

    //构造函数
    list_iterator(node* x=nullptr):_pnode(x)
    {}
 
    //不用自己再定义拷贝构造,赋值运算符重载和析构
    //使用编译器默认的即可,先思考一下

    //前置++
    self& operator++()
    {
        _pnode=_pnode->_next;
        return *this;
    }

    //后置++  使用int占位,与前置重载产生区别
    self operator++(int)
    {
        //方法一:
        // self tmp(_pnode);//构造tmp,留下当前位置
        // _pnode=_pnode->_next;
        // return tmp;

        //方法二:
        self tmp(*this);//使用编译默认的拷贝构造
        ++(*this);//复用前置++
        return tmp; 
    }

    //前置--
    self& operator--()
    {
        _pnode=_pnode->_prev;
        return *this;
    }

    //后置--
    self opeartor--(int)
    {
        self tmp(*this);
        --(*this);
        return tmp;
    }

    bool operator==(const self& x)const
    {
        return _pnode==x._pnode;
    }

    bool operator!=(const self& x)const
    {
        return _pnode!=x._pnode;
    }

    T& operator*()
    {
        return _pnode->_val;
    }

    //虽然返回的是指针,但是使用时编译器会优化掉一个->
    T* opeartor->()
    {
        return &(_pnode->_val);
    }

};

迭代器(指针)的意义就在于指向原本的空间进行访问和修改,而无需另行开辟空间,这也就说明我们没有深拷贝的需要。所以使用编译器提供的默认的拷贝函数和赋值运算符重载即可,我们也没有释放空间的需求故不必自己写析构函数去释放空间。

始终记住一点,迭代器不是链表,不具备开辟空间和释放空间的责任,不可越俎代庖。

有了迭代器类之后,在mylist类中加入迭代器,同时创建begin()和end()函数:

template<T>
class mylist
{
public:
    typedef Listnode<T> node;
    typedef list_iterator<T> iterator;

    //此处省略1.0的函数
    ...

    //迭代器相关函数
	iterator begin()
	{
		return iterator(_head->_next);
	}
	iterator end()
	{
		return iterator(_head);
	}
private:
    node* _head;
};

有了迭代器便可以遍历链表,我们将链表输出:

🚩const_iterator

先抛出一个问题:面对const修饰的mylist对象时,是否能使用const修饰的iterator迭代器(const iterator)?

考虑下段函数,我们将传入一个对象的const引用,然后使用迭代器循环遍历输出:

void Print_mylist(const mylist<int>& l)
{
	const mylist<int>::iterator it = l.begin();
	while (it != l.end())
	{
		cout << (*it) << ' ';
		++it;
	}cout << endl;
}

由于传入的是是const对象,begin()函数需要用const修饰,于是我们特别重载一个接受const版本的begin()const函数,并且继续使用原先的迭代器。

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

由于对于const对象的链表结点只能读不能修改,于是在迭代器类(list_iterator)中重载 operator*()函数,使函数返回 const的引用。

//可读写版本
T& operator*()
{
    return _pnode->_val;
}

//只读不写版本
const T& operator*()const
{
    return _pnode->_val;
}

请问这种迭代器是否能满足要求呢?

答案是否定的❌——因为 ++it被限制住了。

原因:const修饰的是mylist链表对象,并非修饰迭代器。假如使用const修饰了迭代器,固然迭代器不会修改指向的内容,但是同样也不能使用++ --来修改自己的位置,更不必谈遍历了。

如果it不用const修饰,那么 *运算符将进入可修改的重载版本,但是这样可以对const的对象内容进行修改不符条件。

分析:实际上,我们所希望看到的const迭代器的目的是指针指向的值不能修改, 也就是重载的"->“和”*"运算符的返回值不能被修改, 指针本身的指向是可以修改的,这样才能去遍历链表.

我们依旧可以使用++和–来遍历容器(如果你不使用这两个功能, 那就不需要另外实现一个 const迭代器啦)

(不能简单地认为const修饰的对象要用const修饰的迭代器去遍历,我们应限制指针的修改行为,而不是限制指针的活动)

针对const对象的迭代器

所以我们需要制作一个新的迭代器:const_list_iterator——只是限制*和->符号(只读),但是可以左右移动遍历(++ ,–)。

template<class T>
class const_list_iterator
{
public:
	typedef Listnode<T> node;
	typedef const_list_iterator<T> self;

	//解引用
	const T& operator*()//返回const引用不能修改指向内容
	{
		return _pnode->_val;
	}

    const T* operator->();//返回的const T* 不能修改其指向的内容

    //其余用于迭代器移动的函数与list_iterator类相同
}

同时mylist函数中的begin()函数需重载一份 const_list_iterator版本

//传入的是const对象
typedef const_list_iterator<T> const_iterator

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

合并两个迭代器

上述新建的 const_list_iterator类有大部分代码是与 list_iterator类是冗余的,我们需要将两者整合起来。

⭐上面两个类的不同之处在于,当成员函数的返回是 模板参数T的引用(T*)和指针(T*)时:

list_iterator类——返回的是普通引用 👉🏻 T& 和普通指针 👉🏻 T*

const_list_iterator类——返回的const引用 👉🏻 const T& 和const指针 👉🏻 const T*

那么我们可以在 list_iterator类上多设置两个模板参数 Ref、Ptr代表引用与指针,分别在链表类中声明,两个不同的声明将会在实例化时生成区别!

版本 2.0

//迭代器类
template <class T,class Refclass Ptr>
class list_iterator
{
  
public:
    typedef Listnode<T> node;
    typedef list_iterator<T,Ref,Ptr> self;

    //Ref 的用处
    Ref operator*()
    {
        return _pnode->_val;
    }

    //Ptr的用处
    Ptr operator->()
    {
        return &_pnode->_val;
    }
  
    //其余函数不做改变
    ...
}


//链表类
template <class T>
class list
{
public:
    typedef Listnode<T> node;
    typedef list_iterator<T,T&,T*> iterator;//声明iterator实例化的模板参数类型
    typedef list_iterator<T,const T&,const T*> const_iterator;//声明const_iterator实例化的模板参数类型
  
    ...
}

很巧妙的做法!

⚠ 注意:->的重载是经过编译器实际优化过的,否则it->返回地址的话,实际使用上是 it->->_val

🚩造一个好用的轮子(版本3.0)

有了迭代器,我们再添加一些函数:

构造函数

迭代器区间构造:

template <class InputIterator>
mylist(InputIterator first,InputIterator last)
{
    _head = new node;
    _head->_next = _head;
    _head->_prev = _head;
    while (first != last)
    {
        push_back(*first);
        ++first;
    }
}

填充:

mylist(size_t n, const T& x=T())
{
    _head = new node;
    _head->_next = _head;
    _head->_prev = _head;
    while(n)
    {
        push_back(x);
        n--;
    }
}
//此构造会与上方的迭代器区间构造产生冲突——非法的间接寻址 
//因为两个int会自动匹配到迭代器构造,而对一个int类型解引用会导致非法访问。

//增加重载版本——可匹配两个int的构造
mylist(int n, const T& x = T())
{
    _head = new node;
    _head->_next = _head;
    _head->_prev = _head;
    while (n)
    {
        push_back(x);
        n--;
    }
}

拷贝构造

写法一:

//拷贝构造 -尾插
mylist(const mylist& l):_head(new node)
{
    _head->_next=_head;
    _head->_prev=_head;
    const_iterator it = l.begin();
    while (it != l.end())
    {
        push_back((it++)._pnode->_val);
    }
}

写法二:

//拷贝构造 ——复用迭代器的构造函数
mylist(const mylist& l)
{
    _head = new node;
    _head->_next = _head;
    _head->_prev = _head;
    mylist<T> temp(l.begin(), l.end());// 复用构造
    //temp与this互换哨兵结点
    swap(temp._head, _head);
}

赋值运算符重载

写法一:

//赋值重载——尾插
mylist<int>& operator=(const mylist<int>& l)
{
  
    if (this != &l)//防止自己为自己赋值
    {
        clear();
        for (auto e : l)
        {
            push_back(e);
        }
    }
    return *this;
}

写法二:

//赋值重载——在传值中复用拷贝构造
mylist<int>& operator=(mylist<int> l)
{
    swap(_head, l._head);
    return *this;
}

clear 函数

删除结点,只保留哨兵

void clear()
{
    iterator it = begin();
  
    while (it != end())
    {
        delete ((it++)._pnode);
    }
    _head->_next = _head;
    _head->_prev = _head;
}

insert 函数

iterator insert(iterator pos,const T& x)
{
    node* cur = pos._pnode;
    node* before_cur = cur->_prev;

    node* newnode = new node(x);
    newnode->_next = cur;
    newnode->_prev = before_cur;
    before_cur->_next = newnode;
    cur->_prev = newnode;

    return iterator(newnode);
}

earse 函数

iterator erase(iterator pos)
{
    //不能删除哨兵
    assert(pos != end());
    node* before_pos = pos._pnode->_prev;
    node* after_pos = pos._pnode->_next;

    before_pos->_next = after_pos;
    after_pos->_prev = before_pos;

    delete pos._pnode;
    return iterator(after_pos);
    }

push_front pop_back pop_front 函数

void push_front(const T& x)
{
    insert(begin(),x);
}

void pop_back()
{
    erase(--end());
}
void pop_front()
{
    erase(begin());
}

🚩反向迭代器 (版本4.0)

我们建立一个反向迭代器来适配所有的迭代器,而不是给mylist制造专属的反向迭代器。

//reverse_iterator.h
namespace my_std
{
    template <class Iterator, class Ref, class Ptr>
    class reverse_iterator
    {
    public:
        typedef reverse_iterator<Iterator,Ref,Ptr> self;
  
        //构造函数
        reverse_iterator(Iterator it):current(it)
        {}

        // *运算符重载
        Ref opeartor*()
        {
            Iterator tmp(current);
            return *(--tmp);//保证反向迭代器使用的左闭右开
        }

        //-> 运算符重载
        Ptr operator->()
        {
            Iterator tmp(current);
            return &operator*();
        } 

        //前置++
        self& operator++()
        {
            --current;
            return *this;
        }

        //前置--
        self& operator--()
        {
            ++current;
            return *this;
        }

        bool operator==(const self& it)
        {
            return current==it.current;
        }

        bool operator!=(const self& it)
        {
            return current!=it.current;
        }

    private:
        Iterator current;//反向迭代器的本质实为正向迭代器的反向操作
    };


}

于是我们可以在链表类中添加反向迭代器

#include "reverse_iterator.h"
template<class T>
class mylist
{
public:
	typedef Listnode<T> node;

	//迭代器
	typedef list_iterator<T,T&,T*> iterator;
	typedef list_iterator<T,const T&,const T*> const_iterator;

	//反向迭代器
	typedef my_std::reverse_iterator<iterator, T&, T*> reverse_iterator;
	typedef my_std::reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;

    ...

    //首尾 反向迭代器   
	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());
	}

    ...
}

测试:

这样适配的反向迭代器同时可以使用到别的容器迭代器中:

我们用上次所写的myvector.h函数来演示:

//myvector.h
#include "reverse_iterator.h"
template<class T>
class myvector
{
public:
	typedef T* iterator;
	typedef const T* const_iterator;
	typedef my_std::reverse_iterator<iterator, T&, T*> reverse_iterator;
	typedef my_std::reverse_iterator<const_iterator,const T&,const T*> const_reverse_iterator;

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

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

    ...
}

代码全貌

list的重点在于迭代器类,迭代器与链表的成员变量皆为结点指针,但是不同的方法,将给予类不同的运作方式,可以好好咀嚼一下感受类型的力量!
已上传至gitee👉🏻mylist


青山不改 绿水长流

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值