在调用算法函数的时候,有时候会把不同序列的迭代器当成同一序列使用。
下面提供避免这样错误的一种机制。
本内容来自TCPL(特别版)
有时候,我们会犯这样的错误,把两个不同序列的迭代器去构成一个序列了。比如:
void
f(list
<
string
>&
fruit, list
<
string
>&
citrus)
{
typedef list
<
string
>
::const_iterator LI;
LI p1
=
find(fruit.begin(), citrus.end(),
"
apple
"
);
//
error, 不在同一序列
LI p2
=
find(fruit.begin(), fruit.end(),
"
apple
"
);
LI p3
=
find(citrus.begin(), citrus.end(),
"
pear
"
);
LI p4
=
find(p2, p3,
"
peach
"
);
//
这个更加隐蔽。
}
这里Bjarne Stroustrup给出一个解决问题的途径。其关键就是用整个容器代替x.begin, x.end()的输入。
这样,我们要封装两个东西,1、find()函数。 2、begin(),end()
利用重载,封装find函数。
template
<
class
In,
class
T
>
In find(Iseq
<
In
>
r,
const
T
&
v)
//
通过重载机制,得到这个find的扩充版本。
{
return
find(r.first, r.second, v);
//
标准库中的find
};
利用对偶,封装迭代器。
首先,我们构造一个Iseq以保证迭代器是统一序列成对输入的。
template
<
class
In
>
struct
Iseq:
public
pair
<
In, In
>
{
Iseq(In i1, In i2): pair
<
In, In
>
(i1, i2){}
};
接着构造一个协助函数,直接传递容器。
template
<
class
C
>
Iseq
<
typename C::iterator
>
iiseq(C
&
c)
//
C为容器
{
return
Iseq
<
typename C::iterator
>
(c.begin(), c.end());
}
这样,我们可以利用上面的机制,来避免所提出的错误。
void
f(list
<
string
>&
fruit, list
<
string
>&
citrus)
{
typedef list
<
string
>
::const_iterator LI;
LI p1
=
find(iiseq(fruit),
"
apple
"
);
LI p2
=
find(iiseq(citrus),
"
apple
"
);
LI p3
=
find(citrus.begin(), citrus.end(),
"
pear
"
);
//
}
下面我们仔细分析整个机制的几个细节。
先让我们来看看pair的样子。
template
<
class
T1,
class
T2
>
struct
std::pair{
//
这里用struct来定义
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair(): first(T1()), second(T2()){}
pair(
const
T1
&
x,
const
T2
&
y): first(x), second(y){}
template
<
class
U,
class
v
>
pair(
const
pair
<
U, V
>&
p): first(p.first), second(p.second){}
//
};
注意pair的两个数据成员first, second都是public的,所以Iseq继承pair之后可以直接访问。
考察find()函数的重载版本
find(Iseq<In> r, const T& v)
注意“Iseq<In> r”使用值传递,而不用引用传递(Iseq<In>& r)。
这是因为iiseq协助函数返回一个临时对象,所以在find中,不能用引用传递。
template<class C>Iseq<typename C::iterator> iiseq(C& c) //C为容器
{
return Iseq<typename C::iterator>(c.begin(), c.end());
}
大家可能会考虑到效率问题,觉得值传递可能不妥。其实不然,我们可以发现,Iseq里面的数据成员是两个Iterator,一般来说不是很大(有时,就是两个指针),在效率上不会产生很大的影响。
还有这里代码中出现typename,(如return Iseq<typename C::iterator>(c.begin(), c.end());) 可能对初学者来说有些生疏。为什么不直接写: Iseq<C::iterator>(c.begin(), c.end())。这是由于编译器不能直接认出C::iterator是一种类型,所以我们加上修饰符号typename告诉编译器C::iterator使用一种类型。