目录
一、非类型模板参数
二、模板的特化
1.概念
2.函数模板特化
3.类模板特化
3.1.全特化
3.2.偏特化
3.2.1.部分特化
3.2.2.参数更进一步限制
3.3.类模板应用
三、模板为什么不能声明定义分离在两个文件?
一、非类型模板参数
模板参数分为类型形参和非类型形参。
类型形参就是传类型作为模板的参数,这个在之前已经很有体会了;
非类型形参就是传一个常量值作为模板的参数,与define定义宏有相似的地方。
非类型模板参数代码演示:
#include<iostream>
using namespace std;// 非类型模板参数代码演示:
// 非类型模板参数与类型模板参数一样,都可以给缺省值
template<int N = 20>
void print() {cout << "N = " << N << endl;
}int main() {print(); // 输出 N = 20print<10>(); // 输出 N = 10return 0;
}
#include<iostream>
using namespace std;// C++20才支持double类型的非类型模板参数
template<double N = 0.0>
void print() {cout << "N = " << N << endl;
}int main() {print(); // 输出 N = 0.0print<1.1>(); // 输出 N = 1.1return 0;
}
需要注意:
1.非类型模板参数只支持int类型,在C++20之后又支持了double类型,其余的类型均不支持。
2.非类型模板参数必须在编译期就能确定结果
对比宏的好处:
非类型模板参数可以根据用户需求想传什么就传什么,比如你需要建立两个栈,一个需要十个空间,一个需要五个空间,那么即可通过传参的方式任意指定栈的大小,而使用宏的方式实现的栈注定会造成空间的浪费。
二、模板的特化
1.概念
有些时候,一个函数模板不足以实现传所有类型参数所期望达到的功能,就比如比较大小,如果传int,double,这些都好说,但是如果传指针,想比较指针指向位置数据的大小,那么就会出错,因为模板的实现会比较指针的大小而非指针指向位置数据的大小。
因此,如果我们对该模板传入某一特定类型参数的情况进行特殊处理,以适应该情况模板需要实现的功能,这个过程就叫模板的特化。
模板的特化:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。
模板特化中分为函数模板特化与类模板特化。
为了方便后面的叙述,我这里用到了之前实现过的Date类,想了解可以移步我之前的博客。
// 函数模板 -- 参数匹配
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;
}
2.函数模板特化
原理很简单,但是需要着重注意它的语法:
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}// 对传Date*类型的模板进行特化处理
template<>
bool Less<Date*>(Date* left, Date* right)
{return *left < *right;
}
// 注意函数模板特化的语法:
// template<>
// 函数模板名<模板参数列表>(函数参数列表) 一定注意这里的模板参数列表和函数参数列表的匹配
// { 函数体 }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;
}
注意:
如果函数模板有它不能处理或是处理有误的类型,我们通常是直接将该函数给出,这种实现简单明了,可读性高。
bool Less(Date* left, Date* right)
{
return *left < *right;
}
因此,对于一些需要特化的函数模板,我们一般都将其特殊版本的函数直接给出,函数模板不建议特化。
3.类模板特化
3.1.全特化
全特化是将类模板的所有参数都确定化。
template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};// 全特化,将所有模板参数都确定
template<>
class Data<int, char>
{
public:Data() { cout << "Data<int, char>" << endl; }
private:int _d1;char _d2;
};void TestVector()
{// 不符合全特化就走普通模板,符合就走全特化Data<int, int> d1;Data<int, char> d2;
}int main()
{TestVector();return 0;
}
3.2.偏特化
任何针对模板参数进行进一步限制设计的特化版本,都可以叫偏特化
3.2.1.部分特化
template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};// 部分特化:将第二个参数特化为int,模板的<>中仍保留没有特化的参数
template <class T1>
class Data<T1, int>
{
public:Data() { cout << "Data<T1, int>" << endl; }
private:T1 _d1;int _d2;
};int main()
{Data<int, int> d1;Data<int, char> d2;return 0;
}
3.2.2.参数更进一步限制
template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:Data() { cout << "Data<T1*, T2*>" << endl; }
private:T1 _d1;T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:Data(const T1& d1, const T2& d2): _d1(d1), _d2(d2){cout << "Data<T1&, T2&>" << endl;}
private:const T1& _d1;const T2& _d2;
};
void test()
{Data<int, double> d1; // 调用基础的模板Data<int*, int*> d2; // 调用特化的指针版本Data<int&, int&> d3(1, 2); // 调用特化的指针版本
}int main()
{test();return 0;
}
另外需要注意的是,这种特化方式无论是T*还是T&,在实现里面的T都表示T,也就是去掉指针或者引用的类型。
这么做有一种好处,就是有T类型,可以自由使用T*或是T&,反之则不太方便。
3.3.类模板应用
在仿函数比较大小的特例中,有重要应用。
#include "Date.h"
#include<vector>
#include<algorithm>
#include<iostream>
using namespace std;template<class T>
struct Less
{bool operator()(const T& x, const T& y) const{return x < y;}
};// 对Less类模板按照指针方式特化,处理类型为指针的情况
template<>
struct Less<Date*>
{bool operator()(Date* x, Date* 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);sort(v2.begin(), v2.end(), Less<Date*>());// 如果不走特化版本的话,指针的排序结果是不正确的// 所以需要写特化版本来处理指针的排序return 0;
}
三、模板为什么不能声明定义分离在两个文件?
为什么模板不能声明定义分离在两个文件?为什么普通的函数可以?
答:头文件会在预处理的时候展开,
头文件:
展开在cpp文件中:
又经过编译和汇编,在链接时,主函数中的func在func.cpp中找到了它的函数地址(因为已经在当前文件实例化),可以调用。而print在func.cpp中找不到它的函数地址,因为func.cpp中没有实例化出print<1>需要的版本,所以会出错。
解决这个问题的方法很简单:
1.在模板定义的位置显示实例化,比如这样:
然后它就可以正常跑了:
不过这种方式太挫了还是,要用哪种类型的模板时还得显示实例化哪种,太麻烦了。
2. 不要声明和定义分离在两个文件.h和.cpp文件
这样就从源头上避免了链接错误,实际操作时比较推荐这种。
好啦,完结撒花~~~~~~~~~~~~~~