标准模板库
作者:Alexander Stepanov, Meng Lee
翻译:kary
『这篇文章有些地方自己也不太懂。本来不想上传的,只是因为看看现在的某些出版社
实在太黑心了。有些书本来没多少页数,于是在版面上大作文章,例如用了巨无霸的字体,
或者是版面的半边留了大片空白,写点副标题之类的,如同雪白的脸蛋上的点缀一些雀斑一般。
价格也奇贵无比,真不知道为什么这个国家不管一管。有一次在书店,听到店主人在那儿说:
这帮买书的非被气死不可,这本书第一版卖的不好,第一版要卖百把块,第二版却只卖35元。
我看了一下,店主人说的是一本AutoCAD方面的书。所以动了点肝火,把本来自己看看还马马
虎虎,给人家看不免脸红的东西弄上来了。
原文出处:http://upload.smiling.com.cn/file/67539/stl.pdf』
1 概述
标准模板库提供了一系列具有良好结构的通用C++组件,这些组件紧密协作,提供强大的功能。标准库的设计必须确保所有的模板算法既能操作库中的数据类型,也能操作C++固有的数据类型。例如,所有的算法都适用于普通指针类型。库中各组件功能是独立的,也就是说,程序员可以自己设计算法操作库提供的数据结构,也可以使用标准库的算法操作自定义的数据类型。库的广泛通用性正是建立在这种灵活性的基础上。
标准库的另一个重要特点是其高效率。C++成功的原因是它具有强大的功能和高效率。库中的每个模板组件都经过大量的验证,其效率和相应的手工编码相比,只有几个百分点的差别。
标准库的第三个特点是它非常简洁易懂。
2 标准库的结构
标准库主要包含五个部分:
–算法:定义了计算过程。
–容器:管理数据集合。
–迭代器:提供遍历容器的方法。
–函数对象:将函数封装在对象中,供其他组件使用。
–适配器:给组件提供不同的接口。
这样的功能划分有效地减少了代码量。例如,我们无需为每种容器提供搜索算法,而只要提供一个能满足一定基本规则的算法,这个算法就能够用来操作所有的容器。
下面的例子更清楚地说明了标准库的结构。如果将软件组件表示为一个三维数组,其中第一维表示不同的数据类型(如,int,double),第二维代表不同的容器(如,矢量,链表,文件),而第三维代表不同的算法(如,搜索,排序,旋转),假设i,j,k分别是三个维度的尺寸。通过使用以数据类型作为模板参数的模板函数,我们需要j*k个版本。如果进一步地使我们的算法能够适用于不同的容器,则只需要j+k各版本。这就极大地简化了软件设计,并且使得库中组件能和用户自定义组件能很灵活的配合。例如,用户可以定义一个特殊的容器,并用库中的sort函数对它进行排序,也可以为sort函数提供一个自定义的比较函数,这个比较函数既可以是普通函数指针,也可以是一个函数对象(所谓函数对象,就是定义了operator()操作符的对象)。如果用户需要反方向遍历容器,则可以使用reverse_iterator适配器。
标准库以一致的方式扩展了C++,所以C/C++程序员可以轻松上手。例如,库中包含merge模板函数,如果用户需要将两个数组a和b合并为数组c,可以这样做:
int a[1000];
int b[2000];
int c[3000];
...
merge(a, a + 1000, b, b + 2000, c);
//注:merge函数操作:将a和b两个数组的元素中比较小的放在前面。但不考虑各自
// 数组中的顺序。
如果用户想合并一个矢量和一个链表(在库中两者都是模板类),并将结果保存到一块未初始化内存中,则可以这样写:
vector<Employee> a;
list<Employee> b;
...
Employee* c = allocate(a.size() + b.size(), (Employee*)0);
merge(a.begin(), a.end(), b.begin(), b.end(),
raw_storage_iterator<Employee*, Employee>(c));
这儿begin()和end()是容器类的成员函数,分别返回相应类型的迭代器(类似于指针的对象)。而raw_storage_iterator则是一个适配器。通过调用相应类型的复制构造函数,merge算法将结果直接复制到未初始化的内存中。
有时候需要象遍历普通数据结构一样对输入/输出流进行遍历。比如要合并两个数据结构并将结果保存到一个文件中,假如能够将结果直接保存到相应的文件中,而无需创建任何辅助的数据结构,那该多方便!因此模板库提供了istream_iterator和ostream_iterator模板类。通过这两个类,库中的算法也可以应用到具有相同数据类型集合的I/O流中。下面的程序从标准输入读取整数,删除所有可以被指定数字整除的整数,并将结果输出到标准控制台:
main(int argc, char** argv)
{
if (argc != 2)
throw(”usage: remove_if_divides integer\n”);
remove_copy_if(istream_iterator<int>(cin),
istream_iterator<int>(),
ostream_iterator<int>(cout, ”\n”),
not1(bind2nd(modulus<int>(), atoi(argv[1])))
);
}
所有的工作都由remove_copy_if语句完成。remove_copy_if函数接受两个输入迭代器参数和一个输出迭代器参数以及一个断言函数参数。程序中该算法一个一个地标准控制台读取整数并将通过测试的整数写入到输出流中,直到输入迭代器(第一个参数)到达流结束位置迭代器(第二个参数)为止。流结束位置迭代器用不带参数的构造函数创建。(一般来说,所有的算法都以“从这儿到那儿”的方式工作,也就是接受两个迭代器参数,分别表示输入的开始和结束位置。)输出迭代器被捆绑到cout上。remove_copy_of的最后一个参数是一个断言函数(predicate『返回真或假的函数』)。modulus<int>是一个双目函数,该函数接受两个参数i和j并返回i%j。bind2nd将modulus<int>和第二个参数捆绑为一个单目断言函数。最后使用函数适配器not1来生成一个否定单目断言函数,并作为参数传递给remove_copy_if算法。
下面的例子稍微有点复杂。它从标准控制台接受输入,并随机排列每一行后输出到控制台。
main(int argc, char**)
{
if (argc != 1) throw(”usage: shuffle\n”);
vector<string> v;
copy(istream_iterator<string>(cin), istream_iterator<string>(),
inserter(v, v.end()));
random_shuffle(v.begin(), v.end());
copy(v.begin(), v.end(), ostream_iterator<string>(cout));
}
copy将从标准输入中读取的每一行复制到一个矢量容器中。由于该矢量容器没有预先分配内存,所以程序使用了插入迭代器『也叫适配器』将每一行字符串插入到矢量容器中。(这种技术允许所有复制函数既能工作于普通覆盖方式也可以工作于插入方式。)然后random_shuffle随机排列容器元素,并用copy函数将所有的对象复制到输出流中。
3 规则
为了确保库中不同组件能够共同工作,这些组件必须满足一些基本规则。规则的表述应该尽可能准确无误。例如,对于“类x必须定义成员函数operator++()”这样的语句,我们的描述是“对于类X的一个对象x, ++x必须定义”(因为我们不知道该操作是个成员函数还是全局函数。)规则采用严格定义的表达式描述,并指出符合规则的类型。对于一个规则集,用一个表指定合法表达式及其语义集合。任何满足这些规则的范型算法必须根据它的类型参数以合法的表达式编码。
如果我们说某操作的复杂度满足线性时间,意思是它最多只能耗费线性时间。所以一个消耗固定时间的操作也满足该规则。
有时我们使用C++代码描述语义规则。这些代码只是用来说明类似的代码编写方法,而不是说明必须照搬照抄(尽管某些情况下这些代码毫无疑问是一种比较好的实现方法)。
4 核心组件
本节说明一些最基本的模板函数和类。库的其余部分都会用到它们。
4.1 操作符
为避免重复定义,库将!=操作(可以从==操作推导)以及>, <=, 和 >= (可以从<操作推导)定义为:
template <class T1, class T2>
inline bool operator!=(const T1& x, const T2& y) {
return !(x == y);
}
template <class T1, class T2>
inline bool operator>(const T1& x, const T2& y) {
return y < x;
}
template <class T1, class T2>
inline bool operator<=(const T1& x, const T2& y) {
return !(y < x);
}
template <class T1, class T2>
inline bool operator>=(const T1& x, const T2& y) {
return !(x < y);
}
4.2 成对对象(pair)
库中给成对出现的不同类型对象定义了模板类:
template <class T1, class T2>
struct pair {
T1 first;
T2 second;
pair() {}
pair(const T1& x, const T2& y) : first(x), second(y) {}
};
template <class T1, class T2>
inline bool operator==(const pair<T1, T2>& x, const pair<T1, T2>& y) {
return x.first == y.first && x.second == y.second;
}
template <class T1, class T2>
inline bool operator<(const pair<T1, T2>& x, const pair<T1, T2>& y) {
return x.first < y.first || (!(y.first < x.first) &&
x.second < y.second);
}
库中提供了相应的模板函数make_pair来简化成对对象的构造。因此,我们不必这样写:
return pair<int, double>(5, 3.1415926); // explicit types
而只需写为:
return make_pair(5, 3.1415926); // types are deduced
template <class T1, class T2>
inline pair<T1, T2> make_pair(const T1& x, const T2& y) {
return pair<T1, T2>(x, y);
}