【C++】模版进阶

 我们在之前的博客中讲述过模版的使用:【C++】模版初阶,但这只是模版最基本的使用,下面再深入模版,看看还有另外什么用法:

目录

一、非类型模板参数

二、模板的特化

2.1 什么是模版的特化

2.2 函数模版的特化

2.3 类模版的特化

2.3.1 全特化

2.3.2 偏特化

三、模板的分离编译

3.1 解决方法


一、非类型模板参数

我们知道模版可以传递类型参数,除此之外还可以传递非类型参数:

例如:

#define N 10
template<class T>
struct Static_sequence
{T a[N];
};

上诉代码可以建立一个任意类型的静态顺序表,但是N值是确定的,我们如何建立我们想要的大小的静态顺序表呢?

再来看看下面的代码:

template<class T, size_t N = 10>
struct Static_sequence
{T a[N];
};

我们在模版上再加一个非类型参数size_t N,这个参数可以传入我们需要的值来初始化静态顺序表:

int main()
{Static_sequence<int, 20> q;return 0;
}

在上述代码中我们传入了一个20,可以看到开辟了一个20个元素的静态数组:

对于该非类型模板参数,我们只能设定整型家族(int/char/short/...),对于其他类型是不支持的,例如:

对于设定的非类型模板参数,系统是将其作为常量看待的,所以它可以初始化数组元素个数

例如我们想要对传入的值做修改:

编译器是不能通过的

二、模板的特化

2.1 什么是模版的特化

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:指针类型的比较:

class Date
{friend ostream& operator<<(ostream& out, const Date& d);
public:Date(int year, int month, int day)//构造函数{if (month < 1 || month>12 || (day<1 || day>GetMonthDay(year, month)))//判断日期是否合法{cout << "Illegal date!" << endl;}else{_year = year;_month = month;_day = day;}}int GetMonthDay(int year, int month) const//获取year年month月的天数{int MonthDay[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };//用数组来依次存储平年1到12月的天数if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))//判断是否为闰年的2月{return 29;}return MonthDay[month - 1];}//运算符重载bool operator==(const Date& d) const//判断日期是否相同{return 	_year == d._year && _month == d._month && _day == d._day;}bool operator!=(const Date& d) const//判断日期是否不相同{return 	!(*this == d);}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 (*this < d) || (*this == d);}bool operator>(const Date& d) const//判断当前日期是否在传入日期之后{return !(*this <= d);}bool operator>=(const Date& d) const//判断当前日期是否在传入日期之后或相同{return !(*this < d);}private:int _year;int _month;int _day;
};ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "/" << d._month << "/" << d._day << endl;return out;
}// 函数模板 -- 参数匹配
template<class T>
bool Less(const T x, const T y)
{return x < y;
}int main()
{std::cout << Less(Date(2022, 9, 27), Date(2022, 9, 25)) << endl;//Date类型比较std::cout << Less(new Date(2022, 10, 27), new Date(2022, 10, 25)) << endl;//Date指针类型比较return 0;
}

比较结果:

因为我们在Date类中运算符重载了<的比较方法,所以对于Date类可以直接调用该方法进行比较,但是对于Date*类型,模版找不到比较方法,只能默认比较地址空间的大小,导致了结果的错误

为了解决该问题,我们引入模版的特化:

2.2 函数模版的特化

函数模板的特化步骤:

1.必须要先有一个基础的函数模板

2.关键字template后面接一对空的尖括号<>

3.函数名后跟一对尖括号,尖括号中指定需要特化的类型

4.函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误

下面我们对上面的模版函数进行指针的特例化:

// 函数模板 -- 参数匹配
template<class T>
bool Less(const T x, const T y)
{return x < y;
}// 对Less函数模板进行特化
template<>
bool Less<Date*>(Date* x, Date* y)
{return *x < *y;
}

这下结果就正确了: 

该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时要特别给出,因此函数模板不建议特化,平时我们使用实例化解决即可

2.3 类模版的特化

2.3.1 全特化

我们来设置一个类模版:

// 类模板 -- 参数匹配
template<class T>
class Less
{bool operator()(T& x, T& y){return x < y;}
};//全特化
template<>
class Less<Date*>
{bool operator()(Date* x, Date* y){return *x < *y;}
};

全特化即是将模板参数列表中所有的参数都确定化,在上述代码中我们将T类型指明为Date*类型,这种将模板参数列表全部指明为一个确定的类型的操作就是全特化

2.3.2 偏特化

偏特化就是任何针对模版参数进一步进行条件限制设计的特化版本

如下:我们将类模板参数进一步限制为指针类型,但具体为上面类型的指针并未说明:

// 类模板 -- 参数匹配
template<class T>
class Less
{bool operator()(T& x, T& y){return x < y;}
};//偏特化,对模版参数进一步进行条件限制
template<class T>
class Less<T*>
{bool operator()(T* x, T* y){return *x < *y;}
};
// 类模板 -- 参数匹配
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;
};
//一个参数偏特化为引用类型,一个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1&, T2*>
{
public:Data(const T1& d1, const T2* d2)//参数偏特化为引用类型时一定要初始化: _d1(d1){cout << "Data<T1&, T2*>" << endl;}private:const T1& _d1;T2* _d2;
};

注意:参数偏特化为引用类型时一定要初始化(使用初始化列表和缺省参数都可)

偏特化有还有另一种表现方式: 当模版参数有多个时,部分特化一部分参数

例如:

// 类模板 -- 参数匹配
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;
};

三、模板的分离编译

我们现在使用声明和实现分离的编译的方法来实现一个函数模版(将类模版的声明放在Func.h头文件中,实现代码放在Func.cpp中):

Func.h:

#pragma oncetemplate<class T>
T Add(const T& left, const T& right);

Func.cpp:

#include"Func.h"template<class T>
T Add(const T& left, const T& right)
{return left + right;
}

test.cpp:

#include"Func.h"
int main()
{int x = 5, y = 10;Add<int>(x, y);return 0;
}

我们来编译一下:

咦,怎么编译通过不了?还报了一个链接错误?

下面我们来仔细分析一下:

一份C/C++程序要运行,一般要经历一下步骤:预处理—-->编译--->汇编―—-->链接,不熟悉的同学可以看到这里:C语言程序环境_c语言程序运行环境

但是在每个cpp文件经过预处理、编译、汇编,但没有进入链接阶段之前,都是单独被编译器所编译的。这样会导致由于模版参数没有实例化,Func.cpp文件在形成汇编指令时,并没有为Add函数开辟函数栈帧(因为没有参数的实例化,系统压根就不知道要开辟多大的空间来给Add函数用),但是由于头文件的展开,使汇编会形成call指令(这时的call指令并没有填上Add函数所在的栈帧地址),所以最终形成的obj文件进入链接时,会发现Func.cpp形成的obj文件中并没有上Add函数所在的栈帧地址,此时call指令中没有实际地址导致指令无法正常执行,最后报错

3.1 解决方法

知道了上面的原理后,我们就可以得出相应的解决办法了:

1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。推荐使用这种。

2. 模板定义的位置显式实例化。但我们不能给每个类型参数都实例化一份代码吧,这样要模版参数还有什么意义呢?所以这种方法不实用,不推荐使用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/122412.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电脑怎么共享屏幕?电脑屏幕共享软件分享!

如何控制某人的电脑屏幕&#xff1f; 有时我们可能需要远程控制某人的计算机屏幕&#xff0c;例如&#xff0c;为我们的客户提供远程支持&#xff0c;远程帮助朋友或家人解决计算机问题&#xff0c;或在家中与同事完成团队合作。那么&#xff0c;电脑怎么共享屏幕&#xff…

记录--vue3实现excel文件预览和打印

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 前言 在前端开发中&#xff0c;有时候一些业务场景中&#xff0c;我们有需求要去实现excel的预览和打印功能&#xff0c;本文在vue3中如何实现Excel文件的预览和打印。 预览excel 关于实现excel文档在…

java后端返回数据给前端时去除值为空或NULL的属性、忽略某些属性

目录 一、使用场景 二、环境准备 1、引入依赖 2、实体类 三、示例 1、不返回空值 (1)方式 (2)测试 (3)说明 2、不返回部分属性 (1)方式 (2)测试 一、使用场景 在开发过程中&#xff0c;有时候需要将后端数据返回前端&#xff0c;此时有些数据为空属性不需要返回&…

海南海口大型钢结构件3D扫描全尺寸三维测量平面度平行度检测-CASAIM中科广电

高精度三维扫描技术已经在大型工件制造领域发挥着重要作用&#xff0c;特别是在质量检测环节&#xff0c;高效、高精度&#xff0c;可以轻松实现全尺寸三维测量。本期&#xff0c;CASAIM要分享的应用是在大型钢结构件的关键部位尺寸及形位公差检测。 钢结构件&#xff0c;是将…

LabVIEW更改图像特定部分的颜色

LabVIEW更改图像特定部分的颜色 在随附的照片中&#xff0c;想将包围的部分更改为黄色。该怎么做&#xff1f;或者如何将图像的蓝色部分更改为绿色。 绘制拼合像素图不接受数组或输出数组。如果需要有关函数的更多信息&#xff0c;请按 CTRL H 打开上下文帮助&#xff0c;或单…

python下拉框选择测试

把下拉选择的值得打印出来&#xff1a; import tkinter as tk def on_select(event): # 当选择下拉框中的一项时&#xff0c;此函数将被调用 selected event.widget.cget("text") # 获取选中的文本 print(f"You selected: {selected}") # 打印选中…

【机器学习可解释性】3.部分依赖图

机器学习可解释性 1.模型洞察的价值2.特征重要性排列3.部分依赖图4.SHAP Value5.SHAP Value 高级使用 正文 每个特征怎么样影响预测结果&#xff1f; 部分依赖图 Partial Dependence Plots 虽然特征重要性显示了哪些变量对预测影响最大&#xff0c;但部分依赖图显示了特征如…

OpenHarmony docker环境搭建所见的问题和解决

【摘要】OpenHarmony docker环境搭建需要一台安装Ubuntu的虚拟机&#xff0c;并且虚拟机中需要有VScode。 整个搭建流程请参考这篇博客&#xff1a;OpenHarmony docker环境搭建-云社区-华为云 (huaweicloud.com) 上篇博主是用Ubuntu的服务器进行环境搭建的&#xff0c;在使用VS…

深度学习之基于yolov8的安全帽检测系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、基于yolov8的安全帽检测系统四. 总结 一项目简介 在企业作业和工地施工过程中&#xff0c;安全永远高于一切。众所周知&#xff0c;工人在进入…

matlab创建矩阵、理解三维矩阵

1.创建矩阵 全0矩阵&#xff1a;a zeros(2,3,4) 全1矩阵&#xff1a;a ones(2,3,4) &#xff01;和python不一样的地方&#xff01;此处相当于创建了4页2行3列的矩阵&#xff0c;而在python里是2页3行4列。 对第1页的第2行第3列元素进行修改&#xff1a;

WebDAV之π-Disk派盘 + 言叶

言叶是一个功能丰富的笔记软件,为跨平台而设计,可以为你在手机、电脑和其他设备中实现多端同步。从而实现高效率的记事和办公。支持Markdown的语言和多种计算机语法高亮功能,让你笔记中的内容更加主次分明,可以在这里记录一些代码什么的。同时还可以在笔记中插入图片,使其…

ab压力测试

标题相关概念 QPS&#xff0c;每秒查询 QPS&#xff1a;Queries Per Second意思是“每秒查询率”&#xff0c;是一台服务器每秒能够相应的查询次数&#xff0c;是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。 互联网中&#xff0c;作为域名系统服务器的机…

JavaScript_对象_Function_定义与参数

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Function对象</title><script>/*** Function&#xff1a;函数&#xff08;方法&#xff09;对象* 1.创建&#xff1a;* 1.…

六零导航页SQL注入漏洞复现(CVE-2023-45951)

0x01 产品简介 LyLme Spage&#xff08;六零导航页&#xff09;是中国六零&#xff08;LyLme&#xff09;开源的一个导航页面。致力于简洁高效无广告的上网导航和搜索入口&#xff0c;支持后台添加链接、自定义搜索引擎&#xff0c;沉淀最具价值链接&#xff0c;全站无商业推广…

如何在忘记手机密码或图案时重置 Android 手机?

忘记手机密码或图案是 Android 用户一生中不得不面对的最令人沮丧的事情之一。恢复 Android 设备的唯一方法是在 Android 设备上恢复出厂设置。但许多用户不使用此方法&#xff0c;因为此过程会擦除您设备上可用的所有个人数据。 但是&#xff0c;有一种方法可以在不丢失任何数…

PHP聊天系统源码 在线聊天系统网站源码 后台自适应PC与移动端

这个源码提供了前台和后台的自适应布局&#xff0c;可以在PC和移动端上完美展示。它支持一对多的交流&#xff0c;用户可以自由地创建新的房间并解散已创建的房间。 该程序还集成了签到功能和等级功能&#xff0c;让用户享受更多的互动乐趣。房间创建者具有禁言和拉黑用户的权…

苹果cms模板MXone V10.7魔改版源码 全开源

苹果cms模板MXone V10.7魔改版源码 全开源 苹果cms模板MXone魔改版短视大气海报样式 安装模板教程说明&#xff1a; 1、将模板压缩包上传到苹果CMS程序/template下解压 2、网站模板选择mxone 模板目录填写html 3、网站模板选择好之后一定要先访问前台&#xff0c;然后再进…

美术如何创建 skybox 贴图资源?

文章目录 目的PS手绘Panorama To CubemapPS手绘Pano2VRSkybox & Cubemap Tutorial (Maya & Photoshop)Unity 中使用 ReflectionProbe 生成 Cubemap 然后再 PS 调整PS直接手绘 cubemapBlender 导入 Panorama&#xff0c;然后烘焙到 cubemap&#xff0c;再导入unity中使用…

♥ uniapp 环境搭建

♥ uniapp 环境搭建 开发uniapp需要用到的工具有两个&#xff1a; 1、用到的平台和地址&#xff1a; 需要了解的几个平台以及地址&#xff1a; &#xff08;1&#xff09;微信公众平台 https://mp.weixin.qq.com/ &#xff08;2&#xff09;微信开发文档 https://develo…

【Qt之控件QTreeView】设置单元格高度、设置图标尺寸

设置列宽 设置高度 自定义代理 继承QItemDelegate&#xff0c;实现sizeHint ()方法&#xff0c;设置自定义委托。 class itemDelegate : public QItemDelegate {Q_OBJECTpublic:explicit itemDelegate(QObject *parent 0) : QItemDelegate(parent){}~itemDelegate(){}virtua…