所谓模板,实际上是建立一个通用函数或类,其类内部的类型和函数的形参类型不具体指定,用一个虚拟的类型来代表。
就比方说你想要实现 一个Add的加法函数,面对不同的类型,你是否要进行多次函数重载呢,其实这多个函数实现的底层原理都是一样的,只不过是类型不同,所以祖师爷就根据这种情况设计出了模版。
函数模版
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时参数被虚拟化,根据实参类型产生函数的特定类型版本。
格式要求
在定义函数前要写上这一行:template<typename T1, typename T2,......,typename Tn>,这里的typename换成class也是OK的。而函数定义就是正常写,只不过将原来的实例化类型换成虚拟化类型(也就是上一行的T1、T2......)
举例Add函数模版
template<class T> T Add(const T& a, const T& b) { return a + b; }
因为这里需要整体替换的就一个类型所以模版形参中给一个T就行。
但是你的类型也必须要进行统一才可以:
int main() { double sum = Add(1.2, 1); cout << sum << endl; }
如果主函数这样写的话,那就会报错: 没有与参数列表匹配的 函数模板 "Add"
这样也有有解决方法:函数模版实例化(实际也是匹配)
函数模版实例化
函数模版的实例化其实也就是将函数参数进行统一成其中一个模版函数的参数。
隐式实例化
即是不直接告诉编译器参数的类型,让编译器在自己生成的模拟板函数中去找与之匹配的。
template<class T> T Add(const T& a, const T& b) { return a + b; } int main() { double sum = Add(1.2, (double)1);//强制类型转换 cout << sum << endl; }
其实这就是一种隐式转换。
其实在你写模版函数时发生了两次编译:
- 实例化前,先检查模板代码本身,查看语法是否正确。
- 实例化期间,检查模板代码,查看是否所有的调用都有效。
在函数模版生成是就相当于编译器自己生成了多个不同类型的重载函数,在你调用函数模版时,编译器就会在生成的所有重载函数中找一个最匹配的让你调用。
显式实例化
显示实例化的特点就是在函数名后面加 <type>,type就是你调用函数时的具体类型
int main() { double sum = Add<double>(1.2,1); cout << sum << endl; }
这就相当于直接告诉编译器调用的函数参数类型是double型,所以这就是显式实例化 。
函数模版和模版函数
这里和你们讲一下这个基本概念,函数模版与模版函数。
首先,它们俩根本就不是一个东西。
函数模版是个模版,它相当于是一个函数家族,而家族成员就是模版函数(一个实例化的函数)而我们一般会写一个函数模版,然后编译器自动生成不同类型的模版函数,再给使用者使用。
类模版
template<typename T, int N> class Test { public: Test() :_arr(new T[N]) { for (int i = 0; i < N; i++) _arr[i] = i+0.5; } ~Test() { delete[]_arr; } private: T* _arr; };
像这个模版的功能就是单纯的创建一个数组并完成初始化,这里用到了两个模版参数,所以就写成:template<typename T, int N>,int N是数组大小,所以就直接写int类型就行
但是类模版相较于函数模版没有隐式实例化,只能通过显式实例化来创建对象。
类模板中函数放在类外进行定义时,需要加模板参数列表
template<typename T, int N> class Test { public: Test(); ~Test() { delete[]_arr; } private: T* _arr; }; template<typename T, int N> Test<T, N>::Test() :_arr(new T[N]) { for (int i = 0; i < N; i++) _arr[i] = i + 0.5; }
不仅仅要加上模版参数列表,还要加上类类型,模版类的类型和一般类的类型是不一样的,模版类的类型=类名+<T1,T2...>,而相较于一般的类而言,类名就是类型
模版参数
现在谈一谈模版的参数,提前声明:模版参数可不是函数参数和类参数,这一定要分开。
模版参数的形参就是模板参数列表中的T,用上面的模版类为例就是有两个模版参数T和N而有了形参,实参也就容易了,就是double和5.
欢迎莅临指导!!!