🔥博客主页: 小羊失眠啦.
🎥系列专栏:《C语言》 《数据结构》 《C++》 《Linux》 《Cpolar》
❤️感谢大家点赞👍收藏⭐评论✍️
文章目录
一、泛型编程二、函数模板2.1 使用方法2.2 实现原理2.2.1 隐式实例化2.2.2 显示实例化 2.3 匹配原则2.4 注意事项 三、类模板3.1 使用方法3.2 注意事项
早在北宋年间,中国的毕昇就已经发明了泥活字
,标志着四大发明之一的活字印刷术
正式诞生,从此文化传播取得了革命性突破,各种文学作品得以走进千家万户。倘若这项技术还没有被发明,那么恐怕我们现在的书本都还得靠逐字手抄传播,效率是非常低的
我们的程序也是如此,很多需要频繁使用的函数每次都得手动写,这可难不倒程序员,于是在上世纪80年代末,泛型编程
思想正式诞生,它就像是印刷文字的模具,将程序主体刻在其中,需要使用时让编译器
根据参数类型
生成即可,这就是我们今天的主角模板
模板
的产生源自于范型编程
的思想,简单来说,就是将算法抽象化编写
一、泛型编程
那么什么才是一个抽象化
的算法呢?
比如我们常用的两数相加函数,按照以前的写法,处理整型数据时,编写整型的方法;处理浮点型时,又得编写一个浮点型的加法,好在C++
支持函数重载
,使得我们可以存在同名函数,假若是C语言
实现时,我们甚至要写两个不同名的相加函数
//处理整型的加法函数int Add(const int& a, const int& b){return a + b;}//处理浮点型的加法函数double Add(const double& a, const double& b){return a + b;}
两数相加,直接返回两数之和就行了,我们实现方法时,没必要关注具体数据类型
将具体问题抽象化
,直接假设数据类型为 T
,利用模板
实现如下:
//利用模板实现函数template <class T>//模板关键字T Add(const T& a, const T& b){return a + b;}
此时我们只编写了一个加法函数模板,而所有类型的参数都可以调用加法函数
具体问题抽象化就是范型编程
的核心思想
二、函数模板
首先来看模板
在函数实现上的运用
注意:
模板关键字为template
形式为 template <class T>
或者 template <typename T>
其中的T
是模板中的参数名,我们可以自定义模板中可以存在多个参数,通过 ,
号分隔 2.1 使用方法
模板函数
即在函数实现之前,写好模板
,再根据模板
中定义的变量名实现函数
//实现所有类型数组的打印//这种模板写法也是没有问题的template <typename Type>void CoutArray(const Type& arr){//范围 for ,C++11 中的语法糖for (auto e : arr){cout << e << " ";}cout << endl;}
我们还可以实现多参数模板
//多参数模板//这里实现的是val2强制类型转换为val1,并取得和template <class T1, class T2>T1 getTrunVal(const T1& val1, const T2& val2){const T1 tmp = (const T1)val2;return val1 + tmp;}
总之,在函数模板
的存在下,我们不再需要再编写不同类型参数的相似函数了
2.2 实现原理
这个模板看着挺厉害,那么它的实现原理是什么呢?
其实很简单,只需要两样东西:编译器
和 函数重载
当我们编写好函数模板
后,编译器
会记住这个模板
的内容,当我们使用模板
时,编译器
又会根据参数类型,创建相应的、具体的函数供参数使用,而这就是函数重载
的道理
形象化理解:
假设我们的整个程序
就是一个大城市在这个城市中,我们就是造物主
,编译器
则是负责协助我们处理事情的假设在某一天,参数A
提出它需要一栋房子(方法)
,造物主
很不屑的给造好了房子
一天后,参数B
也说它也需要一栋房子(方法)
,造物主
很快就满足了它的需求之后的每一天中,都会有参数
说自己需要房子(方法)
,于是造物主
坐不住了,他觉得这些参数
很麻烦,明明大家都是同一个需求,还得自己不断重复实现于是他想了一个办法:将建造房子的图纸(模板)
交给编译器
,编译器
是完全服从于造物主
的,造物主说:“小编啊,以后再有人找我建房子(方法)
,你就按照这个图纸(模板)
去建造,建好后将房子所有者变成它就行了”,这样一来,造物主
的工作量就减小了很多,重复相似的工作直接提供蓝图(模板)
,然后让编译器
根据参数类型
落实即可于是,函数模板
就这样诞生了 可以看出,不断建房子这件麻烦事仍然存在,毕竟不可能让所有参数都入住一栋房子,函数模板
的本质就是将实现不同参数的相似方法这件事交给编译器去完成,我们只需要提供蓝图(模板)
即可
比如文章开头中的 Add
函数,我们提供了模板
,当实际调用函数时,编译器会自动识别参数类型,然后生成对应的函数,供参数调用,也就是说,编译器根据不同参数,老老实实生成了 int
、double
、char
三个版本的 Add 函数,如果有需要,它还能继续生成
实际参数调用时,调用的是模板生成的对应函数,而非模板本身!
编译器在识别参数类型生成函数时,有两种途径:
自动识别 (隐式
)我们手动指定(显式
) 2.2.1 隐式实例化
隐式实例化
就是编译器自动识别参数后生成函数的过程
隐式实例化很方便,但可能存在问题
//Add 模板template <class T>T Add(const T& a, const T& b){return a + b;}int main(){Add(1, 2.1);//此时编译失败!return 0;}
原因
此时我们的模板是单参数模板因为是编译器隐式实例化
,当编译器识别到 1
时,将生成 int
型方法此时 Add
函数内的两个形参类型都为 int
,实际函数名修饰为 _3Addii
而我们的参数2为 double
,是一个浮点型数据,实际函数调用时,找的是这个函数_3Addid
此时出现明显的链接错误,编译器索性直接在编译前就已经报错阻拦 解决方法:
将参数2强制类型转换为int
,或者将参数1强制类型转换为 double
都能解决问题多参数模板也能解决问题,此时如果识别到两个不同的参数,编译器就会根据实际情况生成函数还有一种解决方法就是显式实例化
注意:
强制类型转换
后生成临时变量进行传参临时变量具有常性,所以Add函数中的引用形参需要被 const 修饰或者不用引用,这样也不需要 const
,但是此时效率会变低 2.2.2 显示实例化
显式实例化
就是给编译器打招呼,让它在建房子时按照我们的意愿来
Add<int> (2, 3.14);//此时编译器会调用 _3Addii 函数,至于传参时的类型转换,由编译器完成Add<char> (2, 5);//调用 _3Addcc 函数
这种行为是完全合法的,< >
符号也正式和我们见面了,在后面的 STL
学习中,< >
会经常使用到,比如生成一个类型为 int
的顺序表,直接 vector<int>
,生成 char
类型的顺序表 vector<char>
,一键生成,非常方便,当然还有很多容器都会用到显式实例化
2.3 匹配原则
具体函数调用时,隐式
生成的模板函数
并不会最先被调用
假设我们已经在程序中写好了参数需要的函数,而同时模板也能生成参数需要的函数,此时编译,编译器会先寻找是否存在目标函数,如果有,编译器便不再根据函数模板生成函数,避免造成代码冗余
我们可以通过调试来观察到这一现象
2.4 注意事项
注意:
函数调用时,并非直接调用函数模板
,而是调用编译器
根据参数类型
和模板
生成的函数
使用模板是在麻烦编译器
帮我们办事,实际事也是办成功的
当隐式
实例化后的函数已存在时,不会去生成模板函数
,而是直接使用已存在的函数
显式
实例化后,编译器则会优先选择显式
生成的普通函数
隐式
生成的模板函数不存在类型隐式类型转换
,显式
后生成的是普通函数,可以隐式类型转换
模板中的参数类型不能为 strcut
template<struct T>//这种定义是非法的
C++
库中存在一个 swap
函数,它能实现所有数据类型的交换,其实它就是通过函数模板
实现的
三、类模板
模板
除了可以用在函数
上面外,还可以用在类
上,此时称为 类模板
STL
库中的容器,都是 类模板
的形式,我们使用时,需要什么类型的 类
,直接显式实例化为对应 模板类
即可
//简单演示下 STL 中的容器,这些都是类模板的实际运用vector<int> v1;//实例化为整型顺序表类list<double> l1;//实例化为浮点型链表类
3.1 使用方法
//简单写一个栈模板template<class T>class Stack{public://构造函数Stack(int capacity = 4);//析构函数~Stack();//……private:int _top;int _capacity; T* _Data;};//注意类模板中方法的实现方式!//定义构造函数template<class T>Stack<T>::Stack(int capacity){_Data = new T[capacity];//内存管理,一次申请4块空间_capacity = capacity;_top = 0;}//定义析构函数template<class T>Stack<T>::~Stack(){delete[] _Data;//注意:匹配使用_capacity = _top = 0;}//……
这就算 STL
库中 stack
的简陋版本,还有很多方法没实现,但大体逻辑都是如此
3.2 注意事项
类模板
使用时需要注意一些问题:
模板类
中的函数在定义时,如果没有在类域中,就需要通过 类模板
+ 类域访问
的方式定义类模板
不支持声明与定义分开在两个文件中实现,因为会出现链接错误