目录
一、非类型模板参数
二、模板的特化
🌟概念
扩展小知识补充(1):
扩展小知识补充(2):
🌟函数模板特化
扩展小知识:
🌟类模板特化
✨全特化
✨偏特化
• 部分特化:将模板参数表中的一部分参数特化。
• 参数更进一步的限制:针对模板参数更进一步的条件限制所设计出来的一个特化版本。
🌟类模板特化应用示例
小贴士:
函数模板特化和类模板特化的写法区分:
三、模板分离编译
🌟什么是分离编译?
🌟模板的分离编译
🌟解决办法
四、模板总结
一、非类型模板参数
模板参数分为:类型形参 和 非类型形参 。
类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
template<class T,size_t N = 10>
class Stack
{
public:
void func()
{
N++;
}
private:
int _a[N];
int _top;
};
• C++20之前,只允许整型做非类型模板参数
• C++20之后,可以支持double等其他内置类型做非类型模板参数
• 非类型的模板参数必须在编译期就能确认结果。
二、模板的特化
🌟概念
通常情况下,使用模板可以实现一些与类型无关的代码,但对由于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板。
//函数模板---参数匹配
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;
}
Less多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上面的例子中,p1指向的d1的对象显然小于p2指向的对象,但是Less内部并没有比较p1和p2指向的对象内容,而比较的是p1和p2指针的地址, 这就无法达到预期而导致错误。
此时,就需要对模板进行特化。即:在原模版类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为 函数模板特化 和 类模板特化 。
扩展小知识补充(1):
这个函数使用的头文件 #include<array> ,这个函数和静态数组有什么区别?
#include<iostream> #include<array> using namespace std; int mian() { //严格的越界检查(自定义类型,里面进行限制) array<int, 10> aa1; //弊端:(1)只开空间,不初始化; //(2)开的空间不是在堆上,是直接在当前位置开的, // 导致局部对象太大(函数栈帧本身不大8兆,800万字节 cout << sizeof(aa1) << endl; //aa1[10];越界检查 //aa1[14] = 1;越界检查 //静态数组 int aa2[10];//对越界的检查是一个抽查 //aa2[15] = 1;//越界读是检查不出来,检查一般是检查临近位置 10 11 //aa2[11] = 1;//最后设计了一个标准位,越界一两个很难检查 //array不如用vector vector<int> v1(10, 1); //v1[14]; cout << sizeof(v1) << endl; return 0; }
• array 函数
优点:有严格的越界检查(自定义类型,里面进行限制);
弊端:(1)只开空间,不初始化;
(2)开的空间不是在堆上,是直接在当前位置开的,导致局部对象太大(函数栈帧本身不大8兆,800万字节)
• 静态数组: 对越界的检查是一个抽查,越界读是检查不出来,检查一般是检查临近位置,最后设计了一个标准位,越界一两个很难检查
• array函数不如用vector
扩展小知识补充(2):
初始化的多种形式:
//初始化的多种形式 int main() { vector<int> v1 = { 1,2,3,4,5,6,7,8,9 }; vector<double> v2{ 1.1,2.2,3.,4.4 }; int i = 1; int j = { 1 }; int k{ 1 }; return 0; }
🌟函数模板特化
扩展小知识:
模板没实例化时,不去里面查细节的东西,无法确认是类型还是静态变量,加 typename 明确告诉编译器是类型。
void PrintVector(const vector<int>& v) { vector<int>::const_iterator it = v.begin(); while (it != v.end()) { cout << *it << " "; ++it; } cout << endl; } template<class T> void PrintVector(const vector<T>& v) { //模板没实例化时,不去里面查细节的东西,无法确认是类型还是静态变量 //加typename明确告诉编译器是类型 typename vector<T>::const_iterator it = v.begin(); while (it != v.end()) { cout << *it << " "; ++it; } cout << endl; } int main() { vector<int> v1 = { 1,2,3,4,5,6,7 }; vector<double> v2 { 1.1,2.2,3.3,4.4,5.5,6.6,7.7 }; PrintVector(v1); PrintVector(v2); return 0; }
函数模板特化的步骤:
• 必须要先有一个基础的函数模板
• 关键字template后面接一对空的尖括号<>
• 函数名后跟一对尖括号,尖括号中指定需要特化的类型
• 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇 怪的错误。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const
{
return (_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day);
}
bool operator>(const Date& d)const
{
return (_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day);
}
friend ostream& operator<<(ostream& _cout, const Date& d);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
//三个同时存在都可以
template<class T>
bool Less(const T& left, const T& right)
{
return left < right;
}
//template<class T>
//bool Less( T left, T right)
//{
// return left < right;
//}
bool Less(Date* left, Date* right)
{
return *left < *right;
}
//函数模板特化
template<>
bool Less<Date*>(Date* const& left, Date* const& right)
{
return *left < *right;
}
//一般情况下,如果函数模板遇到不能处理或者处理有误的类型,
// 为了实现简单通常都是将该函数直接给出
//bool Less(Date* left, Date* right)
//{
// return *left < *right;
//}
int main()
{
Date d1(2022, 7, 7);
Date d2(2022, 7, 8);
cout << Less(d1, d2) << endl;
return 0;
}
一般情况下,如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。
bool Less(Date* left, Date* right) { return *left < *right; }
🌟类模板特化
✨全特化
• 全特化即是 将模板参数列表中所有的参数都确定化。
template<class T1,class T2>//原模版 class Data { public: Data() { cout << "Date<T1,T2>-原模版" << endl; } private: T1 _d1; T2 _d2; }; //特化:针对某些特殊类型,进行特殊化处理 //全特化 template<> class Data<int, char> { public: Data() { cout << "Data<int,char>-全特化" << endl; } };
• class Data<int, char> 所有的参数都是确定的。
✨偏特化
偏特化:任何对模板参数进一步进行条件限制设计的特化版本。
• 部分特化:将模板参数表中的一部分参数特化。
//偏特化/半特化
template<class T1>
class Data<T1, int>
{
public:
Data()
{
cout << "Data<T1,int>-偏特化" << endl;
}
private:
T1 _d1;
int _d2;
};
• 参数更进一步的限制:针对模板参数更进一步的条件限制所设计出来的一个特化版本。
<1>两个参数偏特化为指针类型:
//对指针进行特化
template<typename T1, typename T2>
class Data<T1*, T2*>
{
public:
Data()
{
cout << "Data<T1*,T2*>-偏特化" << endl << endl;
}
};
• T1、T2 只要是指针的类型就用这个特化,无论是什么数据类型,但是实例化出来的 T1、T2 是原类型( * 相当于是一个表示符);
• T1 T2 传的是什么类型就是什么类型,传int*就是int*,并不是int**二级指针
<2> 两个参数偏特化位引用类型:
template<typename T1,typename T2>
class Data<T1&, T2&>
{
public:
Data()
{
cout << typeid(T1).name() << endl;
cout << typeid(T2).name() << endl;
cout << "Data<T1&,T2&>-偏特化引用" << endl<<endl;
}
};
//Data<int&, int&> d6;
<3>一个参数偏特化为引用,一个偏特化为指针:
//一个引用 一个指针
template<typename T1, typename T2>
class Data<T1&, T2*>
{
public:
Data()
{
cout << typeid(T1).name() << endl;
cout << typeid(T2).name() << endl;
cout << "Data<T1&,T2&>-偏特化引用" << endl;
}
};
//Data<int&, int*> d7;
🌟类模板特化应用示例
#include<vector>
#include<algorithm>
template<class T>
struct Less
{
bool operator()(const T& x, const T& y) const
{
return x < y;
}
};
int main()
{
Date d1(2022, 7, 7);
Date d2(2022, 7, 6);
Date d3(2022, 7, 8);
vector<Date> v1;
v1.push_back(d1);
v1.push_back(d2);
v1.push_back(d3);
// 可以直接排序,结果是日期升序
sort(v1.begin(), v1.end(), Less<Date>());
vector<Date*> v2;
v2.push_back(&d1);
v2.push_back(&d2);
v2.push_back(&d3);
// 可以直接排序,结果错误日期还不是升序,而v2中放的地址是升序
// 此处需要在排序过程中,让sort比较v2中存放地址指向的日期对象
// 但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期
sort(v2.begin(), v2.end(), Less<Date*>());
return 0;
}
此时可以使用类模板进行特化:
// 对Less类模板按照指针方式特化
template<>
struct Less<Date*>
{
bool operator()(Date* x, Date* y) const
{
return *x < *y;
}
};
小贴士:
函数模板特化和类模板特化的写法区分:
三、模板分离编译
🌟什么是分离编译?
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
🌟模板的分离编译
• 模板的分离编译会出现链接错误。
🌟解决办法
• 1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.。(不要分离到两个文件,声明和定义放在一起)
• 2. 模板定义的位置显式实例化。
四、模板总结
• 优点:
(1)模块复用了代码(本质是把活交给编译器),节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生;
(2)增强了代码的灵活性。
• 缺点:
(1)模板会导致代码膨胀问题,也会导致编译时间变长;
(2)出现模板编译错误时,错误信息非常凌乱,不易定位错误。
如若对你有帮助,记得点赞、收藏、关注哦!
若有误,望各位,在评论区留言或者私信我 指点迷津!!!谢谢^ ^ ~