我们用容器存放数据,用算法来执行操作,而迭代器就是两者的接合器。
首先来看find算法
inline const char *_Find(const char *_First, const char *_Last, int _Val)
{ // find first char that matches _Val
_First = (const char *)_CSTD memchr(_First, _Val, _Last - _First);
return (_First == 0 ? _Last : _First);
}
这是find的一种实现,其实它提供的接口是
template<class _InIt,
class _Ty> inline
_InIt find(_InIt _First, _InIt _Last, const _Ty& _Val)
把它简写一下就是:
template <class Iterator,class T>
inline find(Iterator first,Iterator last,const T&val)
给予不同的迭代器就可以在不同的容器中查询,通过*来访问对应的容器元素
2.traits技法
迭代器类似指针,常见的行为包括dereference和member access。即*和->运算符,因此所有的迭代器都必须重载operator*和operator->
这里主要有这么一个问题,对于迭代器类型,有时候需要返回其所指容器中包含的类型,但是在迭代器类型的定义中,由于容器内的这个类型是运行时确定的。
所以在定义时就无法获得该类型。
即template<class A>
class B{}
template<class B>void
function(B&){}
在function中对B的处理时,会用到B中的元素,但是这里因为不知道其类型,无法使用。
这个时候可以加一个新函数function1将B中的元素作为参数传入,同时有不同的模板参数
改成
template<class B,class A>void
function1(B&,A&){},其中A类型变量由B中提取,然后再在function中调用function1,就可以将原先计划在function中的处理移动到function1中。
但是,如果function想返回一个A类型的值要怎么办。这只能使用新的方法。
就是traits技法,其实这种技法就是在迭代器中获得其指向容器的保存元素的类型。
这个方法包括如下两步,1,内嵌型别声明,2,偏特化处理原生指针
1.内嵌型别声明
其实就是在容器类型中typedef声明其传入模板参数类型为一个新类型
template<class T>
struct Item
{
typedef T value_type;
T*ptr;
Item(T *t):ptr(t){}
...
};
就类似这个Item结构中的value_type,就是内嵌类型声明。
然后到迭代器类中,我们就可以使用这个value_type。使用方式大致为
struct ItemIterator
{
typename I::value_type function()
{
...
}
};
这里就可以使用这个类型,但是要注意,必须加上typename关键字。注意:这里不能使用class,我们都知道在模板定义中,class与typename基本相同,但是,
在作为嵌套依赖类型的时候,只能使用typename
如果你用class,会报错告诉你,value_type不是一个class。
例子如下:
#include <iostream>
template<class T=int>
struct Item
{
typedef T value_type;
T *ptr;
Item(T *t):ptr(t){}
};
template<class I>
struct ItemIterator
{
ItemIterator(I t):item(t){}
typename I::value_type function()
{
return *(item.ptr);
}
I item;
};
int main()
{
int b = 6;
int *p = &b;
Item<> c(p);
ItemIterator<Item<> > iter(c);
std::cout<<iter.function();
return 0;
}
为什么使用内嵌类型,是为了获取迭代器保存的类型,这个类型是运行时确定的。
接着是偏特化,这里使用偏特化的主要原因是针对原生指针的情况
原生指针指的是类似int*之类的。为什么会有原生指针的问题。
我们来看下面这段代码:
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
template<class T=int>
struct Item
{
typedef T value_type;
T *ptr;
Item(T *t):ptr(t){}
T&operator*()
{
return *ptr;
}
};
void add1(int elem)
{
elem+=1;
}
template<class I>
typename I::value_type func(I t)
{
return *(t.ptr);
}
int main()
{
int b = 6;
int *p = &b;
Item<> c(p);
std::cout<<func(c);
//std::cout<<func(p);
int array[10]={0,1,2,3,4,5,6,7,8,9};
for_each(array,array+9,add1);
return 0;
}
我们准备实现一个算法func,它接收一个输入的迭代器类型,返回一个迭代器内保存的对象,而这个迭代器类型Item有一个内嵌类型声明value_type。这使得我们可以在算法func中返回这个value_type。
同时注意这里我们使用了算法for_each,它不仅仅接收迭代器类型,同时也接受原生指针,如int*。但是我们写的func如果传递int*,它是有问题的,因为int*没有value_type。
那么要如何保证我们写的算法func能接收原生指针呢。
为了解决这个问题,我们就使用一个class template来专门提取value_type即迭代器所指对象的型别,由这个template来架桥,对于非原生指针,它只是相当于一个隔层,对于原生指针乃至之后提到的const指针,它就相当于一个原生指针的接口,这个接口能提取符合要求的类型。
其实这个专门的class template等于一层封装,统一了接口
template<class I>
struct iterator_traits{
typedef typename I::value_type value_type;
};
对于原生指针类型和I不同,它没有value_type,所以就需要使用偏特化
template<class T>
struct iterator_traits<T*>{
typedef T value_type;
}
即将传入的模板参数作为value_type
这样即使int*不是一种类型,也可以获得其value_type
代码如下:
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
template<class T=int>
struct Item
{
typedef T value_type;
T *ptr;
Item(T *t):ptr(t){}
T&operator*()
{
return *ptr;
}
};
template<class I>
struct Iterator_traits
{
typedef typename I::value_type value_type;
};
template<class I>
struct Iterator_traits<I*>
{
typedef I value_type;
};
void add1(int elem)
{
elem+=1;
}
template<class I>
typename Iterator_traits<I>::value_type func(I t)
{
return *t;
};
int main()
{
int b = 6;
int *p = &b;
Item<> c(p);
std::cout<<func(c)<<endl;
std::cout<<func(p)<<endl;
int array[10]={0,1,2,3,4,5,6,7,8,9};
for_each(array,array+9,add1);
return 0;
}
通过Iterator_traits,我们传递的Item调用其*运算符,返回一个Iterator_traits<>::value_type(即Item::value_type类型也即是T类型)
这里还需要特化一次,因为我们会使用这个func的返回值,但是如果内部是const int*类型,那么返回值也是const int我们没办法将其当成一个左值,所以需要再提供一个特化版本。
那就是
template<class I>
struct Iterator_traits<const I*>
{
typedef I value_type;
};
如果是const int*
那么这里的value_type会是const int,这样我们需要将其变成非const。因此这里内嵌的value_type就变成了非const
通过这两步,我们就创建两个模板类,一个是迭代器模板类,一个是萃取模板类,就可以提供一个统一的接口类型用于算法操作迭代器内置类型。
要求迭代器类型必须提供一个内嵌类型value_type用于萃取,一般常用的迭代器要提供5种内嵌类型。这部分留在下一章