template advanced

一.仿函数再探

stl_stack/queue-CSDN博客

在priority_queue中,我们介绍了仿函数作为第三个参数来改变堆的类型,而仿函数还有其他的用处。 

那么我们是否可以借助优先级队列来对日期类进行排序呢?

答案是可以的,但前提是该日期类必须重载><号。 

那什么时候需要使用仿函数呢?

1、类类型不支持比较大小

2、但比较逻辑不是我们想要的

第二点是什么意思呢?

当我们优先级队列中存储的是一个个Date*的数据时,我们期望它们依旧可以完成日期类的比较,但是却不尽人意: 我们看到这里是打印的不是我们想要的结果,而是指针,那么我们对堆顶的数据进行解引用拿到日期本身: 

优先级队列默认是大堆,依次取出堆顶数据应该时降序,这里的顺序却不是。并且这些顺序不是固定的,每次运行都不同: 出现这种现象的原因是因为这里比较的时候比较的是指针而不是日期本身,而我们的比较逻辑是由第三个模板参数实现的,所以我们可以改变第三个模板来到达我们比较日期本身而不是指针的目的:

template<typename T>
struct DateLess
{bool operator()(const T& x, const T& y){return *x < *y;}
};xsc::priority_queue<Date*,vector<Date*>, DateLess<Date*>> q1;

对于整型也一样,如果不借助仿函数来实现自己希望的比较逻辑,就会默认采用已有的: 

 二.模板

1.非类型模板参数

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

类型模板参数即:定义模板时,尖括号里typename/class后面跟着的类型,它既可以内置类型也可以是类类型。

非类型模板参数即:就是用一个类型作为类/函数模板的一个参数,前面没有typename/class,在其中常作为常量使用,需要注意的是:只有整型家族的类型才可以作为非类型模板参数,如int、char、bool,也可以有缺省值

template<size_t N = 10>
class Stack
{
private:int _a[N];int _top;
};

我们可以借助上面的类来构建一个定长数组,但并不会进行初始化:

Stack<1> s1;
Stack<5> s2;
Stack<100> s3;

那么如何使用非类型模板参数的缺省值呢?

//Stack s0;//错误
Stack<> s0;//正确

C++-20支持了double作为非类型模板参数

2.array 

stl中也有一个容器用到了非类型模板参数——array,它其实就是一个静态数组。array - C++ Reference

 它实现的接口和vector类似,但少了push和pop之类的接口,因为就是一个定长的数组,不需要频繁的插入删除。

array<int, 10> a1;
array<int, 100> a2;
array<int, 1000> a3;

那么它和定长数组有什么区别呢?

定长数组:

定长数组对于越界读不进行检查,而对于越界写设置了标志位,也就是进行抽查

越界读:

 越界写:

 array:

 array对越界读和越界写都进行了强制的检查

 array是一个类模板,内部重载了operator[],在operator[]内可以进行断言检查,来避免越界。

 有了vector为什么还要有array?array的接口vector都有,且两个底层都是数组,而且vector是动态的。

array确实很鸡肋,但是他在一些需要频繁申请小块空间时的效率是比vector要高的,array的空间在栈上,而且没有初始化,它在开辟函数栈帧时就开好了;vector空间开在堆上,还对其进行了初始化,所以array的申请效率比vector要高。

三.模板的特化

特化的意思就是,当前的模板在遇到一些特殊情况时会发生意想不到的错误,如果想避免这种错误,可以对该模板进行特化,生成一个特殊的模板,这个模板可以解决那些特殊情况。

 1.函数模板特化

函数模板特化的步骤:

  1. 必须要有一个基础的函数模板
  2. 特化出的函数模板template后面加一个空的<>
  3. 函数名后面加上一个<>,里面指定需要特化的类型
  4. 函数形参表必须要和基础的函数模板的基础参数类型完全相同,如果不同可能会爆出一些奇怪的错误

现在这里有一个函数模板,用来比较,两个变量的大小

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

 我们可以用它来比较内置类型和类类型的对象

 但是当比较的不是变量/对象时,就会发生一些错误:

d1,d2调用lessfunc时就会调到类的运算符重载 ,而p1,p2是指向d1,d2的指针,我们期望他能完成值的比较,而实际上它是用指针进行了比较。

为了解决这种特殊情况,我们就可以对该函数模板进行特化,特化出一个专门比较Date*的lessfunc

//特化
template<>
bool lessfunc<Date*>(Date* left, Date* right)
{return *left < *right;
}

但是我们在这些内部不会改变对象的函数,且对象很大内部涉及深拷贝,我们这样传参就不合适了,需要传const 引用,const表明对象不可修改,传引用可以减少拷贝

但是这样修改的话,特化出来的函数就会报错

template<class T>
//bool lessfunc(T left,T right)
bool lessfunc(const T& left, const T& right)
{return left < right;
}//特化
//报错,不是函数模板的专用化
template<>
bool lessfunc<Date*>(Date* left, Date* right)
{return *left < *right;
}

我们先前在特化的前提中提到了,特化出来的函数必须和原函数模板的参数类型保持一致,否则会出一些奇怪的错误,原函数模板参数是const T&,所以函数模板也得是Date*的const &。

//特化
template<>
//bool lessfunc<Date*>(const Date* & left, const Date* & right) //错误
bool lessfunc<Date*>(Date* const & left, Date* const & right)   //正确
{return *left < *right;
}

这里就有两种写法:第一种直接加const,这种是错误的,原函数模板const修饰的是对象本身即left,而当类型时指针时,且const加在前面,修饰的是指针指向的内容,即*left。所以我们应该将const加在*之后,修饰指针本身。

但是当出现const对象时,这里调用结果又会出错,因为其并没有调用特化的版本,原对象时只读,而特化的版本是可读可写,这里权限放大,所以不会调到这个函数中,而是基础的函数模板。

const Date* p3 = &d1;
const Date* p4 = &d2;
cout << lessfunc(p3, p4) << endl;

为了解决这个问题还得特化出一个const Date* 类型的函数 

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

 我们看到特化函数模板非常复杂,如果特化到指针const的使用也很麻烦,所以我们还不如直接写一个针对该类型的函数

bool lessfunc(const Date* & left, const Date* & right)
{return *left < *right;
}

这种方式也是比较推荐的。


2.类模板特化

类模板也可能遇到一些特殊情况,这里就需要我们特化出一个类模板,专门解决这类问题。

现在这里有一个基础类:

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

2.1全特化

全特化即将类模板的模板参数全部都确定化。

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

2.2偏特化/半特化

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

对其中一部分参数进行偏特化既可以特化后面的,也可以特化前面的

//偏特化/半特化
template<typename T1>
class Data<T1, double>
{
public:Data() { cout << "clsss Data<T1, double>" << endl; }};//偏特化
template<typename T2>
class Data<char,T2>
{
public:Data() { cout << "class Data<char,T2>" << endl; }};

2.3特殊的偏特化

template<typename T1, typename T2>
class Data<T1*, T2*>
{
public:Data() { cout << "class Data<T1*, T2*>" << endl; }};template<typename T1, typename T2>
class Data<T1&, T2&>
{
public:Data() { cout << "class Data<T1&, T2&>" << endl; }};

我们可以对模板参数做进一步的限制,传的类型是指针/或者引用。

但需要注意的是,在这两个类中,T1和T2还是基础类型,不是指针或者引用,T1*,才是指着;T1&才是引用

template<typename T1, typename T2>
class Data<T1*, T2*>
{
public:Data() { cout << "class Data<T1*, T2*>" << endl; }T1 x;T2 y;T1* m;T2* n;
};

 当我们实例化模板时,会调用最匹配的模板。

四.模板的分离编译 

1.什么是分离编译

当一个项目很大时,有很多的源文件。我们通过会采取声明和定义分离的方式来进行实现,即类/函数的声明都放在.h文件中,定义放在.cpp/.c文件中。

分离编译(Separate Compilation)是指将程序的源代码分为多个独立的模块(或文件),每个模块可以单独进行编译生成目标文件。这些目标文件可以在后续的链接阶段合并成最终的可执行文件。分离编译的主要目的是提高程序的开发效率和可维护性。

 2.模板的分离编译

我们之前提到,模板是不可以声明和定义分离的,但其他的类/函数就可以,这是为什么呢?

如果分离就会发生链接错误:

编译和链接-CSDN博客

 编译的过程分为四个阶段预处理——编译——汇编——链接

预处理阶段会将.cpp中的.h文件展开,并进行宏替换,条件编译,去掉注释等工作,然后生成.i文件

编译阶段会进行词法分析,语法分析,语义分析及优化,生成相应的.s为后缀的汇编代码

汇编阶段将编译阶段生成的汇编代码进行翻译,转换成机器能够识别的二进制机器码,生成相应的.o文件

链接阶段会生成要给符号表,记录这一些函数的地址,并且将目标文件合并在一起生成可执行程序

 我们在完成项目时通常会分为三个文件:

.h放声明,.cpp放实现,另一个.cpp用来进行测试

 当我们编译时,就会进行上面四个步骤:

 然后我们在主函数中去调用这两个函数时,汇编代码会调用call指令去找该函数的地址,但是在test.cpp中只有函数的声明而没有定义,不知道该函数的地址,所以不知道call什么。但是在func.cpp中,声明和定义是同时存在的,这里就有函数的地址,所以在生成符号表的时候就将func的地址放到了符号表中,但是add函数此时还没有实例化,没有生成对应的代码,不知道其地址,所以符号表中并没有其地址,然后等到最后一步链接的时候,主函数中的函数调用会去符号表中寻找对应的函数,找到了func,但是没有add,此时就会爆出链接错误。

之所以会在链接时报错时因为,在编译阶段,.h已经展开,而在函数调用出会开始向上搜索函数定义,但是搜索到了声明之后并不会报错,编译器认为定义在另外的文件中。 

解决方法:

1、对于模板来说,将声明和定义放到一个文件中

2、在模板定义的位置显式实例化模板

显式实例化:

五.模板总结

优点

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

2、增强了代码的灵活性

缺陷

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

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

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

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

相关文章

spring源码[spring启动流程]

spring启动流程 AnnotationConfigApplicationContext的构造方法 1.父类构造方法&#xff0c;构造一个DefaultListableBeanFactory 在调用AnnotationConfigApplicationContext的构造方法之前&#xff0c;会调用父类GenericApplicationContext的无参构造方法&#xff0c;会构造…

使用Python做一个微信机器人

使用Python制作微信机器人是一个有趣且实用的项目&#xff0c;它可以让您自动化处理微信消息、监控微信群、甚至实现智能聊天等功能。 请求参数 Header 参数 export interface ApifoxModel {"X-GEWE-TOKEN": string;[property: string]: any; } Body 参数applicat…

Python-创建并调用自定义文件中的模块/函数

背景&#xff1a;在Python编程中&#xff0c;我们常常需要创建自己的专属文件&#xff0c;以便帮助我们更高效&#xff0c;快捷地完成任务。那么在Python中我们怎么创建并调用自己文件中的模块/函数呢? 在Python中调用自定义文件&#xff0c;通常是指调用自己编写的Python模块…

【C++】C++17结构化绑定、std::optional、std::variant、std::any

二十二、C17中的结构化绑定、std::optional、std::variant、std::any 本部分是一个小系列&#xff0c;介绍C17中新引入的、用来解决各种不同返回情况的、标准库新组件。 1、C的结构化绑定 结构化绑定structured bindings是C17中引入的一项特性&#xff0c;它允许开发者方便地…

ntkrnlmp.exe导致蓝屏死机的解决方法

在使用Windows操作系统的过程中&#xff0c;用户可能会遇到由ntkrnlmp.exe文件错误引发的蓝屏死机&#xff08;Blue Screen of Death, BSOD&#xff09;问题&#xff0c;这不仅影响了日常的工作效率&#xff0c;也可能造成数据丢失的风险。本文将为您提供一系列即时排查与修复n…

U3D游戏开发之骨骼动画相关

目录 1 作为U3D程序如何制作骨骼动画 2 骨骼动画程序代码相关 这个内容我在很久之前就想写了&#xff0c;很多项目也与骨骼动画挂钩&#xff0c;今天我们揭秘的是2D骨骼动画。来聊一聊大家可能非常关注的两个问题&#xff1a;作为程序如何制作骨骼动画&#xff1f;接到美术的骨…

java:题目:用Java实现简单的自取取款操作

import java.util.Scanner; public class ATM {public static void main(String[] args){//自主取款主类Scanner scnew Scanner(System.in);System.out.println("请输入账户号码&#xff1a;");String BankAccoutsrsc.nextLine();/BankAccout3 newBankAccoutnew Bank…

VLAN 高级技术 ——QinQ的配置

QinQ的概述&#xff1a; QinQ技术是一种扩展虚拟局域网&#xff08;VLAN&#xff09;数量空间的技术&#xff0c;通过在802.1Q标签报文的基础上再增加一层802.1Q的Tag来实现。以下是对QinQ技术的详细概述&#xff1a; QinQ技术的定义与背景 定义&#xff1a;QinQ&#xff08…

不得不承认供电公司信息宣传向媒体投稿的好方法找到了

初入国网供电公司,我被分配到了信息宣传部门,负责每月的信息宣传投稿任务。这项任务看似简单,实则充满挑战。一开始,我满怀热情,以为只要写出高质量的文章,就能顺利发表。然而,现实给了我当头一棒。传统的邮箱投稿方式,不仅竞争压力大,审核严格,而且周期漫长。每次投稿后,我总是…

『YOLOV5』| 一文搞定训练过程中的意外终止、以及想继续增加训练轮数!

文章目录 情况一&#xff1a;意外训练中断&#xff08;程序未训练完成&#xff0c;想完成目标训练轮数&#xff09;情况二&#xff1a;自动训练完成&#xff08;程序已完成训练&#xff0c;想增加训练轮数&#xff09; 情况一&#xff1a;意外训练中断&#xff08;程序未训练完…

GCC编译器的`-Wall`、`-Wextra`和`-pedantic`选项解读

gcc是广泛使用的开源编译器&#xff0c;-Wall、-Wextra和-pedantic是gcc中用于控制警告信息的选项&#xff0c;以下是详细介绍&#xff1a; -Wall&#xff08;启用大部分警告&#xff09; 功能&#xff1a;-Wall 选项用于启用一系列常用的警告信息&#xff0c;这些警告能帮助…

MMBench-Video:上海 AI Lab 联合多所高校推出长视频理解基准测试工具,全面评估 LVLMs 视频理解的能力

❤️ 如果你也关注大模型与 AI 的发展现状&#xff0c;且对大模型应用开发非常感兴趣&#xff0c;我会快速跟你分享最新的感兴趣的 AI 应用和热点信息&#xff0c;也会不定期分享自己的想法和开源实例&#xff0c;欢迎关注我哦&#xff01; &#x1f966; 微信公众号&#xff…

高频电子线路---调角频谱与频宽

目录 调角频谱(FM单频调制) 带宽 调频方法 直接调频方法与电路 变容二极管 如何提升频偏? 1. 增大调制信号的幅度&#xff08;增大调制深度&#xff09; 2. 提高调制信号的频率 3. 提高调制深度&#xff08;调制指数&#xff09; 4. 增加发射功率 5. 使用特殊的调制…

摘要、数字签名、对称加密、非对称加密综合应用示例以及技术原理说明

图&#xff1a;介绍了数字信封的安全传输过程 关键术语 散列&#xff1a;Hash&#xff08;哈希&#xff09;&#xff0c;一般翻译做散列、杂凑&#xff0c;是把任意长度的输入&#xff08;数据信息&#xff09;通过散列算法变换成固定长度的输出&#xff0c;该输出就是散列值…

java学习3---面向对象

一、设计对象并使用 1.类和对象 类是共同特征的描述&#xff1b;对象是真实存在的具体实例。 2.类的几个补充注意事项 二、封装 对象代表什么&#xff0c;就得封装对应的数据&#xff0c;并提供数据对应的行为。 封装告诉我们如何正确的设计对象 三、this关键字 this可以区…

Maven

Maven 命令方式构建项目 mvn compile&#xff1a;编译项目&#xff0c;生成target文件&#xff08;不编译测试代码&#xff09; mvn package&#xff1a;打包项目&#xff0c;生成jar或war文件&#xff08;不指定默认jar包&#xff09; mvn clean&#xff1a;清理编译或打包后…

leetcode 173.二叉搜索树迭代器

1.题目要求: 2.题目代码: /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nu…

vue插件清除 所有console.log()

一、作用 1、提升性能console.log() 语句会消耗一定的性能&#xff0c;尤其是在频繁调用的情况下。在生产环境中移除这些语句可以提高应用的运行效率。 2、减少信息泄露console.log() 可以输出敏感信息&#xff08;如用户数据、API 响应等&#xff09;。在生产环境中&#xf…

Day102漏洞发现-漏扫项目篇Poc开发Yaml语法插件一键生成匹配结果交互提取

知识点&#xff1a; 1、Nuclei-Poc开发-环境配置&编写流程 2、Nuclei-Poc开发-Yaml语法&匹配提取 3、Nuclei-Poc开发-BurpSuite一键生成插件 Nuclei-Poc开发-环境配置&编写流程 1、开发环境&#xff1a;VscodeYaml插件 Visual Studio Code - Code Editing. R…

【IEEE出版】第六届国际科技创新学术交流大会暨信息技术与计算机应用学术会议(ITCA 2024,12月06-08)

第六届国际科技创新学术交流大会暨信息技术与计算机应用学术会议&#xff08;ITCA 2024) 2024 6th International Conference on Information Technology and Computer Application 会议官网&#xff1a;itca2024.iaecst.org 会议时间&#xff1a;2024年12月06-08日 截稿时…