目录
一. 泛型编程
二. 函数模板
1.格式:
2.定义:
1.隐式实例化
2.显式实例化
3.解决方法3:使用多个T类型
4.在C++中编译器允许非模板函数和模板函数同时存在
先来看一段代码:
void Swap(int& p1, int& p2) {
int tmp = p1;
p1 = p2;
p2 = tmp;
}
int main(){
int a = 1, b = 2;
cout << "a:" << a << " b:" << b << endl;
Swap(a, b);
cout << "a:" << a <<" b:"<
若是我们需要对浮点型数据进行交换函数,还得再去写一个Swap函数 :
void Swap(double& p1, double& p2) {
double tmp = p1;
p1 = p2;
p2 = tmp;
}
void Swap(char& p1, char& p2) {
char tmp = p1;
p1 = p2;
p2 = tmp;
}
int main() {
int a = 1, b = 2;
cout << "a:" << a << " b:" << b << endl;
Swap(a, b);
cout << "a:" << a <<" b:"<
这就说明了一个问题:若有多个变量之间的数据交换,就要频繁去写各种类型的函数,函数体大致是一样的,只是参数类型不同,导致其中含有大量重复代码,代码耦合度高,更容易出bug。
所以我们需要一个模板,模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。
template
返回值类型 函数名(参数列表){}
class用于定义类,在c++引入模板后,最初定义模板的方法为:template,这里class关键字表明T是一个广泛的类型。后来为了避免class在这两个地方的使用可能给人带来混淆,所以引入了typename这个关键字。它的作用同class一样表明后面的符号为一个类型。这样在定义模板的时候就能够使用以下的方式了: template.在模板定义语法中关键字class与typename的作用全然一样。 T就是泛型——广泛类型,它里面包含内置类型和你所创建的自定义类型。
一个类模板(也称为类属类或类生成类)同意用户为类定义一种模式。使得类中的某些数据成员、默写成员函数的參数、某些成员函数的返回值,能够取随意类型(包含系统提前定义的和用户自己定义的)。
假设一个类中数据成员的数据类型不能确定。或者是某个成员函数的參数或返回值的类型不能确定。就必须将此类声明为模板,它的存在不是代表一个详细的、实际的类,而是代表着 (一类)类。
例:
template
void Swap(T& left, T& right) {
T tmp = left;
left = right;
right = tmp;
}
int main() {
int a = 10, b = 20;
Swap(a, b);
cout << "a:" << a << " b:" << b << endl;
double c = 33.3, d = 66.6;
Swap(c, d);
cout << "c:" << c << " d:" << d << endl;
char e = 'y', f = 'r';
Swap(e, f);
cout << "e:" << e << " f:" << f << endl;
return 0;
}
编译器在指向模板函数的调用时,会先进行类型推演,它会把接收到的实参类型推演生成对应类型的函数以供调用。
例如:当double类型数据被作为实参去调用函数模板时,编译器通过实参的double类型进行推演,将T确定为double类型,然后专门产生一份处理double类型的代码。
其次需要注意的是:使用函数模板时,其函数会实例化出对象,在上面的代码中,共有三种类型数据进行三次函数调用,那么就会实例化出三个对象。但若是同种类型的多次调用,编译器只会生成一个对象:
int a = 10, b = 20;
Swap(a, b);
cout << "a:" << a << " b:" << b << endl;
int x = 500, y = 1220;
Swap(x, y);
cout << "a:" << a << " b:" << b << endl;
int r=63,t=79;
Swap(r, t);
cout << "r:" << r << " t:" << t << endl;
如上图,这也是进行了三次模板函数的调用,但只实例化出一个对象,这是因为模板函数对于同种类型的多次调用,也只会实例化出一个对象。
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
看代码:
template
T Add(const T& l, const T& r) {
return l + r;
}
int main() {
int a1= 1,a2=2;
double b = 5.6,b2=7.8;
Add(a1,a1);
Add(b1,b2);
//两种不同类型的数据相加
Add(a1,b1);
return 0;
}
前两个Add函数是两个同类型间相加,第三个是int和double类型相加,这不会通过编译,在编译期间编译器看到模板函数调用时,会实例化出对象,需要推演其实参的类型,通过参数一将T推演成int,通过参数二将T推演成double,但模板只有一个T,无法确定T是int还是double,因此报错。注:编译器可不会因为两种类型的不同而进行类型转换操作!!!
所以解决方法1:就是在编译器推演实参类型前,就把两个不同类型的转换为同一类型
Add(a, (int)c);
Add((double)a,c);
这种方式称为隐式实例化,第一个Add函数先将变量c转换为int类型,这样就可以进行推演了,T最终会变成int类型;第二个Add函数 将变量a转换为double类型,T最终变成double型。
在函数名后加一对尖括号,里面填写你最终要转换出的类型,这也是T的最终类型。
int main() {
int a = 10;
double b = 20.3;
Add(a, b);
cout << "Add:" <(a,b) << endl;
Add(a, b);
cout << "Add:" << Add(a, b) << endl;
return 0;
}
template
T1 Add2(const T1& left, const T2& right) {
return left + right;
}
int main() {
int a1 = 10, a2 = 20;
double b1 = 10.1, b2 = 20.2;
cout << Add2(a1, a2) << endl;
cout << Add2(b1, b2) << endl;
cout << Add2(a1, b2) << endl;
cout << Add2(b1, a2) << endl; //最终结果的类型是根据第一个参数类型所决定
return 0;
}
直接写出两个T类型,这样即使不用隐式和显式转换也可以进行不同类型间的数据相加,最终结果的类型可以设置为int,也可以是double。
例:
template
T1 Add( T1& left,T2& right) {
return left + right;
}
int Add(int& left, int& right) {
return left + right;
}
int main() {
int a = 15, b = 30;
double c = 33.3, d = 99.9;
Add(a, b);
Add(c, d);
return 0;
}
结果:
对于非模板函数和同名函数模板,如果其他条件都相同,编译器会优先调用非模板函数,而模板函数可以生成更加匹配的版本。所以第一个Add函数内部的数据都是int,那么编译器肯定会选择现有的int Add函数去调用,其实编译器也很懒的,哈哈哈~。