1.6模板
与许多算法的典型特征一样,顺序扫描算法是类型无关的。所谓类型无关,就是说这种算法的逻辑与存储在数组中的项的类型无关。相同的逻辑可以适用于整数、浮点数或者具有可比性的任何类型。
本书描述的算法和数据结构都是类型无关的。
本节论述在C++中如何用模板(template)来写类型无关的算法(也称为泛型算法)。我们先讨论函数模板,然后是类模板。
1.6.1函数模板
函数模板(function template)不是真正的函数,而是一个用以产生函数的公式。
template声明的行显示Comparab1e是模板实参,该实参可以被任何类型代替来生成函数。例如,用vectorsstring>作为参数的findMax函数被调用时,就通过用string替换Comparab1e来生成一个新的函数。
函数模板可以应需要而自动扩展。要注意的是随着每种类型的扩展,都会生成附加代码。在大项目里,这被称为代码膨胀(code bloat)。
在使用任何模板前,习惯上都是先包含用以说明关于类模板实参的定义的注释。这也包含了关于需要哪种类型的构造函数的定义。(自定义的class需要重载操作符)
有很多处理函数模板的晦涩的规则,这并不稀奇。大多数的问题都是出现在函数模板不能提供完全匹配的而只是相近的参数(通过隐式类型转换)的情况下。必须有解决这种不确定问题的办法,其规则也是非常复杂的。注意,如果有一个非模板和一个模板都可以匹配的话,那么非模板具有优先权。还要注意到,如果有两个同样相似的匹配,那么代码就是非法的,并且编译器会报错。
1.6.2类模板
MemoryCe11与Intce11类相似,但是可以用于任何类型的object,只要object具有一个零参数构造函数、一个复制构造函数和一个复制赋值运算符。
要注意的是,object是通过常量引用来传递的。还要注意到构造函数的默认参数不是0,因为0有可能不是有效的object。取而代之,默认参数是通过零参数构造函数来构造object的结果。
如果将类模板作为一个单独的单元来实现,那么就很少有语法累赘。事实上,许多类模板就是用这种方式实现的。因为目前模板的分离编译在许多平台上都不能很好地运行。因此,在许多情况下,包括其实现的整个类都必须放在.h文件中。流行的STL的实现就是遵循这个策略。
1.6.3object、comparablr和例子
在本书中,我们总是将object和Comparable作为泛型类型来使用。object定义为含有一个零参数构造函数、一个operator=和一个复制构造函数。正如在findMax例子中推荐的,Comparable在operator<作用下具有可以提供全部数据的排序的附加功能!。
操作符重载(operator overloading).操作符重载可以允许重新定义内置操作符的含义。
为具有实用性,每一个数据成员都必须是public的,否则就必须提供附加的访问函数和修改函数。
1.6.4函数对象
然而,模板有一个重要的限制:只能用于定义了operator<函数的对象,并且使用operator<作为所有比较的基础。在许多情况下,这种方法并不可行。
这些问题的解决方案就是重写finduax,使其可以像接受参数一样接受对象数组和比较函数,并由该比较函数来决定两个对象中哪一个更大、哪一个更小。在实际效果上,数组对象不再知道如何进行相互比较,取而代之,这些信息完全从数组对象中剥离出来。
注意,对象同时包含数据和成员函数,一个如传递参数一样传递函数的巧妙的办法是:定义一个包含零个数据和一个成员函数的类,然后传递这个类的实例。从效果上看就是,通过将其放在对象中实现了函数的传递。该对象通常称为函数对象(function object)。
图1-22是函数对象思想的最简单实现。findMax获得第二个形参,该形参为泛型类型。为使findMax模板可以无误地扩展,泛型类型必须含有名为isLessThan的成员函数。该成员函数获得第一个泛型类型(object)的两个形参,并返回一个boo1量。
C++的函数对象通过这种基本的思想来实现,但有一些奇特的语法。首先,不是使用函数名来调用函数,而是使用操作符重载。不是使用函数intessThan,而是使用operator()。其次,当调用operator()时,cmp.operator()(x,y)可以缩写为cmp(x,y)(换句话说,这看起来就像是函数调用,因此,operator()被称为函数调用操作符(function call operator))。
1.6.5类模板的分离编译
和其他的规则类一样,类模板可以在声明的时候整体实现,也可以将接口从实现中分离出来。但是,曾经支持分离编译模板的编译器已经越来越弱化并且需要特殊的平台来支持。这样来,在许多情况下,整个的类模板连同其实现都放置在一个单独的头文件中。常见的标准库的实现在实现类模板时遵循这个原则。
成员函数的实现会导致看起来很复杂的语法,特别是像operator=一样复杂的函数。更坏的情况是,在进行编译的时候,编译器经常报出函数丢失的信息。要避免这个问题需要特殊的平台来解决。
1.7使用矩阵
C++类没有提供matrix类。但是一个合理的matrix类可以很快就写出来。基本的思想是使用向量的向量。这样做需要附加的操作符重载的知识。对matrix定义operator[],即数组索引操作符。
1.7.1数据成员、构造函数和基本访问函数
1.7.2operator【】
operator[]的思想如下:如果有一个matrixm,那么mti]就应该返回一个对应matrixm的行i的向量。这样一来,m[i][j]就可以通过正常的vector索引操作给出向量m[i]的位置。因此,matrix operator[]不是返回一个object,而是一个vector。
使访问函数版本的operator[]返回常量引用,而修改函数版本的则返回简单引用。如图1-24所示,这样一来所有问题就都解决了。
1.7.3析构函数、复制赋值和复制构造函数
因为vector处理了上述函数,所以,这些函数都是自动处理的。因此,这些函数就是完全功能的matrix类所需要的所有代码。
小结
这一章是本书后续部分的基础。面临大量输入时,一个算法所需要的运行时间是判断其好坏的重要标准。(当然,正确性是最重要的。)速度是相对的。对于某个问题在某台机器上运行很快的算法,对另一个问题或在另一台机器上运行就可能很慢。下一章中我们将讨论这些问题,并使用在这里讨论的数学知识来建立正式的模型。