笔记八--泛型算法

容器通过一对iterator被绑定到某个泛型算法上,这对iterator标记了要遍历的元素范围、特殊的函数对象允许我们改变泛型算法

的缺省操作语义。


一 概述
每个泛型算法的实现都独立于单独的容器类型。因为已经消除了算法的类型依赖性,所以单个的模版实例可以操作在各种容器以及

内置的数组类型上。

iterator提供了对指针的一个泛化,它至少支持下列操作符:递增操作符以用来访问下一个元素、解引用操作符用来访问实际的元

素,以及等于和不等于操作符用来判断两个iterator是否相等。

算法遍历的元素范围由一对iterator标记

泛型算法为每个算法提供两个版本:一个使用元素底层类型的等于操作符,另一个使用函数对象或函数指针来实现比较。

二 使用泛型算法
因为内置数组不支持erase()操作所以unique()算法族不太适合于内置数组类


三 函数对象
template <typename Type>
const Type&
min( const Type *p, int size )
{
int minIndex = 0;
for ( int ix = 1; ix < size; ++ix )
if ( p[ ix ] < p[ minIndex ] )
minIndex = ix;
return p[ minIndex ];
}

局限性的焦点在于小于操作符的使用上。
在第二种情况下,虽然存在小于操作符,但是提供的语义并不合适。

传统的方案是参数化比较操作符,在这种情况下声明一个函数指针,该函数有两个参数并返回一个bool 型的值
template < typename Type,
bool (*Comp)(const Type&, const Type&)>
const Type&
min( const Type *p, int size, Comp comp )
{
int minIndex = 0;
for ( int ix = 1; ix < size; ++ix )
if ( Comp( p[ ix ], p[ minIndex ] ))
minIndex = ix;
return p[ minIndex ];
}

函数指针的主要性能缺点是它的间接引用使其不能被内联。


对函数指针的替代策略是函数对象。

函数对象是一个类,它重载了函数调用操作符。

函数对象与函数指针相比较有两个方面的优点:
a 首先如果被重载的调用操作符是inline函数,则编译器能够执行内联编译,提供可能的性能好处。
b 其次函数对象可以拥有任意数目的额外数据,用这些数据可以缓冲结果,也可以缓冲有助于当前操作的数据。

template < typename Type,
typename Comp >
const Type&
min( const Type *p, int size, Comp comp )
{
int minIndex = 0;
for ( int ix = 1; ix < size; ++ix )
if ( Comp( p[ ix ], p[ minIndex ] ))
minIndex = ix;
return p[ minIndex ];
}

泛型算法一般支持两种形式来应用操作:使用内置或(可能是被重载的)操作符,和使用函数指针或函数对象执行操作

函数对象的来源:
a 标准库预定义的一组算术、关系和逻辑函数对象
b 一组预定义的函数适配器,允许我们对预定义的函数对象进行特殊化或者扩展
c 我们可以定义自己的函数对象,将其传递给泛型算法,或将它们传给函数适配器。

1 预定义函数对象
预定义函数对象被分为算术、关系和逻辑操作。每个对象都有一个类模版,其中操作数的类型被参数化,为了使用它们,我们必须

包含下列头文件:
#include<functional>
例如,支持加法的函数对象时一个名为plus的类模版,为定义一个可以把两个整数相加的实例,我们可以这样写:
#include<functional>
plus<int> intAdd;

int ival1=10,ival2=20;
int sum=intAdd(ival1,ival2);

缺省情况下,sort()用底层元素类型的小于操作符以升序排列容器的元素,为了以降序排列容器,我们传递预定义的类模版greater

,它调用底层元素类型的大于操作符vector<string>svec;
sort(svec.begin(),svec.end(),greater<string>());

预定义的函数对象被分为算术、关系和逻辑三大类



每个类对象可以作为有名对象,也可以作为无名对象传递给一个函数


2 算术函数对象

加法:plus<Type>

减法:minus<Type>

乘法:multiplies<Type>

除法:divides<Type>

求余:modulus<Type>

取反:negate<Type>

3 关系函数对象
等于:equal_to<Type>

不等于:not_equal_to<Type>

大于:greater<Type>

大于等于:greater_equal<Type>

小于:less<Type>

小于等于:less_equal<Type>

4 逻辑函数对象

逻辑与:logical_and<Type>

逻辑或:logical_or<Type>

逻辑非:logical_not<Type>

5 函数对象的函数适配器
标准库还提供了一组函数适配器,用来特殊化或者扩展一元和二元函数对象。适配器是一种特殊的类,它被分为下面两类:

a 绑定器(binder):binder通过把二元函数对象的一个实参绑定到一个特殊的值上,将其转换成一元函数对象。

b 取反器(negator):negator

两种预定义的binder适配器:bind1st和bind2nd
bind1st把值绑定到二元函数的第一个实参上
bind2nd把值绑定在第二个实参上
为了计算容器所有小于等于10的元素个数,我们可以这样想count_if()传递:
count_if(vec.begin(),vec.end(),bind2nd(less_equal<int>(),10));

两种预定义的negator适配器:not1和not2
not1反转一元预定义函数对象的真值
not2翻转二元谓词函数的真值

count_if(vec.begin(),vec.end(),not1(bind2nd(less_equal<int>(),10)));

6 实现函数对象
函数对象类定义的最简单形式包含一个被重载的函数调用操作符。

四 回顾iterator
下列函数模板不能编译你知道这是为什么吗
// 无法通过编译
template < typename type >
int
count( const vector< type > &vec, type value )
{
int count = 0;
vector< type >::iterator iter = vec.begin();
while ( iter != vec.end() ) {
if ( *iter == value )
++count; ++iter; }
return count;
}

问题在于vec 是一个const 引用,但是我们试图把一个非const 的iterator 绑定在它上面。

// ok: 这次可以通过编译了
vector< type >::const_iterator iter = vec.begin();

begin()和end()两种操作都被重载,根据容器的常量性返回一个const 或非const iterator。

vector<int> vec0;
const vector<int> vec1;

在vec0上的begin()和end()调用返回一个非const的iterator,而vec1上的调用返回同一个const的iterator。例如:
vector< int >::iterator iter0 = vec0.begin();
vector< int >::const_iterator iter1 = vec1.begin();

当然给一个const iterator 赋值一个非const iterator 总是可以的例如
// ok: 把一个非 const iterator 初始化为一个 const
vector< int >::const_iterator iter2 = vec0.begin();

1 插入iterator
int ia[] = { 0, 1, 1, 2, 3, 5, 5, 8 };
vector< int > ivec( ia, ia+8 ), vres;
// 导致未定义的运行时刻行为
unique_copy( ivec.begin(), ivec.end(), vres.begin() );
这里的问题是,vres 中没有已被分配的空间来保存从ivec 向其拷贝的8 个整型值。unique_copy()算法用赋位操作拷贝每个元素值

,但是赋值会失败,因为在vres 中没有可用空间。

a back_inserter():它使用容器的push_back()插入操作代替赋值操作符。back_inserter()的实参是容器自己。
// ok: unique_copy() 现在用 vres.push_back() 插入
unique_copy( ivec.begin(), ivec.end(),
back_inserter( vres );

b front_inserter():

c inserter():

2 反向iterator
vector< int >::reverse_iterator r_iter0 = vec0.rbe gin();
vector< int >::const_reverse_iterator r_iter1 = vec1.rbegin();

3 iostream iterator
#include <iterator>

istream_iterator
istream_iterator<Type> identifier( istream& );

// 从标准输入读入一个 complex 对象的序列
istream_iterator< complex > is_complex( cin );

// 从命名的文件中读入一个字符串序列
ifstream infile( "C++Primer" );
istream_iterator< string > is_string( infile );


ostream_iterator

用下列两种形式之一可以声明ostream_iterator
ostream_iterator<Type> identifier( ostream& )
ostream_iterator<Type> identifier( ostream&, char* delimiter )


#include <iterator>
#include <fstream>
#include <string>
#include <complex>
// 向标准输出写一个 complex 对象的序列
// 用空格分割每个元素
ostream_iterator< complex > os_complex( cout, " " );
// 向一个命名文件写一个字符串序列
// 每一行放一个
ofstream outfile( "dictionary" );
ostream_iterator< string > os_string( outfile, "\n" );

五 泛型算法
所有泛型算法的前两个实参都是对iterator,通常被称为first 和last,它们标记出要操作的容器或内置数组中的元素范围。元
素范围概念有时称为左闭合区间通常写为:
// 读作: 包含 first 以及到 但不包含 last 的所有元素
[ first, last )

你可能感兴趣的:(算法)