c++ | 模板进阶

news2024/9/24 1:26:11

  前言

本篇博客讲解c++中的模板的一些其他知识

💓 个人主页:普通young man-CSDN博客

⏩ 文章专栏:C++_普通young man的博客-CSDN博客

⏩ 本人giee:   普通小青年 (pu-tong-young-man) - Gitee.com

      若有问题 评论区见📝

🎉欢迎大家点赞👍收藏⭐文章

目录

概述

1. 非类型模板参数

2. 模板的特化

2.1 函数模板特化

2.2 类模板特化

2.2.1 全特化

2.2.2 偏特化

 类模板特化应用示例

3. 模板分离编译

故事背景

分离编译模式的应用

解决方法

总结


概述

C++模板是一种强大的功能,它允许开发者编写泛型代码,从而提高代码的重用性和灵活性。本文将探讨非类型模板参数的概念及其限制,以及模板特化的方法,包括函数模板特化和类模板特化。

1. 非类型模板参数

非类型模板参数是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。非类型模板参数必须在编译期就能确认结果,并且有一些限制,例如:

  • 浮点数、类对象以及字符串不允许作为非类型模板参数。
  • 非类型的模板参数必须在编译期就能确认结果。
template<class T,size_t size = 10>
int add(T a1) {
	return a1 + size;

}


template<class T,size_t N = 10>
class MyClass
{
public:
	MyClass(const T& val)
		:arr[0] = val;
	{}
private:
	
	int arr[N];
};

void test1() {
	//cout << add<>(10) << endl;
	MyClass<int> s1(10);
	
}
int main() {
	test1();
	return 0;
}

2. 模板的特化

模板特化是在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化分为函数模板特化与类模板特化。

2.1 函数模板特化

函数模板特化是当函数模板在处理某些特定类型时可能无法得到预期结果的情况下使用的。特化过程包括以下几个步骤:

  1. 必须要先有一个基础的函数模板。
  2. 使用关键字 template 后面接一对空的尖括号 <>
  3. 在函数名后跟一对尖括号,尖括号中指定需要特化的类型。
  4. 函数形参表必须与模板函数的基础参数类型完全相同。
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)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}

private:
	int _year;
	int _month;
	int _day;
};

class DateLess
{
public:
	//bool operator()(Date* p1, Date* p2)
	//{
	//	return *p1 < *p2;
	//}
};


//模板特化
template<class T>
bool lessfunc(const T& left, const  T& right) {
	return left < right;
}

//特化--不推荐
//template<>
//bool lessfunc<Date*>(Date* const& left, Date* const& right)
//{
//	return *left < *right;
//}
//
//template<>
//bool lessfunc<const Date*>(const Date* const& left,const Date* const& right)
//{
//	return *left < *right;
//}


//推荐直接写成函数
bool lessfunc(Date* p1, Date* p2) {
	return *p1 < *p2;
}
bool lessfunc(const Date* p1,const Date* p2) {
	return *p1 < *p2;
}

void test2() {
	//cout << lessfunc(1, 2) << endl;
	//int a = 100, b = 20;
	//int* pa = &a;
	//int* pb = &b;
	//cout << lessfunc(pa,pb) << endl;//比较错误

	Date d1(2025, 7, 20);
	Date d2(2022, 7, 8);
	cout << lessfunc(d1, d2) << endl;

	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << lessfunc(p1, p2) << endl;//比较错误


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

这里为什么不推荐写成函数特化,就是因为易读性不好,写函数的话直接一点,简单易操作
 

  1. 代码冗余:特化版本的函数可能需要重复原始模板函数的部分逻辑,这可能导致代码冗余。
  2. 理解难度:特化版本的存在可能会使代码的逻辑变得模糊不清,尤其是当多个特化版本同时存在时,读者需要花费更多的时间去理解哪个版本会被调用。
  3. 调试困难:当出现问题时,调试特化版本可能比调试普通函数更加困难,因为需要考虑特化版本与模板之间的交互。
  4. 过度特化:过度使用特化可能导致代码膨胀,并且增加维护成本。

2.2 类模板特化

类模板特化分为全特化和偏特化。

2.2.1 全特化

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

//全特化
template<>
class Data<int,int>
{
public:
	Data() { cout << "data<int,int>" << endl; }

};
2.2.2 偏特化

偏特化是指对模板参数进行进一步的条件限制设计出的特化版本。偏特化有两种表现形式:部分特化和参数更进一步的限制。

//偏特化/半特化//全特化
template<class T1>
class Data<T1, int>
{
public:
	Data() { cout << "data<T1,int>" << endl; }

};

参数更进一步的限制 是针对模板参数的类型进行更严格的限制。例如,将两个参数特化为指针类型:

template<>
class Data<int*, int*>
{
public:
	Data() { cout << "data<int*,int*>" << endl; }

};


template<>
class Data<int&, int&>
{
public:
	Data() { cout << "data<int&,int&>" << endl; }

};

 类模板特化应用示例

我们可以通过一个例子来看一下如何使用类模板特化来解决特定的问题。假设我们有一个用于比较的类模板 Less,它可以用于直接比较日期对象,但是当比较指针时则会比较指针的地址而非指针指向的内容。我们可以使用类模板特化来解决这个问题。

#include<algorithm>
#include<vector>
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)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}

private:
	int _year;
	int _month;
	int _day;
};


template<class T>
class Less {
public:
	//仿函数
	bool operator()(const T& a1,const T& a2) {
		return a1 > a2;
	}
};


//特化
template<class Data>
class Less <Data*>{
public:
	//仿函数
	bool operator()(Data* a1, Data* a2) const{
		return *a1 > *a2;
	}
};

int main() {
	Date d1(2024, 1, 1);
	Date d2(2024, 11, 1);
	Date d3(2024, 10, 1);

	//vector<Date> s1;
	//s1.push_back(d1);
	//s1.push_back(d2);
	//s1.push_back(d3);
	//sort(s1.begin(), s1.end(), Less<Date>());


	vector<Date*> s1;
	s1.push_back(&d1);
	s1.push_back(&d2);
	s1.push_back(&d3);
	sort(s1.begin(), s1.end(), Less<Date*>());

	return 0;
}

其实就可以发现,特化一般是在一些特殊情况进行一个特殊处理

3. 模板分离编译

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

在使用模板时,如果模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义,则需要特别注意编译过程。解决方法包括:

  1. 将声明和定义放到一个文件 "xxx.hpp" 或者 "xxx.h" 中。这是推荐的做法。
  2. 显式实例化模板定义的位置。这种方法不实用,不推荐使用。

故事背景

假设你正在筹备一场盛大的聚会,你需要向不同的供应商订购所需物品和服务。为了组织好这场聚会,你决定采用一种高效的协调方式——分离编译模式。

  1. 制定采购清单:首先,你需要制定一份采购清单,列出所有需要的物品和服务。这份清单相当于模板的声明,它定义了你需要什么,但没有具体到哪家供应商。

  2. 发送询价单:接下来,你将向各个供应商发送询价单,这些询价单包含了具体的数量和要求。每份询价单相当于模板的具体定义,它们包含了特定的实现细节。

  3. 供应商报价:供应商会根据你的询价单进行报价。这个过程类似于编译阶段,供应商评估需求并给出价格。

  4. 签订合同:如果供应商的报价符合你的预算,你们将签订合同。合同相当于生成的目标文件,它明确了双方的责任和义务。

  5. 完成交易:最后,供应商会根据合同提供货物和服务,你则支付费用。这相当于链接阶段,将所有合同(目标文件)组合在一起形成一个完整的交易过程。

分离编译模式的应用

现在,让我们看看如何将这个故事映射到模板分离编译上:

  • 声明和定义放在同一个文件:这就像你将采购清单和询价单合并到一起,直接交给供应商。这种方式简单明了,因为供应商不需要在不同的地方查找你的清单和询价单详情。同样地,在编程中,如果你将模板的声明和定义放在同一个文件(例如 xxx.hpp)中,那么编译器可以在一个地方找到所有需要的信息,无需额外的链接步骤。

  • 显式实例化:这类似于你在采购清单中明确指出你需要从哪家供应商订购多少数量的商品,并且直接发送询价单。在编程中,这意味着你需要在源文件中显式地实例化模板,告诉编译器你需要哪些具体类型的实例。然而,这种方法在实际操作中并不常见,因为它增加了额外的工作量并且可能导致代码冗余。

这边举了一个例子来更好的理解为什么声明和定义放在两个文件会报错

解决方法

1.模板的声明和定义都在同一个文件(推荐)

2.显示实例化<这一步就是在链接的时候告诉编译器类型>(不推荐,容易代码膨胀)

这边的template不加<>是为了和特化区分(可以理解为文件在链接的时候告诉它这个文件里的模板类型)

总结

模板是C++语言的重要特性,它提供了代码重用的强大手段。然而,使用模板也需要注意其局限性,如非类型模板参数的限制、模板特化的正确使用等。通过合理利用模板特化,我们可以解决特定类型的问题,使得代码更加灵活和高效。

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

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

相关文章

社区防疫物资申报系统--论文pf

TOC springboot414社区防疫物资申报系统--论文pf 第1章 绪论 1.1选题动因 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和工作的领域。随着电脑和笔记本…

容器编排简介

1.1 什么是容器编排 容器编排是管理和自动化容器化应用程序的部署、扩展、运行和维护的过程。随着微服务架构的普及&#xff0c;应用程序被拆分成许多小型、独立的服务&#xff0c;每个服务都可以封装在容器中独立运行。容器编排工具应运而生&#xff0c;帮助开发者和运维团队更…

嵌入式系统实时任务调度算法优化与实现

嵌入式系统实时任务调度算法优化与实现 目录 嵌入式系统实时任务调度算法优化与实现 引言 1.1 嵌入式系统的重要性 1.2 实时任务调度的重要性 实时任务的定义与分类 2.1 实时任务的定义 2.2 实时任务的分类 2.3 实时任务的其他分类方法 硬实时与软实时系统 3.1 硬实…

STM32CubeMX 配置CAN通信 HAL库

一、CAN总线波特率计算 CAN总线通信的各节点通信时会产生相位差&#xff0c;所以要进行位同步&#xff0c;两个节点保持步调一致。 CAN_SJW&#xff1a;重新同步跳跃宽度(SJW) 。定义了在每位中可以延长或缩短多少个时间单元的上限。其值可以编程为1到4个时间单元。 CAN_BS1&a…

记录一次搭建uniapp-vue3的基础项目

1.使用 HBuilder X 创建uniapp vue3的基础项目 2.安装 自动导包插件 unplugin-auto-import npm install unplugin-auto-import或者 pnpm install unplugin-auto-import2.1 根目录下创建 vite.config.js 复制粘贴以下内容 import { defineConfig } from vite import uni fro…

QT基础知识4

思维导图 项目文件里面要加texttospeech模块 widget.h: #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTime>//时间类 #include <QTextToSpeech>//语音播报类QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass…

“论数据分片技术及其应用”写作框架,软考高级,系统架构设计师

论文真题 数据分片就是按照一定的规则&#xff0c;将数据集划分成相互独立、正交的数据子集&#xff0c;然后将数据子集分布到不同的节点上。通过设计合理的数据分片规则&#xff0c;可将系统中的数据分布在不同的物理数据库中&#xff0c;达到提升应用系统数据处理速度的目的…

企业高性能web服务器---nginx详解(基础介绍配置,核心配置)

目录 一、web服务器介绍 1.1 Apache prefork 模型 1.2 Apache worker 模型 ​编辑 1.3 Apache event模型 1.4 Nginx-高性能的web服务端 1.5 服务端 I/O 流程 1.5.1 磁盘 I/O 1.5.2 网络 I/O 二 、nginx 架构及安装 2.1 nginx 进程结构 2.2源码编译安装nginx 2.2.1…

vscode+pyqt5环境搭建

参考&#xff1a;https://blog.csdn.net/qq_37080185/article/details/121616507 一、安装Python 从Python官网上下载安装包&#xff08;https://www.python.org/&#xff09; 安装Python&#xff0c;将安装目录添加到环境变量中。 二、安装Pyqt5 PyQt5以及PyQt5-tools(des…

将光谱数据图片转换成数值格式

文章目录 任务所需工具步骤一&#xff1a;安装必要的Python库步骤二&#xff1a;图像OCR识别步骤三&#xff1a;提取光谱数值并存储完整代码 任务 现测量收集到一批目标色彩样本的光谱响应数据截图(图片保存在spectrum_screenshots文件夹内&#xff0c;截图样例见图1)。其中&a…

PyQt5中如何只使用一个dateEdit控件实现自动选择日期区间功能

wxpython设计GUI&#xff1a;选中wxFormBuilder工具wxCalendarCtrl控件&#xff0c;实现自动选择日期功能 wxPython设计界面转PyQt5设计界面&#xff0c;相同的界面功能&#xff0c;通过移植wxPython源代码实现PyQt5相同界面功能&#xff0c;在实现上述链接提到的自动选择日期…

Windows电脑设置开启自启动Java程序,并且不出现黑窗口

第一步&#xff1a;创建需要运行的批处理文件&#xff08;.bat 文件&#xff09; 在jar文件同级目录下新建文本输入以下内容&#xff0c;其中tunnel-monitoring-server.jar改为自己的程序名称&#xff0c;保存文件后缀改为bat。如下图1&#xff1a; echo off java -jar tunne…

开源的Umi-OCR 文字识别工具

开源的Umi-OCR 文字识别工具&#xff1a;OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片&#xff0c;PDF文档识别&#xff0c;排除水印/页眉页脚&#xff0c;扫描/生成二维码。内置多国语言库。 可以将图片PDF识别文字&#xff0c;并可以保存…

python | 字符串编码问题怎么破

python字符串常见两种类型&#xff1a;str和 bytes类型 str表示Unicode字符&#xff0c;bytes表示二进制数据 两者之间转换使用&#xff1a;encode()和decode()方法 一、enocde()和decode()方法 &#xff08;一&#xff09;encode()方法 encode()—编码&#xff0c;语法&…

软件测试之常见逻辑思维题

一个岔路口分别通向诚实国和说谎国。来了两个人&#xff0c;已知一个是诚实国的&#xff0c;另一个是说谎国的。诚实国永远说实话&#xff0c;说谎国永远说谎话。现在你要去说谎国&#xff0c;但不知道应该走哪条路&#xff0c;需要问这两个人。请问应该怎么问&#xff1f; 如…

[HDCTF 2023]Welcome To HDCTF 2023

方法一&#xff1a;找个炸弹死掉&#xff0c;flag就出现 方法二&#xff1a;查看页面源码&#xff0c;发现底部assets/js/game.js 复制后访问看到jsfuck编码 复制到控制台查看flag

上海交大周冰心博士:锚定稀缺生物数据挑战,图神经网络重塑蛋白质理解与生成

8 月 12 日&#xff0c;上海交通大学 AI for Bioengineering 暑期学校正式开幕&#xff0c;吸引了来自国内外 30 余所高校和 27 家企业的百余名业内人士。在为期 3 天的学习交流中&#xff0c;多位行业专家、企业界代表及优秀青年学者&#xff0c;围绕 AI 与生物工程的融合与创…

【前缀和算法】--- 一维和二维前缀和模板

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 算法Journey 本文开始,博主开始讲解有关前缀和的算法&#xff0c;本篇博客我们先来了解一下有关前缀和的两个模板。 &#x1f3e0; 一维前缀和模板 &…

CPU内部单总线数据通路各阶段的微操作序列利控制信号

1.内部总线与系统总线 内部总线是指同一部件&#xff0c;如CPU内部连接各寄存器及运算部件之间的总线&#xff1b; 系统总线是指同一台计算机系统的各部件&#xff0c;如CPU、内存、通道和各类/0接口间互相连接的总线。 2.寄存器之间数据传送 比如把PC内容送至MAR&#xff…

利用多Lora节省大模型部署成本|得物技术

一、背景 近期&#xff0c;我们在大模型集群的部署过程中遇到了一些挑战。公司有多个业务场景&#xff0c;每个场景都基于自身的数据进行微调&#xff0c;训练出相应的大模型并上线。然而&#xff0c;这些场景的调用量并不高&#xff0c;同时大模型的部署成本较为昂贵&#xf…