参考于《C++程序设计教程——设计实现与实现》
模板的概念:
若一个程序的功能是对某种特定的数据类型进行处理,则将所处理的数据类型说明为参数,就可以把这个程序改写成模板,模板可以让程序对任何其他数据类型进行同样方式的处理。
C++程序由类和函数组成,模板也分为类模板(class template)和函数模板(function template)。因此,可以使用一个带多种不同数据类型的函数和类,而不必在意数据类型的各种情况。
接下来,将分别介绍函数模板和类模板
函数模板:
函数模板的一般定义形式是:
template <类型形式参数> //类型形式参数即此格式:<typename 形式参数> 或 <class 形式参数>
返回类型 函数名 (形式参数)
{
//函数定义体;
}
注意:
其中的类型形式参数可以包含基本数据类型,也可以包含类的类型,如果是类类型,则需加前缀class。
typename(或class)是声明数据类型参数标识符的关键字,用以说明它后面的标识符是数据类型标识符。函数模板允许使用多个类型参数,但在template定义部分的每个形参前必须有关键字typename或class。
(在大多数情况下,typename与class是可以通用的,这里就不再详细说明)
这样的函数模板定义,并不是一个实实在在的函数,编译系统不为其产生任何执行代码。该定义只是对函数的描述,表示它每次能单独处理在类型形式参数中说明的数据类型。
当编译系统发现有一个函数调用:
函数名 (实参)
系统将跟据实参中的类型,确认是否匹配函数模板中对应的形式参数,然后生成一个重载函数。该重载函数的定义体与函数模板的定义体相同,而形式参数的类型则是以实参的类型为基础,该重载函数称函数模板(template function)
——》函数模板与模板函数的区别:
函数模板是模板的定义,定义中的形式参数实际上是通用类型参数。
模板函数是实实在在的函数定义,它由编译系统在碰见具体的函数调用时所生成,具有程序代码。
函数模板例一:
#include<iostream>
using namespace std;
template<typename T> void swapa(T& a, T& b) //将函数名命名为swapa是为了避免与内置函数作用重复
{
T temp = a;
a = b;
b = temp;
cout << "a = " <<a<<" "<< "b = "<<b;
}
int main()
{
int a = 5, b = 4; //需要先定义a,b为int,不能直接传参数(5,4),因为是函数模板,计算机并不知道5,4是到底是什么类型(int? char? float?)
swapa<int> (a,b); //函数名 <类型> (形式参数) 需要指定参数类型为int,否则计算机不知道该使用何种类型
float c = 7.777, d = 2.333;
swapa<float>(c,d);
}
运行结果:
a = 4 b = 5
a = 2.333 b = 7.777
请按任意键继续. . .
函数模板例二:
函数模板可将许多重载函数简单地归为一个:
#include<iostream>
using namespace std;
template<typename T> T max(T a, T b) // T类型的max函数,实现重载函数。 这里typename也可换为class
{
return a > b ? a : b; //条件运算符 (条件表达式)?(条件为真时的表达式):(条件为假时的表达式)
}
int main()
{
cout << "max(3,5) is " << max(3, 5) << endl;
cout << "max('3','5') is " << max('3', '5') << endl;6
}
运行结果:
max(3,5) is 5
max('3','5') is 5
请按任意键继续. . .
当编译发现用指定数据类型调用函数模板时,就创建一个模板函数。
上例中,当编译程序发现max(3,5)调用时,它就产生了一个如下的函数定义,生成其程序代码:
int max(int a,int b)
{
return a > b ? a : b;
}
这时我们会发现一个问题:为什么例二不用像例一那样在使用时要声明类型什么?
我们可以将例一改为例二的格式:
#include<iostream>
using namespace std;
template<typename T> T swapa(T a, T b) //注意之前的void swapa()改为了T wapa()
{
T temp = a;
a = b;
b = temp;
cout << "a = " << a << " " << "b = " << b;
}
int main()
{
swapa(5, 4);
}
运行时即发现报错:
error C4716: “swapa<int>”: 必须返回一个值
从这个报错我们可以知道:
当使用函数模板重载函数时,系统会自动为其指定数据类型。
很明显,例二中的形式比例一更为方便,这样,实参是什么数据类型,返回值也是什么数据类型,不会出现前面的问题。而且模板又避免了相同操作的重载函数定义。
类模板:
类模板的一般说明形式是:
template <类型形式参数>
class 类名
{
//类声明体;
};
template <类型形式参数>
返回类型 类名 <类型> :: 成员函数名1(形式参数)
{
//成员函数定义体;
}
... ...
template <类型形式参数>
返回类型 类名 <类型> :: 函数名N(形式参数)
{
//成员函数定义体;
}
其中的<类型形式参数>与函数,模板中的意义一样,即写为:<typename 形参> 或 <class 形参>
这样的一个说明(包括成员函数的定义),不是一个实实在在的类,而是对类的描述,称为类模板(class template)。
建立类模板之后,可以用下列方式创建类模板的实例:
类名 <类型实参> 该模板类的一个对象;
———》类模板与模板类的区别:
类模板是模板的定义,不是一个实实在在的类,定义中用到通用类型参数。
模板类是实实在在的类定义,是类模板的实例化。类定义中参数被实际类型所代替。
使用类模板的方法为:
- 在程序开始的头文件中说明类模板的定义。
- 在适当的地方,创建一个类模板的实例,编译发现正在创建一个类模板的对象时,便会创建该类模板的定义,同时,创建相应的对象实体。
- 有了对象名,以后的使用就和通常一样。但你规定了什么类型的模板类,在使用成员函数时,所赋的实参也要对应 该类型。
例:
这里我们建立一个RMB的类模板,该类可以实现对RMB对象的加add(),利息interest(),显示display()。该类的模板的定义如下rmb.h:
template <class T> //template <类型形式参数>
class RMB
{
public:
T add(T d);
T interest(T rate); //计算利息
void display()
{
cout << allmoney << endl;
}
private:
T allmoney=0;
};
template <class T>
T RMB<T>::add(T d)
{
allmoney = allmoney + d;
return allmoney;
}
template<class T>
T RMB<T>::interest(T rate)
{
allmoney = allmoney + allmoney*rate;
return allmoney;
}
主函数:
#include<iostream>
using namespace std;
#include"rmb.h" //在程序开始的头文件中说明类模板的定义。
int main()
{
RMB<double> money;
//创建一个类模板的实例,这里就指定了那个T指示为double类型
//在应用程序中,编译发现RMB<double> money; 类似的声明时,要为其生成模板类的实实在在的定义,所以模板中必须包含整个模板(包括其成员函数定义)的完整定义,在生成模板类后,系统又为其创建该类的对象。
double a = 26.86;
//需要先声明参数 为double
//因为T RMB<T>::add(T d) 这里的T即是我们上面创建的double类型,它只接受double类型参数
//这里的情况与上面函数模板的例一类似
money.add(a);
double b = 30.0;
money.add(b);
double rate = 0.14;
money.interest(rate);
money.display();
}
运行结果:
64.8204
请按任意键继续. . .
没了…
感谢某位不愿透露姓名的大佬对本次C++模板的指点