c++模拟实现List

本文详细介绍了C++中如何处理普通迭代器的局限性,包括包装迭代器的重载、引用规则、struct与class的区别,以及newNode、newNode(val)、newNode[3]和Noden的区别。还讨论了const迭代器的设计和反向迭代器的适配器实现方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注:写本博客的目的接在记录学习过程,学习c++的新知识点,以及巩固以前学过的知识!!!

整篇文章都比较大部分都扣的很细节,重点看

/*

知识点概括:

1.当普通迭代器不能满足我们的需求时,需要对迭代器进行包装,包装迭代器需要对++、--、==、!=、*重载。关于对==的重载,是因为迭代器创建完成后不是指针,如果不重载==,那么使用==对比的则为迭代器的地址,而迭代器的地址和容器内存放的数据的地址是不相关的。

2.在使用引用时,如果被引用的值为临时变量(如:被引用的值为函数返回产生的临时变量),那么必须使用const修饰,否则会报错,如:没有与这些操作数匹配的 "!=" 运算符、表达式必须为左值或函数指示符。

3.struct和class为同种意思,只不过struct默认都为publc。

4.new Node、new Node(val) 、new Node[n]、Node tmp的区别

5.设计迭代器根据不同的调用,返回不同的值(const 与非const) 

        template<class T,class Ref> 

此操作用来设计const迭代器

6.反向迭代器的适配器:设计以及使用
 

*/

程序结构:

list为双向头节点,在结构设计上,使用指针分别指向下一个节点与头节点,所以使用单独的struct、class来实现list的节点。

template<class T>
struct list_node {
	list_node<T>* _prev;
	list_node<T>* _next;
	T _val;
	list_node(const T& val = T()) :
		_prev(nullptr),
		_next(nullptr),
		_val(val)
	{}
};

在c++中struct也演变为了class,与class不同的是struct默认为public。

因为带有class属性,所以可以在struct中使用构造函数用于初始化。

有了struct list_node,再来个list来包含list_node的头节点以及个数

	template<class T>
	class list {
	public:
        typedef list_node<T> Node;
        //code..
    private:
        Node* _head;
        size_t size;
    }

1.构造函数

list() {
	_head = new Node;        //question1
	_head->_next = _head;
	_head->_prev = _head;
    _size=0;
}

思考:

new Node                 new Node(val)                         new Node[3];    以及Node n的区别?

答:

new Node :创建单个Node,在堆上开辟空间

new Node(val):创建单个Node,使用val作为初始化

new Node[3]:创建3个Node,成为Node 数组

Node n:局部变量Node

 2.基本的功能:

void push_back(const T& val) {
	Node* newNode = new Node(val);
	Node* tail = _head->_prev;
	tail->_next = newNode;
	newNode->_prev = tail;
	newNode->_next = _head;
	_head->_prev = newNode;
    _size++;
}

3.迭代器的设计:

代码一:

template<class T>
	class list {
	public:
        typedef list_node<T> Node;
        typedef Node* iterator;
        iterator begin(){
            return _head->next;
        }
        iterator end(){
            return _head;
        }
        
        //code..
    private:
        Node* _head;
        size_t size;
    }

代码一这种设计方式可以吗?

答:乍一看确实没有问题,但实际却不正确,由于list底层为链表,为不连续的空间,此时迭代器使用++,会使得迭代器找不到下一个Node,所以这种设计不正确。为此,需要包装迭代器,使得迭代器满足我们的需求。

template<class T, class Ref>
struct __iterator_list
{
	typedef list_node<T> Node;
	typedef __iterator_list<T> self;

	Node* _node;
	__iterator_list(Node* node) :
		_node(node)
	{

	}
	self& operator++() {
		_node = _node->_next;
		return *this;
	}
	self& operator++(int) {
		self tmp(*this);
		//self tmp(_node);//两者皆可
		_node = _node->_next;
		return tmp;
	}
	self& operator--() {
		_node = _node->_prev;
		return *this;
	}
	self& operator--(int) {
		self tmp(*this);
		//self tmp(_node);//两者皆可
		_node = _node->_prev;
		return tmp;
	}
	bool operator==(const self& val) { //question1
			while (it != ret.end()) {
				cout << *it << endl;
				it++;
			}
		return _node == val._node;
	}
	bool operator!=(const self& val) {
		return _node != val._node;
	}
	self& operator*() {
		return _node->_val;
	}
	//如果在这里使用const T &operator*(){}行不?

};


template<class T>
	class list {
	public:
        typedef list_node<T> Node;
        //code..
    private:
        Node* _head;
        size_t size;
    }

迭代器本质上是指向数据的地址,也就是迭代器使用的是数据的地址,迭代器在进行++时,操作的是数据的地址。那么包装迭代器同样也是使用的数据的地址,所以我们在__list_iterator中使用构造函数,接收地址用于初始化迭代器。

代码解释:

既然传统的迭代器++无法实现++,那么我们在迭代器中,对迭代器++进行重载:

前置++    

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

后置++

    self& operator++(int) {
        self tmp(*this);         //self tmp(_node);//两者皆可
        _node = _node->_next;
        return tmp;
    }

重载操作符*

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

思考:

1.为什么返回值为*this,因为++本身就是对迭代器的++,++不能访问里面的值,所以返回的*this,即返回的iterator这个对象

2.self tmp(*this);涉及到了拷贝构造,这里我们需要实现拷贝构造函数吗?

        答:不需要实现拷贝构造函数,c++默认的拷贝构造函数即可,tmp在这里主要是为了保存*this,即tmp就是*this,_node=_node->next后,*this会指向为下一个Node,而tmp仍然指向保存时的Node的值,此时返回tmp,即可实现后置++。

        这里还请注意区分__iterator_list 与list的拷贝构造函数,不能混为一谈,list内实现的拷贝构造函数在__iterator_list不能使用。

迭代器还应可以取值操作,把迭代器指向的值返回,为了支持此操作,需要对*进行重载,返回值为T&。

3.关于对*操作符的重载    

使用演示:

 list<int>ret;

 list<int>::iterator it = ret.begin();

cout<<*it;

还请看迭代器中的*this,迭代器中的*this属于调用*重载符吗?

不属于,因为this为指针,虽然this也是__iterator_list对象,但它终究为指针

*操作符只针对iterator对象,而非iterator的指针。

4.再来看第一点,为什么要返回*this,我们使用void可以吗?

使用void虽然不会报错,但会限制我们许多操作

现将其修改为

void operator++() {
        _node = _node->_next;
    }

然后调用

 list<int>::iterator it = ret.begin();

cout<<*++it;

 "*" 的操作数必须是指针,但它具有类型 "void"   

原因在于:cout的针对函数的使用,必须要有返回值,将返回值进行流输出,虽然*++it为int,但返回值为void,故报错

举例:

void fun(){

}

cout <<fun()<<endl;//这行代码必报错,通过这个试着去理解上面,假设换成void,是不是会报错?

在操作符重载这里,虽然我们没有使用一个变量去接收返回值,但仍需有返回值,为的是能够支持连续赋值,以及上述所说的cout流

5.为什么it.begin 与it.end()的比较不符合我们的预期呢?

list<int>::iterator it = ret.begin();声明出来的it不是指针,而是一个对象,所以使用iterator begin的地址与iterato end返回的iterator本身的地址去对比是不行的
        //00000031B42FF838
        //00000031B42FF858
        //这里两个iterator的地址,不管里面存放多少个数据,他俩的地址之差总是不会改变,所以
        //为了能使得iterator==iterator比较,我们得重载==运算符,使用Node的地址去比较

		bool operator==(const self& val) {
			return _node == val._node;
		}
		bool operator!=(const self& val) {
			return _node != val._node;
		}

重要

假设修改为bool operator==(self &val){}

调用如下:

list<int>::iterator it = ret.begin();
    while (it != ret.end()) {  //而这个比较却提示没有可用的!=重载符号?
        cout << *it << endl;
        it++;

}

目前高老师给出的答案是编译器强制报错,要求比较类的重载必须使用const修饰参数---错错错

    list<int>::iterator it = ret.begin();
    it ==ret.end;//知道为什么这样会报错了吗?
   
    我们直接使用it==ret.end()进行比较。
    因为是直接使用ret.end(),这个函数肯定会有一个返回值,这个返回值由于我们没有用东西去接收,所以返回值为一个临时变量,这个临时变量由于我们不可能会对其进行修改,所以临时变量为const类型,那么iterator it,这个it是非const类型 ret.end()的返回值创建的临时变量为const类型    非const 与 const类型进行"!="操作,由于我们没有实现"!="的非const 与const 类型比较,所以会产生错误。


    关于产生临时变量,这个是被大部分人认可的,可以通过一个测试来测试
    double a = 10.8;
    int &b = (int)a;//报错

这里,(int)a 会产生一个临时变量,用这个临时变量去初始化b,会报错为常引用的变量b不能作为左值,也就是产生的这个临时变量,是一个const类型,所以我们const int &b=(int )a便可以编译通过。而不是编译器强制报错。。。。。。。。。。

那么const 迭代器应该怎么实现?

先来思考const应该修饰什么?修饰iterator本身吗?还是修饰iterator的返回值?

typedef __iterator_list<T> const_iterator;

template<class T>
struct const__iterator_list {
    typedef list_node<T> Node;
    typedef const__iterator_list  self;

}

template<class T>
class list {
public:
    typedef list_node<T> Node;
    typedef const const__iterator_list<T> const_iterator;

}

这样写对吗?

这里的const修饰的是iterator ,既然const修饰iterator,所以 const_iterator it; it++操作还能实现吗?

不能,因为it为const类型,不能被修改,所以我们只能使用const修饰返回值。

template<class T>
struct const__iterator_list {
	typedef list_node<T> Node;
	typedef const__iterator_list<T>  self;
	Node* _node;
	__iterator_list(Node *node):
		_node(node)
	{

	}
	self& operator++() {
		_node = _node->_next;
		return *this;
	}
	self& operator++(int) {
		self tmp(_node);
		_node = _node->_next;
		return *this;
	}
	const T& operator*() const{  仅仅将这里修改
		return _node->_val;
	}

	bool operator==(const self &val) {
		return _node == val._node;
	}
	bool operator!=(const self &val) {
		return _node != val._node;
	}
};

由于其他操作都不会影响到里面的内容,仅仅对*的重载会影响,所以这里也就仅仅修改

T& operator*() const为const T& operator*() const ,返回值用const修饰。

所以现在的代码为:

template<class T>
struct const__iterator_list {
	typedef list_node<T> Node;
	typedef const__iterator_list<T>  self;
	Node* _node;
	__iterator_list(Node *node):
		_node(node)
	{

	}
	self& operator++() {
		_node = _node->_next;
		return *this;
	}
	self& operator++(int) {
		self tmp(_node);
		_node = _node->_next;
		return *this;
	}
	const T& operator*() const{  仅仅将这里修改
		return _node->_val;
	}

	bool operator==(const self &val) {
		return _node == val._node;
	}
	bool operator!=(const self &val) {
		return _node != val._node;
	}
};

template<class T>
struct __iterator_list {
	typedef list_node<T> Node;
	typedef __iterator_list<T>  self;
	Node* _node;
	__iterator_list(Node *node):
		_node(node)
	{

	}
	self& operator++() {
		_node = _node->_next;
		return *this;
	}
	self& operator++(int) {
		self tmp(_node);
		_node = _node->_next;
		return *this;
	}
	T& operator*() const{
		return _node->_val;
	}

	bool operator==(const self &val) {
		return _node == val._node;
	}
	bool operator!=(const self &val) {
		return _node != val._node;
	}
};

有没有发现这样很繁琐?

仅仅就一行代码不同,就需要将整段代码全部整下来。

有!

template<class T,class Ref>
struct __iterator_list {
	typedef list_node<T> Node;
	typedef __iterator_list<T,Ref>  self;
	Node* _node;
	__iterator_list(Node *node):
		_node(node)
	{

	}
	self& operator++() {
		_node = _node->_next;
		return *this;
	}
	self& operator++(int) {
		self tmp(_node);
		_node = _node->_next;
		return *this;
	}
	Ref operator*(){  仅仅将这里修改
		return _node->_val;
	}

	bool operator==(const self &val) {
		return _node == val._node;
	}
	bool operator!=(const self &val) {
		return _node != val._node;
	}
};

模板参数里面再添加一个参数为class Ref,使用Ref当作*的返回值,那么Ref应该怎么使用?或者说怎么定义?

template<class T>
class list {
public:
	typedef list_node<T> Node;
	typedef __iterator_list<T,T&> iterator;
	typedef __iterator_list<T, const T&> const_iterator;


//code...
}

在list里面,我们分别给T 与 Ref参数为T T&,这样在使用iterator初始化迭代器时,上面的模板参数会初始化为   typedef __iterator_list<T,T&>  self;

返回值T& operator*(){  
        return _node->_val;
    }

对于const

我们分别给T 与 Ref参数为T, const T&,这样在使用iterator初始化迭代器时,上面的模板参数会初始化为   typedef __iterator_list<T,const T&>  self;

返回值const T& operator*(){  
        return _node->_val;
    }


反向迭代器:

代码一:

template<class T, class Ref>
struct reserve__iterator_list {
	typedef list_node<T> Node;
	typedef reserve__iterator_list<T, Ref>  self;
	Node* _node;
	reserve__iterator_list(Node* node) :
		_node(node)
	{

	}
	self& operator++() {
		_node = _node->_prev;
		return *this;
	}
	self& operator++(int) {
		self tmp(_node);
		_node = _node->_prev;
		return *this;
	}
	Ref operator*() const {
		return _node->_val;
	}

	bool operator==(self& val) {
		return _node == val._node;
	}
	bool operator!=(self& val) {
		return _node != val._node;
	}
};

这样写也是没有问题的,但还是存在代码冗长。

优化:通过前面例子知道,反向迭代器的++就是正向迭代器的--,反向迭代器的--就是正向迭代器的++,因此反向迭 代器的实现可以借助正向迭代器,即:反向迭代器内部可以包含一个正向迭代器,对正向迭代器的接口进行 包装即可。

template<class Iterator,class Ref>
class reversIterator {
	
public:
	typedef reversIterator self;
	Iterator _con;
	reversIterator(Iterator it)://question1
		_con(it)
	{

	}
	Ref& operator*() {         //question2
		Iterator tmp = _con; 
		tmp--;      
		return *tmp;
	}

	self& operator++() {
		_con--;
		return *this;
	}
	self& operator--() {
		_con++;
		return *this;
	}
	bool operator==(const self& s) {
		return _con == s._con;
	}
	bool operator!=(const self& s) {
		return _con != s._con;
	}

};

}

注解:question1处的 --。

因为tmp为iterator迭代器,所以tmp--实际上是调用的iterator的--。

虽然优化后的迭代器代码长度并没有改变多少,但是这个反向迭代器不仅仅可以让list使用,还可以让string 、vector等使用,只要正向迭代器支持++ 与--即可。


    思考:

question1处,这里要使用const吗?要使用引用吗?
       
           答: 1.不用使用const ,如果接收const_iterator,那么就为const_iterator iterator, 如果接收为iterator ,那么就为iterator iterator。
          
                    2.不用const也可以,return reverse_iterator(end()),调用处为end()返回的是临时变量,为const修饰,但我们这里不是引用,而是局部变量,所以这个const会去初始化局部变量okok

            3.不能使用引用,因为return reverse_iterator(end());调用处为end()返回的是临时变量,这个临时变量不能被引用。

            4.综上只有引用才会考虑要引用的值是否为临时变量。

question2处 ,解释*重载

          答:1.反向迭代器里面包含的是正向迭代器,正向迭代器已经实现了*的重载,所以可以直接使用正向迭代器的重载。

                2.由上图可知,反向迭代器的rbegin实际上指向的是整个数据的结尾+1个位置,

那么为了使*重载能有效返回值,就需要进行--操作,但是这个--操作还不能影响反向迭代器,即不能使反向迭代器移动,所以只能创建一个tmp,拷贝一份迭代器,将拷贝的迭代器--,并且返回。

        
        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蠢 愚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值