C++模版(进阶)

文章目录

  • 一、非类型模版参数
  • 二、模版的特化
    • 2.1 概念
    • 2.2 函数模版特化
      • 2.2.1 函数模版特化为指针类型注意事项
    • 2.3 类模版特化
      • 2.3.1 全特化
      • 2.3.2 偏特化(半特化)
      • 2.3.3 类模板特化应用示例
  • 三、模版分离编译
    • 3.1 什么是分离编译?
    • 3.2 模版的分离编译
    • 3.3 解决方法!
  • 四、模版总结

一、非类型模版参数

1.模板参数分为类类型形参非类型形参
类型形参即: 出现在模板参数列表中,跟在class或者typename之后的参数类型名称。
非类型形参:就是用一个常量作为类(函数)模板的一个参数在类(函数)模板中可将该参数当成常量来使用。

namespace bite
{// 定义一个模板类型的静态数组//非类型模版参数Ntemplate<class T, size_t N = 10>class array{public:T& operator[](size_t index) { return _array[index]; }const T& operator[](size_t index)const { return _array[index]; }size_t size()const { return _size; }bool empty()const { return 0 == _size; }private:T _array[N];size_t _size;};
}

【注意】
(1) 浮点数、类对象以及字符串是不允许作为非类型模板参数的。(非类型模版——只支持传整型(整型家族))
(2) 非类型的模板参数必须在编译期就能确认结果

二、模版的特化

2.1 概念

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

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}int main()
{cout << Less(1, 2) << endl;   // 可以比较,结果正确Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << Less(d1, d2) << endl;  // 可以比较,结果正确(Date类重载<的情况下)Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl;  // 可以比较,结果错误return 0;
}

可以看到,Less绝大多数情况下都可以正常比较,但是在特殊场景下就会得到错误的结果。上述示例中,p1指向的d1显然小于p2指向的d2对象,但是Less内部并没有比较p1和p2指向对象的内容,而比较的是p1和p2指针的地址,这就无法达到预期而出现错误。此时,就需要对模板进行特化。即: 在原模板类的基础上,针对特殊类型进行特殊化的实现方式。模板特化分为函数模板特化类模板特化

2.2 函数模版特化

函数模板的特化步骤:

🥑1.必须要先有一个基础的函数模板
🥑2.关键字template后面接一对空的尖括号<>
🥑3.函数名后跟一对尖括号尖括号中指定需要特化的类型
🥑4.函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}// 对Less函数模板进行特化
template<> //template<>不能省略
bool Less<Date*>(Date* left, Date* right)
{return *left < *right;
}int main()
{cout << Less(1, 2) << endl;Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << Less(d1, d2) << endl;Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl;  //调用特化之后的版本,而不走模板生成的return 0;
}

2.2.1 函数模版特化为指针类型注意事项

假设有如下一个函数模版:

template<class T>
bool Less(const T& left, const T& right)
{return left < right;
}

注意:上面的函数模版的模版参数是引用类型,并且是const修饰的。加引用是为了减少拷贝,因为我们只是要比较传过来的两个参数的大小而已,如果传的参数是自定义类型,且该自定义类型的对象又比较大时,用引用传参最合适不过。那加const修饰的原因是:我们并不想修改对象的值,所以加const修饰以防对象被修改。

如果我们想对某些特殊类型的变量/对象进行比较,那我们可以对函数模版进行特化,比如针对int类型进行特化:

template<>
bool Less<int>(const int& left, const int& right)
{return left < right;
}

这样的话我们传两个整型给Less函数的话就会调特化版本的函数模版进行比较。那如果这里特化的参数类型是指针可以吗?

template<>
bool Less<int*>(const int*& left, const int*& right)
{return left < right;
}
int main()
{int a = 2;int b = 5;int* pa = &a;int* pb = &b;Less(pa, pb);return 0;
}

程序运行报错:
在这里插入图片描述这里报错的原因是:原基础模版中const修饰的其实是T的引用(T的别名)。比如:T是int类型的话,那const修饰的就是int类型的变量。那如果这里T是一个指针类型,比如int*类型,则const修饰的就是int*类型的指针变量;注意const不是修饰指针指向的内容,而是修饰指针变量本身。所以正确的特化写法应该是如下形式:

template<>
bool Less<int*>(int* const& left, int* const& right)
{return left < right;
}

即将const放在int*的后面,修饰的就是指针变量本身。所以如果特化的参数是由const修饰并且加了引用的指针变量,如果不能正确理解const修饰的是谁?就可能会出问题。所以建议如果是遇到上面的函数模版,你又要通过传指针去比较指针指向的内容时,可以针对这个特定的指针类型直接将函数定义出来,而不用定义函数模版或特化。比如你要比较两个int*类型的指针指向的内容:

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

当你传的是int*类型的参数时,编译器会直接匹配上面的这个函数。

注意: 一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单,通常都是将该函数直接给出

2.3 类模版特化

2.3.1 全特化

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

//类模板的特化也要先有一个基础的类模版才可以
template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};//全特化
template<> //template<>不能省略
class Data<int, char>
{
public:Data() { cout << "Data<int, char>" << endl; }
private:int _d1;char _d2;
};int main()
{Data<int, int> d1;Data<int, char> d2;
}

程序运行结果:
在这里插入图片描述

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

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

//两个参数偏特化为指针类型 
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:Data(const T1& d1, const T2& d2): _d1(d1), _d2(d2){cout << "Data<T1&, T2&>" << endl;}
private:const T1& _d1;const T2& _d2;
};
void test()
{Data<double, int> d1; //调用特化的int版本Data<int, double> d2; //调用基础的模板Data<int*, int*> d3; //调用特化的指针版本Data<int&, int&> d4(1, 2);  //调用特化的引用类型版本
}

程序运行结果:
在这里插入图片描述这里就可以看到,当所给的参数不同时,编译器会根据你所给的参数类型去推演匹配最适合的特化的类模版。

2.3.3 类模板特化应用示例

设计如下专门用来按照小于比较的类模板Less(函数对象):

template<class T>
struct Less
{bool operator()(const T& x, const T& y) const{return x < y;}
};int main()
{Date d1(2022, 7, 7);Date d2(2022, 7, 6);Date d3(2022, 7, 8);vector<Date> v1;v1.push_back(d1);v1.push_back(d2);v1.push_back(d3);//可以直接排序,结果是日期升序sort(v1.begin(), v1.end(), Less<Date>());vector<Date*> v2;v2.push_back(&d1);v2.push_back(&d2);v2.push_back(&d3);// 可以直接排序,结果错误日期还不是升序,而v2中放的地址是升序// 此处需要在排序过程中,让sort比较v2中存放地址指向的日期对象// 但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期sort(v2.begin(), v2.end(), Less<Date*>());return 0;
}

通过观察上述程序的结果发现,对于日期类对象可以直接排序,并且结果是正确的。但是如果待排序元素是指针,结果就不一定正确。因为sort最终是按照Less模板中的方式进行比较的,所以只会比较指针,而不是比较指针指向空间中的内容,此时可以使用类版本特化来处理上述问题:

//对Less类模板按照指针方式特化
template<>
struct Less<Date*>
{bool operator()(Date* x, Date* y) {return *x < *y;}
};

三、模版分离编译

3.1 什么是分离编译?

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

3.2 模版的分离编译

假如有以下场景,模板的声明定义分离开,在头文件中进行声明,在源文件中完成定义:

// a.h
template<class T>
T Add(const T& left, const T& right);//a.cpp
#include"a.h"
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}// test.cpp(main函数里调用Add)
#include"a.h"
int main()
{Add(1, 2);Add(1.0, 2.0);return 0;
}

【分析】

C/C++程序要运行,一般要经历一下步骤:
预处理—> 编译—>汇编—>链接
编译: 对程序按照语言特性进行词法、语法、语义分析,程序检查无误后生成汇编代码。注意头文件不参与编译,编译器对工程中的多个源文件是分离开单独编译的。
链接: 将多个obj文件合并成一个,并处理没有解决的地址问题

在这里插入图片描述上面就解释了为什么会报链接错误,就是因为a.cpp这个源文件在汇编出的.obj文件中没有生成Add函数的地址(因为Add只是定义了函数模版,并没有实例化,所以汇编时不会生成Add函数的地址)

3.3 解决方法!

1.将声明和定义放到一个文件"xxx.hpp"里面或者xxx.h其实也是可以的。推荐使用这种。(.hpp为后缀的文件其实跟.h是差不多的,只是.hpp的文件里是既有声明也有定义)

//a.h或a.hpp
//函数声明
template<class T>
T Add(const T& left, const T& right);template<class T>
class stack
{
public://类的成员函数声明void push(const T& x);void pop();
private:T* _a = nullptr;size_t _size = 0;size_t _capacity = 0;
};//函数定义
template<class T>
T Add(const T& left, const T& right)
{cout << "T Add(const T& left, const T& right)" << endl;return left + right;
}//类的成员函数定义
template<class T>
void stack<T>::push(const T& x)
{cout << "void stack<T>::push(const T& x)" << endl;
}template<class T>
void stack<T>::pop()
{cout << "void stack<T>::pop()" << endl;
}
//test.cpp
#include"a.h"
int main()
{Add(1, 2);Add(1.0, 2.0);stack<int> st1;st1.push(1);st1.pop();stack<double> st2;st2.push(2);st2.pop();return 0;
}

程序运行结果:
在这里插入图片描述可以看到如果将声明和定义都放在.h(或.hpp)的文件中,就可以实现在包含这个头文件的.cpp文件里展开,这样编译汇编的时候,就直接确定了这个函数的地址,那在链接的时候就不用再去找这个函数的地址了。

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

//a.cpp
#include"a.h"
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}//显式实例化
template //template不能省略
int Add(const int& left, const int& right);template
double Add(const double& left, const double& right);//对于定义在.cpp中的类模板显式实例化也是类似的
template<class T>
class stack
{
public:void push(const T& x);void pop();
private:T* _a = nullptr;size_t _size = 0;size_t _capacity = 0;
};//显式实例化
template //template不能省略
stack<int>;template
stack<double>;

四、模版总结

🍋【优点】
1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
2. 增强了代码的灵活性

🍊【缺陷】
1. 模板会导致代码膨胀(针对不同的类型会实例化出一份类型或函数)问题,也会导致编译时间变长
3. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux配置yum仓库,服务控制,防火墙

一、yum仓库 1.在安装软件时&#xff0c;首先第一步就是要考虑软件的版本的问题&#xff01; 2.软件的安装&#xff1a;最安全可靠的方法就是去软件对应的官网上查看安装手册&#xff08;包括的软件的下载&#xff09; 红帽系软件安装的常见的3种方式 &#xff08;1&#x…

布谷直播系统源码开发实战:从架构设计到性能优化

作为山东布谷科技的一名技术研发人员&#xff0c;我参与了多个直播系统平台从0到1的开发和搭建&#xff0c;也见证了直播行业从萌芽到爆发的全过程。今天&#xff0c;我想从研发角度&#xff0c;分享一些直播系统软件开发的经验和心得&#xff0c;希望能对大家有所帮助。 一、 …

实战设计模式之解释器模式

概述 作为一种行为设计模式&#xff0c;解释器模式提供了一种方法来定义语言的文法规则&#xff0c;并通过这些规则解析和处理特定类型的语言句子。简单来说&#xff0c;解释器模式允许我们定义一个代表某种语言中语法规则的对象结构&#xff0c;从而能够根据这些规则理解并处理…

物联网边缘计算网关是什么?

在物联网的浩瀚架构中&#xff0c;边缘计算网关宛如一位坚毅的前沿哨兵&#xff0c;默默守护着数据处理与传输的关键防线&#xff0c;为整个物联网系统的高效运转发挥着不可或缺的作用。 一、边缘计算网关的定义与基本功能 边缘计算网关是一种智能设备&#xff0c;它被部署在…

计算机视觉算法实战——障碍物识别(主页有源码)

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​​ ​​​​​​ ​ ​ 1. 引言 计算机视觉是人工智能领域的一个重要分支&#xff0c;旨在通过计算机模拟人类的视觉系统&#xff0c;从…

Win11锁屏后显示“天气、市场、广告”如何取消显示

关闭方法&#xff1a;设置>个性化>锁屏界面>锁屏界面状态>"无"。 方法一&#xff1a;通过“个性化”设置 打开“设置”应用&#xff1a; 点击屏幕左下角的“开始”按钮&#xff08;Windows 图标&#xff09;。点击齿轮状的“设置”图标。或者按下 Win I…

10天速通强化学习-008

TRPO 思考-TRPO-在线策略-给定信任区域防止更新不稳定 Actor-Critic网络随着网络深度的增加&#xff0c;步长太长&#xff0c;梯度更新会变差。改变方法-增加信任区域。(trust region policy optimization)-TRPO算法&#xff1a; 核心思想&#xff1a; 是在每次迭代中&…

整合百款经典街机游戏的模拟器介绍

对于80、90后而言&#xff0c;街机游戏承载着童年的欢乐记忆。今天要给大家介绍一款超棒的软件——「MXui街机厅经典游戏101款」&#xff0c;它能带你重回那段热血沸腾的街机时光。 「MXui街机厅经典游戏101款」是一款绿色免安装的街机模拟器&#xff0c;体积约1.39G。无需繁琐…

springboot第三站(1) web开发引入

目录 1.简介 2.SpringBoot对静态资源的映射规则 3.模版引擎 1.简介 使用SpringBoot&#xff1b; 1&#xff09;、创建SpringBoot应用&#xff0c;选中我们需要的模块&#xff1b; 2&#xff09;、SpringBoot已经默认将这些场景配置好了&#xff0c;只需要在配置文件中指定…

12-二叉树-二叉树高度(给定前序和中序确定二叉树)

题目 来源 23. 二叉树的高度 思路 其实跟09那篇很像&#xff0c;反正核心就是要通过前序和中序来建树&#xff0c;只不过现在多了一个返回值&#xff1b;因为建树的时候&#xff0c;其实左子树和右子树的深度就可以知道。其余详见代码。 代码 /* 前序遍历根左右,中序&…

PSI5接口

文章目录 前言PSI5接口简介操作模式命名规则异步操作模式&#xff08;PSI5-A&#xff09;同步操作模式&#xff08;PSI5-P&#xff09; 传感器->ECU物理层&#xff08;位编码&#xff09;数据链路层数据帧帧格式串行消息帧10bits 传感器帧定义超10bits传感器帧定义 ECU->…

垃圾处理全流程监管平台

在当前城市化进程中&#xff0c;垃圾处理已成为城市管理的重要课题。随着技术的发展&#xff0c;垃圾处理全流程监管平台的建设显得尤为重要。该平台能够实现垃圾从产生、收集、运输到最终处理的全流程监管&#xff0c;提高垃圾处理效率&#xff0c;促进资源回收利用&#xff0…

【Linux编程】IPC之消息队列从踩坑到实战:核心原理、实战案例与C++封装详解(含完整代码)

一、消息队列基础概念 消息队列是Linux系统提供的一种进程间通信&#xff08;IPC&#xff09;机制&#xff0c;具有以下特点&#xff1a; 消息以链表形式存放在内核中每个消息包含类型标识&#xff08;mtype&#xff09;支持多生产者/多消费者模式消息总长度受限于系统配置&a…

Unity 项目工程结构目录

1. Unity.VisualScripting.Core 作用: Visual Scripting 的核心模块&#xff0c;提供了可视化编程的基础功能&#xff08;前身为 Bolt&#xff09;。它允许开发者通过节点图创建游戏逻辑&#xff0c;而无需编写代码。 典型用途: 非程序员快速构建原型&#xff0c;或简化…

从pdf提取文本数据的c/cpp库(非OCR)

Aspose.PDF for C 商业付费版&#xff0c;无源码。 功能强大&#xff0c;支持多种PDF操作。 对应的官方示例代码&#xff1a;Aspose.PDF-for-C Spire.PDF for C 商业付费版 对应的官方示例代码&#xff1a;Spire.PDF-for-C- PDFTron SDK 商业付费版 PoDoFo 开源 当前版本…

【Linux操作系统——学习笔记二】Linux简单导航命令操作

一、前言 学习Linux&#xff0c;本质上是学习在命令行下熟练使用Linux的各类命令。 命令行&#xff1a;是一种通过输入命令和参数与计算机系统进行交互的方式&#xff0c;可以使用各种字符化命令对系统发出操作指令&#xff0c;打开Linux终端&#xff0c;进入命令行界面。 …

赛逸展2025创新模式,以科技创新奖赋能展位战略价值

CES Asia2025第七届亚洲消费电子技术贸易展&#xff08;赛逸展&#xff09;主办方负责人提出的创新理念&#xff0c;为展会的战略价值注入了新活力&#xff1a;“我们不是在卖展位&#xff0c;而是在分发政策红利入场券——企业每平方米的展位投入&#xff0c;都可能通过科技创…

深度革命:ResNet 如何用 “残差连接“ 颠覆深度学习

一文快速了解 ResNet创新点 在深度学习的历史长河中&#xff0c;2015年或许是最具突破性的一年。这一年&#xff0c;微软亚洲研究院的何恺明团队带着名为ResNet&#xff08;残差网络&#xff09;的模型横空出世&#xff0c;在ImageNet图像分类竞赛中以3.57%的错误率夺冠&#…

将Django连接到mysql

将Django连接到mysql 文章目录 将Django连接到mysql一.按照我的文章 在Django模型中的Mysql安装 此篇 的步骤完成mysql的基础配置二.Django配置 一.按照我的文章 ‘在Django模型中的Mysql安装’ 此篇 的步骤完成mysql的基础配置 基础配置具体内容 1.打开PowerShell 安装mysql的…

Pycatia自动化开发:智能焊点生成与数据管理一体化解决方案

引言&#xff1a;机械设计自动化的新范式 在汽车白车身、航空结构件等复杂装配体设计中&#xff0c;焊点定位精度直接影响产品性能和制造可行性。传统CAD软件操作模式存在两大痛点&#xff1a;1&#xff09;重复性点创建操作效率低下&#xff1b;2&#xff09;坐标数据缺乏结构…