目录

一.泛型编程

二.函数模板

2.1什么是函数模板

2.2函数模板的实例化

2.2.1函数模板的隐式实例化

2.2.1函数模板的显示实例化

2.3函数模板实例化的原理

2.4模板函数调用实例化原则

三.类模板

3.1什么是类模板

3.2类模板的实例化


一.泛型编程

泛型编程,就是编写与类型无关的通用代码,使同一段代码可以适用于所有类型的数据。如:编写一个Swap函数,使其可以实现对所有内置类型数据的交换、编写一个链表类List,使其能够适用于存储所有数据类型的链表。

之前,如果我们想要编写支持所有数据类型的Swap函数,就需要编写多个重载函数,如:Swap(int& x, int& y)、Swap(double& x, double& y)。但是,函数重载会造成大量的代码冗余,而良好的代码对冗余是非常忌讳的。为了避免代码冗余,实现编写一份代码就适用于所有类型的数据,C++引入了模板的概念,而模板就是实现泛型编程的基础。

C++中的模板分为函数模板和类模板,由于C语言不支持模板,所有C语言也不支持泛型编程。

图1.1 C++模板分类

二.函数模板

2.1什么是函数模板

函数模板,本质上来说就是一个函数家族,在调用函数时,根据函数模板实例化出具体的函数,函数模板与参数类型无关。

定义函数模板的格式:

template

函数返回值 函数名(参数列表) { }。

typename是函数模板关键字,可以使用class来替代,但不能使用struct来替代。

演示代码2.1以Swap函数为例,编写了一个Swap函数的函数模板,这个Swap函数可以对任何内置类型数据进行交换操作。

演示代码2.1:Swap函数模板

templatevoid Swap(T& x, T& y){T temp = x;x = y;y = temp;}

2.2函数模板的实例化

根据实例化函数模板时是否指定参数类型,可分为隐式实例化和显示实例化。

2.2.1函数模板的隐式实例化

隐式实例化,就是在调用函数时,不指定函数参数和返回值的类型,通过传入的数据,让编译器自己去推断类型。

演示代码2.2以Swap函数为例,分别向函数中传入了两个int型数据和两个double型数据进行交换,输出交换后的结果。根据输出结果可见,Swap函数模板成功对int型数据和double型数据进行了交换,证实了函数模板的通用性。

演示代码2.2:

int main(){int a1 = 10, a2 = 20;double d1 = 10.11, d2 = 20.22;Swap(a1, a2);cout << "a1 = " << a1 << ", " << "a2 = " << a2 << endl;Swap(d1, d2);cout << "d1 = " << d1 << ", " << "d2 = " << d2 << endl;return 0;}
图2.2 演示代码2.2的运行结果

2.2.1函数模板的显示实例化

假设,我们传入一个int型参数和一个double型参数,试图实例化函数模板Swap:Swap(a1, d2)。当编译器看到a1时,会将T推断为int型数据,当编译器看到d2时,会将T推断为double型数据。但是,由于函数的模板参数列表中只有一个T,而一个T不能表示两种类型的数据,因此,这里会编译失败。

如果我们确实希望通过演示代码2.1中定义的Swap函数模板,实现int型数据和double型数据之间的数据交换,有以下两种方式可以实现:

  • 强制类型转换:Swap(a1, (int)d1)
  • 显示实例化:Swap(a1, d1)

函数模板的显示实例化,就是通过在函数调用时,在函数名后面添加,显示地指定模板参数类型,从而省去了编译器自动推断模板参数类型的过程。

演示代码2.3定义了加法Add的函数模板,通过显示指定模板参数的类型为int和double进行调用,分别完成了整形数据和浮点型数据的加法操作。

演示代码2.3:

templateT Add(T x, T y){return x + y;}int main(){int a = 10;double d = 20.2;cout << Add(a, d) << endl;cout << Add(a, d) << endl;return 0;}

2.3函数模板实例化的原理

函数模板其实并不是一个具体的函数,它相当于浇注制造中用到的模具,可用于生成具体的函数,但其本身不是函数。当要通过函数模板调用函数时,我们会传入模板参数,编译器则会根据模板参数类型,利用函数模板实例化出具体的函数。

如Swap函数,我们先后用它来交换char类型、int型和double型数据,编译器就会生成3个重载函数:void Swap(char& x, char& y)、void Swap(int& x, int& y)和void Swap(double& x, double& y),这三个函数存储在内存中不同的位置。

图2.3函数模板实例化的原理

2.4模板函数调用实例化原则

  • 模板函数可以与非模板函数同名。
  • 如果非模板函数能够很好地匹配调用参数的类型,那么编译器会优先调用非模板函数而不是根据函数模板实例化出函数来调用。
  • 模板函数在传参时不允许存在隐式类型转换,但非模板函数允许隐式类型转换。

三.类模板

3.1什么是类模板

类模板与函数模板类似,通过模板参数列表,来使类中的成员函数参数和成员变量可以为任何想要的数据类型。

类模板的定义格式:

template<typename T1, typename T2, … , typename n)

class类名

{

成员函数列表 ……

成员变量列表 ……

}

演示代码3.1以栈类为例,定义了一个可以实例化为存储任何数据类型的栈。其中包括4个显示定义的成员函数:构造、析构、压栈和打印,包括三个成员变量:指向存储数据内存区域的指针_a、栈顶下标_top、栈容量_capacity。

该栈模板类引入T作为模板参数,其中压栈函数的参数和成员变量_a均涉及参数T,在实例化时,T可以被解析为多种类型(如int、double、char等)。

抛开类模板的实际功能来讲,T还可以实例化为自定义类型,如果T被实例化为自定义类型,那么这创建模板类对象时,会去调用T的构造函数。

演示代码3.1:

template class Stack  //栈类{public:Stack(int capacity = 10);~Stack();void Push(T x);void Print();private:T* _a;int _top;int _capacity;};//类模板成员函数定义和声明分离//函数返回值 类名::函数名(参数列表)template Stack::Stack(int capacity): _capacity(capacity), _top(0){_a = new T[capacity];}template Stack::~Stack(){delete[] _a;_top = _capacity = 0;}template void Stack::Push(T x){_a[_top++] = x;}template void Stack::Print(){int i = 0;for (i = 0; i < _top; ++i){std::cout << _a[i] << " ";}std::cout << std::endl;}

3.2类模板的实例化

与函数模板既可以显示实例化也可以隐式实例化不同,类模板只能显示实例化。即:告知编译器模板参数的类型。

类模板实例化格式:类名对象名

演示代码3.2展示了对于栈类模板的实例化,分别实例化处一个int栈对象st1和double栈对象st2,对两个栈进行压栈操作并打印栈中数据,证实了该栈类模板能够适用于各种数据类型的栈。

演示代码3.2:

int main(){//函数模板可以显示调用也可以隐式调用//但类模板只能显示调用Stack st1;st1.Push(1);st1.Push(2);st1.Push(3);st1.Print();Stack st2;st2.Push(1.1);st2.Push(2.2);st2.Push(3.3);st2.Print();return 0;}
图3.2演示代码3.2的运行结果