C++提供了二种功能强大的抽象方法:面向对象编程与泛型编程。面向对象编程大家一定很熟悉了,这里就不再哆嗦了。提到泛型编程(Generic Programming),有的人可能还不太熟悉,但是提到STL,你就一定会有所耳闻了。
generic Programming的思想精髓是基于接口编程(相对于OOP,连多态所需的基类都不要了),它的技术出发点是选择子,核心技术是:类型推导、类型萃取、特化/偏特化,其成果是STL库:一组通用容器和一组操作于通用容器上的通用算法。STL(Standard Template Library,标准模板库) 其实就是泛型编程的实现品,STL是由Alexander Stepanov(STL之父)、David R Musser和Meng Lee三位大师共同发展,于1994年被纳入C++标准程序库。STL虽然加入C++标准库的时间相对较晚,但它却是C++标准程序库中最具革命性的部分,同时也是C++标准程序库中最重要的组成部分。由于新的C++标准库中几乎每一样东西都是由模板(Template)构成的,当然,STL也不会例外。所以,在这里有必要先概要说明一下模板的有关概念。
模板概念
通过使用模板可以使程序具有更好的代码重用性。记住,模板是对源代码进行重用,而不是通过继承和组合重用对象代码,当用户使用模板时,参数由编译器来替换。模板由类模板和函数模板二部分组成,以所处理的数据类型的说明作为参数的类就叫类模板,而以所处理的数据类型的说明作为参数的函数叫做函数模板。模板参数可以由类型参数或非类型参数组成,类型参数可用class和typename关键字来指明,二者的意义相同,都表示后面的参数名代表一个潜在的内置或用户定义的类型,非类型参数由一个普通参数声明构成。
说明一个问题最好的方法就是举例,stl的使用暂且不说(这个开发环境带有参考手册),先来看看一个函数(算法)或容器(数据结构)是怎么模板化的,原理清楚了使用应该就不是问题。
依据简单原则,我们来看一个最简单的算法: “线性查找”,此算法用来从一含有一堆未排序的数据的容器中查找一个值,如果找到,返回下标,否则,返回一个表示没找到的值。
首先, 我们看最简单的用例: 从整行数组中查找值, 约定失败返回 -1, 因为正常下标都是从0开始的整数。
int find(const int a[], int n, int key)
{
for(int i=0; i< n; i++)
{
if(a[i] == key)
return i;
}
return -1;
}
这个算法大概是最简单最常用的了, 现在, 我们希望它不仅可以对int对象,还可以对double对象,对用户定义的任何对象使用,安以前的常规做法,就需要对每个类型编写一个同样的算法。嗯,当然,你可以 Ctrl+C, Ctrl+V来完成^^, 不过,现在我们用模板,有更好的方法了:
template<typename T>
int find(const T a[], int n, T key)
{
for(int i=0; i< n; i++)
{
if(a[i] == key)
return i;
}
return -1;
}
// 调用:
int a[5] ={ 1, 2, 3, 5, 6};
int i = find(a, 5, 2); // i==1
现在,我们可以对任意的类型T进行查找,要求是 T 类型可以用" == " 来比较, 这对大部分来说是没问题的,但是对C风格字符串来说就不对了,因为我们希望比较的是字符串的内容,而不是地址, 好在可以为特别的类型定义特别的实现(这称之为“模板偏特化”):
template< >
int find< char* > (const char* a[], int n, char* key)
{
for(int i=0; i< n; i++)
{
if(strcmp(a[i], key)==0)
return i;
}
return -1;
}
好了, 现在可对字符串操作了, 但是, 我如果想要不分大小写比较呢? 或者,是其它的对象,我需要多种不同的比较呢? 我们把测试条件也模板出来!
template<typename Type, typename Equaler>
int find(const Type* array, int n, Type key)
{
int i = 0;
for(int i = 0; i < n; i++ )
{
if(Equaler()(key, array[i]))
{
return i;
}
}
return -1;
}
现在,对于比较部分, 全部用第二个模板参数来确定, Equaller 的定义可以如下
//等于比较器
template<typename Ty>
struct equal
{
bool operator()(const Ty& _left, const Ty& _right)
{
return (_left == _right);
}
};
分大小写的比较器可以如下:
template< >
struct equal<char* >
{
bool operator()(const char* & _left, const char* & _right)
{
return (strcmp(_left, _right) == 0 );
}
};
不分大小写的比较器可以如下:
struct equalNoCase
{
bool operator()(const char* & _left, const char* & _right)
{
return (stricmp(_left, _right) == 0 );
}
};
//不分大小写的查找可以这样调用, 分大小写的 用 euqal<char*>
int i = find< char*, equalNoCase<char* > >(strs, n, strKey);
函数也可以换一种写法, 这样就可以直接传递参数给函数, 不需要用尖括号指定模板了
template<typename Type, typename Equaler>
int find(const Type* array, int n, Type key,Equaler eql )
{
int i = 0;
for(int i = 0; i < n; i++ )
{
if( eql(key, array[i]) )
{
return i;
}
}
return -1;
}
// 调用
int i = find(strs, n, strKey,equalNoCase() );
好了,现在我们看看, 加入要查找符合某个条件的呢, 可以用一个一元操作符来测试
template< typename T >
struct Pr
{
bool operator()(const T& v)
{
if(...) //满足测试条件
return true;
else
return false;
}
};
现在可以写一个 find_if
template<typename Type, typename Pr>
int find_if(const Type* array, int n, Type key,Pr pr )
{
int i = 0;
for(int i = 0; i < n; i++ )
{
if( pr(array[i]) )
{
return i;
}
}
return -1;
}
现在,我们看看,我们的find函数可以工作得很好了,不过, 如果要找小于5的你得写一个pr, 小于6的你得又写pr, 是不是很烦呢? 现在,我们实现一个绑定器,绑定我们要的参数,以便可以这样使用:
int find_if(array, n, key,bind2nd(less<int>,5) ); //小于5的条件
// 下面实现一个简单的绑定器
为了实现绑定,我们需要修改下 less比较器, 加上类型信息
template<typename T>
{
typdef T arg_type;// 增加这个类型定义
//
};
template < typename Fn_type, Fn_type::arg_type rgiht>
struct bind2nd
{
typedef Fn_type::arg_type arg_tye;
bind2nd(Fn & fn, arg_type rgiht): _Func(fn), _Value( right)
bool operator()( const arg_tye & left)
{
return fn(left, right);
}
Fn_type _Func;// 二参数的函数对象
arg_type _Value;// 右边的操作数
};
同样你给greater, equal 添加一个 typedef 定义。 好了,现在你可以用这个绑定器绑定大于, 等于, 小于的比较器了。
stl 的实现为了更通用, 比这个要复杂一些, 但是原理是一样的。
现在我们的 find_if、find 函数可以作用在任何 可以通过下标访问的数组上了, 但是, 如果使用的是不能通过下标的容器呢? 比如 list 等,下次我们将把位置指示器模板化, 这个就是 iterator 了, 通常翻译的是 迭代器, 其实叫位置指示器更确切,其行为类似于指针。