hello,这里是bangbang,今天来讲下模板
目录
1. 泛型编程
如何实现一个通用的交换函数?
有的小伙伴或许会想到用函数重载,如下:
void Swap( int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
void Swap(double& left, double& right)
{
double temp = left;
left = right;
right = temp;
}
但是这就出现问题了,只要我类型不符合函数参数,那我就要再重载一份,这也太麻烦了!
1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数2. 代码的可维护性比较低,一个出错可能所有的重载均出错
那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?
2. 函数模板
2.1 函数模板概念
2.2 函数模板格式
template<typename T1, typename T2,......,typename Tn>
返回值 函数名(参数列表)
{}
交换函数的函数模板(2项参数类型相同):
template<typename T>
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
2.3 函数模板的实例化
1.隐式实例化:让编译器根据实参推演模板参数的实际类型
函数模板(假设1个模板参数),如果出现类型不匹配,会发生什么呢?
我们都知道C++内置类型进行运算,会向上算术转换,比如int+double=double,那么函数模板在这块是如何处理的呢,也可以直接将结构转换成double?我们来看:
此时有两种处理方式:1.用户自己强制转换。2.使用显示实例化。
用户自己强制转换:
Add(a1,(int)d1);
2.显示实例化:在函数名后的<>中指定模板参数的实际类型
Add<int>(a1,d1);//显示实例化
2.4 模板参数的匹配原则
-
一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
在VS下,打断点按F11进行查看,发现只有Add<int>()进入函数模板
- 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
3. 类模板
3.1 类模板定义格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
注意:类模板中函数放在类外进行定义时,需要加模板参数列表
//类模板
template<class T>
class Vector
{}
//类外定义
template<class T>
Vector<T>::函数定义
3.2 类模板实例化
//Vector类名,Vector<int>类型
Vector<int> v;
4. 非类型模板参数
namespace lh
{
// 定义一个模板类型的静态数组
template<class T, size_t N = 10>
class array
{
public:
T& operator[](size_t index) { return _array[index]; }
const T& operator[](size_t index)const { return _array[index]; }
size_t size()const { return _size; }
bool empty()const { return 0 == _size; }
private:
T _array[N];
size_t _size;
};
}
在这里补充一下array,我们想想平时写代码,数组用的是C++的array,还是C的数组形式。为什么C++要整出一个array类呢?
既然大佬整了出来,那肯定是和C有一定区别的!不知道小伙伴平时有没有发现C语言的数组形式对越界检查是非常不严格的!
优势:
C语言中越界检查采取抽查方式(指针解引用),越界读不报错,越界写有限报错。
C++11,array只要越界一定能查到(函数调用)。
5. 模板特化
5.1 模板特化概念
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
return left < right;
}
int main()
{
cout << Less(1, 2) << endl; // 可以比较,结果正确
Date d1(2022, 7, 7);
Date d2(2022, 7, 8);
cout << Less(d1, d2) << endl; // 可以比较,结果正确
Date* p1 = &d1;
Date* p2 = &d2;
cout << Less(p1, p2) << endl; // 可以比较,结果错误
return 0;
}
此时,就需要对模板进行特化。即: 在原模板类的基础上,针对特殊类型所进行特殊化的实现方式 。模板特 化中分为 函数模板特化 与 类模板特化 。
5.2 函数模板特化
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
return left < right;
}
// 对Less函数模板进行特化
template<>
bool Less<Date*>(Date* left, Date* right)
{
return *left < *right;
}
void date_test()
{
cout << Less(1, 2) << endl;
Date d1(2022, 7, 7);
Date d2(2022, 7, 8);
cout << Less(d1, d2) << endl;
Date* p1 = &d1;
Date* p2 = &d2;
cout << Less(p1, p2) << endl; // 调用特化之后的版本,而不走模板生成了
}
bool Less(Date* left, Date* right)
{
return *left < *right;
}
5.3 类模板特化
typename还有告诉编译器这是类型还是变量的作用!
因为静态类型变量指定类域也可以访问,所以在使用类型时,我们前面加typename进行区分。(不加编译器区分不清楚是类型还是变量)
6. 模板分离编译
6.1 什么是分离编译
6.2 模板的分离编译
链接问题:都是有声明找不到定义
7. 总结
优点:
1.模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。(重复的工作交给编译器做)
2.增强了代码的灵活性。
缺点:
1.模板会导致代码膨胀问题,也会导致编译时间变长。
2.出现模板编译错误时,错误信息非常凌乱,不易定位错误。