【list】手撕C++ list!从0到1实现双向链表,迭代器、const迭代器、模板全解析,面试官都惊呆了!

list

不支持通过下标访问(operator ),不支持下标加减,支持++和--(访问下一个和前一个)






迭代器的分类

  1. 按照功能
    1. iterator
    2. reverse_iterator
    3. const iterator
    4. consr reverse_iterator
  2. 按照性质:
    1. 单向(forward iterator):forward_list/unordered_map/unordered_set------>只支持++
    2. 双向(bidirectional iterator):list/map/set------>支持++/--
    3. 随机(random access iterator):string/vector/deque------>支持++/--/+/-
  • sort:只支持随机迭代器

    template <class RandomAccessIterator> void sort (RandomAccessIterator first, RandomAccessIterator last);

  • reverse :支持随机和双向

    template <class BidirectionalIterator>

    void reverse (BidirectionalIterator first, BidirectionalIterator last);

  • find:都可(是InputIterator,是单向迭代器的子类)






emplace_back

  • emplace_back和push_back的区别
cpp 复制代码
struct A
{
public:
    A(int a1=1,int a2=1)
        :_a1(a1)
        ,_a2(a2)
    { }

private: 
    int _a1;
    int _a2;
};

void test()
{
    list<A>lt;
    A a(1, 1);

    lt.push_back(a);
    lt.push_back(A(3, 4));//支持匿名对象

    lt.emplace_back(a);
    lt.emplace_back(A(3, 4));//支持匿名对象
    lt.emplace_back( 3, 4 ); //支持直接传构造A的参数

}





实现中间插入

list 是不支持迭代器的+/-找到下标的,但我们也可以先通过其他方式找到下标,再insert




  • 在下标为3的位置前面插入
cpp 复制代码
void test1()
{
    list<int>lt;

    lt.push_back(1); 
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
    lt.push_back(5);

    auto it = lt.begin();

    int k = 3;

    while (k--)
    {
        it++;
    }

    lt.insert(it, 66);

    for (auto e : lt)
    {
        cout << e << " ";
    }
    cout << endl;


}

int main() 
{ 
    test1();
    return 0;
}





erase

  • 删除4:
    • 要判断:(it!=lt.end()),因为如果find没有找到,会返回最后一个迭代器
cpp 复制代码
    it = find(lt.begin(), lt.end(), 4);
    if(it!=lt.end())
    {
        lt.erase(it);
    }





sort排序:升/降

  • 升序: lt.sort( );

  • 降序:

    greater<int>gt; ​ lt.sort(gt);

    或者直接写成: lt.sort(greater<int>())匿名对象






merge:合并链表

  • A和B都是有序的才能合并
  • A.merge(B)后**,B就没了**
cpp 复制代码
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>lt2;

lt2.push_back(3);
lt2.push_back(4);
lt2.push_back(5); 
lt2.push_back(6);

lt.merge(lt2);

for (auto e : lt)
{
    cout << e << " ";
}
cout << endl;





unique:去重

  • 必须是有序的才能使用(相同的值都挨在一起)------>先sort再去重
cpp 复制代码
    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>lt2;

    lt2.push_back(3);
    lt2.push_back(4);
    lt2.push_back(5); 
    lt2.push_back(6);

    lt.merge(lt2);

 
    for (auto e : lt)
    {
        cout << e << " ";
    }
    cout << endl;



    lt.unique();

    for (auto e : lt )
    {
        cout << e << " ";
    }
    cout << endl;
cpp 复制代码
1 2 3 3 4 4 5 5 6
1 2 3 4 5 6





remove:删除

传的是值,不是迭代器

cpp 复制代码
    lt.remove(2);
    lt.remove(3);





splice:转移元素

3种用法:(被转移的结构的对应元素会消失)

cpp 复制代码
entire list (1)	//转移整个链表
	void splice (iterator position, list& x);
single element (2)	//转移单个迭代器位置的元素
	void splice (iterator position, list& x, iterator i);
element range (3)	//转移某个范围
	void splice (iterator position, list& x, iterator first, iterator last);



举例:

  • 构建一个1,2,3,4,5,6的链表:

    cpp 复制代码
        list<int>lt;
      
        lt.push_back(1); 
        lt.push_back(2);
        lt.push_back(3);
        lt.push_back(4); 
        lt .push_back(5); 
        lt .push_back(6);



  • 将某个数字移到开头:
cpp 复制代码
    int x = 0;
    cin >> x;

    auto it = find(lt.begin(),lt.end(), x);

    ///void splice (iterator position, list& x, iterator i);
    lt.splice(lt.begin(), lt, it);
    



  • 将某个数字及其之后的所有数字移到开头:
cpp 复制代码
    int x = 0;
    cin >> x;

    auto it = find(lt.begin(),lt.end(), x);

    ///void splice (iterator position, list& x, iterator first, iterator last);
    lt.splice(lt.begin(), lt, it, lt.end());





实现

list.h

在namespace lcj中,创建class list






  • 先实现链表节点list_node,为一个结构体(类),其中存 内容,上一个节点指针,下一个节点指针
  • 实现list类:list
cpp 复制代码
namespace lcj
{
	template<class T>
	class list_node
	{
		T _data;
		list_node<T>* _prev;
		list_node<T>* _next;

	};


	template<class T>
	class list 
	{
		typedef list_node<T> Node;

	public: 

	private:
		Node* _head;

	};
}





1.构造函数:

  1. 创建节点
  2. 将节点中的前后指针加上

_head = new Node; ​ _head->_next = _head; ​ _head->_prev = _head;

cpp 复制代码
namespace lcj
{
	template<class T>
	class list_node
	{
		T _data;
		list_node<T>* _prev;
		list_node<T>* _next;

	};


	template<class T>
	class list 
	{
		typedef list_node<T> Node;

	public:
		list()//////////////////////////////////////////////////////////////
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;

		}

	private:
		Node* _head;

	};
}





2.实现尾插:

实现size()和empty()

为了计数方便,增加_size

cpp 复制代码
	template<class T>
	class list 
	{
		typedef list_node<T> Node;

	public: 

	private:
		Node* _head;
		size_t _size;/////////////////////////////////////////////////////////////

	};



push_back

cpp 复制代码
	template<class T>
	class list 
	{
		typedef list_node<T> Node;

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

		void push_back(const T& x)
		{
			Node* newnode = new Node(x);


			Node* tail = _head->_prev; // 先拿到原来的尾节点

			newnode->_next = _head;    // 新节点的后继指向头节点
			newnode->_prev = tail;     // 新节点的前驱指向旧尾

			tail->_next = newnode;     // 旧尾的后继指向新节点
			_head->_prev = newnode;    // 头节点的前驱指向新节点  

			++_size;
		}

		size_t size() const///////////////////////////
		{
			return _size;
		}

		bool empty() const//////////////////////////////
		{
			return _size == 0;
		}

	private:
		Node* _head;
		size_t _size;

	};





3.实现迭代器

为什么要自己实现一个迭代器,因为list的节点在内存中不是线性存储的。如果是原来迭代器的一些操作,比如++、--,可能造成访问越界,所以我们要自己实现一个类,从而实现List的迭代器对应的操作

运算符重载函数,是专门为类类型准备的------>参数至少有一个自定义类型:不能参数全是 int、double 这种内置类型, 防止你修改 C++ 原本的语法。






代码+逐句讲解‼️

cpp 复制代码
	template<class T>
	struct list_iterator//迭代器的类
	{
		typedef list_node<T> Node;//简化节点名称
        
        
        
		Node* _node;//这是整个迭代器这个类中   唯一的成员变量
        //为什么要有这个成员变量呢?
        //因为我们知道迭代器的作用就是返回一个节点的指针,
        //所以我们创造这个成员变量,
            //然后呢,在后面的操作中直接对这个指针进行修改,
           // 然后可以直接返回它
        
        
        
        
		typedef list_iterator Self; //简化迭代器这个名字,在迭代器最低的类中,就叫self

        
        
        
		list_iterator(Node* node)//迭代器这个类的 构造函数------>支持list类中的这样  iterator it(_head->_next);的使用。用来在类外把对应的节点指针变成迭代器
			:_node(node)
		{ }


		T& operator*()//*解引用的运算符重载,直接返回list对应的数据_data
		{
			return _node->_data; 
		}

		Self  operator++()//返回下一个节点的迭代器,返回的是迭代器这个类, // 必须返回自身引用,符合STL迭代器规范
		{
			_node = _node->_next;
			return *this;
		}
        
        Self&  operator++()
		{
			_node = _node->_next;
			return *this;
		}

		Self&  operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}

		bool operator==(const Self& s)const
		{
			return _node == s._node;
		}
	};

struct 和class:

  1. struct类,不加访问限定符,纯公有
  2. class,不加,纯私有

1.为什么list_node用 struct?

节点类是一个纯数据结构 ,它的三个成员_data_prev_next,需要被list类和list_iterator类随时随地直接读写。

2. 为什么list_iterator用 struct?

它的核心成员_node指针,需要被list类的成员函数(begin()end()insert()erase())直接访问。比如:

​ ``bool operator!=(const Self& s){return _node != s._node; ​ }




把list_node改为struct

cpp 复制代码
	struct  list_node
	{
		T _data;
		list_node<T>* _prev;
		list_node<T>* _next;

	};








test_list1()

cpp 复制代码
	void test_list1()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);

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

		while (it != lt.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

push_back上面说了,

接下来,想要实现:list<int>::iterator it = lt.begin();,就要先实现

  1. 迭代器iterator
  2. begin函数
  • Iterator是我们自己定义的一个类:struct list_iterator//迭代器,然后再list这个类中typedef出来的名称

    • 我们是这样创建list的迭代器的,list<int>::iterator it = lt.begin();,但看起来这个iterator它是在list类这个类里面的,可是我们知道并不是这样,

      ------>因为list类内部有一个typedef(类型别名),把外部的list_iterator<T>重命名成了内部的iterator

      ------>编译器会自动把它翻译成: list_iterator<int> it = lt.begin();






构造函数的对比

  • 每一个类都有对应的构造函数

    • 构造一个节点的时候

      cpp 复制代码
      		list_node(const T& data = T())
      			:_data(data)
      			,_next(nullptr)
      			,_prev(nullptr)
      		{ }
    • 构造一个list的时候

      cpp 复制代码
      		list()
      		{
      			_head = new Node(T());
      			_head->_next = _head;
      			_head->_prev = _head;
      			_size = 0; 
      		}
    • 构造一个迭代器的时候

      因为我们在实现begin、 and的时候,一般是这样的:

      iterator it(_head->_next);

      return it;

      所以迭代器的构造函数要是带参的

      cpp 复制代码
      		list_iterator(Node* node)
      			:_node(node)
      		{ }

4.前一部分总代码+逐句讲解‼️

下面的end函数使用了隐式类型转换,相关知识请看这里

指针类型直接转化成了一个iterator这个类类型,相当于内置类型转化成类类型, 条件就是类有对应的构造函数就可以

cpp 复制代码
namespace lcj
{
    
    
    
    /////////////////////////////////////////////////////////////////////////////////////////////
	template<class T>
	struct  list_node//节点的类
	{
		T _data;
		list_node<T>* _prev;
		list_node<T>* _next;

		list_node(const T& data = T())//节点的实现,是一个默认 构造函数, 并且同时支持你传参和不传参都可以生成对应的节点
			:_data(data)
			,_next(nullptr)
			,_prev(nullptr)
		{ } 
	};

    
    
    
    
    /////////////////////////////////////////////////////////////////////////////////////////////
	template<class T>
	struct list_iterator//迭代器的类
	{
		typedef list_node<T> Node;//简化节点名称
		Node* _node;//这是整个迭代器这个类中   唯一的成员变量,是一个节点的指针。因为像是 ++、--、*这些操作,都需要通过这个指针找到对应的节点(Node* 类型),然后返回对应的迭代器
        
        
        
		typedef list_iterator Self; //在自己这个类中简化自己这个类的名称,然后在后面的++、--中,返回时也写得更方便一些

        
        //传参传的是(_head->_next)这样的节点指针。
        //用来在类外把对应的节点指针变成list_iterator迭代器这种类
		list_iterator(Node* node)//迭代器这个类的 构造函数------>支持list类中的这样  iterator it(_head->_next);的使用 
			:_node(node)
		{ }


		T& operator*()//*的运算符重载,直接返回list对应的_data
		{
			return _node->_data; 
		}

		Self&  operator++()//++的运算符重载,返回对应的迭代器
		{
			_node = _node->_next;
			return *this;
		}

		bool operator!=(const Self& s)//通过判断迭代器类(list_iterator)创建的对象  中唯一的成员变量_node ,来判断两个迭代器类(list_iterator)是否是相同的迭代 类 对象(list_iterator)
		{
			return _node != s._node;
		}
	};

    
    
    
    
    
	/////////////////////////////////////////////////////////////////////////////////////////////
	template<class T>
	class list // 链表的类
	{
		typedef list_node<T> Node;//简化链节的写法

	public:
		list()// 构造函数,生成一个只有头节点的list
		{
			_head = new Node(T());
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0; 
		}

        
        
		typedef list_iterator<T> iterator;//简化迭代器的写法,符合STL

		iterator begin()//通过 节点指针,创造出一个迭代器类(iterator),然后返回这个迭代器类
		{
			iterator it(_head->_next);
			return it;//编译器会做值拷贝,把局部迭代器里的_node指针复制给外面接收的变量
		}

		iterator end()//指向哨兵头节点(最后一个节点的下一个节点),作为遍历结束的标志,创造出一个迭代器类(iterator),然后返回这个迭代器类
		{
			return _head;
		}


		void push_back(const T& x)
		{
			Node* newnode = new Node(x);//创建节点,并且调用带参构造函数


			Node* tail = _head->_prev; // 先拿到原来的尾节点

			newnode->_next = _head;    // 新节点的后继指向头节点
			newnode->_prev = tail;     // 新节点的前驱指向旧尾

			tail->_next = newnode;     // 旧尾的后继指向新节点
			_head->_prev = newnode;    // 头节点的前驱指向新节点  

			++_size;
		}

		size_t size()
		{
			return _size;
		}

		bool empty() const
		{
			return _size == 0;
		}

	private:
		Node* _head;
		size_t _size;

	};


	void test_list1()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);

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

		while (it != lt.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
}





5.实现insert

通过insert实现push_front和push_back

cpp 复制代码
		void insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;///这里应该写成.而不是->,因为 pos是个类(结构体),_node是结构体的成员变量
			Node* prev = cur->_prev;

			Node* newnode = new Node(x);

			newnode->_next = cur;
			cur->_prev = newnode; 
			newnode->_prev = prev;
			prev->_next = newnode;

			++_size;
		}

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

		void push_back(const T& x)
		{
			insert(end(), x);
		}





6.实现erase

通过erase实现pop_back

通过erase实现pop_front

cpp 复制代码
		void erase(iterator pos)
		{
			assert(pos != end());///end是哨兵位头节点

			///迭代器只是一个类,想要访问对应的节点需要访问其中的_node成员变量
			Node* prev = pos._node->_prev;
			Node* next= pos._node->_next;

			prev->_next = next;
			next->_prev = prev;

			delete pos._node;
			--_size;
		}

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

		void pop_front()
		{
			erase(begin());
		}





7.重载->

想打印一个结构体的类,而且

如下,我定义了一个结构体struct AA,创建了对应的list:list<AA> lta

想打印,

  • 方法一:当然可以先:list<AA>::iterator it = lta.begin();,找到对应元素的指针(AA类的指针),然后解引用,得到一个AA类,然后通过"."来访问对应的_a1和_a2
  • 方法二:list的迭代器,就是一个结构AA对象的地址------>通过"->",像访问一个结构体一样访问不行吗?不行🙅‍♀️,因为这时候list的迭代器并不是地址,而是一个类,所以不能用"->"
cpp 复制代码
	struct AA
	{
		int _a1=1;
		int _a2=2;
	};


		list<AA> lta;
		lta.push_back(AA());
		lta.push_back(AA());
		lta.push_back(AA());
		lta.push_back(AA());
		lta.push_back(AA());


		list<AA>::iterator it = lta.begin();

		while (it != lta.end())
		{
			///使用.的方法
			cout << (*it)._a1 << " : " << (*it)._a2 << endl;

			❌❌❌///使用->的方法:重载
			cout<<  it->_a1 << " : " << _a2 << endl;
		}
		cout << endl;





实现

‼️operator->必须返回指向数据的指针 ,而我之前犯错:返回的了数据本身(_node->_dataT类型,不是T*)。




实际上的访问应该是这样的:

cout << it.operator->()->_a1 << " : " << it.operator->()->_a1 << endl;

  1. it.operator->()返回对应的_data的指针,
  2. ->_a1相当于就是对结构体指针进行了"->"操作

但是编辑器省略了一个"->",只需要这样写就行:

cout<< it->_a1 << " : " << it->_a2 << endl;

cpp 复制代码
	///list_iterator 模板 
	template<class T>
	struct list_iterator
	{
		typedef list_node<T> Node;
		typedef list_iterator Self;
		Node* _node;
 
        
        
		T* operator->()/////////////////////////////////////////////////////////////////
		{
			return & _node->_data; ///operator->必须返回指向数据的指针

		}   
	};


..................
    
    
	struct AA
	{
		int _a1=1;
		int _a2=2;
	};


	void test_list1()
	{
		list<AA> lta;
		lta.push_back(AA());
		lta.push_back(AA());
		lta.push_back(AA());
		lta.push_back(AA());
		lta.push_back(AA());


		list<AA>::iterator it = lta.begin();

		while (it != lta.end())
		{
			///使用.的方法
			cout << (*it)._a1 << " : " << (*it)._a2 << endl;

			///使用->的方法:重载
			cout<< it->_a1 << " : " << it->_a2 << endl;
			++it;
		}
		cout << endl;
	}   





8.实现const_iterator

之前我们在实现vector中实现的print_container在这里用不了了,原因是没有实现const迭代器

cpp 复制代码
	template<class Container>
	void print_container(const Container& con)//两遍打印
	{
        
		list<int>::const_iterator it = con.begin();
		while (it != con.end())
		{
			//*it += 10;

			cout << *it << " ";
			++it;
		}
		cout << endl;



		for (auto& e : con)
		{
			//e *= 10;
			cout << e << " ";
		}
		cout << endl;
	}





8.1为什么要实现const迭代器const_iterator而不是const iterator?

因为const修饰iterator,是iterator不能修改,

而const_iterator代表这个迭代器指向的内容不能修改






8.2如何实现?

最基础的理解:

------>对迭代器进行修改的list_iterator 类模板 中的成员函数,只有"解引用"和"箭头访问->"两个函数可能对迭代器指向的内容进行修改*

------>那只要修改这两个函数就行了:

cpp 复制代码
//原来的:
		T& operator*()
		{
			return _node->_data;

		}


		T* operator->()
		{
			return & _node->_data; ///注意  :返回的是T* 指针,不是T的引用
		}

//修改后的:

		const T& operator*()
		{
			return _node->_data;

		}


		const T* operator->()
		{
			return & _node->_data;  ///注意  :返回的是T* 指针,不是T的引用
		}

❌‼️但不能直接这样改,这样导致普通迭代器的解引用和箭头访问也失效了




8.3‼️两个模板类的版本实现const_iterator

✅实现两个不同的模板类,list_iterator 模板 类 和 list_const_iterator 模板 类

cpp 复制代码
	///list_iterator 模板 
	template<class T>
	struct list_iterator
	{
		typedef list_node<T> Node;
		typedef list_iterator Self;
		Node* _node;


		list_iterator(Node* node)
			:_node(node)
		{ }


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

		}


		T* operator->()
		{
			return & _node->_data; ///注意  :返回的是T* 指针,不是T的引用
		}

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

		Self&  operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		bool operator!=(const Self& s) const 
		{
			return _node != s._node;
		}

		bool operator==(const Self& s)const
		{
			return _node == s._node;
		}
	};







	///list_const_iterator 模板 
	template<class T>
	struct list_const_iterator
	{
		typedef list_node<T> Node;
		typedef list_const_iterator Self;
		Node* _node;


		list_const_iterator(Node* node)
			:_node(node)
		{ }


		const T& operator*()
		{
			return _node->_data;

		}


		const T* operator->()
		{
			return & _node->_data; ///注意  :返回的是T* 指针,不是T的引用
		}

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

		Self&  operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		bool operator!=(const Self& s) const 
		{
			return _node != s._node;
		}

		bool operator==(const Self& s)const
		{
			return _node == s._node;
		}
	};

当然,list模板中也要修改:

cpp 复制代码
	///list 模板
	template<class T>
	class list 
	{
		typedef list_node<T> Node;

	public:
		typedef list_iterator<T> iterator;
		typedef list_const_iterator<T> const_iterator;/////////////////////////////
        
         iterator begin()
		{
			iterator it(_head->_next);
				return it;
		}

		iterator end()
		{
			return _head;
		}
        
        
		const_iterator begin()const  ////////////////////////////////////////////////////////////
		{
			const_iterator  it(_head->_next);/////////////////////////////////////改iterator为const_iterator
				return it;
		} 

		const_iterator end()const//////////////////////////////////////////////////////////////
		{
			return _head;
		}



const的辨析:

const_iterator:看后面

const的位置 作用 核心规则
返回值类型前 修饰返回值本身,表示返回值不能被修改 ++只有返回引用 / 指针时才有意义++ ,返回值类型(如intbool)加了没用
函数参数前 修饰形参,表示函数内部不能修改这个形参 传类对象时必须加const&,既避免拷贝又保护实参
函数末尾 只能加在类的成员函数 后面,修饰this 指针 表示这个函数不能修改类的任何成员变量




✅ 形参的const

  • 形参是普通变量:void func(const int x) → x 在函数内部不能改
  • 形参是类对象:void func(const list<int>& lt) → lt 在函数内部不能改,且只能调用 lt 的 const 成员函数
  • 形参是指针:void func(const int* p)不能通过 p 修改指向的内容,(不能解引用修改和通过"->"修改)
操作 是否允许 原因
p = &b; ✅ 可以 指针本身可以指向其他地址
int a = *p; ✅ 可以 可以读取指向的内容
*p = 100; ❌ 不可以 不能通过 p 修改指向的内容
(*p)++; ❌ 不可以 不能通过 p 修改指向的内容
p++; ✅ 可以 指针本身可以移动



✅函数末尾的const

  • 普通成员函数的 this 指针是:T* const this → 指针本身不能改,指向的内容可以改
  • 末尾加 const 的成员函数的 this 指针是:const T* const this → 指针本身和指向的内容都不能改





8.5‼️彻底搞懂const_iterator

1.给谁用:

const 类对象

cpp 复制代码
// 普通对象 
list<int> lt;
list<int>::iterator it = lt.begin();
*it = 100; // ✅  

// const对象,不能修改
const list<int> clt = lt;
list<int>::iterator it = clt.begin(); // ❌  
list<int>::const_iterator cit = clt.begin(); // ✅  
*cit = 200; // ❌  const_iterator指向的内容不能改



2. 为什么有const_iterator

const对象不能修改,而普通迭代器可以访问并修改,故普通iterator不行‼️




3.const_iterator vs const iterator
类型 迭代器本身能不能改(++、--) 指向的内容能不能改(*it = x)
iterator ✅ 可以 ✅ 可以
const_iterator ✅ 可以 ❌ 不可以
const iterator ❌ 不可以 ✅ 可以
const const_iterator ❌ 不可以 ❌ 不可以



4. 为什么const_iteratoroperator*返回const T&,但函数末尾没加 const?

对象+const

  • 普通对象(非 const 对象)既可以调用 const 成员函数,也可以调用非 const 成员函数。
  • const 对象只能调用 const 成员函数
  • operator++会修改迭代器自己的_node成员,所以它绝对不能加 const。(不然const成员也能用了,不行)
  • 因为const_iterator也只是一种特殊的迭代器啊,实现的功能和iterator是相同的,都是++/--/*



5.函数末尾加 const 和返回值加 const 有没有必然联系?

没有‼️

但有一个注意点:

cpp 复制代码
class A
{
public: 
    int& get_a() const//❌
    {
        return _a;
    }
private:
    int _a = 10;
}; 

int main()
{
    const A a;
    a.get_a() = 20; // ❌ 竟然修改了const对象的成员!权限放大了
    return 0;
}
  • int& get_a()函数后面加了const,代表不论是普通对象还是const对象都能调用来得到_a的引用,自然也都能直接修改_a‼️,如果是const对象,这自然是错的

✅正确写法:

cpp 复制代码
const int& get_a() const
{
    return _a;
}










8.6‼️通过模板实现

因为两个iterator模板重复度太高,所以直接传不同的地方对应的内容到模板里面来生成不同的类




先来比较一下吧:

list模板
  • 原来的list模板
cpp 复制代码
	///list 模板
	template<class T>
	class list 
	{ 
        
	public:
		typedef list_iterator<T> iterator; 
	};

  • 现在的list模板
cpp 复制代码
	///list 模板
	template<class T>
	class list 
	{ 
        
	public:
	/*	typedef list_iterator<T> iterator;
		typedef list_const_iterator<T> const_iterator;*/
		typedef list_iterator<T, T& , T*> iterator;
		typedef list_iterator<T,const  T&,const T*> const_iterator;
 

	};





list_iterator 模板
  • 原来的list_iterator (和list_const_iterator 模板)模板
    • 实现了两个类模板,而大部分函数都一样,只有operator*()operator->()有区别

太长了就不写了,上面 "++8.3两个模板类的版本实现const_iterator++ "有写,唯三的区别在下面:

cpp 复制代码
	///list_iterator 模板 
	template<class T>
	struct list_iterator
	{ 

		➡️list_iterator(Node* node)
			:_node(node)
		{ }


		➡️T& operator*()
		{
			return _node->_data;

		}


		➡️T* operator->()
		{
			return & _node->_data; ///注意  :返回的是T* 指针,不是T的引用
		} 
	};







	///list_const_iterator 模板 
	template<class T>
	struct list_const_iterator
	{ 
		➡️list_const_iterator(Node* node)
			:_node(node)
		{ }


		➡️const T& operator*()
		{
			return _node->_data;

		}


		➡️const T* operator->()
		{
			return & _node->_data; ///注意  :返回的是T* 指针,不是T的引用
		}
 
	};

现在的list_iterator 类模板:

cpp 复制代码
	///list_iterator 模板 
	template<class T, class Ref, class Ptr>
	struct list_iterator
	{
		typedef list_node<T > Node;
		typedef list_iterator<T, Ref, Ptr> Self;
		Node* _node;


		➡️list_iterator(Node* node)
			:_node(node)
		{ }


		➡️Ref operator*()
		{
			return _node->_data;

		}


		➡️Ptr operator->()
		{
			return & _node->_data; ///注意  :返回的是T* 指针,不是T的引用
		}

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

		Self&  operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		bool operator!=(const Self& s) const 
		{
			return _node != s._node;
		}

		bool operator==(const Self& s)const
		{
			return _node == s._node;
		}
	};
相关推荐
TMT星球1 小时前
他用WPS笔记,把AI报错变成了可复用的“避坑指南”
笔记·wps
niaiheni1 小时前
MySQL JDBC 不出网攻击 → Spring 临时文件利用:完整攻击链复现笔记
笔记·mysql·spring
玖釉-2 小时前
Vulkan Specialization Constants 详解:在“运行时配置”和“编译期优化”之间取得平衡
c++·windows·图形渲染
kgduu2 小时前
cosmos学习笔记
笔记·学习
05候补工程师2 小时前
【408 数据结构】图论核心算法(拓扑/关键路径)与二叉搜索树精髓夺分笔记
数据结构·经验分享·笔记·考研·算法·图论
烛之武2 小时前
《深度学习基础与概念》笔记(2)
人工智能·笔记·深度学习
-FxYaM-2 小时前
【UE】渲染框架学习路径-初次修改源码
服务器·网络·c++·windows·ue5·unreal engine
郝学胜-神的一滴2 小时前
Qt 高级开发 025:打造优雅界面的艺术与高效重构之道
开发语言·c++·qt·程序人生·重构·软件构建·用户界面
froyoisle2 小时前
CSP 真题解析:[CSP-J 2025-T3] 异或和
c++·算法·csp·算法竞赛·信奥赛