【C++】模板进阶

大耳朵土土垚 2024-06-12 10:05:03 阅读 98

hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹

在这里插入图片描述

💥个人主页:大耳朵土土垚的博客

💥 所属专栏:C++入门至进阶

这里将会不定期更新有关C++的内容,希望大家多多点赞关注收藏💖💖

之前我们学习过C++初阶模板,学习了泛型编程以及函数模板和类模板,详情可点击C++——模板初阶查看,今天我们继续学习C++进阶模板的有关内容🥳🥳

目录

1.非类型模板形参2.模板的特化2.1引例2.2模板特化介绍2.3模板特化分类 3.模板分离编译3.1 什么是分离编译3.2模板的分离编译3.3解决方法 4.模板总结

1.非类型模板形参

在初阶模板中,函数模板和类模板所传的参数前面都是class或者typename修饰的,是类类型形参,但是模板除了可以传递类类型形参之外还可以传递非类型形参

模板参数分类类型形参和非类型形参

类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。非类型形参:用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

常量前面加上自己的类型

例如定义一个静态数组的类,其大小是固定的就可以传递非类型模板参数:

namespace tutu { // 定义一个模板类型的静态数组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];//非类类型模板参数,N=10size_t _size;};}

注意:

浮点数、类对象以及字符串是不允许作为非类型模板参数的。非类型的模板参数必须在编译期就能确认结果。

非类型模板参数必须是整数类型、枚举类型或指向对象的指针或引用类型。这是因为在编译时,非类型模板参数需要在编译器确定其值,而浮点数、类对象以及字符串在编译时无法确定其值。

注:

C++20之前,只允许整型做非类型模板参数

C++20之后,支持浮点数等其他内置类型

2.模板的特化

2.1引例

引例

给一个日期类:

//日期类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(T left, T right){ return left < right;}

当我们使用日期类对象比较时,发现可以借用类对象中重载的<来比较,但是如果我们使用日期类对象的指针比较日期大小时就可能得不出我们想要得结果,代码如下:

int main(){ cout << Less(1, 2) << endl; // 可以比较,结果正确Date d1(2024, 6, 6);Date d2(2024, 12, 14);cout << Less(d1, d2) << endl; // 可以比较,结果正确Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl; // 可以比较,结果错误return 0;}

那么如果我们想要通过日期类的指针比较其所指向日期的大小时,就需要对它解引用再比较,这时上述的比较日期大小的函数模板就不适用了

我们就可以使用函数模板的特化,当使用日期类指针进行日期比较时就使用特化版本,特化代码如下:

//函数模板template<class T>bool Less(T left, T right){ return left < right;}//函数模板特化版本template<>bool Less<Date*>( Date* left, Date* right){ return *left < *right;}

这样上述使用日期类指针比较日期的函数就可以使用特化版本来实现比较了

2.2模板特化介绍

模板的特化是指为具体类型或具体参数提供专门的实现。通常情况下,模板的特化是为了解决某些特殊类型或参数的处理需求,使得模板能更好地适应不同的情况。

函数模板的特化步骤: 必须要先有一个基础的函数模板关键字template后面接一对空的尖括号<>函数名后跟一对尖括号,尖括号中指定需要特化的类型函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时需要特别给出,因此函数模板不建议特化。

对于上述第4点我们可以看个例子:

通常情况下比较大小得函数模板得参数应该这样给:

//函数模板template<class T>bool Less(const T& left, const T& right){ return left < right;}

需要加const修饰,一般使用传引用传参

那么为了使得特化之后得函数参数与之前的保持一致,特化版本参数也需要加const修饰和引用:

//函数模板特化版本template<>bool Less<Date*>( const Date*& left,const Date*& right){ return *left < *right;}

运行时却发现报错了:

在这里插入图片描述

这是因为const在修饰指针时有两种情况:

const在* 左边

int main(){ int a = 10;const int * p = &a;/* const 放在*的左边,修饰的是*p,表示的是指针指向的内容,不能通过指针来改变 但是指针变量本身是可以被修改的*/int n = 20;*p = n; // 报错 指针指向的内容不能被修改p = &n; // 指针变量p可以修改 return 0;} const在* 右边

int main(){ int a = 10;const int * p = &a;/* const 放在*的左边,修饰的是*p,表示的是指针指向的内容,不能通过指针来改变 但是指针变量本身是可以被修改的*/int n = 20;*p = n; // 报错 指针指向的内容不能被修改p = &n; // 指针变量p可以修改 return 0;}

上述函数模板中const修饰的是变量本身,所以对于特化版本来说,指针的修饰也应该是自己本身,所以我们应该使用第二种情况:

//函数模板特化版本template<>bool Less<Date*>( Date* const& left, Date* const& right){ return *left < *right;} 类模板的特化步骤: 必须要先有一个基础的类模板关键字template后面接一对空的尖括号<>类名后跟一对尖括号,尖括号中指定需要特化的类型

使用类模板特化:使用类模板特化时,需要明确指定特化的类型。使用特化的方式与使用普通的类一样,只是在使用时需要提供特化的类型。

2.3模板特化分类

有两种类型的模板特化:全特化(full specialization)和偏特化(partial specialization)。

全特化是指为模板的所有模板参数都提供具体的类型或值,使用特定的实现来处理特殊情况。全特化的语法形式如下:

template <> // < > 中为空表示全特化return_type function_name<specific_type>(parameters) { // 特定类型的实现} 偏特化是指为模板的部分模板参数提供具体的类型或值,使用特定的实现来处理特殊情况。偏特化的语法形式如下:

template <specific_type1, specific_type2, ...> // < > 中为具体类型return_type function_name<generic_type<specific_type1, specific_type2>>(parameters) { // 特定类型的实现}

3.模板分离编译

3.1 什么是分离编译

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。

3.2模板的分离编译

假如有以下场景,模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义:

// a.htemplate<class T>T Add(const T& left, const T& right);//声明// a.cpptemplate<class T>T Add(const T& left, const T& right)//定义{ return left + right;}// main.cpp#include"a.h"int main(){ Add(1, 2); Add(1.0, 2.0); return 0;}

运行时就会出现以下情况:

在这里插入图片描述

这是因为C/C++程序运行一般需要经历以下步骤:

预处理——编译——汇编——链接

预处理:在编译之前,预处理器对源文件进行处理,包括宏展开、头文件包含等操作。

编译:对程序按照语言特性进行词法,语法,语义分析,检查无误之后生成汇编代码,注意头文件不参与编译,编译器对工程中的多个源文件是分离开单独编译的

汇编:将汇编代码翻译成机器代码。

链接:将编译后的目标文件与库文件进行链接,生成可执行文件。

在这里插入图片描述

3.3解决方法

将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实也是可以的,这样声明定义就放在一个文件中了,推荐使用这种。模板定义的位置显式实例化。这种方法不实用,不推荐使用。

显式实例化代码如下:

//a.cpp#include"a.h"template<class T>T Add(const T& left, const T& right){ return left + right;}//显示实例化templateint Add(const int& left, const int& right);templatedouble Add(const double& left, const double& right);

4.模板总结

优点 模板复用了代码,但本质上编译器会帮我们生成实例化的代码,只是减少了人工的消耗,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生增强了代码的灵活性 缺陷 模板会导致代码膨胀问题,也会导致编译时间变长出现模板编译错误时,错误信息非常凌乱,不易定位错误

以上就是今天进阶模板所有的内容啦~ 完结撒花🥳🎉🎉



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。