第16章 模板介绍
继承和组合提供了重用对象代码的方法,而C++的模板特征提供了重用源代码的方法。
虽然C++模板是通用的程序设计工具,但当它们引入了C++后,似乎就不再鼓励使用基于对象的容器类层次结构(在第15章的最后论证)了。例如,标准C++容器类和算法(在本书的第2卷中有两章对此问题进行解释,可以从www.BruceEckel.com下载本书的第2卷。)是完全应用模板完成的,对程序员来说相对易于使用。
本章不仅阐述模板的基础,而且还介绍容器,它是面向对象程序设计的基本构件,几乎可以完全通过标准C++库中的容器实现。可以看到,本书使用的容器的例子—Stash和Stack—刚好适合于学习容器。在本章中,增加了迭代器(iterator)的概念。虽然容器是模板使用的理想的例子,但是在第2卷中(其中有一章是专门讨论高级模板)将会学到模板的许多别的用法。
16.1 容器
假定想创建一个栈,正如全书所做的这样。为了简单,这个栈类只存放int类型的值。
类IntStack是最为常见的下推栈的例子。为了简化,此处栈的尺寸是固定的,但是也可以对其进行修改,使得它能通过在堆中分配内存而自动扩展,如同在本书中到处被考查的Stack类一样。
main()向这个栈添加一些整数,然后再弹出它们。为了让这个例子更有趣,这些整数用fibonacci()函数生成,它生成传统的兔子繁殖数。下面是声明这个函数的头文件。
下面是实现:
这是一个相当有效的实现,因为它绝不会多次生成这些数。它使用int的static数组,编译器将这个static数组初始化为零。第一个for循环把下标i移到第一个数组元素为零的地方,然后while循环向这个数组添加斐波纳契数,直到期望的元素达到。但是注意,如果经过元素n的斐波纳契数(Fibonacci number)都已经被初始化,则完全跳过这个while循环。
16.1.1 容器的需求
很明显,一个整数栈不是一个重要的工具。容器类的真正需求是在堆上使用new创建对象和使用delete销毁对象的时候体现的。在一般程序设计问题中,程序员在编写程序时并不知道将来需要创建多少个对象。例如在设计空中交通指挥系统时不应限制这个系统能处理的飞机数目。我们不希望由于实际飞机的数目超过设计值而导致这个系统失败。在计算机辅助设计系统中,可以处理许多造型,只有用户能够(在运行时)确定到底需要多少造型。我们一旦注意到上述问题,便可以在程序开发中发现许多这样的例子。
依赖虚拟存储去处理“存储器管理”的C程序员常常发现new、delete和容器类的思想的混乱。表面上看,创建一个足够大的能包括任何可能需求的巨型全局数组是可行的。这可能不需要太多思考(或者并不需要弄清楚malloc()和free()),但是这样的程序接口性能较差,而且暗藏着难以捕捉的错误。
另外,如果我们创建一个巨型的C++对象的全局数组,那么构造函数和析构函数的开销会使系统效率显著地下降。C++中有更好的解决方法:用new创建需要的对象,将其指针放入容器中,待实际使用时将其取出并进行处理。用这种方法,所创建的只是确实需要的对象。通常,在启动程序时没有可用的初始化条件。new允许等待,直到在环境中相关事件发生后,再实际地创建这个对象。
在大多数情况下,应当创建用来存放感兴趣对象指针的容器。应当用new创建这些对象,然后把结果指针放在容器中(在这个过程中这是向上类型转换),当需要用到这些对象时再将指针从容器中取出。这项技术使得程序更具灵活性和一般性。