C++ 模板进阶知识

news2024/11/27 4:22:41

目录

一. 非类型模板参数

与类型模板参数确认的区别

何时确认

确认方式

二. 模板的特化

1. 概念

2. 函数模板特化

3. 类模板特化

3.1 全特化

3.2 偏特化

(1). 部分特化

(2). 进一步限制

4. 实际应用

三. 模板分离编译

1. 概念

 2. 模板的分离编译

3. 解决方法

四. 优缺点总结

1. 优点

2. 缺点


一. 非类型模板参数

模板参数分为 类型形参与非类型形参。

1. 类型形参

即为出现在模板参数列表中,跟在class或者typename之后的参数类型名称

2. 非类型形参

就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用

 如下代码

#include<iostream>
#include<algorithm>

using namespace std;
namespace Pc
{
	template<class T,size_t N=20,int X=1111>
	class array
	{
	public:
		void printstatic()
		{
			cout << "N  " << N << endl;
			cout << "X  " << X << endl;
		}
		T& operator[](size_t key)
		{
			return _array[key];
		}
		const T& operator[](size_t key) const
		{
			return _array[key];
		}
		size_t size() const
		{
			return _size;
		}
		bool empty() const
		{
			return _size == 0;
		}
	private:
		T _array[N];
		size_t _size;
	};
}

int main()
{
	Pc::array<int,66,10> a1;
	a1.printstatic();
	Pc::array<int> a2;
	a2.printstatic();
	Pc::array<int, 88, 22> a3;
	a3.printstatic();
	return 0;
}

输出结果为

需要注意

1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。

2. 非类型的模板参数必须在编译期就可以确认结果

如以下代码

	template<float X=1.11,array<int> A1,string S>
	void test()
	{

	}

编译结果如下

 直接就通过编译发现问题了

与类型模板参数确认的区别
何时确认

1. 类型模板参数在模板实例化后才确认,即根据提供的参数类型来确定模板中类型参数的具体方式。

2. 非类型的模板参数必须在编译时就可以确认结果,因为非类型模板参数作为模板定义中的一个常量表达式,其值必须在编译时已知,便于编译器生成相应代码

确认方式

1. 类型的模板参数: 函数模板是通过传参来推导,类模板是通过显式指定

2. 非类型的模板参数: 通过一个显示指定一个编译时常量表达式来确定。这个常量表达式可以是整形字面量、,枚举常量、全局或静态const整形变量等。不能是浮点数、类对象、字符串等类型,因为这些类型无法在编译时确定其值或表示形式

二. 模板的特化

1. 概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门来进行小于比较的函数模板

#include<iostream>
#include<algorithm>

using namespace std;

template<class T>
bool numless(T num1,T num2)
{
	return num1 < num2;
}

int main()
{
	cout << numless(3, 6) << endl;
	cout << numless(1.3, 1.6) << endl;

	int a = 11,b=10;
	int* pa = &a;
	int* pb = &b;
	cout << numless(a, b) << endl;
	cout << numless(pa, pb) << endl;

	return 0;
}

输出结果如下

可以看到我们的numless绝大多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。

我们上方代码所演示的a与b的比较正确,但是pa与pb并没有比较pa与pb所指向的内容而是比较pa与pb指针的地址,所以无法达到我们的预期

这个时候就需要对模板进行特化。即:在原模板基础上,针对特殊类型所进行特殊化的实现方式。模板特化分为函数模板特化与类模板特化

2. 函数模板特化

函数模板特化的步骤

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

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

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

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

 如以下代码

#include<iostream>
#include<algorithm>

using namespace std;

template<class T>
bool numless(T num1,T num2)
{
	return num1 < num2;
}
template<>
bool numless<int*>(int* pa, int* pb)
{
	return *pa < *pb;
}
int main()
{
	int a = 11,b=10;
	int* pa = &a;
	int* pb = &b;
	cout << numless(a, b) << endl;
	cout << numless(pa, pb) << endl;

	cout << endl;

	int c = 66, d = 77;
	int* pc = &c, * pd = &d;
	cout << numless(c, d)<<endl;
	cout << numless(pc, pd) << endl;

	return 0;
}

 输出结果为

比较int* 指针时调用模板特化其余正常调用模板

但是我们一般情况下函数模板遇到不能处理或者是处理有误的类型,为了简单实现通常都是直接通过函数给出

bool numless(int* pa, int* pb)
{
	return *pa < *pb;
}

 这样简单明了,代码可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化

3. 类模板特化
3.1 全特化

全特化即是将模板参数列表中所有参数都确定化。

如以下代码

#include<iostream>
#include<algorithm>

using namespace std;
template<class T1, class T2>
class Test
{
public:
	Test()
	{
		cout << "Test<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};
template<>
class Test<int, char>
{
public:
	Test() 
	{
		cout << "Test<int, char>" << endl;
	}
private:
	int _t1;
	char _t2;
};
void TestVector()
{
	Test<int, int> t1;
	Test<int, char> t2;
}

int main()
{
	TestVector();
	return 0;
}

上述代码是:如果第一个参数是int第二个参数是char就走特化,其余都走模板生成

输出结果为

 验证了我们的说法

3.2 偏特化

偏特化: 针对模板参数进一步进行条件限制设计的特化版本。比如对下方模板

template<class T1, class T2>
class Test
{
public:
	Test()
	{
		cout << "Test<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

偏特化有两种表现方式:

(1). 部分特化

将模板参数列表中的一部分参数特化。

template<class T1>
class Test<T1, int>
{
public:
	Test()
	{
		cout << "Test<T1, int>" << endl;
	}
private:
	T1 _d1;
	int _d2;
};
(2). 进一步限制

偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。

template<class T1 ,class T2>
class Test<T1*, T2*>
{
public:
	Test()
	{
		cout << "Test<T1*, T2*>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};
template<class T1, class T2>
class Test<T1&, T2&>
{
public:
	Test()
	{
		cout << "Test<T1&, T2&>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

分别为将两个参数偏特化为指针类型

和将两个参数偏特化为引用类型

4. 实际应用

假设我们实现了一个日期类使用仿函数来将其进行比较,但是依然对指针比较不一定保持正确

可以进行一下特化来处理问题

template<class T>
struct Less
{
	bool operator()(const T& x1,const T& x2)
	{
		return x1 < x2;
	}
};
template<>
struct Less<int*>
{
	bool operator() (const int* x1, const int* x2)
	{
		return *x1 < *x2;
	}
};
//template<typename T>
//struct Less<T*>
//{
//	bool operator() (const T* x1, const T* x2)
//	{
//		return *x1 < *x2;
//	}
//};

三. 模板分离编译

1. 概念

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

 2. 模板的分离编译

假如模板声明与定义分开了,头文件中进行声明Add.h

//头文件
template<class T>
T Add(const T& left, const T& right);

Add.cpp中完成定义

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

 test.cpp中调用

#include"add.h"

int main()
{
    Add(1, 2);
    Add(2.1, 1.2);
    return 0;
}

C\C++程序要运行,一般要经历以下步骤:

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

编译:对对程序按照语言特性进行词法、语法、语义分析,错误检查无误后生成汇编代码

链接:将多个obj文件合成一个,并处理没有解决的地址问题

obj文件

程序编译时生成的中间代码文件。 目标文件,一般是程序编译后的二进制文件,再通过链接器 (LINK.EXE)和资源文件链接就成可执行文件了。

我们上面的写法,在源文件中编译器没有看到对Add模板函数的实例化,因此不会生成具体的加法函数(就没有地址)

在main.obj中调用的Add<int>与Add<double>,编译器在链接时才会找其地址,但是这个两个函数没有实例化没有生成具体代码,因此链接时报错。

3. 解决方法

1. 将声明与定义放到一个文件"xxx.cpp"里 或者 直接在头文件里定义也可以

直接写定义到头文件

在.h头文件里就不是声明了,在编译时就会实例化成对应函数,就有对应的地址可以call过去

2. 模板定义的位置显式实例化。这种方法不实用,不推荐使 用

在Add.cpp里

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

template
int Add(const int&left , const int& right);

template
float Add(const float& left, const float& right);

要调用什么类型就怎么样显式的写,一种类型写一个,极其麻烦

四. 优缺点总结

1. 优点

1. 模板复用了代码,节省资源,更快的迭代开发,C++标准模板库(STL)因此而生

2. 增强了代码的灵活性

2. 缺点

1. 模板会导致代码膨胀问题,也会导致编译时间变长

2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误


这篇就到这里啦

(づ ̄3 ̄)づ╭❤~

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

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

相关文章

【学习笔记】STM32F407探索者HAL库开发(一)STM32F4资源概要

【学习笔记】STM32F407探索者HAL库开发&#xff08;一&#xff09;STM32F4资源概要 1 硬件资源2 STM32命名规则3 STM32数据手册3.1 数据手册各章节内容概要3.2 引脚分布3.3 引脚定义3.4 引脚定义表的具体说明 1 硬件资源 STM32F407ZGT6具体的 内部资源如表 资源数量资源数量内…

鸿蒙Harmony实战开发:Touchscreen驱动器件硬件接口使用实例

功能简介 Touchscreen驱动用于驱动触摸屏使其正常工作&#xff0c;该驱动主要完成如下工作&#xff1a;对触摸屏驱动IC进行上电、配置硬件管脚并初始化其状态、注册中断、配置通信接口&#xff08;I2C或SPI&#xff09;、设定Input相关配置、下载及更新固件等操作。 在HDF&am…

考试:数据库系统(01)

数据库系统 ◆数据&#xff1a;是数据库中存储的基本对象&#xff0c;是描述事物的符号记录。 数据的种类&#xff1a;文本、图形、图像、音频、视频、学生的档案记录、货物的运输 情况等。 ◆数据库DB: 是长期存储在计算机内、有组织的、可共享的大量数据的集合。 ◆数据库…

安恒信息总裁宋端智,辞职了!活捉一枚新鲜出炉的餐饮人!

吉祥知识星球http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247485367&idx1&sn837891059c360ad60db7e9ac980a3321&chksmc0e47eebf793f7fdb8fcd7eed8ce29160cf79ba303b59858ba3a6660c6dac536774afb2a6330#rd 《网安面试指南》http://mp.weixin.qq.com/s?…

雅菲奥朗 FinOps 认证培训:开启企业云财务管理转型之路

前言&#xff1a; 在当今快速变化的商业环境中&#xff0c;企业面临着前所未有的IT财务挑战。随着云计算和数字化转型的推进&#xff0c;传统的财务管理方式已经不能满足“企业上云”的需求。FinOps&#xff0c;即“云财务管理”应运而生&#xff0c;成为帮助企业实现IT财务流…

Compose TextField详解

首先明确Compose TextField的底层依赖是&#xff1a; TextField BasicTextField CoreTextField 相较于Text&#xff0c;TextField需要提供可输入的能力以及渲染效果动态更新的能力。 // CompositionLocals// If the text field is disabled or read-only, we should not d…

智能菜谱推荐系统_ct3p7

TOC springboot575智能菜谱推荐系统_ct3p7--论文 第一章 概述 1.1 研究背景 近些年&#xff0c;随着中国经济发展&#xff0c;人民的生活质量逐渐提高&#xff0c;对网络的依赖性越来越高&#xff0c;通过网络处理的事务越来越多。随着智能菜谱推荐管理的常态化&#xff0c…

F1 F4 Fn lock 指示灯不亮 联想笔记本 thinkpad

问题描述&#xff1a;F1 F4 Fn lock 指示灯开机的时候亮&#xff0c;但是使用的时候虽然能够发挥正常功能&#xff0c;但是指示灯一直熄灭&#xff0c;指示灯不亮。 电脑型号&#xff1a;联想笔记本 thinkpad E14 Gen 2 。本方案应该适用于所有联想电脑。 解决方法&#xff1a;…

嵌入式和单片机有什么区别?

目录 &#xff08;1&#xff09;什么是嵌入式&#xff1f; &#xff08;2&#xff09;什么是单片机&#xff1f; &#xff08;3&#xff09;嵌入式和单片机的共同点 &#xff08;4&#xff09;嵌入式和单片机的区别 &#xff08;1&#xff09;什么是嵌入式&#xff1f; 关…

掉毛不愁!浮毛怎么去掉比较干净?这宠物空气净化器用上真能解决

这阵子天气热得让人只想宅家&#xff0c;门窗紧闭&#xff0c;空调一开就是一整天。室内凉爽宜人&#xff0c;但一出门再回来&#xff0c;那满屋的浮毛和异味简直让人措手不及&#xff0c;仿佛从天堂跌入地狱。幸好&#xff0c;我家有台宠物空气净化器这位“救星”&#xff0c;…

【Linux】线程控制|POSIX线程库|多线程创建|线程终止|等待|线程分离|线程空间布局

目录 ​编辑 POSIX线程库 多线程创建 独立栈结构 获取线程ID pthread_self 线程终止 return终止线程 pthread_exit pthread_cancel 线程等待 退出码问题 线程分离 测试 线程ID及地址空间布局 ​编辑 POSIX线程库 pthread线程库是 POSIX线程库的一部分&#xf…

MySQL运维学习(1):4种日志

1.错误日志 mysql错误日志记录了mysql发生任何严重错误时的信息&#xff0c;若数据库无法正常使用时&#xff0c;可以先查看错误日志 默认情况下错误日志是开启的&#xff0c;文件名为/var/log/mysqld.log&#xff0c;如果文件不在默认位置&#xff0c;可以通过下面的命令查看…

【Java】Junit的使用

Java系列文章目录 补充内容 Windows通过SSH连接Linux 第一章 Linux基本命令的学习与Linux历史 文章目录 Java系列文章目录一、前言二、学习内容&#xff1a;三、问题描述四、解决方案&#xff1a;4.1 Junit测试方法的使用4.2 测试规范 五、总结 一、前言 学习测试JunitMock后…

LLM如何理解图数据? Graph+LLM综述

对图推理&#xff08;RoG&#xff09;&#xff1a;忠实可解释的大语言模型推理方法&#xff08;ICLR2024&#xff09; https://github.com/RManLuo/reasoning-on-graphs 推理图&#xff08;Reasoning on Graphs, RoG&#xff09;提出了一个计划-检索-推理框架&#xff0c;该…

『基础』OS-1计算机系统概述_操作系统发展历程及它的运行环境

操作系统发展历程 常考的三种操作系统对比 批操作系统脱机使用计算机&#xff1b;作业是分批处理的&#xff1b;系统内多道程序并发执行&#xff1b;交互能力差分时操作系统多个用户同时使用计算机&#xff1b;人机交互强&#xff1b;具有每个用户独立使用计算机的独占性&…

学习大数据DAY42 hive 分桶表

目录 分桶表 分桶表注意事项 hive 分桶表-创建分桶表 hive 排序关键字 hive 排序语句 上机练习 分桶表 分区提供一个隔离数据和优化查询的便利方式。不过&#xff0c;并非所有的数据集都可形 成合理的分区。对于一张表或者分区&#xff0c;Hive 可以进一步组织成桶&…

8.21T1 草莓蛋糕(拆max + 权值线段树)

http://cplusoj.com/d/senior/p/NODSX2302A 看到式子&#xff1a; 我们就应该想到拆max 若 我们可以整理推出&#xff1a; 记&#xff1a; 由 L L L 算 C C C&#xff0c;我们满足 h a ≤ h b h_a\le h_b ha​≤hb​&#xff0c;找 c c c 的最小值 C C C 算 L L L 同…

05、Redis实战:优惠券秒杀、全局唯一ID、超买乐观悲观锁、一人一单逻辑、分布式锁、分布式锁的原子性

3、优惠卷秒杀 3.1 -全局唯一ID 每个店铺都可以发布优惠券&#xff1a; 当用户抢购时&#xff0c;就会生成订单并保存到tb_voucher_order这张表中&#xff0c;而订单表如果使用数据库自增ID就存在一些问题&#xff1a; id的规律性太明显受单表数据量的限制 场景分析&#x…

第2章-02-网页中的Document元素

🏆作者简介,黑夜开发者,CSDN领军人物,全栈领域优质创作者✌,CSDN博客专家,阿里云社区专家博主,2023年CSDN全站百大博主。 🏆数年电商行业从业经验,历任核心研发工程师,项目技术负责人。 🏆本文已收录于专栏:Web爬虫入门与实战精讲,后续完整更新内容如下。 文章…

2024思维导图工具评测:性能、易用性全面对比

现在工作日常要处理的数据纷繁复杂&#xff0c;如何高效地组织、理解和记忆这些信息&#xff0c;成为了每个人都需要面对的挑战。不知道你有没有尝试过使用思维导图软件呢&#xff1f;这次我们看看它们是如何帮助我们优化思维、提升效率的。 1.福晰思维导图 链接一下&#xf…