十五、迭代器适配器

01 什么是适配器

上一章 list 迭代器实现时,我们提到了反向迭代器的实现

代码:

namespace yxt
{
	/* 定义反向迭代器 */
	template <class Iterator>
	class reverse_iterator 
    {
		typedef reverse_iterator<Iterator, Ref, Ptr> Self;
	public:
		reverse_iterator(Iterator it)
			:_it(it)
		{}
 
		Ref operator*() 
        {
			Iterator prev = _it;
			return *--prev;
		}
		Ptr operator->() 
        {
			return &operator*();
		}
		Self& operator++() 
        {
			--_it;
			return *this;
		}
		Self& operator--() 
        {
			++_it;
			return *this;
		}
		bool operator!= (const Self& rit) const 
        {
			return _it != rit._it;
		}
 
	private:
		Iterator _it;
	};
}

我们先讨论一个问题,什么是迭代器适配器?

想了解这里的适配器,我们不如先看看电源适配器:

【百度百科】电源适配器又叫外置电源,是小型便携式电子设备及电子电器的供电电压变换设备,常见于手机、液晶显示器和笔记本电脑等小型电子产品上。

电源适配器是进行转换的,本质上可以理解为是一个变压器

标准家庭电压为 220V,我们设备用电其实并不需要那么高

而电源适配器可以使较高的交流电压降到合适于手机电池的直流工作电压

也就是说,电源适配器使用来转换的

反向迭代器和正向迭代器非常像,区别是:反向迭代器和正向迭代器加加的方向不一样

(即正向迭代器加加是向后走,反向迭代器加加是向前走)

基于这一系列的原因,在实现反向迭代器的时,没有必要像正向迭代器这样去包装并重新实现。

举个例子:

我们可以对其进行封装地转换,比如我们需要 list 的反向迭代器

就将 list 的正向迭代器传给 iterator,来实例化 iterator

然后通过包装转换出需要的反向迭代器:
 

namespace yxt
{
	/* 反向迭代器 */
	template <class Iterator>
	class reverse_iterator 
    {
		typedef reverse_iterator<Iterator> Self;
	public:
		reverse_iterator(Iterator it)
			: _it(it) 
		{}

如果你 vector 需要反向迭代器,那就把 vector 的正向迭代器传给 Iterator,

它就可以通过正向迭代器转换出 vector 的反向迭代器。

也就是说,我们实现的反向迭代器并包装的这个类,不是针对某个容器而是针对所有容器的。

任何一个容器只要你实现了正向迭代器,就可以通过其适配出反向迭代器。

它的本质是一种复用,是一种适配与转换。这就是迭代器适配器!

02 反向迭代器的错位访问

上一章我们提到过的 operator* 这里返回的是 *--prev,是一种有意思的错位

解引用取的不是当前的位置,而是前一个位置

对于 list 而言,正向迭代器的 begin 和 end 位置如下:

那对于反向迭代器的 rbegin 和 rend在哪里呢?

rbegin 和 rend 是和 end 和 begin 相对称的形式设计的

你的 end 就是我的 rbegin,你的 begin 就是我的 rend

这时就产生了一个错位的问题,我们解引用的时候会对不上,举个例子:

rit 在  rbegin() 的位置,rbegin 在头结点处,解引用取该位置么?

不,我们是反向迭代器啊,我们要取的是最后一个位置!

为了解决这个问题,我们在 operator* 上动手脚,让其返回 *--prev:

这样取得就是前一个位置了,rbegin 位置的前一个位置正是我们要取的地方

03 利用内嵌类型实现反向迭代器

实际上,STL3.0 的反向迭代器只有一个模板参数

而我们上一章模拟实现 list 的反向迭代器时,设计了 3 个模板参数

这是因为在上一章为了更方便地区讲解 list 的模拟实现,我们将其简化了。

我们现在来学习下库里面是如何实现的:

它没有传 Ref 和 Ptr,也就意味着不能显式地去传,自己去控制

比如,如果你是普通迭代器的反向迭代器,传的就是 T& 和 T* :

如果你是 const 迭代器的反向迭代器,传的就是 const T& 和 const T* :

我们这么做是为了更清楚地表示,降低学习的成本。

而库中采用了一种更难的方式去实现,不传 Ref 和 Ptr 这两个模板参数,只用了一个模板参数。

这就意味着要去取 pointer 和 reference,因为它们要做 operator* 和 operator-> 的返回值。

而这里只有迭代器 Iterator,我们该怎么去取呢?

他在其中定义了一个内嵌类型:

 把 Ptr 和 Ref 定义成了 pointer 和 reference,这样我们就可以从内嵌类型中去取这个类型了

代码:

namespace yxt
{
	//template <class Iterator, class Ref, class Ptr>
	template <class Iterator>
	class reverse_iterator 
    {
		typedef reverse_iterator<Iterator, Ref, Ptr> Self;
		typedef Iterator::Ref reference;  // 取内嵌类型
		typedef Iterator::Ptr pointer;
	public:
		reverse_iterator(Iterator it)
			:_it(it)
		{}
    
    ...
    }
}

如果你自己试一试你就会发现一个问题:

Iterator 是一个模板参数,你要取模板参数中的内嵌类型 Ref,编译器这里就编不过去了。

如果你对模板参数取 typedef,没有任何问题。但是你要取它的内嵌类型,就有问题了:

因为编译器编译到这一块的时候,模板参数还没有实例化!

既然没有实例化,那编译器也不知道这个 Iterator 的类型是什么,typedef 一下没什么问题。

但是你去 : : 取 Iterator 里面的东西,模板都还没实例化,编译器怎么知道里面到底是什么呢?

那我们该怎么让编译器知道呢?

凡是要取一个模板、类模板或模板参数的内嵌类型(内部类),就可以用 typename:

对于现阶段而言,这种写法是有缺陷的。vector、string 的迭代器怎么适配?

这里在取内嵌类型,只有自定义类型才能在里面搞内嵌类型。如果是一个原生指针呢?

原生指针哪来的内嵌类型?

如果仅看这一部分的实现,自然还是三个模板参数更爽,自己控制。

但是 STL 是非常复杂的,它还要考虑去兼顾全局,不是说只有迭代器和容器,

像 vector、string 这样的容器的迭代器是原生指针,无法取内嵌类型。那么上面的方式就寄了。

04 迭代器萃取

基于这一系列的原因,STL 源码中使用了一个叫 "迭代器萃取" 的技术解决的。

原理:再套一层,去萃取迭代器中的 reference 和 pointer(迭代器管理的数据中的 T& 和 T* 或 const T* 和 constT& 等取出来)。相当于把其他容器的迭代器传给 iterator_traits,在其中再套一层,对于链表而言最终还是要取:

对于 vector、string 这样的原生指针,iterator_traits 里有使用了一个技术 —— "模板特化技术"

大概意思是当你这里是原生指针时,这里会直接自己套一层取解决。萃取的本质就是特化。

总结:list 用一个参数实现还行,但是 vector 和 string 这样的原生指针还是用一个参数去实现会牵扯到更复杂的迭代器萃取。就目前而言,学会实现 3 个模板参数的反向迭代器即可

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值