C++(模板进阶)


目录

前言:

本章学习目标:

1.非类型模版参数 

1.1使用方法 

1.2注意事项

1.3 实际引用

2.模版特化 

 2.1概念

2.2函数模板特化

 2.3类模板特化

2.3.1全特化

2.3.2偏特化 

 3.模版分离编译

​编辑 3.1失败原因

​编辑 3.2解决方案

4 总结 



前言:

本章节是在学习完STL之后,对高阶模版进行的总结,模板给泛型编程注入了灵魂,模板提高了程序的灵活性,模板包括:非类型模版参数、全特化、偏特化等,同时本文还会对模板声明和定义不能分离的问题做出介绍。

本章学习目标:

1. 非类型模板参数
2. 类模板的特化
3. 模板的分离编译

1.非类型模版参数 

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

1.1使用方法 

非类型模版参数 既然是使用常量作为参数,那么浮点数、类对象以及字符串是不允许作为非类型模版参数的,   非类型模版参数必须在编译期就能确认结果。

利用非类型模版参数创建一个可以自由调整大小的整形数组代码如下:

#include <assert.h>
using namespace std;


template <size_t N>
class arr
{
public:
	int& operator[](size_t pos)
	{
		assert(pos >= 0 && pos < N);
		return _arr[pos];
	}

	size_t size() const
	{
		return N;
	}

private:
	int _arr[N];
};
int main()
{
	arr<5> a1;
	arr<10> a2;
	arr<15> a3;

	cout <<a1.size() << endl;
	cout << a2.size() << endl;
	cout << a3.size() << endl;
	return 0;
}

定义一个模板类型的静态数组

template <class T ,size_t N>
class arr
{
public:
	T& operator[](size_t pos)
	{
		assert(pos >= 0 && pos < N);
		return _arr[pos];
	}

	size_t size() const
	{
		return N;
	}

private:
	int _arr[N];
};
int main()
{
	arr<int,5> a1;
	arr<double,10> a2;
	arr<char,15> a3;

	cout <<a1.size()<< typeid(a1).name() << endl;
	cout << a2.size() << typeid(a2).name()<< endl;
	cout << a3.size() << typeid(a3).name()<< endl;
	return 0;
}

1.2注意事项

 非类型模板参数要求类型为 整型家族,其他类型是不被编译器通过的,如果我们使用非整形的家族成员就会报错,代码如下:

//浮点型,非标准
template<class T, double N>
class arr 
{
 /*……*/ 
};

 整形家族:char     short      int      bool         long           longlong

1.3 实际引用

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

注意: 部分老编译器可能不支持使用此容器

新引入arry非类型模版参数的代码 如下:

#include <iostream>
#include <cassert>
#include <array>  //引入头文件

using namespace std;

int main()
{
	int arrOld[10] = { 0 };	//传统数组
	array<int, 10> arrNew;	//新标准中的数组

	//与传统数组一样,新数组并没有进行初始化
	
	//新数组对于越界读、写检查更为严格 优点

	arrOld[15];	//老数组越界读,未报错
	arrNew[15];	//新数组则会报错

	arrOld[12] = 0;	//老数组越界写,不报错,出现严重的内存问题
	arrNew[12] = 10;	//新数组严格检查
	return 0;
}

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

需要提醒的是:arry能做到严格的越界检查 得益于 []的重载,对下标进行了严格检查。

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

造成每次结果不一样是因为 我们每次都日期类对象比较的时候 系统每次分配的地址都是随机的,我们对地址进行比较是不符合实际情况的。 

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

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

 2.3类模板特化

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

后面用的日期类举例比较多,先把日期类放出来

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

2.3.1全特化

 全特化指 将所有的模板参数特化为具体类型,将模板全特化后,调用时,会优先选择更为匹配的模板类

//原模板
template<class T1, class T2>
class Test
{
public:
	Test(const T1& t1, const T2& t2)
		:_t1(t1)
		,_t2(t2)
	{
		cout << "template<class T1, class 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 << "template<>" << endl;
	}

private:
	int _t1;
	char _t2;
};

int main()
{
	Test<int, int> T1(1, 2);
	Test<int, char> T2(20, 'c');
	return 0;
}

对模板进行全特化处理后,实际调用时,会优先选择已经特化并且类型符合的模板。

2.3.2偏特化 

 偏特化,指 将泛型范围进一步限制,可以限制为某种类型的指针,也可以限制为具体类型

//原模板---两个模板参数
template<class T1, class T2>
class Test
{
public:
	Test()
	{
		cout << "class Test" << endl;
	}
};

//偏特化之一:限制为某种类型
template<class T1>
class Test<T1, int>
{
public:
	Test()
	{
		cout << "class Test<T, int>" << endl;
	}
};

//偏特化之二:限制为不同的具体类型
template<class T>
class Test<T*, T*>
{
public:
	Test()
	{
		cout << "class Test<T*, T*>" << endl;
	}
};

int main()
{
	Test<double, double> t1;
	Test<char, int> t2;
	Test<Date*, Date*> t3;
	return 0;
}

 

偏特化(尤其是限制为某种类型)在 泛型思想 和 特殊情况 之间做了折中处理,使得 限制范围式的偏特化 也可以实现 泛型

  • 比如偏特化为 T*,那么传 int*char*Date* 都是可行的

应用实例:

 有如下专门用来按照小于比较的类模板 :Less

#include<vector>
#include <algorithm>
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) const
 {
 return *x < *y;
 }
};

 3.模版分离编译

 

早在 模板初阶 中,我们就已经知道了 模板不能进行分离编译,会引发链接问题

 3.1失败原因

 声明与定义分离后,在进行链接时,无法在符号表中找到目标地址进行跳转,因此链接错误

下面是 模板声明与定义写在同一个文件中时,具体的汇编代码执行步骤

test.h 

#pragma once

//声明
template<class T>
T add(const T x, const T y);

//定义
template<class T>
T add(const T x, const T y)
{
	return x + y;
}

 main.cpp

#include <iostream>
#include "Test.h"

using namespace std;

int main()
{
	add(1, 2);
	return 0;
}

 

 声明与定义在同一个文件中时,可以直接找到函数的地址

编译器 生成可执行文件的四个步骤:

  1. 预处理:头文件展开、宏替换、条件编译、删除注释,生成纯净的C代码
  2. 编译:语法 / 词法 / 语义 分析、符号汇总,生成汇编代码
  3. 汇编:生成符号表,生成二进制指令
  4. 链接:合并段表,将符号表进行合并和重定位,生成可执行程序

当模板的 声明 与 定义 分离时,因为是 【泛型】,所以编译器无法确定函数原型,即 无法生成函数,也就无法获得函数地址,在符号表中进行函数链接时,必然失败 

 3.2解决方案

 解决方法有两种:

  1. 在函数定义时进行模板特化,编译时生成地址以进行链接
  2. 模板的声明和定义不要分离,直接写在同一个文件中
//定义
//解决方法一:模板特化(不推荐,如果类型多的话,需要特化很多份)
template<>
int add(const int x, const int y)
{
	return x + y;
}

 

//定义
//解决方法二:声明和定义写在同一个文件中
template<class T>
T add(const T x, const T y)
{
	return x + y;
}

这也就解释了为什么涉及 模板 的类,其中的函数声明和定义会写在同一个文件中 (.h),著名的 STL 库中的代码的声明和定义都是在一个 .h 文件中

为了让别人一眼就看出来头文件中包含了 声明 与 定义,可以将头文件后缀改为 .hpp,著名的 Boost 库中就有这样的命名方式 

 

4 总结 

 

模板是 STL 的基础支撑,假若没有模板、没有泛型编程思想,那么恐怕 "STL" 会变得非常大

模板的优点:

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

模板会导致代码膨胀问题,也会导致编译时间变长
出现模板编译错误时,错误信息非常凌乱,不易定位错误位置
总之,模板 是一把双刃剑,既有优点,也有缺点,只有把它用好了,才能使代码 更灵活、更优雅

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练营第28天|93.复原IP地址 78.子集 90.子集II

JAVA代码编写 93 .复原IP地址 有效 IP 地址 正好由四个整数&#xff08;每个整数位于 0 到 255 之间组成&#xff0c;且不能含有前导 0&#xff09;&#xff0c;整数之间用 . 分隔。 例如&#xff1a;"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址&…

常见面试题-Redis 主从复制原理以及痛点

Redis 主从复制如何同步数据呢&#xff1f; 参考文章&#xff1a;https://blog.csdn.net/Seky_fei/article/details/106877329 https://zhuanlan.zhihu.com/p/55532249 https://cloud.tencent.com/developer/article/2063597 https://xie.infoq.cn/article/4cffee02a2a12c2…

RedisTemplate使用详解

RedisTemplate介绍StringRedisTemplate介绍RedisConnectionFactory介绍RedisConnectionFactory源码解析 RedisOperations介绍RedisOperations源码解析 RedisTemplate使用连接池配置RedisTemplate连接池连接池配置 RedisTemplate应用场景RedisTemplate主要特点RedisTemplate使用…

一文搞懂什么是 GNU/Linux 操作系统

Author&#xff1a;rab 目录 前言一、UNIX二、Linux三、GNU 前言 你是否经常看见或听说过这么一句话&#xff1a;这是一个类 Unix 的 GNU/Linux 操作系统&#xff0c;你是怎么理解这句话的呢&#xff1f;想要搞懂这句话的含义&#xff0c;你需要了解以下三点基本常识。 一、U…

什么是索引下推

索引下推介绍 索引下推&#xff08;INDEX CONDITION PUSHDOWN&#xff0c;简称 ICP&#xff09;是在 MySQL 5.6 针对扫描二级索引的一项优化改进。总的来说是通过把索引过滤条件下推到存储引擎&#xff0c;来减少 MySQL 存储引擎访问基表的次数以及 MySQL 服务层访问存储引擎的…

使用paddleocr进行OCR文字识别

1 OCR介绍 OCR&#xff08;Optical Character Recognition&#xff09;即光学字符识别&#xff0c;是一种将不同类型的文档&#xff08;如扫描的纸质文件、PDF文件或图像文件中的文本&#xff09;转换成可编辑和可搜索的数据的技术。OCR技术能够识别和转换印刷或手写文字&…

Jenkins扩展篇-流水线脚本语法

JenkinsFile可以通过两种语法来声明流水线结构&#xff0c;一种是声明式语法&#xff0c;另一种是脚本式语法。 脚本式语法以Groovy语言为基础&#xff0c;语法结构同Groovy相同。 由于Groovy学习不适合所有初学者&#xff0c;所以Jenkins团队为编写Jenkins流水线提供一种更简…

redis运维(十五) 集合

一 集合 ① 概念 集合的元素在redis里面的世界是member集合&#xff1a; setset集合当中不允许重复的元素&#xff0c;而且set集合当中元素是没有顺序的,不存在元素下标 ② sadd、smembers、srem ③ sismember、srandmember、spop、scard spop 命令用于移除集合中的指定 …

有用!2023汉字小达人市级比赛填空题专项训练,在线模拟题来了

只剩下一周了&#xff0c;2023年第十届汉字小达人市级比赛就要正式开始了。 敲黑板&#xff01;汉字小达人区级比赛时间为2023年11月30日&#xff08;星期四&#xff09;下午16&#xff1a;00-18&#xff1a;00&#xff0c;记得设置闹钟。提前和老师确认学校统一组织比赛&…

设计模式——结构型模式

结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。 由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更…

在win10上安装pytorch-gpu版本2

安装anaconda即下载了python&#xff0c;还可以创建虚拟环境。 目录 1.1 anaconda安装 1.2 pytorch-gpu安装 1.1 Anaconda安装 anaconda的安装请看我之前发的tensoflow-gpu安装&#xff0c;里面有详细的安装过程&#xff0c;这里不做重复描述&#xff0c;传送门 1.2 pyt…

SpringBoot集成Swagger2登录功能和安全认证

本篇文章要实现的功能&#xff1a; 1.集成swagger2.集成swagger登录功能&#xff0c;访问 /swagger-ui.html需要先登录3.集成安全认证&#xff0c;访问接口时携带header 请求接口时携带了上一步输入的header参数和值 1.集成swagger jdk11&#xff0c;SpringBoot 2.7.13 pom…

pmp敏捷十二个考点!考前必背!

自从PMP更换了新的考纲&#xff0c;考试中对于敏捷项目管理的知识比重越来越大。因此&#xff0c;掌握敏捷知识成为备考的重点。为了帮助大家更好地掌握敏捷知识点&#xff0c;我整理了12个敏捷考点&#xff0c;希望能对大家有所帮助&#xff01; 一、敏捷宣言十二原则改写如下…

学习Opencv(蝴蝶书/C++)——3. OpenCV的数据类型

文章目录 1. 总览2. 基础类型2.0 基础类型总览2.1 cv::Vec<>类2.2 cv::Matx<>类2.3 cv::Point类(cv::Point3_< >和cv::Point_< >)2.4 cv::Scalar(cv::Scalar_)类2.5 cv::Size(cv::Size_)类、cv::Rect(cv::Rect_)类和cv::RotatedRect 类2.6 基础类型…

嵌入式Linux学习(1)——经典CAN介绍(上)

目录 一. CAN与ISO-OSI Model 二. CAN通信 2.1 接线方式 2.1.1 闭环网络 2.1.2 开环网络 2.2 收发流程 2.2.1 发送 2.2.2 接收 三. CAN BUS访问与仲裁 3.1 “线与”机制​ 3.2 仲裁机制 REF CAN&#xff08;Controller Area Network&#xff09;总线协议是由 BOSC…

五大匹配算法

五大匹配算法 五大匹配算法 BF 匹配、RK 匹配、KMP 匹配、BM 匹配、Sunday 匹配。 1、BF 匹配 // BF 匹配&#xff08;暴力匹配&#xff09; public static int bfMatch(String text, String pattern) {char[] t text.toCharArray();char[] p pattern.toCharArray();int i …

关于2023年11月25日PMI认证考试有关事项的通知

PMP项目管理学习专栏https://blog.csdn.net/xmws_it/category_10954848.html?spm1001.2014.3001.54822023年8月PMP考试成绩出炉|微思通过率95%以上-CSDN博客文章浏览阅读135次。国际注册项目管理师(PMP) 证书是项目管理领域含金量最高的职业资格证书&#xff0c;获得该资质是项…

2023亚太杯数学建模思路 - 案例:粒子群算法

文章目录 1 什么是粒子群算法&#xff1f;2 举个例子3 还是一个例子算法流程算法实现建模资料 # 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 什么是粒子群算法&#xff1f; 粒子群算法&#xff08;Pa…

被Linux之父称其为艺术品的WireGuard

简介 WireGuard 是一种极其简单但快速且现代的 VPN&#xff0c;它利用了最先进的加密技术。它的目标是比 IPsec 更快、更简单、更精简和更有用&#xff0c;同时避免令人头疼的问题。旨在提供比 OpenVPN 更高的性能。WireGuard 被设计为在嵌入式接口和超级计算机等上运行的通用 …

哈夫曼树你需要了解一下

哈夫曼树介绍哈夫曼数特点哈夫曼应用场景哈夫曼构建过程哈夫曼树示例拓展 哈夫曼树介绍 哈夫曼树&#xff08;Huffman Tree&#xff09;是一种特殊的二叉树&#xff0c;也被称为最优二叉树。在计算机科学中&#xff0c;它是由权值作为叶子节点构造出来的一种二叉树。哈夫曼树的…