1. 函数模板
1.1 函数模板概念及格式
我们前面学过在面对同一种计算方式不同类型的时候,我们可以使用函数重载,但是这种方式又太过复杂,所以我们今天要学习泛型编程。简单来说,就是告诉编译器自己去推导类型。
我们来看下面两段代码,第二个代码只要通过template<typename T>就可以告诉编译器这个是需要你自己去推导的类型。然后我们就可以把参数的类型填为T。
cpp
void Swap(int& left, int& right) {
int temp = left;
left = right;
right = temp;
}
void Swap(double& left, double& right) {
double temp = left;
left = right;
right = temp;
}
void Swap(char& left, char& right) {
char temp = left;
left = right;
right = temp;
}
cpp
template<typename T>
void Swap( T& left, T& right) {
T temp = left;
left = right;
right = temp;
}
注意:typename是 用来定义模板参数关键字 ,**也可以使用class(**切记:不能使用struct代替class)。
PS:我们要注意,如果说我们在一份函数模版里面有多个不同类型的参数时,那么我们要写多个typedef,简单来说就是template<typename T1,typename T2,typename T3,typename T4.......>。因为每一个typedef是在告诉编译器这个类型你自己推导,同时会固定下来这一种类型。
1.2 函数模板的原理
在原理上来说,函数模版就是把本来我们自己做的事情交给编译器去做。简单来说就是编译器根据我们传入函数的类型来自己生成不同的模版。
比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码。
1.3 函数模板的实例化
当编译器把T确定为一种类型然后生成一份专门一份代码的时候,这份代码就是这个函数模版的实例化。
实例化又分为隐式实例化 和显式实例化。简单来说就是隐式实例化是让编译器完全自己推导,而显式实例化就是指定编译器推导为一个类型(就是在使用这个函数的时候再函数名后面加上你想要指定推导的类型,举个例子,Swap<int>,如果说有多个typedef的话就要在<>里面写多个类型)。
PS:如果显式实例化的类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。
1.4 模板参数的匹配原****则
简单来说就是一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
我们来看下面这段代码在Test里面如果说是直接写add(1,2),那么匹配的就是第一个add函数,如果我们在Add后面加了一个<int>,那么就是在告诉编译器我指定要使用第二个Add,而如果说是double类型的话那就是只可以使用第二个Add。
我们可以理解为一个判断,if有指定那就使用指定,else if和非模板函数契合就使用非模板函数,else的话就让系统自己调用函数模板。
cpp
int Add(int left, int right) {
return left + right;
}
template<class T>
T Add(T left, T right) {
return left + right;
}
void Test()
{
Add(1, 2); //1
Add<int>(1, 2); //2
Add(1.1,2.2);//3
}
我们来看下面这个代码,各位觉得在这个Test里面的这个Add会是调用哪个函数呢?
答案是第一个Add,因为它更合适。因为编译器会把这个2.2变为2,然后进行计算,那么计算出的结果会是3。
这个涉及到隐式类型转换(和上面说的隐式实例化不是同一个东西),简单来说就是编译器直接把小数后面的东西给断掉。
cpp
int Add(int left, int right) {
return left + right;
}
template<class T1, class T2>
T1 Add(T1 left, T2 right) {
return left + right;
}
void Test()
{
Add(1, 2.2);
}
我们再来看下面这个代码,各位觉得这回是调用哪个Add呢?
答案是第二个Add ,最后计算出来的结果是3.3。这是由 C++ 的重载决议规则 和模板实参推导机制共同决定的。
总结一下就是一句话:模板实参推导阶段不允许自动类型转换,但函数调用时允许对实参进行标准转换。
说大白话就是能非模板函数就使用非模板函数,不方便的话就使用模版函数。
cpp
int Add(int left, int right) {
return left + right;
}
template<class T1, class T2>
T1 Add(T1 left, T2 right) {
return left + right;
}
void Test()
{
Add(1.1, 2.2);
}
2. 类模版
我们想一下,一个函数我们可以通过模版的方式来对它进行模板化使它可以实例化匹配各种参数,那么一个类是不是也可以通过这样的方式来使它可以根据不同的参数来实例化成不同类型的类。
我们来看下面这个类,我们便可以通过输入不同的值来进行初始化从而实例化为不同的类模版。
cpp
#include <iostream>
#include <string>
using namespace std;
template <typename T>
class Student {
public:
Student(T _member, T _age, T _score)
:member(_member),
age(_age),
score(_score)
{}
private:
T member;
T age;
T score;
};
int main()
{
Student<int> Student1(11111,19,100);
Student<double> Student2(22222.1,20.1,99.1);
}
PS:Student不是具体的类,是编译器根据被实例化的类型生成具体类的模具。只有实例化候的结果也就是Studednt1和Student2才是类。
这个类模版的使用我们是一定要会的,因为在后面的STL库的实现中我们会经常使用到。