发布时间:2023-05-02 10:00
C++提供了模板(template)编程的概念。所谓模板,实际上是建立一个通用函数或类,其类内部的类型和函数的形参类型不具体指定,用一个虚拟的类型来代表。这种通用的方式称为模板。模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
简单来说,就是我们提供一个抽象的函数,并不具体指定其中数据的类型,而是某个虚拟类型代替。只提供基本的功能。其具体的数据类型,只在其被调用时视具体情况实例化。
这么说可能还是很抽象,举个例子就好理解了。
#include
#include
using namespace std;
template <typename T1,typename T2> //模板函数声明与定义
T2 test(T1 tmp, T2 tmp1) {
T2 tmp2 = tmp + tmp1;
return tmp2;
}
int main(void) {
cout << "test(10, 5)=" << test(10, 5) << endl; //调用模板函数,模板函数通过传入的参数自动推导未实例化的类型
cout << "test(5,'A')=" << test(5,'A') << endl;
cout << "test(10.5, 5.5) =" << test(10.5, 5.5) << endl;
system("pause");
return 0;
}
输出结果为:
函数模板的声明通过关键字template与typename 实现。其中,template告知编译器这是函数模板的声明,typename用来声明虚拟类型。比如你要声明一个模板函数,里面需要两个不同的变量,那么你就需要通过typename声明两个不同的虚拟类型T1,T2。
声明好后,你就可以在函数定义中使用虚拟类型来定义变量,但是要注意,用同一个虚拟类型定义的变量就只能是同种类型,比如用T1定义的变量只能是同种变量,可以是int,也可以是char。这取决于其实例化时被实例化为哪种类型。
可以显式的调用模板函数,即在调用时人为地指明虚拟类型的具体类型。
#include
#include
using namespace std;
template <typename T1,typename T2> //模板函数声明与定义
T2 test(T1 tmp, T2 tmp1) {
T2 tmp2 = tmp + tmp1;
return tmp2;
}
int main(void) {
cout << "test(5,'A')=" << test<int,char>(5, 'A') << endl; //显式的指明模板的类型
system("pause");
return 0;
}
即不指明具体的数据类型,而让编译器根据传入的数据自动推导出数据类型。
#include
#include
using namespace std;
template <typename T1,typename T2> //模板函数声明与定义
T2 test(T1 tmp, T2 tmp1) {
T2 tmp2 = tmp + tmp1;
return tmp2;
}
int main(void) {
cout << "test(5,'A')=" << test(5, 'A') << endl; //自动推导数据类型
system("pause");
return 0;
}
模板函数在被实例化后,那么它就会被具体的函数所取代。举个例子,某一版代码有一个模板函数,其被调用后虚拟类型被自动推导为int型。那么在编译后就会生成实例化为int型的函数(即所有虚拟类型被int取代的普通函数),而不会调用模板函数,也就是在调用时,模板函数会被实例化为普通函数的函数所取代。
由此我们或许可以知道,无论是自动推导,还是显式的指定类型。都不允许出现不能被编译器知道的虚拟类型。更清楚的说,就是使用自动推导方式推导模板函数类型时必须要让编译器能推导出具体的类型。
看个不能被编译器推导出的例子。
错误代码:
#include
#include
using namespace std;
template <typename T1,typename T2> //模板函数声明与定义
T1 test(T1 tmp, T1 tmp1) {
T2 tmp2; //T2无法被推导出具体的类型
return (tmp + tmp1);
}
int main(void) {
cout << "test(5, 6)=" << test(5, 6) << endl;
system("pause");
return 0;
}
熟悉函数重载的人应该会好奇,如果既有模板函数又有同名的普通函数,而且参数列表的参数个数是一样的,那么在主函数中调用同名函数,编译器具体会调用哪一个呢?
下面看一个例子:
#include
#include
using namespace std;
template <typename T1,typename T2> //模板函数声明与定义
T1 test(T1 tmp, T2 tmp1) {
cout << "调用模板函数!" << endl;
return (tmp + tmp1);
}
int test(int tmp, int tmp1) { //重载的普通函数
cout << "调用普通函数!" << endl;
return 0;
}
int main(void) {
char tmp = 'c';
int tmp1 = 0;
int a = 5;
cout << "test(5,'c')=" << test(a, tmp) << endl;
cout << "test(5,0)=" << test(a, tmp1) << endl;
system("pause");
return 0;
}
结果为:
普通函数的两个参数都是int型,在第一次调用test时第二个参数使用的是char型,调用的是模板函数,第二次使用的是int型,调用的是普通函数。
这是为什么呢?理论上来说,模板函数两个都能匹配,使用。而普通函数也能匹配这两次调用的参数(在C语言中,char型变量是可以作为int型参数使用的)。
这是因为模板函数可以自动推导类型,在第一次调用中,两个类型分别被推导为int型与char型。而普通函数是两个int型,虽然也能使用传入的参数,但模板函数明显能更好的匹配参数列表。
也就是说,如果模板函数实例化后的类型能更好的匹配参数列表的话就使用模板函数。
那么当这两个函数都能完全匹配参数列表的时候呢?通过第二次test的调用结果不难发现,这时候,编译器会调用普通函数。
如果一定要使用模板函数,可以使用<>显式的指定使用模板函数。看下面的例子。
#include
#include
using namespace std;
template <typename T1,typename T2> //模板函数声明与定义
T1 test(T1 tmp, T2 tmp1) {
cout << "调用模板函数!" << endl;
return (tmp + tmp1);
}
int test(int tmp, int tmp1) { //重载的普通函数
cout << "调用普通函数!" << endl;
return 0;
}
int main(void) {
char tmp = 'c';
int tmp1 = 0;
int a = 5;
cout << "test(5,'A')=" << test(a, tmp) << endl;
cout << "test<>(5,0)=" << test<>(a, tmp1) << endl; //使用<>显式的调用模板函数
system("pause");
return 0;
}
下篇文章我们介绍类模板。
链接:
C++类模板