【c++指南】模板VS手写代码:这场效率对决你站哪边?【下】
🌟 各位看官好,我是egoist2023!
🌍 种一棵树最好是十年前,其次是现在!
🚀 今天来学习非类型模板及特化的相关知识。
👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦!
目录
非类型模版参数
特化
函数模版特化
类模版特化
全特化
偏特化
模版分离编译
解决方案
总结
非类型模版参数
在 模版【上】章节中模版参数当作类型来处理,实际上模版参数还有非类型模版参数
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常 量来使用。
#define N 10templateclass myvector{public://...private:T _arr[N];int _capacity;};int main(){myvector v1;myvector v2;return 0;}
在上面这段程序当中,N的大小是确定的,即如果要插入100个数据时,N是10是不够的,需要手动将N的大小进行修改;当要插入10个数据时,N是100又太多了,导致很多空间被浪费。因此槽点很多,如果使用非类型模版参数可以改善这种问题。
templateclass myvector{public://...private:T _arr[N];int _capacity;};
此时N的大小我们可以自行决定,此时一个double类型的vector要开100个空间
myvector v;
int类型的vector要开10个空间
myvector v;
实际上,在C++库中的array(数组)也是这样处理的,那它又和int array[N]有什么区别呢?
C语言中的int array对越界问题检查实际是不严格的。
C++中array对越界问题的检查。
需要注意的是
- 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
- 非类型的模板参数必须在编译期就能确认结果。
特化
通常情况下, 使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些 错误的结果 ,需要特殊处理。
templatebool Less(T left, T right){return left < right;}int main(){cout << Less(1, 2) << endl;Date d1(2025, 3, 1);Date d2(2025, 3, 2);cout << Less(d1, d2) << endl;return 0;}
结合之前Date类实现,如上一段程序中,可以对日期类进行正确比较。但是也有一些情景是需要特殊处理的。
在上面这段程序当中,明显d3>d4的,运行结果为1才对。但运行几次后发现有两个结果,为什么会出现0这个现象呢?
此时我们进行调试观察d3和d4。
可以发现d4的内存地址是大于d3的内存地址,这也解决了我们的疑惑:这里的比较是指针地址的比较,因此需要引入特化或提供函数支持Date比较 。
函数模版特化
函数模板的特化步骤: 1. 必须要先有一个基础的函数模板 2. 关键字template后面接一对空的尖括号 3. 函数名后跟一对,中指定需要特化的类型 4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇 怪的错误。 使用函数模版特化解决Date*按指针比较的方式。
//函数模版特化templatebool Less(Date* left, Date* right){return *left < *right;}
普通函数同样可以解决问题,且这种方式简单明了,难道不是更香吗?
bool Less(Date* left, Date* right){return *left < *right;}
我们再来看看传右值的情况(右值指的是匿名对象、常量等)
templatebool Less(const T& left, const T& right) //传右值-->左值引用右值要加const{return left < right;}//特化templatebool Less(Date* const & left, Date* const & right) //const修饰本身{return *left < *right;}int main(){cout << Less(new Date(2025, 3, 1), new Date(2025, 2, 28)) << endl;return 0;}
再针对右值的情况下,左值引用要引用右值就必须+const修饰,那在特化的时候,也需要进行对应修改。可以看到,模版函数特化在上面这段程序中显得特别坨,再来看看普通函数的实现。
bool Less(Date* const & left, Date* const & right) //const修饰本身{return *left < *right;}
因此,为了提高代码的可读性,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。这样看特化好像无用武之地,别着急我们继续往下看。
类模版特化
全特化
全特化即是将模板参数列表中所有的参数都确定化。
templateclass Date{public:Date(){cout << \"Date\" << endl;}};templateclass Date{public:Date(){cout << \"Date\" << endl;}};templateclass Date{public:Date() {cout << \"Date\" << endl;}};
偏特化
偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。 部分模版参数特化
templateclass Date{public:Date(){cout << \"Date\" << endl;}};
参数更进一步的限制
templateclass Date{public:Date(){cout << \"Date\" << endl;}};templateclass Date{public:Date(){cout << \"Date\" << endl;}};
类模版特化的场景
针对日期进行排序
// 对Less类模板按照指针方式特化templatestruct Less{ bool operator()(Date* x, Date* y) const { return *x < *y; }};
模版分离编译
为什么在 模版【上】章节说到不建议模版声明与定义分离呢?这里做下解释。
分离编译:一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程。
我们知道C/C++程序运行一般要经历以下步骤:
- 预处理 --> 展开头文件、宏替换、条件编译、去掉注释等;
- 编译 --> 检查语法、生成汇编代码;
- 汇编 --> 将汇编代码转成二进制机器码;
- 链接 --> 合成可执行程序,链接函数地址等;
运行时链接出现了报错,我们来具体分析下原因在哪。
解决方案
- 显示实例化(但是不建议如果有多个类型)
func2(1);func2(200.1);
- 不做声明和定义分离(避开找地址,在声明处直接有地址了)
总结
优点远大于缺点
【优点】 1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生 2. 增强了代码的灵活性 【缺陷】 1. 模板会导致代码膨胀问题,也会导致编译时间变长 2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误