模板的引入
继承和组合提供了重用对象代码的方法,而模块提供重用源代码的方法。
一个容器是可以容纳其它对象的对象。它允许向它存储对象,而后可以取出使用的高速、智能的暂存存储器。容器类是解决不同类型的代码重用问题的另一种方法。
#include <iostream>
using namespace std;
class IntStack {
enum { ssize = 100 };
int stack[ssize];
int top;
public:
IntStack() : top(0) {}
void push(int i) {
if ( top >= ssize ) {
fputs( "Too many push()es\n", stderr);
exit(1);
}
stack[top++] = i;
}
int pop() {
if ( top <= 0 ){
fputs( "Too many pop()s \n", stderr);
exit(1);
}
return stack[--top];
}
};
int main() {
IntStack is;
// Add some Fibonacci numbers, for interest:
for(int i = 0; i < 20; i++)
is.push(i);
// Pop & print them:
for(int k = 0; k < 20; k++)
cout << is.pop() << endl;
}
在下面的例子中,IntStack可以存放整数,但是如果想存放浮点数、形状或其它数据对象,如果每次都依赖源码的更新,显然不是一个好办法。有三种源代码重用方法:
1)C方法,拷贝源代码并手动修改。
2)Smalltalk方法:通过继承的方法实现。所有类都是单个继承树的一部份。当创建新类时必须继承树的某一枝。大多数树是已经存在的(它是Smalltalk的类库),树的根称作object——每个Smalltalk包容器所包含的相同的类。
由于C++支持多个无关联的层次结构,所以Smalltalk的“基于object的层次结构”不能很好地工作。当然我们可以用如下的多重继承方法解决这个问题:
![](https://img-my.csdn.net/uploads/201304/15/1365991980_3473.png)
oshape具有shape的特点和行为,但它也是object的派生类,所以可将其置于容器内。但是由于多重继承的复杂性,除某些特殊情况,最好避免使用它。
3)模板方法
模板对源代码进行重用,而不是通过继承和组合重用对象代码。包容器不再存放称为object的通用基类,而由一个非特化的参数来代替。当用户使用模板时,参数由编译器来替换,这非常像原来的宏方法,却更清晰、更容易使用。
模板的使用
“模板(template)”这一关键字会告诉编译器它之后的类定义将操作一个或更多的非特定类型。当实际的类对象被定义时,这个非特定类型必须被指定以使编译器能够替代它们。
#include <iostream>
using namespace std;
template<class T>
class Array {
enum { size = 100 };
T A[size];
public:
T& operator[](int index) {
if (index < 0 && index >= size ) {
fputs( "Index out of range \n", stderr);
exit(1);
}
return A[index];
}
};
int main() {
Array<int> ia;
Array<float> fa;
for(int i = 0; i < 20; i++) {
ia[i] = i * i;
fa[i] = float(i) * 1.414;
}
for(int j = 0; j < 20; j++)
cout << j << ": " << ia[j]
<< ", " << fa[j] << endl;
}
除了template<class T>这一行之外,它看上去像一个通常的类。这里T是替换参数,它表示一个类型名称。在包容器类中的原本由某一特定类型出现的地方出现。
在main( )中,非常容易地创建包含了不同类型对象的数组。当出现代码:
array<int> ia;
array<float> fa;
这时,编译器两次扩展了数组模板(称为实例),创建两个新产生的类,我们可以把它们当作array_int和array_float(不同的编译器对名称有不同的修饰方法)。这些类就像手工创建的一样,但其实当定义对象ia和fa时,编译器自动的创建它们。注意要避免类在编译和连接中被重复定义。
在头文件中声明和定义
template<class T>
class Array {
enum { size = 100 };
T A[size];
public:
T& operator[](int index);
};
template<class T>
T& Array<T>::operator[](int index) {
if (index < 0 && index >= size ) {
fputs( "Index out of range \n", stderr);
exit(1);
}
return A[index];
}
在定义非内联函数时,模板的头文件中也可以放置所有的声明和定义。这似乎违背了通常的头文件规则:“不要在头文件里放置分配存储空间的东西”,这条规则是为了防止在连接时的多重定义错误。但模板定义很特殊。由template <…>处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。
有时,也可能为了满足特殊的需要(例如,强制模板实例仅存在于简单的Windows DLL文件中)而要在一个独立的CPP文件中放置模板的定义。大多数编译器有一些机制允许这么做,那么应该先确认特定的编译器说明文档以便使用它。