目录
前言
🎗️照以前的想法,若我们想实现一个交换函数,需要这样写。
void swap(int& x, int& y) { int tmp = x; x = y; y = tmp; } int main() { int a = 5, b = 6; swap(a, b); return 0; }
🎗️若想写通用的交换函数呢?根本没完没了,换一个类型就要重新写一次,就算有了函数重载也不能减少多少工作量。
void swap(int& x, int& y) { int tmp = x; x = y; y = tmp; } void swap(char& x, char& y) { int tmp = x; x = y; y = tmp; } void swap(double& x, double& y) { int tmp = x; x = y; y = tmp; } ......
🎗️就像古代最早出现的印刷术那样,使用一个模板可以去除掉那些大量并重复的工作。落实到语言中则诞生了模板(泛型编程)。
1.函数模板
1.1使用
🎗️想要将这个函数定义成模板只需要在函数上加上这句话:template<class T ...> 其中参数的个数取决于实际需要的个数。
🎗️其中的 class 还可以使用 typename 替换,二者都是定义模板参数关键字,区别不大,但是不可以使用 struct 。
🎗️这样子,我们便可以使用模板对上面交换函数进行修改。
template<class T> void swap(T& x, T& y) { T tmp = x; x = y; y = tmp; }
🎗️我们可以将这个 T 想象成一个抽象的数据类型,他具体是什么我们不知道,但之后这个 T 会自己进行推演并转化成传入的类型。
1.2实现逻辑
🎗️让我们思考一下,以下两次 swap 调用的是同一个函数吗?
template<class T> void swap(T& x, T& y) { T tmp = x; x = y; y = tmp; } int main() { int a = 5, b = 6; double c = 5.5, d = 6.6; swap(a, b); swap(c, d); return 0; }
🎗️通过查看汇编,我们可以发现,其中调用的函数地址并不相同,即调用了两个不同的函数。
🎗️我们曾经将类比作蓝图,对象比作楼房。其实模板也是一样的,它会根据传入参数的不同类型进行推演,之后再进行实例化,生成不同版本的代码。因此不同类型的参数调用的函数并不是同一份。
🎗️本质上不同类型的函数还是要实现一份的,但使用模板就将实例化这个重复的工作交给了编译器,由编译器代我们实现。
1.3实例化
🎗️用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
- 隐式实例化:让编译器根据实参推演模板参数的实际类型。
- 显式实例化:在函数名后的<>中指定模板参数的实际类型。
🎗️若模板中只有一个数据类型,用两个类型的参数进行调用,编译器不知道该推演成哪个类型,便会出现歧义。
🎗️可以传参时强制类型转换,使其满足隐式实例化。也可以指定模板参数的实际类型,让参数发生隐式类型转换,使其满足显式类型转换。
1.4匹配规则
🎗️一个非模板函数和一个同名的函数模板可以同时存在,那么在这种情况下会调用哪一个函数呢?
template<class T> int add(const T& x,const T& y) { return x + y; } int add(int x, int y) { return x + y; } int main() { int a = 5, b = 6; add(a, b); return 0; }
🎗️为了节约程序开销,编译器会优先选择成本较低的,参数更加匹配的的函数进行调用。
2.类模板
2.1使用
🎗️类模板的定义与函数模板类似,根据需要定义任意数量的模板参数,之后便可以使用模板参数作为抽象的数据类型到类中。
template<class T> class A { public: A(T a = 1) :_a(a) {} private: T _a; }; int main() { A<int> a1; return 0; }
🎗️类模板中函数放在类外进行定义时,需要加模板参数列表。
template<class T> class A { public: A(T a = 1) :_a(a) {} ~A(); //类中声明函数 private: T _a; }; template<class T> //需加上函数参数列表 A<T>::~A() //类外定义函数 { _a = 0; } int main() { A<int> a1; return 0; }
🎗️在实例化之前,当前模板类并不能算作一个真正的类,只是编译器根据被实例化的类型生成具体类的模具 。
实例化
🎗️与函数模板实例化不同,类模板实例化时需要在类模板名字后加上 <>,<>中放的是实例化的类型。因为编译器无法推演出其中的类型应该是如何对应的,因此需要手动指定。
A是类名,A<int>才是类型
🎗️好了,今天模板基础内容的讲解到这里就结束了,如果这篇文章对你有用的话还请留下你的三连加关注。