【C++】模板:进阶(仿函数深度剖析)

news2024/9/23 3:14:21

目录

一.仿函数的概念

二.仿函数的作用

三.模板的非类型模板参数

四.array

五.模板的特化 

 六.函数模板

七.类模板

1.全特化

2.偏特化

八.模板分离编译


一.仿函数的概念

仿函数本质就是一个类,此类中重载了运算() ,因此它使用起来就和函数很像,就叫仿函数

在之前了解的优先级队列中,有这样一个缺省参数叫less,其实这就是一个仿函数,它会将优先级队列变成大堆,同样在标准库的sort中也使用了less,默认排出的升序。与less对应的是greater,它和less相反,greater将优先级队列变成小堆,sort变成降序。

在这里可以模仿实现一下less的使用场景:

namespace zyh
{
	//本质是一个类
	class less
	{
	public:
		bool operator()(int x, int y)
		{
			return x < y;
		}
	};
}

int main()
{
	zyh::less fun;
	cout << fun(1, 2) << endl;
    //打印结果为1(true)

	return 0;
}

二.仿函数的作用

仿函数在使用库中的某些函数时很有用,只需要传不同的仿函数就能改变升降序或大小堆

//升序写法
vector<int> v{ 5,4,3,2,1 };
sort(v.begin(), v.end());
sort(v.begin(), v.end(), less<int>());

//降序写法
vector<int> v{ 1,2,3,4,5 };
sort(v.begin(), v.end());
sort(v.begin(), v.end(), greater<int>());

//大堆写法
priority_queue<int, vector<int>, less<int>> p;

//小堆写法
priority_queue<int, vector<int>, greater<int>> p;

三.模板的非类型模板参数

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

  • 类型形参:出现在模板参数列表中,跟在class/typename之后的参数类型
  • 非类型形参:用一个常量作为类(函数)的一个参数

例如:

//T是类型参数,N是非类型参数
template<class T,int N = 10>
class test
{
	T a[N];
};

//int[20]
test<int,20> t1;
//double[10]
test<double> t2;

注意:

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

四.array

array是STL中一个不常用的容器,array是静态数组,也就是固定了大小的顺序容器,使用时,要显式传参N来初始化数组,这里就使用了前文所说非类型形参

array属于C++的数组,使用array时,不管是越界读还是越界写都能被检测到并且报错,而使用C语言的数组时,越界读写不一定会报错 


五.模板的特化 

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

//用于小于比较的函数模板
template<class T>
bool less(T left, T right)
{
	return left < right;
}

less绝大多数情况下都可以正常比较,但是在特殊场景下就会得到错误结果。例如这里若传入指针过来,目标是比较指针指向的内容,而传入指针的话只会比较地址的高低,达不到想要的效果,此时,就需要对模板进行特化,即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式,接下来将分为函数模板和类模板来进行详解


 六.函数模板

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号<>,尖括号内指定需要特化的类型
  4. 函数形参表必须要和模板函数的基础参数完全相同,如果不同编译器可能会报一些奇怪的错误
// 函数模板 -- 参数匹配
template<class T>
bool less(T left, T right)
{
 return left < right;
}
// 对less函数模板进行特化
template<>
bool less<int*>(int* left, int* right)
{
	return *left < *right;//比较指针指向的内容
}

此时,若传入了int类型的指针,那么就不会调用第一个函数了,而是直接走第二个特化的函数。但一般情况下,如果函数模板遇到了不能直接处理或处理有误的类型,最简单的方法是将该函数直接给出,如下,因此实际上函数模板的特化是不常用的

bool less(int* left, int* right)
{
	return *left < *right;
}

七.类模板

类模板的特化分为全特化偏特化

1.全特化

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

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

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

Data<int, int> d1;//不会走模板特化
Data<int, char> d2;//走模板特化

2.偏特化

偏特化是指任何针对模板参数进一步进行条件限制设计的特化版本,偏特化分为两种表现形式:部分特化和对参数做进一步限制

例如对上面例子中的模板类做部分特化:

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;
};

此时,只要第二个参数是int,就会走偏特化,第二个参数不是int就不会走偏特化

当然也可以对上面的例子做参数的进一步限制:

//两个参数偏特化为指针类型
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:
    //const类型要在定义的地方初始化
	Data(const T1& d1, const T2& d2)
	: _d1(d1)
	, _d2(d2)
	{
		cout<<"Data<T1&, T2&>" <<endl;
	}
private:
	const T1 & _d1;
	const T2 & _d2; 
 };

八.模板分离编译

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

如果将模板声明和定义分在不同的文件会如何?

// a.h
template<class T>
T Add(const T& left, const T& right);
 
// a.cpp
template<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;
}

一运行就会出现编译报错,原因是找不到Add函数,这又是什么原因呢?

我们知道C/C++程序的运行一般包括了预处理、编译、汇编和链接等步骤

  1. 预处理(preprocessing):这个阶段会处理源代码中的预处理指令,例如#include、#define等,将宏展开、头文件包含等操作。预处理的结果是生成一个纯粹的C++源文件,而没有预处理指令
  2. 编译(compilation):编译器将预处理后的源代码翻译成汇编语言。在这个阶段,编译器会进行词法、语法、语义分析,并生成相应的汇编代码。每个源文件都会被单独编译,生成相应的目标文件(object File,通常以.obj、.o等为拓展名)
  3. 汇编(assembly):汇编器将汇编代码转换成机器语言的目标文件
  4. 链接(linking):链接器将多个目标文件、库文件以及系统的一些运行时代码合并成一个可执行文件。链接的过程包括地址解析、符号解析、重定向等步骤,确保各个目标文件中的符号能够正确关联

从main函数开始执行,遇到了Add(1,2),因为包含了.h头文件(有声明),就会到链接部分去找实现,但是在文件另一方的实现中不知道我进行了实例化,也就没有进行实例化,所以链接后找不到

因此需要记住一个结论:模板的声明和定义不能分离


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

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

相关文章

新160个crackme - 053-devilz KeyGen me#3

运行分析 解压出来4个文件运行程序发现要破解Name和Serial PE分析 32位&#xff0c;petite壳 手动脱壳 使用windows XP虚拟机OD打开程序按2下F8&#xff0c;发现ESP变红&#xff0c;根据ESP定律&#xff0c;在该地址右键 -> HW break下断点 继续按2下F9&#xff0c;来到灰色…

从0到1:中小企业如何用内容营销吸引客户,塑造品牌魅力?

今天咱们来聊聊中小企业如何通过内容营销这把“金钥匙”&#xff0c;打开客户的心门&#xff0c;同时塑造出独特的品牌魅力。别紧张&#xff0c;这其实就是一场精彩的交流盛宴&#xff0c;让我们一起探索其中的奥秘吧&#xff01; 一、内容营销&#xff1a;真诚对话的开始 想象…

OAuth 2.0 授权流程详解与 FastAPI 实现

在现代网络应用中&#xff0c;OAuth 2.0 已成为授权和认证的标准协议。它允许用户将访问权限授予第三方应用&#xff0c;而无需暴露自己的用户名和密码。本文将详细介绍 OAuth 2.0 的常见授权流程&#xff0c;并展示如何在 FastAPI 中实现这些流程。 OAuth 2.0 简介 OAuth 2…

数字化转型不是终点,数字技术服务平台如何陪伴企业持续进化?

数字化转型确实不是终点&#xff0c;而是一个持续的过程&#xff0c;它要求企业不断适应变化、优化流程、创新业务模式。数字技术服务平台在陪伴企业持续进化方面扮演着至关重要的角色&#xff0c;具体体现在以下几个方面&#xff1a; 灵活性与可扩展性&#xff1a;数字技术服…

误删文件后的数据救赎实战恢复指南

误删文件的痛与思 在数字化时代&#xff0c;数据已成为我们生活与工作中不可或缺的一部分。无论是个人用户保存的家庭照片、工作文档&#xff0c;还是企业用户存储的财务数据、客户资料&#xff0c;都承载着无法估量的价值。然而&#xff0c;误删文件这一简单却致命的操作&…

【数组与广义表】(基本概念与思路)

1.数组的定义及特点 数组&#xff1a;按一定格式排列起来的&#xff0c;具有相同类型的数据元素的集合。 1.1一维数组 若线性表中的数据元素为非结构的简单元素&#xff0c;则称为一维数组。一维数组的逻辑结构:线性结构&#xff0c;定长的线性表声明格式:数据类型 变量名称…

仪表板展示丨DataEase看中国:中国月饼行业消费趋势报告

中秋节是中国最重要的传统节日之一&#xff0c;月饼是具有浓厚节日特色的传统美食。近年来&#xff0c;月饼市场呈现出诸多新趋势和消费特点。在本文中&#xff0c;我们使用DataEase开源BI工具&#xff08;http://github.com/dataease&#xff09;对中国月饼行业的消费趋势进行…

敏捷开发方法例题

答案&#xff1a;B 敏捷方法 特点 极限编程XP 4大价值观&#xff0c;5大原则&#xff0c;12个最佳实践 水晶法 认为每一个不同的项目都需要一套不同的策略&#xff0c;约定和方法论&#xff0c;认为人对软件质量有重要影响&#xff0c;因此随着项目质量和开发人员须知的提…

洛谷 P7391 「TOCO Round 1」自适应 PVZ

原题链接t 题目来源于&#xff1a;洛谷 题目本质&#xff1a;贪心&#xff0c;排序&#xff0c;平衡树 题目思路&#xff1a;把僵尸出现时刻和走进房子时刻想成左端点和右端点&#xff0c;按照右端点从小到大排序。第二对于同样可以使用的豌豆射手&#xff0c;我们一定选择上…

在 HKCR 新增项和值

; 1. Win11 HKCR 根键默认是 System 所有, Win10 HKCR 根键默认是 Administrators 所有。 ; 2. 以 System、管理员 还是 普通用户 登录系统&#xff1f; ; 在注册表里&#xff0c;操作 HKCR 行为与以上两项无关&#xff0c;都统一如下: ; 项 查权限所有者 当…

QT5实现https的post请求(QNetworkAccessManager、QNetworkRequest和QNetworkReply)

QT5实现https的post请求 前言一、一定要有sslErrors处理1、问题经过2、代码示例 二、要利用抓包工具1、问题经过2、wireshark的使用3、利用wireshark查看服务器地址4、利用wireshark查看自己构建的请求报文 三、返回数据只能读一次1、问题描述2、部分代码 总结 前言 QNetworkA…

单片机-STM32 看门狗(八)

目录 一、看门狗概念 1、定义&#xff1a; 二、单片机中的看门狗 1、功能描述&#xff1a; 2、看门狗设置部分 预分频寄存器(IWDG_PR) 3、窗口看门狗 特性&#xff1a; 4、看门狗配置&#xff1a; 一、看门狗概念 看门狗--定时器&#xff08;不属于基本定时器、通用定…

HTTP 协议和 APACHE 服务

WEB 服务基础 Internet 因特网 因特网是 Internet 的中文译名 在 20 世纪 60 年代&#xff08;冷战时期&#xff09;&#xff0c;美国国防部高等研究计划署&#xff08;ARPA&#xff09;出于军事上的目的&#xff0c;建立了 ARPA 网络&#xff0c;该网络由四个分布在不同地方…

FreeRTOS基础入门——FreeRTOS互斥信号量(十六)

个人名片&#xff1a; &#x1f393;作者简介&#xff1a;嵌入式领域优质创作者&#x1f310;个人主页&#xff1a;妄北y &#x1f4de;个人QQ&#xff1a;2061314755 &#x1f48c;个人邮箱&#xff1a;[mailto:2061314755qq.com] &#x1f4f1;个人微信&#xff1a;Vir2025WB…

本地部署Llama 3.1大模型

Meta推出的Llama 3.1系列包括80亿、700亿、4050亿参数版本&#xff0c;上下文长度扩展至12.8万tokens&#xff0c;并增加了对八种语言的支持。 部署模型需要用到Ollama的一个工具&#xff0c;访问官方网站https://ollama.com 点击下载&#xff0c;选择下载你对应的操作系统下…

opencv图像透视处理

引言 在图像处理与计算机视觉领域&#xff0c;透视变换&#xff08;Perspective Transformation&#xff09;是一种重要的图像校正技术&#xff0c;它允许我们根据图像中已知的四个点&#xff08;通常是矩形的四个角&#xff09;和目标位置的四个点&#xff0c;将图像从一个视…

2024.9.10 作业

代码&#xff1a; /*******************************************/ 文件名&#xff1a;widget.h /*******************************************/ #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QLabel> #include <QTimeEdit> #includ…

opencv学习:信用卡卡号识别

该代码用于从信用卡图像中自动识别和提取数字信息。该系统将识别信用卡类型&#xff0c;并输出信用卡上的数字序列。 1.创建命令行参数 数字模板 信用卡 # 创建命令行参数解析器 ap argparse.ArgumentParser() # 添加命令行参数 -i/--image&#xff0c;指定输入图像路径 ap.…

破局DRG/DIP亏损,医院应该怎么做

DRG/DIP付费实施后&#xff0c;医院各临床科室可结合前期数据积累&#xff0c;根据DRG/DIP专科病组/病种四级手术占比与医疗收入占比之间的变化关系、建立DRG/DIP战略分布象限图&#xff0c;将病组分为优势病组&#xff08;病种&#xff09;、潜力病组&#xff08;病种&#xf…

线程(Thread)

目录 线程&#xff08;Thread&#xff09; 线程的创建方式 实现方式 Runnable和Callable的区别 线程的命名和优先级 线程的六种状态 线程的插队 线程的中断 线程的让出 守护线程 设置线程为守护线程 sleep()和wait()的区别 线程的同步synchronized锁 语法格式 实现…