目录
1.泛型编程
2.函数模板
2.1 函数模板概念
2.2 函数模板格式
2.3 函数模板的原理
2.4 函数模板的实例化
2.5 模板参数的匹配原则
3.类模板
3.1 类模板的定义格式
3.2 类模板的实例化
1.泛型编程
在C中如果让你写一个交换函数,应该怎么做呢?
//用于整型的交换
void Swap1(int* x, int* y)
{
int* tmp = *x;
*x = *y;
*y = *tmp;
}
//用于双精度浮点数的交换
void Swap2(double *x, double *y)
{
double *tmp = *x;
*x = *y;
*y = *tmp;
}
在C中需要使用指针来完成对两个数的交换,而且函数名也必须不同
在C++中我们学习了引用加重载,现在我们使用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;
}
可以看到学了重载和引用实现两个数的交换依旧很麻烦,参数类型不同必然需要我们写出不同的函数来实现x , y的交换,在C++中依靠重载引用实现这种操作,但是有很多不好的的地方
1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
2. 代码的可维护性比较低,一个出错可能所有的重载均出错
那能否告诉编译器一个模具,让编译器根据不同的类型利用该模子来生成代码呢?
就像活字印刷术和造纸术一样,我们可以依靠这两样东西在不同的纸上刻不同的内容,C++中的摸具就好比造纸术一样,每一张都是同一摸具制作出,但每一张纸都是不一样的内容
在二十多年前,我们C++的鼻祖引入了模板(template)这个概念,模板是C++语言重要组成之一
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
2.函数模板
2.1 函数模板概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
2.2 函数模板格式
template<typename T1, typename T2, typename T3 ....... typename Tn>
返回值类型 函数名(参数列表)
{
//函数体
}
template<typename T>
void Swap(T& x, T& y)
{
T temp = x;
x = y;
y = temp;
}
int main()
{
//函数模板实例化生成具体函数
//函数模板根据调用,自己推导模板参数的类型,实例化出对应的函数
int a = 10, b = 12;
Swap(a, b);
cout << a << ' ' << b << '\n';
double c = 10.10, d = 20.20;
Swap(c, d);
cout << c << ' ' << d << '\n';
swap(c, d);
return 0;
}
使用模板,我们只需要一个函数就能解决上面的问题
因为swap是很常用的类型所以库里面它帮你已经写好了,swap函数直接使用就可以了
int main()
{
//函数模板实例化生成具体函数
//函数模板根据调用,自己推导模板参数的类型,实例化出对应的函数
int a = 10, b = 12;
swap(a, b);
cout << a << ' ' << b << '\n';
double c = 10.10, d = 20.20;
swap(c, d);
cout << c << ' ' << d << '\n';
swap(c, d);
return 0;
}
tips:
typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)
2.3 函数模板的原理
那么如何解决上面的问题呢?大家都知道,瓦特改良蒸汽机,人类开始了工业革命,解放了生产力。机器生 产淘汰掉了很多手工产品。本质是什么,重复的工作交给了机器去完成。有人给出了论调:懒人创造世界。
马云:世界是懒人创造的
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器
编译器,很辛苦,哈哈哈
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然 后产生一份专门处理double类型的代码,对于字符类型也是如此。
2.4 函数模板的实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
1. 隐式实例化:让编译器根据实参推演模板参数的实际类型
template <class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
Add(1, 9);
Add(2, 8);
return 0;
}
tips:以下这段代码不会通过编译
int main()
{
int a = 10;
double b = 20.2;
Add(a, b);
return 0;
}
在编译器编译期间,在模板实例化的时候,编译器需要推理a和b的类型,编译器将a的类型推演为int,b为double,但是模板只有一个参数T,即:编译器无法推断T的类型为int还是double,所以报错
注意:
在模板中,编译器一般不会自动转换类型,因为一旦错了,编译器就需要背黑锅
这里有两种解决方式:
1.使用强制类型转换,将这两个参数的类型转换为同一个类型
例:Add(a, (int)b);
2.就是我们下面要将的显式实例化
2. 显式实例化:在函数名后的<>中指定模板参数的实际类型
int main(void)
{
int a = 10;
double b = 20.2;
// 显式实例化
Add<int>(a, b);
return 0;
}
如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。
使用场景:
template <class T>
T* Alloc(int n)
{
return new T[n];
}
int main()
{
double* p1 = Alloc<double>(10);
return 0;
}
需要传递形参n来申请空间,这里就必须要使用显式实例化
2.5 模板参数的匹配原则
1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
//非模板函数,int类型的Add加法函数
int Add(int x, int y)
{
return x + y;
}
template <typename T1,typename T2>
//模板函数,通用类型Add加法函数
T Add(const T1& x, const T2& y)
{
return x + y;
}
int main()
{
Add(10, 20); //调用int类型的Add加法函数,编译器不会去实例化
Add<int>(10, 20); //调用通用类型的Add加法函数,编译器会去实例化
return 0;
}
2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
//非模板函数,int类型的Add加法函数
int Add(int x, int y)
{
return x + y;
}
template <typename T1, typename T2>
//模板函数,通用类型Add加法函数
T Add(const T1& x, const T2& y)
{
return x + y;
}
int main()
{
int a = 10, b = 20;
Add(10, 20); //与非模板函数更加吻合,编译器会使用int Add函数
Add(10, 20.0); //编译器会选择模板函数T Add,模板函数会根据需求生成更加匹配的Add函数
return 0;
}
3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
template<typename T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int sum = Add(10, 10.5);
return 0;
}
编译器不会帮你自动类型转换,将10转换为10.0,或10.5转换为10
3.类模板
3.1 类模板的定义格式
template<class T1, class T2, class T3 .... class Tn>
class 类模板名
{
// 类内成员定义
};
类成员函数声明和定义分离
template<class T>
class Date
{
public:
Date();
void Print();
private:
int _year;
int _month;
int _day;
};
//普通类:类名和类型一样
//类模板:类名和类型不一样
//类模板 类名:Date 类型:Date<T> (类型是类名加<模板参数(不用加class或typename)>)
template<class T>
Date<T>::Date()
:_year(1)
,_month(1)
,day(1)
{
}
template<class T>
void Date<T>::Print()
{
cout << _year << endl;
cout << _month << endl;
cout << _day << endl;
}
tips:类模板中函数放在类外进行定义时,需要加模板参数列表
3.2 类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
template<class T1, class T2>
class sroce
{
public:
sroce(const T1& math, const T2& English);
void Print();
private:
T1 _math;
T2 _English;
};
template<class T1, class T2>
sroce<T1, T2>::sroce(const T1& math, const T2& English)
:_math(math)
,_English(English)
{}
template<class T1, class T2>
void sroce<T1, T2>::Print()
{
cout << _math << '\n';
cout << _English << '\n';
}
int main()
{
sroce<int, double> d1(10, 10.5);
d1.Print();
return 0;
}