C++初阶学习——模版进阶

news2024/9/22 10:14:55

1. 非类型模板参数

模板参数分类类型形参与非类型形参。

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

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

1.1使用方法

首先讲一下为什么会引入这个功能

如果单个参数的情况下,st1想要十个空间,st2想要一百,这是用宏做不到的

我们可以通过非类型模版参数实现

1.2使用时的注意事项 

只能将 整型家族 类型作为非类型模板参数,其他类型不在标准之内

非类型模板参数必须为常量(不可被修改),且需要在编译阶段确定结果

整型家族:

char、short、bool、int、long、long long 等

注意:非类型模版参数的常量是按需实例化的

在函数中修改了常量是检查不出错误的

调用修改常量的函数才会报错 

1.3举例说明

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

array 是泛型编程思想中的产物,支持了许多 STL 容器的功能,比如 迭代器 和 运算符重载 等实用功能,最主要的改进是严格检查越界行为

但实际开发中,很少使用 array,因为它对标传统数组,连初始化都没有,vector 在功能和实用性上可以全面碾压,并且 array使用的是栈区上的空间,存在栈溢出问题,可以说 array 是一个鸡肋的容器 

namespace bite
{
 // 定义一个模板类型的静态数组
 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;
 };
}

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

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

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

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<> 
class Data<int, char>
{
public:
 Data() {cout<<"Data<int, char>" <<endl;}
private:
 int _d1;
 char _d2;
};
 
void TestVector()
{
 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; 
 };



运行试试看 :

2.3.3 类模板特化应用示例

在上次模拟priority_queue的时候

C++初阶学习——探索STL奥秘——标准库中的priority_queue与模拟实现

如果调用默认的仿函数Less,如果push的是Data类的地址,每次pop出来的数据都是
随机的,并不是按大小排序的,因为比较的是地址,地址是随机的。 

我们通过再次构建一个仿函数实现Date类型大小的控制

但是每次创建对象的时候,都要输入一长串

 所以我们直接使用模板的特化就行了 

 3 模板分离编译

3.1 什么是分离编译

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

3.2 模板的分离编译

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

 

会发现类模版对象调用的函数push还有函数func1、func2的函数链接失败报错

 

编译阶段会符号汇总,这个符号就是指变量名和函数名

函数只看声明,声明是一种承诺

所以编译检查声明函数名参数返回可以对上

汇编的时候会生成符号表,符号表有函数已经函数的地址,如果函数没有地址符号表就会自动生成一个无效的地址

拿着修饰后的函数去其他文件进行符号表合并就会链接跳转到函数的地址,如果地址无效就会报错

如上图所示,push与func2都没有地址,所以会链接错误

所以主要问题是:

为什么size和top这两个函数都有地址呢?--------------------这个答案在文章3.3解决方法末尾会讲

fuc2()只有声明没有定义,所以没地址,这很正常。

但是为什么push没地址呢?

主要原因是模版要实例化才能生成地址

模版实例化指的是创建一个模板定义的具体版本

如何解决声明与定义分离,让分离编译的类模版的成员函数实现模版实例化呢

3.3解决方法

这是模版的显式实例化,这样可以让这个模版函数在链接的时候生成地址,然后编译成功

可以理解为 :为模版类的成员函数,提供指定类型的模版参数 

但是很麻烦的一点是:

我们每创建不同类型的模版对象的时候调用其成员函数的时候,都要在那个成员函数所在的文件中进行模版显式实例化

所以最好的解决方法就是:

 

所以将定义与声明放在同一个文件中就不用特地的去进行模板的显示实例化 

stl库也用这种方法将声明和定义分开

这里size和top还有push都可以正常执行

因为是预处理的时候头文件展开

类模版创建对象,此时对象进行了隐式实例化

然后类的成员函数声明和定义都在头文件中,都跟着类的对象进行了隐式实例化

4. 模板总结

【优点】

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

2. 增强了代码的灵活性

【缺陷】

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

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

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

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

相关文章

不同编程语言的互相调用

C语言与Python 步骤&#xff1a; 编写C代码 (hello_c.c): #include <stdio.h>void printHello(const char *name) {printf("Hello, %s!\n", name); }编译C代码为共享库: gcc -shared -o hello_c.so hello_c.c编写Python代码 (hello_c_py.py): import ctypes# …

【数据库】常用数据库简介

目录 &#x1f354; 常用的关系型数据库 &#x1f354; Mysql简介 &#x1f354; SQL 简介 SQL语句的分类 SQL 写法 SQL 常用的数据类型 &#x1f354; DDL语句 对数据库的操作 对数据表的操作 &#x1f354; DML语句 插入数据 insert into 修改数据 update 删除数…

<<编码>> 第 16 章 存储器组织(3)--3-8 译码器 示例电路

3-8 译码器 info::操作说明 “写入” 开关先断开(Q 为低电平, 表示不写入) S2-S1-S0 设置一个二进制数, 选中 Q0~Q7 其中一个作为 Q 的输出 “数据输入” 端置入要保存的数(0或1) 闭合 “写入” 开关, 对应的一位锁存器中的 W 为高电平, 表示可以写入, “数据输入” 的值最终…

Java安全(加密+HTTPS+WEB安全)

Java加密 单向加密 接收一段明文&#xff0c;然后以一种不可逆的方式将它转换成一段密文 ①、MD5&#xff0c;将无论多长的数据最后编码128位数据&#xff0c;常用文件校验、密码加密、散列数据 byte[] data ...;//明文数据 MessageDigest md5 MessageDigest.getInstance…

大数据新视界 --大数据大厂之Kubernetes与大数据:容器化部署的最佳实践

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

【软件资料集】系统培训方案(Word项目参考2024)

1. 培训概述 2. 培训目的 3. 培训对象及要求 3.1. 培训对象 3.2. 培训人员基本要求 4. 培训方式 5. 培训内容 6. 培训讲师 7. 培训教材 8. 培训质量保证 8.1. 用户培训确认报告 8.2. 培训疑问解答 获取方式&#xff1a;本文末个人名片直接获取。 软件资料清单列表部分文档清单&…

opencv实战项目二十四:棋盘格相机内参标定

文章目录 前言一、简介&#xff1a;二、使用步骤2.1制作标定板2.2 拍摄不同角度的标定板2.3计算棋盘格角点并优化2.4计算相机参数 三、整体代码实现&#xff1a; 前言 在数字图像处理和计算机视觉领域&#xff0c;相机标定是一个至关重要的步骤。它为相机提供了一个准确的数学…

Tomcat 乱码问题彻底解决

1. 终端乱码问题 找到 tomcat 安装目录下的 conf ---> logging.properties .修改ConsoleHandler.endcoding GBK &#xff08;如果在idea中设置了UTF-8字符集&#xff0c;这里就不需要修改&#xff09; 2. CMD命令窗口设置编码 参考&#xff1a;WIN10的cmd查看编码方式&am…

Nature|PathChat:病理学多模态生成性AI助手的创新与应用|顶刊精析·24-09-21

小罗碎碎念 今日顶刊&#xff1a;Nature 这篇文章今年6月就发表了&#xff0c;当时我分析的时候&#xff0c;还是预印本&#xff0c;没有排版。今天第一篇推文介绍的是Faisal Mahmood &#xff0c;所以又把这篇文章拉出来详细分析一下。 作者角色作者姓名单位名称单位英文名称第…

PMP--二模--解题--61-70

文章目录 4.整合管理61、 [单选] 为解决具有挑战性的客户请求&#xff0c;启动了一个项目。该项目必须在短时间内交付。项目经理应该怎么做来尽可能提高项目的成功率&#xff1f; 14.敏捷--MVP--最小可行产品--使用最小可行产品获得客户尽早地反馈。完整性和交付是主观的。团队…

构建高可用和高防御力的云服务架构第二部分:SLB负载均衡(2/5)

在现代云服务中&#xff0c;负载均衡&#xff08;Load Balancing&#xff09;是一种关键技术&#xff0c;用于优化资源利用、最小化响应时间、提高系统的可伸缩性和可靠性。负载均衡器位于客户端和服务器之间&#xff0c;根据预设的策略将请求分发到多个服务器上&#xff0c;以…

华为HarmonyOS地图服务 4 - 通过“地图相机“控制地图的可见区域

场景介绍 华为地图的移动是通过模拟相机移动的方式实现的,您可以通过改变相机位置,来控制地图的可见区域,效果如图所示。 本章节将向您介绍相机的各个属性与含义,并移动相机。 相机移动前 接口…

基于lnmp搭建wordpress

一、案例目标 &#xff08;1&#xff09;了解LNMP环境的组成。 &#xff08;2&#xff09;了解LNMP环境的部署与安装。 &#xff08;2&#xff09;了解WordPress应用的部署与使用。 二、节点规划 IP 主机名 节点 192.168.200.20 lnmp lnmp服务节点 三、案例实施 LN…

基于微信小程序的购物系统+php(lw+演示+源码+运行)

基于微信小程序的购物系统 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了基于微信小程序的购物系统的开发全过程。通过分析基于微信小程序的购物系统管理的不足&#xff0c;创建了一个计算机管理基于微信小…

1.Spring-容器-注册

一、Bean和获取Bean &#xff08;1&#xff09;创建IoC容器&#xff1a; SpringApplication.run(类名.class, args); ConfigurableApplicationContext ioc SpringApplication.run(Spring01IocApplication.class, args); &#xff08;2&#xff09;将对象注册到IoC容器中&am…

[Vue] 从零开始使用 Vite 创建 Vue 项目

&#x1f4da; 一、安装 Node.js Node.js 是一个运行 JavaScript 代码的 JavaScript 运行时&#xff0c;它允许我们在服务器端运行 JavaScript 代码。以下是安装 Node.js 的步骤&#xff1a; &#x1f310; 访问 Node.js 国内网站&#xff1a;https://nodejs.cn/ &#x1f4…

【如何在 Windows 10 主机上通过 VMware 安装 Windows 11 虚拟机,并共享主机网络】

环境说明 主机操作系统&#xff1a;Windows 10虚拟机操作系统&#xff1a;Windows 11虚拟机软件&#xff1a;VMware 步骤一&#xff1a;确保主机&#xff08;Windows 10&#xff09;网络连接正常 启动网络加速软件&#xff1a;在主机上启动软件&#xff0c;确保主机可以正常访…

基于LSTM的温度时序预测

1.背景 本文接【时序预测SARIMAX模型】 一文&#xff0c;采用LSTM模型进行平均温度数据预测。具体的背景和数据分析就不做重复说明&#xff0c;感兴趣可以去看上文即可。 2.LSTM模型 RNN&#xff08;Recurrent Neural Network&#xff0c;循环神经网络&#xff09;是一种特殊…

AI驱动TDSQL-C Serverless 数据库技术实战营-ai学生选课系统数据分析

以前用过腾讯的TDSQL-MYSQL&#xff0c;TBASE&#xff0c;最近了解到TDSQL-C serverless&#xff0c;本次试验结合的AI大模型驱动来学习实战TDSQL-C serverless&#xff0c;体验服务化的数据库&#xff0c;和一句简单描述进行学生选课系统数据分析&#xff1b; 我使用的分析数据…

C++初阶-list用法总结

目录 1.迭代器的分类 2.算法举例 3.push_back/emplace_back 4.insert/erase函数介绍 5.splice函数介绍 5.1用法一&#xff1a;把一个链表里面的数据给另外一个链表 5.2 用法二&#xff1a;调整链表当前的节点数据 6.unique去重函数介绍 1.迭代器的分类 我们的这个迭代器…