STL概论
1.1 STL概论
为了建立数据结构和算法的一套标准,并且降低其间的耦合(coupling)关系以提升各自的独立性、弹性、交互操作性(相互合作性,interoperability), 提高复用性,所以C++诞生了STL。
STL的价值在于两方面:
- 低层次:STL带给我们一套极具实用价值的零部件,以及一个整合的组织。
- 高层次:以泛型思维(Generic Paradigm)为基础的、 系统化的、条理分明的“软件组件分类学(components taxonomy) "。
STL是一个抽象概念库(library of abstract concepts) , 这些“抽象概念“ 包括最基础的:
- Assignable(可被赋值)
- Default Constructible (不需任何参数就可构造)
- Equality Comparable (可判断是否等同)
- LessThan Comparable (可比较大小)
- Regular(正规)
高阶一点的概念则包括:
- Input Iterator (具输入功能 的迭代器)
- Output Iterator (具输出功能的迭代器)
- Forward Iterator (单向迭代 器)
- Bidirectional Iterator (双向迭代器)
- Random Access Iterator (随机存取迭 代器)
- Unary Function (一元函数)
- Binary Function (二元函数)
- Predicate(传回真假值的一元判断式)
- BinaryPredicate (传问真假值的二元判断式)
更高阶的概念包括:
- sequence container (序列式容器)
- associative container (关联式容器)
STL的创新价值便在于具体叙述了上述这些抽象概念,并加以系统化。
1.2 STL六大组件 功能与运用
STL提供六大组件,彼此可以组合套用:
- 容器(containers): 各种数据结构,如vector,list, deque, set, map,用来存放数据,从实现的角度来看,STL容器是一种class template。就体积而言,这一部分很像冰山在海面下的比率。
- 算法(algorithms): 各种常用算法如sort,search, copy, erase··· 从实现的角度来看,STL算法是一种function template。
- 迭代器(iterators): 扮演容器与算法之间的胶合剂,是所谓的泛型指针, 共有五种类型,以及其它衍生变化。从实现的角度来看,迭代器是一 种将operator*, operator->, operator++, operator-- 等指针 相关操作予以重载的class template。 所有STL容器都附带有自己专属的迭代器, 只有容器设计者才知道如何遍历自己的元素。 原生指针(native pointer)也是 一种迭代器。
- 仿函数(functors): 行为类似函数,可作为算法的某种策略(policy)。从实现的角度来看,仿函数是一种重载了operator()的class或class template。一般函数指针可视为狭义的仿函数。
- 配接器(adapters) : 一种用来修饰容器 (containers)或仿函数(functors) 或迭代器(iterators)接口的东西, 例如,STL提供的queue和 stack, 虽然看似容器,其实只能算是一种容器配接器,因为它们的底部完全借助deque, 所有操作都由底层的deque供应。 改变functor接口者, 称为function adapter; 改变container接口者,称为container adapter; 改变iterator 接口者, 称为 iterator adapter。 配接器的实现技术很难一言以蔽之, 必须逐 一分析。
- 配置器(allocators) : 负责空间配置与管理, 从实现的角度来看,配置器是一个实现了动态空间配置、空间管理、空间释放class template。
STL六大组件的交互关系:
Container通过Allocator取得数据储存空间,Algorithm通过Iterator存取Container内容,Functor可以协助Algorithm 完成不同的策略变化,Adapter可以修饰或套接Functor。
1.3 GUN源代码开放精神
- 全世界所有的STL实现版本,每一个头文件都有一份声明,允许任何人任意运用、拷贝、修改、传播、贩卖这些代码, 无需付费,唯一的条件是必须将该份声明置于使用者新开发的文件内。这种开放源代码的精神,一般统称为open source。
-
GNU这个名称是计算机族的幽默展现,代表GNU is Not Unix。当时Unix是 计算机界的主流操作系统。
- GNU以所谓的GPL(General Public License, 广泛开放授权)来保护(或说控制)其成员:使用者可以自由阅读与修改GPL软件的源代码,但如果使用者要 传播借助GPL软件而完成的软件,他们必须也同意GPL规范。GPL对于版权(copyright)观念带来巨大的挑战,甚至被称为“反版权”。
- 本书所采用的GCC套件是CygnusC++Z.91 for Windows,又称为EGCSU 1.1。
-
1.4 HP实现版本
HP版本是所有STL实现版本的始祖。每一个HP STL头文件都有如下一份声明,允许任何人免费使用、拷贝、修改、传播、贩卖这份软件及其说明文件,唯一需要遵守的是,必须在所有文件中加上HP的版本声和运用权限声明,这种授权并不属于GNU GPL范畴,但属于 open source范畴.
1.5 P.J.Plauger实现版本
P.J. Plauger版本由P.J.Plauger开发,本书后继章节皆以PJSTL称呼这一版 本。PJ版本承继HP版本,所以它的每一个头文件都有HP的版本声明,P .J. Plauger版本被VisualC++采用,此外还加上P.J.Plauger的个人版权声明:
这个产品既不属于open source范畴,更不是GNUGPL。这么做是合法的,因为HP的版权声明并非GPL,并没有强迫其衍生产品必须开放源代码。
1.6 Rouge Wave 实现版本
Rouge Wave版本由RougeWave公司开发,本书后继章节皆以RWSTL称呼,这一版本,RW版本继承HP版本,所以它的每一个头文件都有HP的版本声明,Rouge Wave版本被C++Builder采用,此外还加上Rouge Wave的公司版权声明:
这份产品既不属于opensource范畴,更不是GNUGPL。这么做是合法的,因为HP的版权声明并非GPL,并没有强迫其衍生产品必须开放源代码。
1.7 STLport实现版本
网络上有个STLport站点,提供个以SGI STL为蓝本的高度可移植性实现版本。本书附录C列有孟岩先生所写的一篇文章,介绍STLport移植到VisualC++和C++Builder的经验。SGISTL (下节介绍)属于开放源代码组织的一员,所以STLport有权利那么做。
1.8 SGI STL实现版本
SGI版本由Silicon Graphics Computer Systems, Inc. 公司发展,承继HP版本。所以它的每一个头文件也都有HP的版本声明。 此外还加上SGI的公司版权声明。 从其声明可知,它属千open source的一 员,但不属于GNUGPL (广泛开放授权),SGI版本被GCC采用。
1.8.1 GNU C++ headers文件分布(按字母排序)
CygnusC++ 2.91 for Windows安装于磁盘目录C:\cygnus。图1-2是 这个版本的所有头文件,置千C:\cygnus\cygwin-b20\include\g++,共128个文件,773,042 bytes,下面只是部分:
其中子目录[std]内有8个文件,70,669bytes:
1.8.2 SGI STL 文件分布与简介
上面所呈现的 头文件,概略可以分为五组:
- C++标准规范下的C头文件(无扩展名),例如cstdio, cstdlib, cstring…
- C++标准程序库中不属于STL范畴者, 例如stream, string … 相关文件。
- C++标准头文件(无扩展名),例如vector, deque, list, map, algorithm,functional •••
- Standard定案前,HP所规范的STL头文件,例如vector.h, deque.h, list.h, map.h, algo.h, function.h…
- SGI STL内部文件(STL真止实现于此),例如stl_vector.h,stl_deque.h,stl_list.h, stl_rnap.h, stl_algo.h, stl_function.h … 其中前两组不在本书讨论范围内。后三组头文件详细列表于下。
(1) STL标准头文件(无扩展名)
请注意,各文件之“本书章节”栏如未列出章节号码,表示其实际功能由“说明栏中的stl_xxx取代,因此,实际剖析内容应观察对应之stl_xxx文件所在章节,见稍后之第三列表。
(2) C++ Standard定案前,HP规范的STL头文件(扩展名.h)
请注意,各文件之本书章节栏如未列出章节号码,表示其实际功能由说明栏中的stl_xxx取代,因此,实际剖析内容应观察对应之s已_xxx文件所在章节,见稍后之第三列表。
(3) SGI STL内部私用文件CSGISTL真正实现千此)
1.8.3 GISTL的编译器组态设置(configuration)
不同的编译器对C++语言的支持程度不尽相同。作为一个希望具备广泛移植能力的程序库,SGI STL准备了一个环境组态文件<Stl_config.h>,其中定义了许多常量,标示某些组态的成立与否。所有STL头文件都会直接或间接包含这个组 态文件, 并以条件式写法, 让预处理器(pre-processor)根据各个常量决定取舍哪一段程序代码。 例如:
<stl_config.h>文件起始处有一份常量定义说明,然后即针对各家不同的编 译器以及可能的不同版本。
stl_config .h中的各种组态(configurations):
1.9 可能令你困惑的C++语法
1.8所列出的几个组态常量,用来区分编译器对C++Standard的支持程度。这几个组态,也正是许多程序员对千C++语法最为困扰之所在。
1.9.1 stl_config .h中的各种组态(configurations)
以下所列组态编号与上一节所列的<stl_config.h>文件起头的注释编号相同。
组态3:__ STL_ST A TIC_TEMPLA TE_M EMBER_BUG
//file: lconfig3.cppp
//测试在class template中拥有static data members.
// test__STLSTATIC_TEMPLATE_MEMBER_BUG, defined in <stl_config.h> // ref. C++ Primer 3/e, p.839
// vc6[o) cb4[x) gcc[o)
// cb4 does not support static data member ini七ializa巨on.
#include <iostrearn>
using narnespace std;
template <typename T>
class testClass {
public://纯粹为了方便测试,使用public
static int _data;
};
//为static data members进行定义(配置内存),并设初值 int testClass<int>::_data = 1;
int testClass<char>::_data = 2;
int main()
{
//以下,CB4表现不佳,没有接受初值设定
cout << testClass<int>::_data << endl; //GCC, VC6: 1 CB4: 0
cout << testClass<char>::_data << endl; //GCC, VC6: 2 CB4: 0
testClass<int> obj il, obj i2;
testClass<char> obj cl, obj c2;
cout << obji1._data << endl;
cout << obji2._data<<endl;
cout << objc1._data << endl;
cout << objc2._data << endl;
obj il._data = 3;
objc2._data = 4;
cout << obji1._data << endl;
cout<<obji2._data << endl;
cout << objc1._data << endl;
out << objc2._data<< endl;
}
组态5: _ _STL_CLASS_PARTIAL_SPECIA LIZA TION
// file: lconfig5.cpp
//测试class template partial specialization:在class template的
// 一般化设计之外,特别针对某些template参数做特殊设计
// test _STL_CLASS_PARTIAL_SPECIALIZATION in <stl_config.h> // ref. C++ Primer 3/e, p.860
// vc6[x) cb4[o] gcc[o]
#include <iostream>
using namespace std;
//一般化设计
template <Class I, class O>
struct test Class
{
testClass(){ cout << "I, O" << endl;}
};
//特殊化设计
template <class T>
struct testClass<T*,T*>
{
testClass(){cout<<"T*,T*"<<endl;}
};
//特殊化设计
template <class T>
struct testClass<const T*, T*>
{
testClass(){ cout <<"const T*, T*"<< endl; }
};
int main()
{
testClass<int, char> obj1; //I O
testClass<int*, int*> obj2; //T* T*
testClass<const int*,int*> obj3;//const T*,T*
}
组态6: __ STL_FUNCTION _ TMPL_PARTIAL_ORDER
//file:lconfig6.cpp
// test _STL_FUNCTION_TMPL_PARTIAL_ORDER in <stl_config.h>
// vc6[x] cb4[o] gcc[o]
#include <iostream>
using namespace std;
class alloc{
};
template <class T, class Alloc = alloc>
class vector {
public:
void swap(vector<T, Alloc>&) { cout << "swap()" << endl; }
};
#ifdef _STL_FUNCTION_TMPL_PARTIAL_ORDER //只为说明,非本程序内容
template<class T, class Alloc>
inline x.swap(y);
void swap(vector<T, Alloc>& x, vector<T, Alloc>& y) {
x.swap(y);
}
#endif //只为说明,非本程序内容
//以上节录自stl_vector.h,灰色部分系源代码中的条件编译,非本测试程序内容 int main()
{
vector<int> x,y; swap(x, y);
}
组态7:__ STL_EXPLICIT _FUNCTION _TMPL_ARGS
整个SGISTL内都没有用到这一常量定义。
组态8:__ STL_M EMBER_TEMPLA TES
// file: lconfig8.cpp
//测试class template之内可否再有template(members) //test__STL MEMBER_TEMPLATES in <Stl_config.h>
// ref. C++ Primer 3/e, p.844
// ve6 [o] eb4 [o] gee [o]
#include <iostream>
using namespace std;
class alloc {
) ;
template <class T, class Alloc = alloc>
class vector{
public:
typedef T value_type;
typedef value_type* iterator;
template <class I>
void insert(iterator position,I first,I last){
cout << "insert()" << endl;
}
};
int main (){
int ia[5] = {0,1,2,3,4);
vector<int>x;
vector<int>::iterator ite;
x.insert(ite, ia, ia+S); // insert()
}
组态10:_STL_LIMITED_DEFA ULT TEMPLATES
// file: lconfiglO.cpp
//测试template参数可否根据前一个template参数而设定默认值
//test_STL_LIMITED_DEFAULT_TEMPLATESin <stl_config.h> // ref. C++ Primer 3/e, p.816
// vc6[o] cb4[o] gcc[o]
#include <iostream>
#include <cstddef>// for size_t
using namespace std;
class alloc {
};
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque{
public:
deque() { cout << "deque" << endl; }
};
//根据前一个参数值T,设定下一个参数Sequence的默认值为deque<T>
template <class T, class Sequence=deque<T> >
class stack {
public:
stack(){ cout << "stack" << endl; }
private:
Sequence c;
);
int main()
{
stack<int> x;// deque
// stack
};
组态11:__ STL_NON _ TYPE_ TMPL_PARAM_BUG
// file: lconfigll.cpp
//测试class template可否拥有non-type template参数
// test _STL_NON_TYPE_TMPL_PARAM_BUG in <Stl_config.h>
// ref. C++ Primer 3/e, p.825
// vc6[o] cb4[o] gcc[o]
#include <iostream>
#include <cstddef>
using namespace std;
class alloc{
};
inline size_t _deque_buf_size(size_t n,size_t sz)
{
return n != O ? n : (sz < 512 ? size_t(512/sz) : size_t(1));
}
template <class T, class Ref, class Ptr, size_t BufSiz>
struct_deque_iterator{
typedef _deque_iterator<T,T&, T*, BufSiz> iterator;
typedef _deque_iterator<T, const T&, const T*, BufSiz> const_iterator;
static size_t buffer_size() {return _deque_buf_size(BufSiz, sizeof(T)); }
};
template<class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
public: // Iterators
typedef _deque_iterator<T,T&, T*, BufSiz> iterator;
};
int main()
{
cout << deque<int>::iterator::buffer_size() << endl;// 128
cout<< deque<int,alloc,64>::iterator::buffer_size() << endl; // 64
}
以下组态常量虽不在前列编号之内,却也是<stl_config.h>内的定义,并使用于整个SGI STL之中,有认识的必要。
组态:__ STL_N ULL_ TMPL_ARGS (bound friend template friend)
<stl_config.h>定义_STL_NULL_TMPL_ARGS如下:
#ifdef _STL_EXPLICIT_FUNCTION_TMPL_ARGS
#define _STL_NULL_TMPL_ARGS <>
#else
#define _STL_NULL_TMPL_ARGS
#endif
这个组态常量常常出现在类似这样的场合(class template的friend函数声明:
// in <stl_stack.h>
template <class T, class Sequence = deque<T> >
class stack{
friend bool operator== _STL_NULL_TMPL_ARGS (const stack&, const stack&);
friend bool operator<_STL_NULL_TMPL_ARGS (const stack&, const stack&);
...
} ;
展开后就变成了:
template <class T, class Sequence = deque<T> >
class stack {
friend bool operator== <> (const stack&, const stack&);
friend bool operator< <> (const stack&, const stack&);
...
} ;
这种奇特的语法是为了实现所谓的bound friend templates, 也就是说class
template的某个具现体(instantiation)与其friend function template的某个具现体
有一对一的关系。下面是一个测试程序:
// file: lconfig-null-template-arguments.cpp
// test _STL_NULL_TMPL_ARGS in <stl_config. h>
// ref. C++ Primer 3le, p.834: bound friend funct ion template
// vc6[x] cb4[x] gcc[o]
#include <iostream>
#include <cstddef>//for size_t
using namespace std;
class alloc{
};
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque{
public:
deque() { cout << "deque" << ''; }
};
//以下声明如果不出现, GCC也可以通过。 如果出现, GCC也可以通过。 这一点和C++ Primer 3/e p.834的说法有出入。 书上说一定要有这些前置声明
/*
template <class T, lass Sequence>
class stack;
template <class T,class Sequence>
bool operator==(const stack<T, Sequence>& x,const stack<T, Sequence>& y);
template <class T,class Sequence>
bool operator<(const stack<T, Sequence>& x,const stack<T, Sequence>& y};
*/
template <class T,class Sequence = deque<T> >
class stack {
//写成这样是可以的
friend bool operator==<T> {const stack<T>&, const stack<T>&);
friend bool operator<<T> {const stack<T>&, const stack<T>&);
//写成这样也是可以的
friend bool operator==<T> (const stack&,const stack&);
friend bool operator<<T> (const stack&,const stack&);
//写成这样也是可以的
friend bool operator==<>(const stack&,const stack&);
friend bool operator<<>(const stack&,const stack&);
//写成这样就不可以
// friend bool operator==(const stack&,const stack&);
// friend bool operator<(const stack&,const stack&);
public:
stack() { cout << "stack”<< endl; }
private:
Sequence c;
};
template <class T, class Sequence>
bool operator==(const stack<T, Sequence>& x,const stack<T, Sequence>& y) {
return cout << "operator=="<<'\t';
}
template <class T, class Sequence>
bool operator<(const stack<T,Sequence>& x,const stack<T, Sequence>& y) {
return cout << "operator<"<<'\t';
}
int main()
{
stack<int> x;// deque stack
stack<int> y;//deque stack
cout << (x == y) << endl; //operator==1
cout<<(x < y) << endl;// operator<1
stack<char> yl; //deque stack
// cout << (x==yl) << endl; // error: no match for .. .
// cout << (x < yl) << endl;// error: no matchfor .. .
}
组态:__STL TEMPLATE_NULL( class tern p late explicit speciahzation)
<stl_config.h>定义了一个_STL_TEMPLATE_NULL如下:
#ifdef STL CLASS PARTIAL_SPECIALIZATION
#define _STL_TEMPLATE_NULL template<>
#else
#define _STL_TEMPLATE_NULL
#endif
这个组态常量常常出现在类似这样的场合:
// in <type_traits.h>
template<class type> struct_type_traits { ...};
__STL_TEMPLATE_NULL struct _type_traits<char>{ . . . } ;
// in <stl_hash_fun.h>
template <class Key> struct hash{ };
__STL TEMPLATE_NULL struct hash<char>{...};
__STL TEMPLATE_NULL struct hash<unsigned char>{...};
展开后就变成了:
template<class type>struct_type_traits{ ...};
template<>struct _type_traits<char> { ... };
template<class Key> struct hash { };
template<>struct hash<char> { ... };
template<> struct hash<unsigned char> { ... };
这是所谓的class template explicit specialization。下面这个例子适用于GCC和VC6, 允许使用者不指定template<>就完成explicit specialization,C++Builder则是非常严格地要求必须完全遵照C++标准规格,也就是必须明白写出template<>。
// file: lconfig-ternplate-exp-special.cpp
//以下测试 class template explicit specialization
//test _STL_TEMPLATE_NULL in <Stl_config.h>
// ref. C++ Primer 3/e, p.858
// vc6[o] cb4[x] gcc[o]
#include <iostream>
using namespace std;
//将_STL_TEMPLATE_NULL定义为template<>, 可以
//若定义为blank, 如下, 则只适用于 GCC
#define__STL_TEMPLATE_NULL / * blank * /
template <class Key> struct hash {
void operator () () { cout<<"hash<T>"<<endl;}
};
// explicit specialization
__STL_TEMPLATE_NULL struct hash<char> {
void operator()() { cout<< "hash<char>" << endl; }
};
__STL_TEMPLATE_NULL struct hash<unsigned char> {
void operator()() { cout<< "hash<unsigned char>" << endl; }
};
int main()
{
hash<long> t1;
hash<char>t2;
hash<unsigned char>t3;
t1() ;//hash<T>
t2() ; // hash<char>
t3();// hash<unsigned char>
}
1.9.2 临时对象的产生与运用
临时对象就是一种无名对象(unnamed objects)。它的出现如果不在程序员的预期之下(例如任何pass by value操作都会引发copy操作,于是形成一个临时对象),往往造成效率上的负担。但有时候刻意制造一些临时对象,却是很好的技巧。
制造临时对象的方法:在型别名称之后直接加一 对小括号,并可指定初值,例如Shape(3,5)或int(8),其意义相当于调用相应 的constructor且不指定对象名称。STL最常将此技巧应用于仿函数(functor)与算法的搭配上,例如:
//file: lconfig-temporary-object.cpp
//本例测试仿函数用于for_each()的情形
// vc6[o] cb4[o] gcc[o]
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
template<typename T>
class print
{
public:
void operator(){const T& elem)//operator重载
{cout<<elem<<‘’;}
};
int main()
{
int ia[6] = { 0, 1, 2, 3, 4, 5 };
vector< int > iv(ia, ia+6) ;
// print<int> ()是一个临时对象,不是一个函数调用操作
for_each(iv.begin(), iv.end(), print<int>());
}
最后一行便是产生"function template具现体"print的一个临时对象。 这个对象将被传人for_each()之中起作用。当for_each()结束时,这个临时对 象也就结束了它的生命。
1.9.4 静态常量整数成员在class 内部直接初始化 (in-class static constant integer initialization )
如果class内含const static integral data member(常量静态整型数据成员), 那么根据 C++ 标准规格,
我们可以在class之内直接给予初值。 所谓integral泛指所有整数型别, 不单只是指int。 下面是 个例子:
// file: lconfig-inclass-init .cpp
//test in-class initialization of static cons七 in七egral members
// ref. C++ Primer 3/e, p.643
// vc6[x] cb4[o] gcc[o]
#include <iostream>
using namespace std;
template <typename T>
class testClass{
public: // expedient
static const int _datai=5;
static const long _datal =3L;
static const char _datac='c';
}
1.9.4 increment/ decrement/ dereference操作符
increment/ dereference操作符在迭代器的实现上占有非常重要的地位,因为任何一个迭代器都必须实现出前进(increment, operator++)和取值(dereference,operator*)功能, 前者还分为前置式(prefix)和后置式(postfix)两种, 有些迭代器具备双向移动功能,那么就必须再提供decrement操作符(也分前置式和后置式两种)。下面是 个范例:
// file: lconfig-operator-overloading.cpp
// vc6[x) cb4[o] gcc[o]
// vc6的friend机制搭配C++标准程序库,有bug
#include <iostrearn>
using namespace std;
class INT
{
friend ostream& operator<<(ostream&os, const INT& i);
public:
INT(int i) : m_i(i){ } ;
//prefix: increment and then fetch
INT& operator++()
{
++(this->m_i); //随着class的不同,该行应该有不同的操作
return *this;
}
// postfix : fetch and then incrernent
const INT operator++(int)
{
INT temp=*this;
++(*this);
return temp;
}
// prefix : decrernentandthen fetch
INT& operator--()
{
--(this->rn_i);//随着class的不同,该行应该有不同的操作 return *this;
}
//postfix: fetch and then decrement
const INT operator--(int)
{
INT temp=*this;
--(*this);
return temp;
}
// dereference
int&operator*() const
{
return (int&) m_i;
//以上转换操作告诉编译器,你确实要将const int转为non-const 1value.
//如果没有这样明白地转型,有些编译器会给你警告,有些更严格的编译器会视为错误
}
private:
int m_i;
};
ostream& operator<<(ostream&os, const INT&i)
{
os <<'['<< i.m_i <<'l';
return os;
}
int main()
{
INT I(5);
cout << I++; //[5]
cout<< ++I;//[7]
cout << I--; //[7]
cout << --I; //[5]
cout << *I; //5
}
1.9.5 前闭后开区间表示法[)
任何一个STL算法, 都需要获得由 一对迭代器(泛型指针)所标示的区间, 用以表示操作范围。这一对迭代器所标示的是个所谓的前闭后开区间 , 以[first, last)表示。 也就是说,整个实际范围从first开始, 直到last-1。 迭代器last所指的是最后一个元素的下一位置”。这种off by one (偏移一格,或说pass the end)的标示法,带来了许多方便,例如下面两个STL算法的循环设计,就显得干净利落:
template <class InputIterator, class T>
Inputiterator find(Inputiterator first, InputIterator last, const T& value) {
while (first!= last && *first != value) ++first;
return first;
}
template<class Inputiterator, class Function>
Function for_each(Inputiterator first, InputIterator last,Function f) {
for (; first != last; ++first)
f(*first);
return f;
}
前闭后开区间图示如下(注意,元素之间无需占用连续内存空间):
1.9 .6 function call操作符(operator())
函数调用操作(C++语法中的左右小括号)也可以被重载。许多STL算法都提供了两个版本:
一个用于一般状况(例如排序时以递增方式排列),
一个用于特殊状况(例如排序时由使用者指定以何种特殊关系进行排列)。
像这种情况,需要用户指定某个条件或某个策略,而条件或策略的背后由一整组操作构成,便需要某种特殊的东西来代表这“一整组操作"。
代表”一整组操作”的,当然是函数。过去C语言时代,欲将函数当做参数传递,唯有通过函数指针(pointerto function, 或称function pointer)才能达成,例如:
// file: lqsort.cpp
#include <cstdlib>
#include <iostream>
using namespace std;
int fcmp(const void* elem1, const void* elem2);
void main()
{
int ia[lO] = {32,92,67,58,10,4,25,52,59,54};
for(int i=0;i<10;i++)
cout<<ia[i]<<" ";// 32 92 67 58 10 4 25 52 59 54
qsort(ia,sizeof(ia)/sizeof(int) ,s1.zeof(int), fcmp);
for(int i=O; i < 10; i++)
cout << ia [ i ]<< " ";// 4 10 25 32 52 54 58 59 67 92
}
int fcmp(const void* elem1, const void*elem2)
{
const int* il = (const int*)eleml;
const int* i2 = (const int*)elem2;
if (*il < *i2)
return -1;
else if (*il == *i2)
return O;
else if(*il > *i2)
return 1;
}
但是函数指针有缺点,最重要的是它无法持有自己的状态(所谓局部状态,local states) , 也无法达到组件技术中的可适配性(adapt ability)-也就是无法再将某些修饰条件加诸于其上而改变其状态。
为此,STL算法的特殊版本所接受的所谓“条件”或“策略”或“一整组操作 ,都以仿函数形式呈现。所谓仿函数(functor)就是使用起来像函数一样的东西,如果你针对某个class进行operator()重载,它就成为一个仿函数。至于要 成为一个可配接的仿函数,还需要做一些额外的努力。
下面是一个将operator()重载的例子:
// file: lfunctor.cpp
#include <iostream>
using namespace std;
//由于将operator()重载了,因此plus成了一个仿函数 template <class T>
struct plus{
T operator() (const T&x, const T&y) const{ return x + y; }
};
//由于将operator()重载了,因此minus成了一个仿函数 template <class T>
struct minus {
T operator() (const T& x, const T& y) const{ return x -y; }
};
int main()
{
//以下产生仿函数对象
plus<int> plusobj;
minus<int>minusobj;
//以下使用仿函数,就像使用一般函数一样
cout << plusobj (3,5) << endl;//8
cout<< minusobj (3,5) << endl;//-2
//以下直接产生仿函数的临时对象(第一对小括号),并调用之(第二对小括号)
cout<<plus<int>()(43,50)<<endl;//93
cout << plue<int>()(43,50) << endl;//-7
}
上述的plus和minus已经非常接近STL的实现了,唯一的差别在于它缺乏”可配接能力”。关于“可配接能力”,将在第8章详述。