目录
1.如果需要函数声明与定义分离,则需要在定义时,也要有模版头
2.类模版的实例化虽然也有隐式实例化,但是不建议使用,应该明确指出需要的类型。
泛型编程
首先根据字面意思理解一下:广泛类型的编程。
C++中有一个特性就是可以进行函数重载。比如:
// 交换两个整型
void Swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
// 交换两个双精度浮点型
void Swap(double& x, double& y)
{
double tmp = x;
x = y;
y = tmp;
}
上述代码就是一个swap函数的重载。
重载:C++中同名函数可以出现多个,只要参数不是完全一样即可。因为和c语言直接以函数名保存函数不同,C++在保存函数时,是以函数名和参数一起保存的。
在重载时,是直接生成了对应的函数
但是和泛型编程有什么关系呢?
和重载不同的在于,泛型编程是将函数变成了一个模版,再通过传入的参数确定需要什么类型的函数,再进行生成。传入多组不同类型的数据时,这个模版也就变成了一个重载函数了。是一种代码复用的手段。
可以把泛型编程当做抖音一个爆火的舞蹈,都是一种舞蹈,但是有不同的人会来跳,来拿流量。
函数模版
上面我们说泛型编程就是一个套模版。所以下面的代码,就是给了一个函数模版 Swap。其中T根据传入参数的类型,自动变成这个类型。
template<typename T>
void Swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}
所以说函数模版是什么很明了了,技术的发展都是朝着越来越简便简单发展的。模版的出现大大简化了程序员敲代码时重复函数的书写。有句话说的好:懒人创造世界。
模版格式:
template<typename T1,typename T2,…,typename Tn>
返回类型 函数名(参数列表)
{
//函数体
}
从格式中可以看出,T不只是可以一个,也可以多个。 typename 可以用 class替换,但是不可以用struct。
函数模版实例化:
实例化:本来这只是一串代码,实例化就是对这串代码进行使用,这时就会开辟空间给这块代码的执行。
这里函数模版的实例化就是用不同的参数类型使用模版,类似于函数重载,每次使用不同的参数进入模版时,会生成一个新的函数重载进行使用。
两种类型:隐式实例化和显示实例化
隐式实例化
隐式就是隐藏,我们不指定出我们要使用的类型,而是让编译器自己去推演
#include <iostream>
using namespace std;
template<typename T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 10, b = 20;
int c = Add(a, b); //编译器根据实参a和b推演出模板参数为int类型
return 0;
}
如上述代码:Add函数就是传入两个int变量,然后编译器根据模版会推演出int版本的add。
但是对于这样的实例化不可以传两个类型不一样的变量。
int a = 10;
double b = 1.1;
int c = Add(a, b);
如上代码我们传入的是:double和int ,模版要求的是严格匹配,所以在遇到参数a时开出来的Add就是int类型,double和int 虽然平时可以转换,但是模版要求严格匹配所以此时就会报错。
解决方法有两个:1.在传入前就把double变量强转为int
int a = 10;
double b = 1.1;
int c = Add(a, (int)b);
2.就是下面的显示实例化
显示实例化
显示实例化就是在函数调用时用<>将模版类型提前输入,这样就生成了int 的函数,然后b就会在进入函数前就隐式转换为int。
#include <iostream>
using namespace std;
template<typename T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 10;
double b = 1.1;
int c = Add<int>(a, b); //指定模板参数的实际类型为int
return 0;
}
为什么显示实例化可以隐式类型转换,而上面的隐式实例化不能呢?
可以这样想:显示实例化是在模版未推导前生成了一份int 的 add函数,这个函数的使用方法和以往的函数一样,所以可以隐式类型转换,就是在使用函数前将参数进行转换后再传入函数。但是我们的隐式实例化是先推导出类型再形成函数,模版的推导是必须严格匹配的,所以这时不会进行隐式类型转换。
函数模版的匹配
1.如果有函数匹配,则优先调用函数而不是推导模版
#include <iostream>
using namespace std;
//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{
return x + y;
}
//通用类型加法的函数模板
template<typename T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 10, b = 20;
int c = Add(a, b); //调用非模板函数,编译器不需要实例化
int d = Add<int>(a, b); //调用编译器实例化的Add函数
return 0;
}
如上代码:如果我们两个参数都是int,则就优先调用已经有的int版的Add。但是如果有显示实例化了,那优先使用模版实例化的函数(因为显示实例化是模版的,普通函数没有这种规则。)
2.如果没有函数匹配,则优先推导模版
#include <iostream>
using namespace std;
//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{
return x + y;
}
//通用类型加法的函数模板
template<typename T1, typename T2>
T1 Add(const T1& x, const T2& y)
{
return x + y;
}
int main()
{
int a = Add(10, 20); //与非模板函数完全匹配,不需要函数模板实例化
int b = Add(2.2, 2.2); //函数模板可以生成更加匹配的版本,编译器会根据实参生成更加匹配的Add函数
return 0;
}
如上代码:a 调用的依旧是已有的int版Add,但是b由于找不到匹配的函数,所以它先推导模版实例化,再使用实例化的函数。
3.隐式实例化的模版不可以参数类型不匹配
#include <iostream>
using namespace std;
template<typename T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = Add(2, 2.2); //模板函数不允许自动类型转换,不能通过编译
return 0;
}
和上述显示实例化和隐式实例化解释相同;
类模板
和函数模版的定义类似都是在类名或者函数名上面加template<typename T ....>
template<class T>
class Score
{
public:
void Print()
{
cout << "数学:" << _Math << endl;
cout << "语文:" << _Chinese << endl;
cout << "英语:" << _English << endl;
}
private:
T _Math;
T _Chinese;
T _English;
};
两个注意点:
1.如果需要函数声明与定义分离,则需要在定义时,也要有模版头
template<class T>
class Score
{
public:
void Print();
private:
T _Math;
T _Chinese;
T _English;
};
//类模板中的成员函数在类外定义,需要加模板参数列表
template<class T>
//上面就是模版头,只有声明与定义分离时使用,如果在类内定义就不需要了
void Score<T>::Print()
{
cout << "数学:" << _Math << endl;
cout << "语文:" << _Chinese << endl;
cout << "英语:" << _English << endl;
}
2.类模版的实例化虽然也有隐式实例化,但是不建议使用,应该明确指出需要的类型。
//对的使用
Score<int> s1;
Score<double> s2;
Score s1(3);//这是根据3使用构造函数,推导出Score<int> s1(3),来使用的
//错的使用
Score s1;//这样既没有参数给构造函数,也没有指定类模版的类型,这样是错的