StoneのBLOG

生活这种事情,从来都是自我陶醉

0%

C++编程思想-第16章-模板介绍

记下关于模板的知识与理解。

继承和组合提供了重用对象代码的方法,而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
21
using 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_intArray_float(不同的编译器对名称有不同的修饰方法)。这些类就像是手工创建的一样,只是这里是当定义了对象ia和fa后由编译器来创建这些类。我们还会注意到,编译器避免了或者连接器合并了类的重复定义。

16.3.1非内联函数定义

希望有非内联函数定义的时候,这时编译器需要在成员函数定义之前看到template声明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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){
require(index >=0 && index < size), "Index out of range";
return A[index];
}

关于内联函数和非内联函数的定义在别的章节会有提到。在此不赘述。

在引用模板的类名的地方,必须伴有该模板的参数列表,这样在按照模板生成实例的时候,因为模板参数中的参数修饰类名,以便为每一个模板实例产生唯一的类名标识符。

  • 因此模板的参数列表在引用模板类名的时候,也要有一席之地。
  • 模板的非内联函数定义之前一定要加上template声明。

16.3.1.1头文件

即使是在创建非内联函数定义时,我们还是把模板的所有声明和定义都放入一个头文件中。这似乎违背了通常的头文件规则:“不要放置分配存储空间的任何东西”(这条规则是为了防止在连接期间的多重定义错误),但模板定义很特殊。在template<…> 之后的任何东西都意味着编译器在当时不为他分配存储空间,而是一直处于等待状态直到被一个模板示例告知。在编译器和连接器中有机制能去掉统一模板的多重定义。所以为了使用方便,几乎总是在头文件中防止全部的模板声明和定义。

16.3.3模板中的常量

模板参数并不局限于类定义的类型,可以使用编译器内置类型。这些参数值在编译期间变成模板的特定示例的常量。我们甚至可以对这些参数使用默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
template<class T, int size = 100>
class Array{
T array[size];
public
T& operator[](int index){
require(index >=0 && index < size, "Index out of rande");
return array[index];
}
int length() const { return size; }
};

class Number {
float f;
public:
Number(float ff = 0.0f) : f(ff) {}
Number& operator=(const Number& n){
f = n.f;
return *this;
}
operator float() const { return f; } // ????
friend ostream& operator<<(ostream& os, const Number& x){
return os << x.f;
}
};

template<class T, int size = 20>
class Holder{
Array<T, size>* np;
public
Holder() : np(0) {}
T& operator[](int i) {
require(0 <=i && i < size);
if(!np) np = new Array<T, size>;
return np->operator[](i);
}
intlength() const{ returnsize; }
~Holder() {delete np;}
};

int main(){
Holder<Number> h;
// ......
}

上面的例子中的size的值没有房放在类中,但是对他的使用就如同是成员函数中的数据成员。

我记录下来上面这段代码的原因还有一个就是懒惰初始化(lazy initialization).上面的Holder中有一个指向Array的指针,而不是指向类型Array的嵌入对象。该指针在构造函数中不被初始化,而是被推迟到了第一次访问的时候。还有等等的稀有的用法。

这个章节还有许多其他内容,但是貌似跟我现在急需要理解的内容相差甚远,暂时保留。

第二卷第五章 深入理解模板

关于模板的使用问题,在真正的使用模板之前一定要先让编译器看到模板的声明和定义,比如说在同一个文件里写一个类继承一个模板的时候,模板的声明和定义不能在类的下方,应该先写模板再用。