当前位置: 首页 > news >正文

【C++进阶】模板进阶

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


前言

初阶模板地址:点击跳转

目录

  • 前言
  • 一、typename和class的区别
  • 二、非类型模板参数
      • 2.1 概念
      • 2.2 实际例子:array容器
  • 三、模板特化
      • 3.1 概念
      • 3.2 函数模板特化
      • 3.3 类模板特化
        • 3.3.1 全特化
        • 3.3.2 偏特化
  • 四、模板分离编译问题
      • 4.1 什么是分离编译
      • 4.2 模板的分离编译
      • 4.3 解决方法
  • 五、小结

一、typename和class的区别

在以前博客我们说过,定义模板参数关键字可以用typename,也可以用class,它们是没有区别的,可是真的没有区别吗?来看看以下这个例子

假设要打印容器的数据,要封装一个打印函数(以迭代器的方式)

#include <iostream>
#include <vector>
using namespace std;void Print(const vector<int>& v)
{vector<int>::const_iterator it = v.begin();while (it != v.end()){cout << *it << ' ';it++;}cout << endl;
}int main()
{vector<int> v{ 10,20,30,40,50 };Print(v);return 0;
}

【输出结果】

在这里插入图片描述

以上代码虽然可以正常输出,但它只能打印vector<int>类型容器的数据,若要打印vector<double>,又或者是list容器的数据,那么这样就写死了,有人想可以用函数模板。

代码如下:

#include <iostream>
#include <vector>
using namespace std;// 把class换成typename也是可以的
template<class Container>
void Print(const Container& v)
{Container::const_iterator it = v.begin();while (it != v.end()){cout << *it << ' ';it++;}cout << endl;
}int main()
{vector<int> v{ 10,20,30,40,50 };Print(v);return 0;
}

【输出结果】

在这里插入图片描述

使用函数模板后发现以上代码报错了,提示说需要在Container前加上typename

在这里插入图片描述

那么为什么必须要加上typename呢?

这是因为编译器在编译的时候是从上往下的,当编译到Container::const_iterator it时,Container还没实例化,那么此时编译器区分不了Container是类型还是类对象(静态成员变量Container::const_iterator)。vector<int>是实例化出来的,加上域作用限定符::去找其内嵌类型(迭代器),所以不会报错。因此,编译器要求加上typename告诉Container是类型。

二、非类型模板参数

2.1 概念

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

  • 类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。

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

举个例子:定义一个模板类型的静态数组

#include <iostream>
#include <vector>
using namespace std;// size_t N = 10 - 非类型形参
template<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. 必须是整型。整型家族有:charshortboolintlonglong long

因此可以得出,非类型模板参数一般是用来定义一个数组的大小的

2.2 实际例子:array容器

在这里插入图片描述

在C++11标准中,引入了一个容器array,它的底层使用了非类型模板参数,是一个真正意义上的泛型数组,这个是用来对标传统数组的。

#include <iostream>
#include <array>
using namespace std;int main()
{int arr[10] = { 0 }; //传统数组array<int, 10> _array; //array容器// 读arr[12];	  _array[12];// 写arr[12] = 0;	_array[12] = 10;return 0;
}

对比传统数组:

  • array也并没有进行初始化。
  • array新数组对于越界读、写检查更为严格。传统数组越界读写,不会发生报错;而array数组则会报错。

虽然对越界行为检查严格 ,但在实际开发中,很少使用array容器,因为它对标传统数组,连初始化都没有,而vector也是类似于数组的容器,在功能和实用性上可以全面碾压,并且 array使用的是栈区上的空间,会存在栈溢出问题,因此可以说array是一个鸡肋的容器。

三、模板特化

3.1 概念

模板特化顾名思义就是对模板(泛型思想)的特殊化处理 。模板特化中分为函数模板特化与类模板特化

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

比如:实现一个专门用来进行小于比较的函数模板

#include <iostream>
using namespace std;template<class T>
bool Less(T x, T y)
{return x < y;
}int main()
{int a = 1;int b = 2;cout << Less(a, b) << endl; // 可以比较,结果正确int* p1 = &a;int* p2 = &b;cout << Less(p1, p2) << endl; // 可以比较,结果错误return 0;
}

【输出结果】

在这里插入图片描述

上述示例中,p1指向的a显然小于p2指向的b,但是Less内部并没有比较p1p2指向的对象内容,而比较的是p1p2指针的地址。

因此,就需要对模板进行特化。即在原模板函数的基础上,针对特殊类型所进行特殊化的实现方式。

3.2 函数模板特化

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参必须要和基础的模板函数的基础参数类型完全相同
#include <iostream>
#include <vector>
using namespace std;// 必须要先有一个基础的函数模板
template<class T>
bool Less(T x, T y)
{return x < y;
}// 对Less函数模板进行特化
template<> // 关键字template后面接一对空的尖括号<>
// 函数名后跟一对尖括号,尖括号中指定需要特化的类型
bool Less<int*>(int* x, int* y) // 函数形参必须要和基础的模板函数的基础参数类型完全相同
{return *x < *y;
}int main()
{int a = 1;int b = 2;cout << Less(a, b) << endl; // 可以比较,结果正确int* p1 = &a;int* p2 = &b;cout << Less(p1, p2) << endl; // 可以比较,结果错误return 0;
}

【输出结果】

在这里插入图片描述

不过对于函数模板特化来说,存在一个更加方便的东西:函数重载同样也能解决特殊需求

bool Less(int* x, int* y)
{return *x < *y;
}

3.3 类模板特化

模板特化主要用在类模板中,它可以在泛型思想之上解决大部分特殊问题,并且类模板特化还可以分为:全特化偏特化

3.3.1 全特化

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

注意:在进行全特化前

  1. 需要存在最基本的泛型模板
  2. 全特化模板中的模板参数可以不用写
  3. 需要在类名之后,指明具体的参数类型,否则无法实例化出对象
// 原模板
template<class T1, class T2>
class Test
{
public:Test(const T1& t1, const T2& t2):_t1(t1), _t2(t2){cout << "Test(const T1& t1, const T2& t2)" << endl;}private:T1 _t1;T2 _t2;
};// 全特化后的模
// 将原模板参数列表中所有的参数都确定化
template<>
class Test<int, char>
{
public:Test(const int& t1, const char& t2):_t1(t1), _t2(t2){cout << "Test<int, char>" << endl;}private:int _t1;char _t2;
};int main()
{Test<int, int> T1(1, 2);Test<int, char> T2(20, 'c');return 0;
}

调用时会优先选择更为匹配的类模板

在这里插入图片描述

3.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;
};
  • 参数更进一步的限制

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

借助偏特化解决指针无法正常比较问题

class Date
{
public:Date(int year = 1970, 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);}private:int _year;int _month;int _day;
};//原来的比较模板
template<class T>
class Less
{
public:bool operator()(T x, T y) const{return x < y;}
};//偏特化后的比较模板
template<class T>
class Less<T*>
{
public:bool operator()(T* x, T* y) const{return *x < *y;}
};int main()
{Date d1 = { 2018, 4, 10 };Date d2 = { 2023, 5, 10 };cout << "d1 < d2: " << Less<Date>()(d1, d2) << endl;cout << "&d1 < &d2: " << Less<Date*>()(&d1, &d2) << endl;int a = 1;int b = 2;cout << "&a < &b: " << Less<int*>()(&a, &b) << endl;return 0;
}

在这里插入图片描述

四、模板分离编译问题

4.1 什么是分离编译

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

4.2 模板的分离编译

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

在这里插入图片描述

【程序结果】

在这里插入图片描述

出现了链接错误!!!

【分析】

代码从文本变为可执行程序所需要的步骤:

  1. 预处理:头文件展开、宏替换、条件编译、删除注释,生成纯净的C/C++代码
  2. 编译:语法/词法/语义分析,错误检查无误后生成汇编代码。注意:头文件不参与编译,编译器对工程中的多个源文件是分离开并且单独编译的
  3. 汇编:生成符号表,生成二进制指令
  4. 链接:将符号表进行合并,并处理地址问题

当模板的声明和定义分离时,在realize.cpp中,由于是 泛型,编译器无法确定函数原型(实例化),因此无法生成函数,也就无法获得函数地址,在进行链接时,无法在符号表中找到目标地址进行跳转,导致链接错误

除了模板以外,还有一个很常见的连接错误,有函数声明,却没有定义

在这里插入图片描述

4.3 解决方法

  1. 将声明和定义放到一个文件(推荐使用这种)
template<class T>
T add(const T x, const T y)
{return x + y;
}
  1. 在函数定义时进行模板特化,编译时生成地址以进行链接(不推荐使用)
template<>
int add(const int x, const int y)
{return x + y;
}

五、小结

模板的优点:

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

模板的缺陷:

  1. 模板会导致代码膨胀问题,也会导致编译时间变长
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

相关文章:

【C++进阶】模板进阶

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…...

Vim如何清空文件

在Vim中&#xff0c;可以使用以下命令清空文件内容&#xff1a; 打开需要清空的文件&#xff1a;在终端中输入vim filename打开文件&#xff0c;其中filename是你要编辑的文件名。 进入命令模式&#xff1a;按下键盘上的Esc键&#xff0c;确保处于Vim的命令模式。&#xff08;…...

问道管理:什么信号?煤飞色舞钢花溅

近期重磅利好不断&#xff0c;对应到A股商场&#xff0c;究竟哪个板块最获益&#xff0c;商场讨论热烈。 地产分析师&#xff1a;方针力度超预期&#xff0c;主张加仓。 银行分析师&#xff1a;存量房贷对银行股心情上的压制完毕&#xff0c;值得重视。 消费分析师&#xff…...

C# PaddleDetection yolo 印章检测

效果 项目 代码 using OpenCvSharp; using OpenCvSharp.Extensions; using Sdcb.PaddleDetection; using Sdcb.PaddleInference; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq…...

常用框架分析(7)-Flutter

框架分析&#xff08;7&#xff09;-Flutter 专栏介绍Flutter核心思想Flutter的特点快速开发跨平台高性能美观的用户界面 Flutter的架构框架层引擎层平台层 开发过程使用Dart语言编写代码编译成原生代码热重载工具和插件 优缺点优点跨平台开发高性能美观的用户界面热重载强大的…...

清空 Docker 容器的日志文件

删除容器中netcore控制台存储到docker日志记录 在shell命令下执行如下语句&#xff1a; docker ps -aq | xargs docker inspect --format{{.LogPath}} | xargs truncate -s 0 这个命令会执行以下操作&#xff1a; docker ps -aq&#xff1a;列出所有容器的ID&#xff08;包括…...

01-虚拟机安装Windows Server操作系统

1、创建并配置虚拟机 2、安装操作系统 找到windows Server镜像 等待安装 3、设置密码...

应用案例 | 基于三维机器视觉的机器人麻袋拆垛应用解决方案

​Part.1 项目背景 在现代物流和制造行业中&#xff0c;麻袋的拆垛操作是一个重要且频繁的任务。传统的麻袋拆垛工作通常由人工完成&#xff0c;分拣效率较低&#xff0c;人力成本较高&#xff0c;现场麻袋堆叠、变形严重&#xff0c;垛型不规则、不固定&#xff0c;严重影响分…...

1018 Public Bike Management 结题记录(dfs剪枝)

个人觉得直接放入代码是最管用的。 其他方法类似&#xff0c;题意请参考其他博主。 #include <bits/stdc.h> using namespace std; const int N 1e4 50;int maxn 2000000000; int c, n, ed, s[N], m, minlen, needn, backn, pre[N]; bool flag, book[N]; vector<p…...

C++ deque底层原理

deque底层原理 一、目的二、底层实现三、原理图四、类结构五、push_back六、pop_back 一、目的 实现双端数组 二、底层实现 双向开口的连续线性空间 三、原理图 四、类结构 class deque : protected Deque base _Deque_base._Deque_impl M_map 指针数组 _M_map_size …...

打破对ChatGPT的依赖以及如何应对ChatGPT的错误和幻觉

​ OpenAI的ChatGPT是第一个真正流行的生成式AI工具&#xff0c;但它可能不是最好的。现在是时候扩大你的AI视野了。 ChatGPT成为了基于大语言模型(LLM)的聊天机器人的同义词。但是现在是时候停止对ChatGPT的痴迷&#xff0c;开始发现这个新世界中强大的替代品了。 首先&a…...

【git】【IDEA】在idea中使用git

目录 一、 在IDEA中配置git 二、 获取git仓库 2.1 本次初始化仓库 2.2 从远程仓库克隆 三、 本地仓库操作 3.1 将文件加入暂存区 3.2 将暂存区的文件提交到版本库 3.3 快捷键 使用快捷键 实现加入到暂存区与提交到版本库 3.4 查看日志 Show History 四、 远程仓库操…...

【设计模式】装饰者模式

目录 一、定义二、结构三、优点四、使用场景五、代码示例六、截图示例 一、定义 1.在不改变现有对象结构的情况下&#xff0c;动态给该对象添加额外功能的模式 2.类B继承于类A&#xff0c;并将类A作为B类的属性&#xff08;B类聚合A类&#xff09; 3.BufferedInputStream、Buff…...

open cv快速入门系列---数字图像基础

目录 一、数字图像基础 1.1 数字图像和图像单位 1.2 区分图片分辨率与屏幕分辨率 1.3 图像的灰度与灰度级 1.4 图像的深度 1.5 二值图像、灰度图像与彩色图像 1.6 通道数 二、数字图像处理 2.1 图像噪声及其消除 2.2 数字图像处理技术 2.2.1 图像变换 2.2.2 图像增强…...

基础知识回顾:借助 SSL/TLS 和 NGINX 进行 Web 流量加密

原文作者&#xff1a; Robert Haynes 原文链接&#xff1a; 基础知识回顾&#xff1a;借助 SSL/TLS 和 NGINX 进行 Web 流量加密 NGINX 唯一中文官方社区 &#xff0c;尽在 nginx.org.cn 网络攻击者肆无忌惮、作恶多端&#xff0c;几乎每天都有网络入侵、数据窃取或勒索软件攻击…...

iPhone 14 Plus与iPhone 14 Pro:你应该买哪一款

又到了iPhone季,这意味着你可能会在几种不同的机型之间左右为难,无法决定买哪一款。更令人困惑的是,苹果推出的iPhone变体——iPhone 14 Plus,只比老款iPhone 14 Pro低100美元。 有这么多选择,你可能想知道哪款iPhone最适合你。你应该买一部大屏幕的iPhone 14 Plus并节省…...

操作系统清华同步笔记:定义概述+计算机内存和硬盘布局+启动流程顺序+中断、异常和系统调用

定义概述 从用户角度来看&#xff0c;操作系统是一个控制软件&#xff0c;用以管理应用程序&#xff0c;为应用程序提供服务&#xff0c;杀死应用程序等。从内部文件角度来看&#xff0c;操作系统是一个资源管理器&#xff0c;用以管理外设&#xff0c;分配资源。层次结构&…...

uniapp 配置并使用 VueX

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 uni-app 内置了 VueX 1、创建需要的文件 右键点击 根目录【我的是 uni-shop】&#xff0c;然后新建 目录&a…...

vue v-on 艾特@

vue v-on 内联代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</titl…...

【Ajax】发送跨域的POST请求时,浏览器会先发送一次OPTIONS请求,然后才发送原本的POST请求

当发送跨域的POST请求时&#xff0c;浏览器会先发送一次OPTIONS请求&#xff0c;这是因为浏览器的同源策略。OPTIONS请求被称为预检请求(pre-flight request)&#xff0c;它是CORS(跨源资源共享)机制中的一部分。 预检请求的目的是为了确保实际请求&#xff08;例如POST、PUT等…...

第19节 Node.js Express 框架

Express 是一个为Node.js设计的web开发框架&#xff0c;它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用&#xff0c;和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

Linux链表操作全解析

Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表&#xff1f;1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密

在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

ServerTrust 并非唯一

NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列&#xff0c;以便知晓哪些列包含有价值的数据&#xff0c;…...

DBLP数据库是什么?

DBLP&#xff08;Digital Bibliography & Library Project&#xff09;Computer Science Bibliography是全球著名的计算机科学出版物的开放书目数据库。DBLP所收录的期刊和会议论文质量较高&#xff0c;数据库文献更新速度很快&#xff0c;很好地反映了国际计算机科学学术研…...

spring Security对RBAC及其ABAC的支持使用

RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型&#xff0c;它将权限分配给角色&#xff0c;再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...