【掌握C++模板进阶】:高级编程的艺术

在这里插入图片描述

📃博客主页: 小镇敲码人
💚代码仓库,欢迎访问
🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏
🌏 任尔江湖满血骨,我自踏雪寻梅香。 万千浮云遮碧月,独傲天下百坚强。 男儿应有龙腾志,盖世一意转洪荒。 莫使此生无痕度,终归人间一捧黄。🍎🍎🍎
❤️ 什么?你问我答案,少年你看,下一个十年又来了 💞 💞 💞

【掌握C++模板进阶】:高级编程的艺术

  • ♊️ 非类型模板参数
  • ♊️ 模板的特化
    • 🕐 概念
    • 🕐 函数模板
      • 🤾🏻 问题的引入
      • 🤾🏻 问题的解决--函数模板的特化
    • 🕐 类模板的特化
      • 🤾🏻 全特化
      • 🤾🏻 偏特化
  • ♊️ 模板的分离编译问题
    • 🕐 什么是分离编译
    • 🕐 模板的分离编译问题
      • 🤾🏻 找不到函数地址的原因
      • 🤾🏻 解决办法

♊️ 非类型模板参数

我们在模板初阶的时候介绍到:模板是用于传类型的,其实模板还可以传非类型的参数,比如一个值(整形)。可以将其和函数传参的形参和实参联系起来理解,下面我们用一段代码来演示一下,非类型模板参数的使用:

// 定义一个预处理指令,用于在编译时忽略与安全性相关的警告。  
// 这通常用于Visual Studio编译器中,以避免在使用某些旧的C库函数(如strcpy, sprintf等)时出现的警告。  
#define  _CRT_SECURE_NO_WARNINGS 1  
  
// 引入所需的头文件  
#include<iostream>       // 用于输入输出流操作,如cout  
#include<vector>         // 引入STL中的动态数组模板  
#include<map>            // 引入STL中的关联容器模板,用于存储键值对  
#include<string>         // 引入STL中的字符串类  
#include<stack>          // 引入STL中的栈  
  
// 使用std命名空间,这样我们就可以直接使用其中的名称(如cout、vector等),而无需前缀std::  
using namespace std;  
  
// 定义一个模板类A,该类接受一个非类型模板参数n(一个size_t类型的常量表达式)  
template<size_t n>  
class A  
{  
public:  
	// 类的构造函数,用于初始化对象  
	A()  
	{  
		// 使用memset函数将数组a的所有元素初始化为0  
		// sizeof(int) * n计算数组a的总字节数  
		memset(a, 0, sizeof(int) * n);  
	}  
  
	// 一个成员函数,用于打印数组a的所有元素  
	void PrintArr()  
	{  
		// 使用for循环遍历数组a  
		for (int i = 0; i < n; ++i)  
			// 输出数组a的当前元素,并在每个元素后添加一个空格  
			cout << a[i] << " ";  
  
		// 输出一个换行符,使输出更整洁  
		cout << endl;  
	}  
  
private:  
	// 类的私有成员,一个大小为n的整数数组  
	int a[n];  
};  
  
// 主函数,程序的入口点  
int main()  
{  
	// 创建一个A类的对象x,其中n被指定为12(即数组a的大小为12)  
	A<12> x;  
	// 调用x的PrintArr函数来打印数组a的所有元素(此时所有元素都是0)  
	x.PrintArr();  
	// 返回0,表示程序正常退出  
	return 0;  
}

运行结果:

在这里插入图片描述
也可以给缺省值,就可以什么都不传,一样可以运行成功。

在这里插入图片描述

运行结果:

在这里插入图片描述

  • 注意,这里的非类型模板参数的类型只能为整形类型,像浮点型、自定义类型是不允许作为非类型的模板参数的。

非类型模板参数的值应该在编译时就被确定,而不是运行时,否则会报错,也就是说它应该是一个常量。

在这里插入图片描述

♊️ 模板的特化

🕐 概念

模板的特化我们可以理解为一种模板的特殊处理,因为可能普通的模板函数或者模板无法满足我们传的这个类型的要完成的功能,需要自己特化一个出来。

🕐 函数模板

🤾🏻 问题的引入

看下面的代码,让你更加直观的感受模板特化所要解决的问题:

// 定义一个预处理指令,用于在编译时忽略与安全性相关的警告。  
// 这通常用于Visual Studio编译器中,以避免在使用某些旧的C库函数(如strcpy, sprintf等)时出现的警告。  
#define  _CRT_SECURE_NO_WARNINGS 1  
  
// 引入所需的头文件  
#include<iostream>       // 用于输入输出流操作,如cout  
#include<vector>         // 引入STL中的动态数组模板  
#include<map>            // 引入STL中的关联容器模板,用于存储键值对  
#include<string>         // 引入STL中的字符串类  
#include<stack>          // 引入STL中的栈  
  
// 使用std命名空间,这样我们就可以直接使用其中的名称(如cout、vector等),而无需前缀std::  
using namespace std;  

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 Greater(const T& left, const T& right)
{
	return left > right;
}

int main()
{
	int a, b;//创建两个整形变量
	a = 1;
	b = 2;

	double c, d;//创建两个浮点数变量
	c = 1.1;
	d = 2.3;

	char e, f;//创建两个char型变量
	e = 'c';
	f = 'a';

	//创建两个Date* 变量
	Date* d1 = new Date(1983, 1, 1);
	Date* d2 = new Date(1980, 1, 1);

	//分别调用比较函数比较大小
	cout << Greater(a, b) << endl;
	cout << Greater(c, d) << endl;
	cout << Greater(e, f) << endl;
	cout << Greater(d1,d2) << endl;
	
	return 0;
}

运行结果:

在这里插入图片描述
可以看到比较非自定义类型的时候,这个模板函数都没有问题,问题就出现了比较自定义类型的指针类型时,我们的本意是希望它去调用自定类型的operator >,但是它是直接比较的指针地址的大小。这个时候就需要我们使用模板特化来解决问题。

🤾🏻 问题的解决–函数模板的特化

改进后的代码(只给出了关键部分):

template<class T>
bool Greater(T left,T right)
{
	return left > right;
}

template<>
bool Greater<Date*> (Date* left,Date* right)
{
	return *left > *right;
}

这里特化的语法要注意:

  1. 必须先要有一个函数模板。
  2. 特化的函数前面要加template<>,在函数名后面加<>里面指定需要特化的类型。
  3. 模板函数的参数与其对应的特化函数的参数类型要一致(&)。

如果遇见函数模板特化不能满足要求的情况,可以将那个函数直接给出(将函数直接给出的可读性高,所以一般建议直接给出,而不走函数模板的特化)。

特化函数的调用规则:如果实参的类型和模板特化函数的形参类型一致就会去调用,否则会去走普通模板函数,不会特化(包括修饰符const&也要一致)。

运行结果:

在这里插入图片描述

🕐 类模板的特化

🤾🏻 全特化

全特化是将类的模板参数全部给出。

// 定义一个预处理指令,用于在编译时忽略与安全性相关的警告。  
// 这通常用于Visual Studio编译器中,以避免在使用某些旧的C库函数(如strcpy, sprintf等)时出现的警告。  
#define  _CRT_SECURE_NO_WARNINGS 1  
  
// 引入所需的头文件  
#include<iostream>       // 用于输入输出流操作,如cout  
#include<vector>         // 引入STL中的动态数组模板  
#include<map>            // 引入STL中的关联容器模板,用于存储键值对  
#include<string>         // 引入STL中的字符串类  
#include<stack>          // 引入STL中的栈  
  
// 使用std命名空间,这样我们就可以直接使用其中的名称(如cout、vector等),而无需前缀std::  
using namespace std;  

class A
{
public:
	A()
	{
		cout << "A<T1,T2>" << endl;
	}
private:
	T1 t1;
	T2 t2;
};

template<>
class A<int,char>
{
public:
	A()
	{
		cout << "A<int,char>" << endl;
	}
private:
	int t1;
	char t2;
};

int main()
{
	A<int, int> a;
	A<int, char> b;
	return 0;
}

运行结果:

在这里插入图片描述

🤾🏻 偏特化

类的偏特化有两种,一种是类的模板参数部分特化。另一种是将参数进一步限制,比入加上修饰符*const&

代码演示:

// 定义一个预处理指令,用于在编译时忽略与安全性相关的警告。  
// 这通常用于Visual Studio编译器中,以避免在使用某些旧的C库函数(如strcpy, sprintf等)时出现的警告。  
#define  _CRT_SECURE_NO_WARNINGS 1  
  
// 引入所需的头文件  
#include<iostream>       // 用于输入输出流操作,如cout  
#include<vector>         // 引入STL中的动态数组模板  
#include<map>            // 引入STL中的关联容器模板,用于存储键值对  
#include<string>         // 引入STL中的字符串类  
#include<stack>          // 引入STL中的栈  
  
// 使用std命名空间,这样我们就可以直接使用其中的名称(如cout、vector等),而无需前缀std::  
using namespace std;  

template<class T1, class T2>
class A
{
public:
	A()
	{
		cout << "A<T1,T2>" << endl;
	}
private:
	T1 t1;
	T2 t2;
};

template<class T1>//将第二个参数特化为char类型的
class A<T1,char>
{
public:
	A()
	{
		cout << "A<T1,char>" << endl;
	}
private:
	T1 t1;
	char t2;
};

template<class T1,class T2>
class A<const T1,const T2>
{
public:
	A()
	{
		cout << "A<const T1,const T2>" << endl;
	}
private:
	T1 t1;
	T2 t2;
};

template<class T1, class T2>
class A<T1*,T2*>
{
public:
	A()
	{
		cout << "A<T1*,T2*>" << endl;
	}
private:
	T1 t1;
	T2 t2;
};

template<class T1, class T2>
class A<T1&, T2&>
{
public:
	A()
	{
		cout << "A<T1&,T2&>" << endl;
	}
private:
	T1 t1;
	T2 t2;
};

int main()
{
	A<int, int> a;//走普通的模板
	A<int, char> b;//走指定第二个参数为char的模板
	A<double, char> c;//走指定第二个参数为char的模板
	A<int*, char*> d;//走加了指针限制的模板
	A<int&, int&> e;//走加了引用限制的模板
	A<const int, const int> f;//走加了const限制的模板
	return 0;
}

运行结果:

在这里插入图片描述

全特化指定了具体的类型,但是偏特化的第二种可以加修饰符的方式更加常用,这样如果是指针类型使用偏特化一份代码就全部解决了,但是全特化得写很份代码。

  • 当一个类模板既有全特化、偏特化、正常模板时,调用顺序是全特化 、偏特化、正常模板(前提得满足要求能够调用)。

♊️ 模板的分离编译问题

🕐 什么是分离编译

一个程序由很多源文件组成,要将所有的源文件单独编译生成目标文件,最后再将这些目标文件链接起来生成单一可执行文件的过程叫做分离编译。

🕐 模板的分离编译问题

假设我们现在要写一个加法的函数,但是这个加法函数是函数模板,我们将其声明和定义分别放在头文件(.hpp/.hp)和源文件(.cpp)。

.h中放的:

template<class T1,class T2>

int Add(T1 a, T2 b);

.cpp中放的:

#include"Add.h"

template<class T1, class T2>
int Add(T1 a, T2 b)
{
	return a + b;
}

main.cpp:

#include"Add.h"

int main()
{
	cout << Add(1, 3) << endl;
	return 0;
}

报错结果:

在这里插入图片描述

很奇怪,报的是链接错误,找不到函数地址。我们正常函数声明和定义放在两个文件里面就是这样来做的呀,为什么到函数模板这里就不行了呢,我们来画图分析原因:

🤾🏻 找不到函数地址的原因

在这里插入图片描述

🤾🏻 解决办法

  1. 将模板加法函数的声明和实现放在一个.h/.hpp文件中。(常用做法)

在这里插入图片描述
程序成功运行:

在这里插入图片描述

  1. 显示实例化
    Add.cpp中加上两行代码即可。
#include"Add.h"



template<class T1, class T2>
int Add(T1 a,T2 b)
{
	cout << "Add(T1 a,T2 b)" << endl;
	return a + b;
}

template
int Add<int,int>(int ,int);

template
int Add<int,int>(int ,int);会告诉编译器让它生成这个实例化的函数。但是其它类型的Add函数不会生成。

运行结果:

在这里插入图片描述

所以这种方式并不常用,太单一,一般使用第一种较多。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【FreeRTOS】创建任务-声光色影

参考《FreeRTOS入门与工程实践(基于DshanMCU-103).pdf》 目录 1 基本概念2 任务创建与删除2.1 什么是任务2.2 创建分配内存2.2.1 动态任务2.2.1 静态分配内存 2.3 示例1: 创建任务2.3.1 声2.3.1.1 music.c2.3.1.2 music.h2.3.1.4 硬件接线 2.3.2 光2.3.3 色2.3.4 影 在本章中&a…

海南云亿商务咨询有限公司解锁抖音电商新纪元

在当今数字化浪潮中&#xff0c;抖音电商以其独特的魅力和强大的用户基础&#xff0c;迅速成为企业营销的新宠。海南云亿商务咨询有限公司&#xff0c;作为专注于抖音电商服务的领先企业&#xff0c;凭借专业的团队和丰富的经验&#xff0c;为众多企业提供了高效、精准的电商服…

试乘试驾预约小程序源码系统 前后端分离 带完整的代码包+搭建教程

系统概述 试乘试驾预约小程序源码系统是一款专为汽车行业打造的便捷工具。它采用前后端分离的架构&#xff0c;确保了系统的稳定性和高效性。通过这套源码系统&#xff0c;用户可以轻松搭建自己的试乘试驾预约平台&#xff0c;为汽车经销商和消费者提供更好的服务体验。 代码…

手把手教程本地调试Datax

背景&#xff1a;使用Datax做数仓同步数据得工具&#xff0c;有时需要自己开发或者修改某个reader或writer插件&#xff0c;那么本地调试就很重要。 一. 下载 从GitHub上下载或者clone下来Datax项目。 https://github.com/alibaba/DataX 找到Core模块&#xff0c;运行入口就…

个人商业模式画布 | 10分钟+6张图,帮你重新定位个人发展!

在个性化的时代浪潮中&#xff0c;构建个人IP成为了提升个人影响力的黄金通道。之前分享过企业的商业模式画布&#xff0c;很受大家喜欢&#xff0c;今天我们分享个人商业模式画布&#xff0c;它适用于个人发展&#xff0c;可以帮助你有效地打造个人品牌&#xff0c;重塑你的职…

java如何预防sql注入

1 sql注入 1.1 使用字符串拼接导致数据异常 sql语句拼接 // 构建SQL查询语句&#xff0c;注意这里存在SQL注入风险String sql "select name,age from user where name" username " and password " password "";System.out.println("s…

Stable diffusion3效果比midjourney强很多吗,未来会开源吗?

经过一个多月的期待&#xff0c;Stable Diffusion 3&#xff08;SD3&#xff09; 终于向会员开放了API接口&#xff0c;尽管每个用户的使用配额相对有限&#xff0c;据用户反馈&#xff0c;生成六张图像便可能耗尽配额。 SD曾依靠开源策略与竞争对手抗衡&#xff0c;但目前似…

和鲸科技携手浙江大学地球科学学院,助推地球科学研究范式变革

近日&#xff0c;浙江省资源与环境信息系统重点实验室&#xff08;下简称“实验室&#xff09;与上海和今信息科技有限公司&#xff08;下简称“和鲸科技”&#xff09;签订合作框架协议&#xff0c;双方将以助推“数据算力模型科研场景”的地球科学研究范式变革&#xff0c;孕…

kettle从入门到精通 第六十九课 ETL之kettle kettle cdc mysql,轻松实现增量同步

1、之前kettle cdc mysql的时候使用的方案是canalkafkakettle&#xff0c;今天我们一起学习下使用kettle的插件Debezium直接cdc mysql。 注&#xff1a;CDC (Change Data Capture) 是一种技术&#xff0c;用于捕获和同步数据库中的更改。 1&#xff09;Debezium步骤解析mysql b…

【PyTorch 新手基础】Regularization -- 减轻过拟合 overfitting

Overfit 过拟合&#xff0c;效果如最右图所示 常见应对方案如下&#xff1a; 增大数据集入手&#xff1a;More data or data argumentation简化模型参数入手&#xff1a;Constraint model complexity (shallow model, regularization) or dropout dropout: torch.nn.Dropout(0…

沉睡而且“狡猾”的特工:大模型也可以是!

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则提…

跳舞电动机器人单片机方案

这款机器人形状智能电子玩具是一款集娱乐、教育和互动于一身的高科技产品。它的主要功能包括&#xff1a; 1、智能对话&#xff1a;机器人可以进行简单的对话&#xff0c;回答用户的问题&#xff0c;提供有趣的互动体验。 2、前进、后退、左转、右转、滑行&#xff1a;机器人…

Flink作业执行之 2.算子 StreamOperator

Flink作业执行之 2.算子 StreamOperator 前文介绍了Transformation创建过程&#xff0c;大多数情况下通过UDF完成DataStream转换中&#xff0c;生成的Transformation实例中&#xff0c;核心逻辑是封装了SimpleOperatorFactory实例。 UDF场景下&#xff0c;DataStream到Transf…

分布式高性能计算 (HPC)的工作负载管理平台和作业调度程序—— IBM Spectrum® LSF® Suites

IBM Spectrum LSF Suites 是面向分布式高性能计算 (HPC) 的工作负载管理平台和作业调度程序。基于 Terraform 的自动化现已可用&#xff0c;该功能可在 IBM Cloud 上为基于 IBM Spectrum LSF 的集群供应和配置资源。 借助我们针对任务关键型 HPC 环境的集成解决方案&#xff0…

uni-app利用renderjs实现安卓App上jssip+freeswitch+webrtc音视频通话功能

效果图 前置知识 利用renderjs在app端加载for web库 JsSIPFreeSwitchVue实现WebRtc音视频通话 原始模块 <template><viewclass"test-sip":userExtension"userExtension":change:userExtension"JsSIP.handleUserExtenSionChange":tar…

1+x(Java)中级题库易混淆理论题(五)

Java 语言具有许多优点和特点&#xff0c;多线性反映了 Java 程序并行机制的特点 字符流与字节流的区别在于每次读写的字节数不同 如果需要从文件中读取数据&#xff0c;则可以在程序中创建FileInputStream的对象 void 的含义是方法没有返回值 设 x1&#xff0c;y2&#xf…

SpringBoot集成slf4j日志配置

目录 前言 1、slf4j概述 2、pom.xml的日志依赖 3、application.yml的日志配置 4、logback.xml配置文件定义 5、logback.xml配置文件解析 5.1 定义日志的存储路径 5.2 定义日志的输出格式 5.3 定义控制台输出 5.4 定义日志相关参数 5.5 定义日志的输出级别 6、测试日…

AI大模型时代:一线大厂为何竞相高薪招揽AI产品经理?

前言 在当今日新月异的科技浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;技术已经渗透至各行各业&#xff0c;成为推动社会进步的重要力量。在这样的背景下&#xff0c;AI产品经理这一新兴职位逐渐崭露头角&#xff0c;成为各大企业竞相争夺的稀缺人才。那么&#xf…

​带三维重建和还原的PACS源码 医院PACS系统源码 PACS系统源码医院PACS系统源码 C/S架构 带三维重建和还原​

带三维重建和还原的PACS源码 医院PACS系统源码 PACS系统源码医院PACS系统源码 C/S架构 带三维重建和还原 ​ 主要的任务就是把日常产生的各种医学影像&#xff08;包括核磁&#xff0c;CT&#xff0c;超声&#xff0c;各种X光机&#xff0c;各种红外仪、显微仪等设备产生的图…

cleanmymacX和腾讯柠檬到底哪个好用 2024最新使用测评

CleanMyMac X和腾讯柠檬都是Mac系统清理软件&#xff0c;各有其特点和优势&#xff0c;选择哪个更好用取决于用户的具体需求和使用习惯。 经常有新关注的粉丝问&#xff0c;同样做为垃圾清理软件&#xff0c;付费CleanMyMac和免费的柠檬清理哪个更好用&#xff1f;其实&#xf…