记下关于模板的知识与理解。
继承和组合提供了重用对象代码的方法,而C++的模板特征提供了重用源代码的方法。
截止到最近为止我对模板的理解也就之停留在表面:作为一种类型的容器。比如说实现一个可以存储任类型的Stack,有一些共同的操作,当你想使用的时候放入具体的类型进行实例化(instanation)。容器,貌似很好理解的样子。
这也是文章开头所提的到的部分,最最简单且易为理解的部分。
小tips:
staic int a[100];
这样的写法,编译器会将这个static数组初始化为0.
模板介绍(第十六章)
16.3模板语法
template这个关键字会告诉编译器,随后的类定义将操作一个或更多未指明的类型。当由这个模板产生实际类代码的时,必须指定这些类型以使编译器能够替换他们。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21using namespace std;
template<class T>
class Array
{
enum { size = 100; };
T A[size];
public:
T& operator[](int index){
require(index >= 0 && index < size, "Index out of range");
return A[index];
}
}
int main(){
Array<int> ia;
Array<float> fa;
// ......
}
这时,编译器两次扩展了Array模板【这被称为实例化(instantiation)】,创建两个新的生成类(generated class),可以把它们看做Arrray_int和Array_float(不同的编译器对名称有不同的修饰方法)。这些类就像是手工创建的一样,只是这里是当定义了对象ia和fa后由编译器来创建这些类。我们还会注意到,编译器避免了或者连接器合并了类的重复定义。
16.3.1非内联函数定义
希望有非内联函数定义的时候,这时编译器需要在成员函数定义之前看到template声明。
1 | template<class T> |
关于内联函数和非内联函数的定义在别的章节会有提到。在此不赘述。
在引用模板的类名的地方,必须伴有该模板的参数列表,这样在按照模板生成实例的时候,因为模板参数中的参数修饰类名,以便为每一个模板实例产生唯一的类名标识符。
- 因此模板的参数列表在引用模板类名的时候,也要有一席之地。
- 模板的非内联函数定义之前一定要加上template声明。
16.3.1.1头文件
即使是在创建非内联函数定义时,我们还是把模板的所有声明和定义都放入一个头文件中。这似乎违背了通常的头文件规则:“不要放置分配存储空间的任何东西”(这条规则是为了防止在连接期间的多重定义错误),但模板定义很特殊。在template<…> 之后的任何东西都意味着编译器在当时不为他分配存储空间,而是一直处于等待状态直到被一个模板示例告知。在编译器和连接器中有机制能去掉统一模板的多重定义。所以为了使用方便,几乎总是在头文件中防止全部的模板声明和定义。
16.3.3模板中的常量
模板参数并不局限于类定义的类型,可以使用编译器内置类型。这些参数值在编译期间变成模板的特定示例的常量。我们甚至可以对这些参数使用默认值。
1 | template<class T, int size = 100> |
上面的例子中的size的值没有房放在类中,但是对他的使用就如同是成员函数中的数据成员。
我记录下来上面这段代码的原因还有一个就是懒惰初始化(lazy initialization).上面的Holder中有一个指向Array的指针,而不是指向类型Array的嵌入对象。该指针在构造函数中不被初始化,而是被推迟到了第一次访问的时候。还有等等的稀有的用法。
这个章节还有许多其他内容,但是貌似跟我现在急需要理解的内容相差甚远,暂时保留。
第二卷第五章 深入理解模板
关于模板的使用问题,在真正的使用模板之前一定要先让编译器看到模板的声明和定义,比如说在同一个文件里写一个类继承一个模板的时候,模板的声明和定义不能在类的下方,应该先写模板再用。