目录
一、需求引入:
我们如何实现一个满足不同种类型的通用交换函数?
写上不同种类型的重载函数?显然所需要的精力和需求满足不适宜。如下还需要其他多种类型的重载函数的书写
缺点显而易见:
①重载的函数仅仅是类型不同,代码复用率比较低
②代码的可维护性比较低
void swap(int left, int right)
{
int temp = right;
right = left;
left = temp;
}void swap(double left, double right)
{
double temp = right;
right = left;
left = temp;
}void swap(char left, char right)
{
char temp = right;
right = left;
left = temp;
}
二、模板
如果可以使用一个模具,我只需要填充我所需要放入的不同材料(类型)来达到我刻印出来不同类型的方法。
如下我只需要填入不同的类型,就可以得到我所需要的对应方法。
c++中的模板分为函数模板与类模板
三、函数模板
来看下面这段代码:
template <typename T>
void swap(T& left, T& right)
{
T temp = right;
right = left;
left = temp;
}
template <typename T>
void swap(T& left, T& right)
{
T temp = right;
right = left;
left = temp;
}
template <typename T, typename S, class U>
void Print(T date1, S date2, U date3)
{
cout << date1 << endl;
cout << date2 << endl;
cout << date3 << endl;
}
template <typename T> 函数模板的引入使其能够根据传入参数的类型来自己判断T的类型。
此处的typename可以替换为class,那么我们又学到了class与struct的第二个区别了
1、class与struct区别
①是在类中定义时的默认访问权限不同(struct为public,class为private)
②模板参数列表只能用class声明,不能使用struct/
2、例一:add函数
3、 例一的优化:一般都使用const万能引用
(const引用是万能引用)
我们知道如果传入的参数是一个自定义类型的参数,那么我们就需要对参数进行拷贝构造,这显然降低了我们代码的效率,所以我们可以选择传引用,来解决这个问题。
可是这一优化,却带来了编译报错的问题,这时候这里的引用需要更改为const类型的引用
4、const引用回顾
上篇中为啥使用了const就解决了问题呢?
我们先来回顾一个const引用:看一下这个代码:
下面这个代码中用ar引用 a没问题,可是用br引用一个const修饰的变量b时,出现了问题,
因为在c++中由const修饰的变量就成为了一个常量了,(c语言中const修饰后是一个不能更改的变量)
一个常量使用一个可能被修改的别名br来引用,因此违背了我b是一个常量的原则。
所以引发错误。
那我们上面的add函数在传参时传递了俩个常量参数(10,20),然而引用上却使用了一个可能被修改的参数就会发生错误,因此这里的引用参数需要被const限定符修饰。
四、函数模板参数实例化
函数模板:该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的待定类型版本
函数模板不是函数,是一个模具、是一个规则
是编译器根据实例化结果推演出的类型生成处理具体类型代码的一个规则
1、add函数的调试编译
使用typid(T).name()来查看所转化的参数类型结果。
2、更多参数的引入
看下面这个代码出现的问题,想到传入一个int类型与一个double类型的参数,发现却无法实现,因为编译器为了安全性不会去猜这个所传入的参数到底是int还是double。
解决:
一、使用强转使,int改为一个double类型
二、多使用一个模板参数(隐式实例化)
隐式实例化:没有明确指出模板参数的类型,需要编译器对实参类型进行推演
编译器不会进行隐式类型转换
进行函数模板实例化
可以根据调用参数的类别进行自己识别,10-int,20-int编译器会推演出int类型的add函数
1.2-double,3.4-double编译器会推演出double类型的add函数
‘a’ -char ‘b’-char编译器会推演出char类型的add函数
另外可以写入多个模板参数。
三、使用显示实例化 (会进行隐式类型转换)
3、小结:
五、模板参数的使用规则
①同名的模板函数可以和一个非模板函数同时存在
template <class T>
T Add(const T& left,const T& right)
{
return left + right;
}
int Add(int left, int right)
{
return left + right;
}
int main()
{
// 直接调用普通函数
Add(1, 2); // 如果有类型完全匹配的方法则直接调用,不需要让编译器根据模板生成
// 以下俩种方法调用模板函数
Add<int>(1, 2); // 显示实例化
Add<>(1, 2); // 隐式实例化 告诉编译器根据模板来生成对应类型的int
return 0;
}
② 对于非模板函数和模板函数,在调用时会优先调用非模板函数而不会从模板函数中实例化。
如果模板可以产生一个更好的具有更好匹配的函数,那么将选择模板
③模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
六、函数模板 编译原理
在参数实例化之前,只是对模板进行简单的语法检测
在参数实例化之后,根据用户对函数模板的实例化结果来生成处理对类型的代码,并对这些代码进行再次编译。
实例:复数的运算(匿名函数的返回)
看下部分这段代码,采用了上部分模板函数的使用,可是无法指明所需要对应的加的对象,需要进行更新
①例一:
②加入了运算符重载(返回值推荐使用匿名对象)
一二俩步都可以达到目标,一使用的是构造一个临时对象来保存加和之后的值,不仅需要进行一次构造函数,还会进行一步ret结果的拷贝构造,而二使用的是匿名对象的返回,可以直接返回加和之后的结果,少了一次拷贝构造。因此能使用匿名对象返回的尽量使用匿名对象返回,因为这样会少调用一次拷贝构造,效率得到了提高。
七、类模板
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类
编译器会根据所需要定义和插入的数据类型来进行自我推演,推演出合适的对象类型。
#include<iostream>
using namespace std;
template <class T>
class SeqList
{
public:
SeqList(size_t cap = 10)
: _array(nullptr)
, _capacity(cap == 0 ? 3 : cap)
, _size(0)
{
_array = new T[10];
}
void Push(const T& date)
{
_array[_size] = date;
_size++;
}
void Pop()
{
if (Empty())
return;
else
_size--;
}
bool Empty()
{
return 0 == _size;
}
~SeqList()
{
if (_array)
{
delete[] _array;
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
// 类内声明 类外定义
T& Front();
T& Back();
private:
T* _array;
size_t _capacity;
size_t _size;
};
template <class T>
T& SeqList<T>::Front()
{
return _array[0];
}
// 此处的SeqList为类模板
// SeqList<T>才是类型
template <class T>
T& SeqList<T>::Back()
{
return _array[_size - 1];
}
int main()
{
// SeqList不是一个类,而是一个类模板
// SeqLits<int> 才是真正的类型
SeqList<int> s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
SeqList<double> s1;
s1.Push(1.1);
s1.Push(2.2);
s1.Push(3.3);
s1.Push(4.4);
return 0;
}
八、知识点辨析
1、类模板与模板类
可以看后置的宾语来判断 类模板为一个模板
模板类为一个类 是一个实例 也就是模板实例化后的一个产物
2、类模板的成员函数均为模板函数(√)
起初我以为模板函数只有存在template的才为模板函数,显然是错误的,因为一个类模板能够成为模板,他的类内成员函数也均能够被实例化。
3、
4、D. 模板类跟普通类以一样的,编译器对它的处理时一样的(×)
模板函数的处理分为俩个阶段:
①实例化之前,只是对模板进行简单的语法检测
②实例化之后,根据用户对函数模板的实例化结果来生成处理对应类型的代码,并对这些代码进行再次编译。