【C++】C++模板进阶 —— 非类型模板参数、模板的特化以及模板的分离编译

news2024/11/30 12:52:12

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:C++学习
🎯长路漫漫浩浩,万事皆有期待

上一篇博客:【C++】C++多态——实现、重写、抽象类、原理

文章目录

  • 非类型模板参数
  • 模板的特化
    • 概念
    • 函数模板特化
    • 类模板特化
      • 全特化
      • 偏特化
  • 模板的分离编译
    • 什么是分离编译
    • 模板的分离编译
    • 解决方法
  • 模板总结
  • 总结:

非类型模板参数

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

例如,我们要实现一个静态数组的类,就需要用到非类型模板参数。

template<class T, size_t N> //N:非类型模板参数
class StaticArray
{
public:
	size_t arraysize()
	{
		return N;
	}
private:
	T _array[N]; //利用非类型模板参数指定静态数组的大小
};

使用非类型模板参数后,我们就可以在实例化对象的时候指定所要创建的静态数组的大小了。

int main()
{
	StaticArray<int, 10> a1; //定义一个大小为10的静态数组
	cout << a1.arraysize() << endl; //10
	StaticArray<int, 100> a2; //定义一个大小为100的静态数组
	cout << a2.arraysize() << endl; //100
	return 0;
}

注意

非类型模板参数只允许使用整型家族,浮点数、类对象以及字符串是不允许作为非类型模板参数的。
非类型的模板参数在编译期就需要确认结果,因为编译器在编译阶段就需要根据传入的非类型模板参数生成对应的类或函数。

模板的特化

概念

下面是用于比较两个任意相同类型的数据是否相等的函数模板。

template<class T>
bool IsEqual(T x, T y)
{
	return x == y;
}

我们大概会这样使用该函数模板:

cout << IsEqual(1, 1) << endl; //1
cout << IsEqual(1.1, 2.2) << endl; //0

这样使用是没有问题的,它的判断结果也是我们所预期的,但是我们也可能会这样去使用该函数模板:

char a1[] = "sherry";
char a2[] = "sherry";
cout << IsEqual(a1, a2) << endl; //0

判断结果是这两个字符串不相等,这很好理解,因为我们希望的是该函数能够判断两个字符串的内容是否相等,而该函数实际上判断是确实这两个字符串所存储的地址是否相同,这是两个存在于栈区的字符串,其地址显然是不同的。

类似于上述实例,使用模板可以实现一些与类型无关的代码,但对于一些特殊的类型可能会得到一些错误的结果,此时就需要对模板进行特化,即在原模板的基础上,针对特殊类型进行特殊化的实现方式

函数模板特化

对于上述实例,我们知道当传入的类型是char* 时,应该依次比较各个字符的ASCII码值进而判断两个字符串是否相等,或是直接调用strcmp函数进行字符串比较,那么此时我们就可以对char* 类型进行特殊化的实现。

函数模板的特化步骤:

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

对于上述实例char*类型的特化如下:

//基础的函数模板
template<class T>
bool IsEqual(T x, T y)
{
	return x == y;
}
//对于char*类型的特化
template<>
bool IsEqual<char*>(char* x, char* y)
{
	return strcmp(x, y) == 0;
}

注意: 一般情况下,如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。例如,上述实例char*类型的特化还可以这样给出:

//基础的函数模板
template<class T>
bool IsEqual(T x, T y)
{
	return x == y;
}
//对于char*类型的特化
bool IsEqual(char* x, char* y)
{
	return strcmp(x, y) == 0;
}

类模板特化

不仅函数模板可以进行特化,类模板也可以针对特殊类型进行特殊化实现,并且类模板的特化又可分为全特化和偏特化(半特化)。

全特化

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

例如,对于以下类模板:

template<class T1, class T2>
class sherry
{
public:
	//构造函数
	sherry()
	{
		cout << "sherry<T1, T2>" << endl;
	}
private:
	T1 _D1;
	T2 _D2;
};

当T1和T2分别是double和int时,我们若是想对实例化的类进行特殊化处理,那么我们就可以对T1和T2分别是double和int时的模板进行特化。

函数模板的特化步骤:

首先必须要有一个基础的类模板。
关键字template后面接一对空的尖括号<>。
类名后跟一对尖括号,尖括号中指定需要特化的类型。

对于T1是double,T2是int的特化如下:

//对于T1是double,T2是int时进行特化
template<>
class sherry<double, int>
{
public:
	//构造函数
	sherry()
	{
		cout << "sherry<double, int>" << endl;
	}
private:
	double _D1;
	int _D2;
};

那么如何证明当T1是double,T2是int时,使用的就是我们自己特化的类模板呢?
当我们实例化一个对象时,编译器会自动调用其默认构造函数,我们若是在构造函数当中打印适当的提示信息,那么当我们实例化对象后,通过观察控制台上打印的结果,即可确定实例化该对象时调用的是不是我们自己特化的类模板了。

偏特化

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

例如,对于以下类模板:

template<class T1, class T2>
class sherry
{
public:
	//构造函数
	sherry()
	{
		cout << "sherry<T1, T2>" << endl;
	}
private:
	T1 _D1;
	T2 _D2;
};

偏特化又可分为以下两种表现形式:
1、部分特化
我们可以仅对模板参数列表中的部分参数进行确定化。
例如,我们可以对T1为int类型的类进行特殊化处理。

//对T1为int的类进行特化
template<class T2>
class sherry<int, T2>
{
public:
	//构造函数
	sherry()
	{
		cout << "sherry<int, T2>" << endl;
	}
private:
	int _D1;
	T2 _D2;
};

此时只要实例化对象时指定T1为int,就会使用这个特化的类模板来实例化对象。

2、参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数进一步的条件限制所设计出来的一个特化版本。
例如,我们还可以指定当T1和T2为某种类型时,使用我们特殊化的类模板。

//两个参数偏特化为指针类型
template<class T1, class T2>
class sherry<T1*, T2*>
{
public:
	//构造函数
	sherry()
	{
		cout << "sherry<T1*, T2*>" << endl;
	}
private:
	T1 _D1;
	T2 _D2;
};
//两个参数偏特化为引用类型
template<class T1, class T2>
class sherry<T1&, T2&>
{
public:
	//构造函数
	sherry()
	{
		cout << "sherry<T1&, T2&>" << endl;
	}
private:
	T1 _D1;
	T2 _D2;
}; 

此时,当实例化对象的T1和T2同时为指针类型或同时为引用类型时,就会分别调用我们特化的两个类模板。

模板的分离编译

什么是分离编译

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

模板的分离编译

在分离编译模式下,我们一般创建三个文件,一个头文件用于进行函数声明,一个源文件用于对头文件中声明的函数进行定义,最后一个源文件用于调用头文件当中的函数。
按照此方法,我们若是对一个加法函数模板进行分离编译,其三个文件当中的内容大致如下:

Add.h

#include<iostream>
using namespace std;

//函数模板申明
tempalte<class T>
T Add(const T&x, const T& y);

Add.cpp

//函数模板定义
template<class T>
T Add(const T& x, const T& y)
{
	return x +y;
}

main,app

#include "Add.h"
int main()
{
	//调用函数模板实例化的函数
	cout <<Add(10,20)<<endl;
	cout << Add(10.1,20.2)<<endl;
	return 0;
}

但是使用这三个文件生成可执行文件时,却会在链接阶段产生报错。

下面我们对其进行分析:
我们都知道,程序要运行起来一般要经历以下四个步骤:

预处理: 头文件展开、去注释、宏替换、条件编译等。
编译: 检查代码的规范性、是否有语法错误等,确定代码实际要做的工作,在检查无误后,将代码翻译成汇编语言。
汇编: 把编译阶段生成的文件转成目标文件。
链接: 将生成的各个目标文件进行链接,生成可执行文件。

以上代码在预处理阶段需要进行头文件的包含以及去注释操作。
在这里插入图片描述

这三个文件经过预处理后实际上就只有两个文件了,若是对应到Linux操作系统当中,此时就生成了 Add.i 和 main.i 文件了。

Add.i

template<class T>
T Add(const T& x, const T& y)
{
	return x +y;
}

main.i

iostream代码内容
using namespace std;

tempalte<class T>
T Add(const T&x, const T& y);

int main()
{
	//调用函数模板实例化的函数
	cout <<Add(10,20)<<endl;
	cout << Add(10.1,20.2)<<endl;
	return 0;
}

预处理后就需要进行编译,虽然在 main.i 当中有调用Add函数的代码,但是在 main.i 里面也有Add函数模板的声明,因此在编译阶段并不会发现任何语法错误,之后便顺利将 Add.i 和 main.i 翻译成了汇编语言,对应到Linux操作系统当中就生成了 Add.s 和 main.s 文件。

之后就到达了汇编阶段,此阶段利用 Add.s 和 main.s 这两个文件分别生成了两个目标文件,对应到Linux操作系统当中就是生成了 Add.o 和 main.o 两个目标文件。

前面的预处理、编译和汇编都没有问题,现在就需要将生成的两个目标文件进行链接操作了,但在链接时发现,在main函数当中调用的两个Add函数实际上并没有被真正定义,主要原因是函数模板并没有生成对应的函数,因为在全过程中都没有实例化过函数模板的模板参数T,所以函数模板根本就不知道该实例化T为何类型的函数。

模板分离编译失败的原因:
在函数模板定义的地方(Add.cpp)没有进行实例化,而在需要实例化函数的地方(main.cpp)没有模板函数的定义,无法进行实例化。

解决方法

解决类似于上述模板分离编译失败的方法有两个,第一个就是在模板定义的位置进行显示实例化。
例如,对于上述代码解决方案如下:
Add.h

#include<iostream>
using namespace std;

//函数模板申明
tempalte<class T>
T Add(const T&x, const T& y);

Add.cpp

//函数模板定义
template<class T>
T Add(const T& x, const T& y)
{
	return x +y;
}
//显示实例化template
int Add(const int& x, const int& y);
template double Add(const double& x, const double& y);

main,app

#include "Add.h"
int main()
{
	//调用函数模板实例化的函数
	cout <<Add(10,20)<<endl;
	cout << Add(10.1,20.2)<<endl;
	return 0;
}

在函数模板定义的地方,对T为int和double类型的函数进行了显示实例化,这样在链接时就不会找不到对应函数的定义了,也就能正确执行代码了。

虽然第一种方法能够解决模板分离编译失败的问题,但是我们这里并不推荐这种方法,因为我们需要用到一个函数模板实例化的函数,就需要自己手动显示实例化一个函数,非常麻烦。

现在就来说说解决该问题的第二个方法,也是我们所推荐的,那就是对于模板来说最好不要进行分离编译,不论是函数模板还是类模板,将模板的声明和定义都放到一个文件当中就行了。

模板总结

优点:

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

缺陷:

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

总结:

今天我们学习了非类型模板参数、模板的特化以及模板的分离编译的相关知识,了解了一些有关的底层原理。接下来,我们将进行搜索二叉树的学习。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

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

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

相关文章

史玉柱复出一年:巨人股权第三次被冻结,力推AI+游戏

大数据产业创新服务媒体 ——聚焦数据 改变商业 一则股权冻结信息&#xff0c;又一次将复出一年的史玉柱推上风口浪尖。 天眼查APP显示&#xff0c;巨人投资近日新增一则股权冻结信息&#xff0c;被执行人为史玉柱&#xff0c;冻结股权数额1.14亿元&#xff0c;冻结期限为三年…

Visual Studio 中将TAB设置为空格

将TAB设置为空格的原因很多&#xff0c;其中一点是为了统一不同编译器对TAB的解释&#xff0c;防止代码风格在不同编译器下不一致等。 在菜单中选择: 工具-->选项-->文本编辑器--->所有语言-->制表符 在窗口中选择&#xff0c;制表符大小和缩进大小都选为4&#xf…

【从入门到起飞】JavaSE—Stream流

&#x1f38a;专栏【JavaSE】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【如愿】 &#x1f970;欢迎并且感谢大家指出我的问题 文章目录 &#x1f354;Stream流的作用&#x1f354;Stream流的使用步骤&#x1f384;获取Strea…

机器人过程自动化(RPA)入门 7. 处理用户事件和助手机器人

在UiPath中,有两种类型的Robot用于自动化任何流程。一个是后台机器人,它在后台工作。它独立工作,这意味着它不需要用户的输入或任何用户交互。另一个是前台机器人,也被称为助理机器人。 本章介绍前台机器人。在这里,我们将了解自动化过程中通过简单按键、单击鼠标等触发事…

代码随想录算法训练营第四十六天 | 动态规划 part 8 | 139.单词拆分、多重背包、背包问题总结

目录 139.单词拆分思路代码 多重背包背包问题总结 139.单词拆分 Leetcode 思路 dp[i] : 字符串长度为i的话&#xff0c;dp[i]为true&#xff0c;表示可以拆分为一个或多个在字典中出现的单词。如果确定dp[j] 是true&#xff0c;且 [j, i] 这个区间的子串出现在字典里&#xf…

仿真数据检查器如何比较数据

可以定制仿真数据检查器比较过程&#xff0c;以多种方式满足您的需求。在比较各运行时&#xff0c;仿真数据检查器会执行以下操作&#xff1a; 根据对齐设置&#xff0c;对齐基线运行和比较项运行中的信号对组。 仿真数据检查器不会比较无法对齐的信号。 根据指定的同步方法同…

Leetcode 剑指 Offer II 046. 二叉树的右视图

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底…

【中秋国庆不断更】OpenHarmony定义可动画属性:@AnimatableExtend装饰器

AnimatableExtend装饰器用于自定义可动画的属性方法&#xff0c;在这个属性方法中修改组件不可动画的属性。在动画执行过程时&#xff0c;通过逐帧回调函数修改不可动画属性值&#xff0c;让不可动画属性也能实现动画效果。 可动画属性&#xff1a;如果一个属性方法在animation…

批量剪辑视频软件,支持免费试用,合成视频,预览不限量不收费

几乎对于所有的短视频创作者来说&#xff0c;批量剪辑软件都是现在或者将来必备的办公软件。现如今市场上的批量剪辑软件也是层出不穷&#xff0c;质量也是良莠不齐。 今天给大家实实在在的推荐一款性价比非常高的批量剪辑软件——超级编导&#xff0c;适用于既想提升团队视频…

volatile关键字以及使用场景

在多线程环境下&#xff0c;如果编程不当&#xff0c;可能会出现程序运行结果混乱的问题。 出现这个原因主要是&#xff0c;JMM 中主内存和线程工作内存的数据不一致&#xff0c;以及多个线程执行时无序&#xff0c;共同导致的结果。 同时也提到引入synchronized同步锁&#x…

Android Studio插件版本与Gradle 版本对应关系

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览三、Gradle各版本对应关系3.1 Gradle 版…

杀掉进程但是fastapi程序还在运行

两个脚本&#xff0c;一个运行fastapi服务&#xff0c;一个重启服务&#xff1a; 启动服务先&#xff1a; 发现问题&#xff0c;杀掉 server.sh 后&#xff0c;依旧有&#xff1a; 不知道为什么会出现这个&#xff0c;直接kill吧&#xff1a; server.sh: #!/bin/bashparpath/…

Unity/WebGL打包/跨域问题/简单解决“......has been blocked by CORS policy: ......“

报错原文&#xff1a; Access to XMLHttpRequest atfile:///C:/Users/13171/My%20project%20(1)/Test/Build/test.data.gz from origin null has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-e…

NX 1988 如何将组件转为部件

打开组件 文件-导出-部件 指定部件名为1206&#xff0c;类选择&#xff1a;所有要导出的部件 选择完全加载 完成

(vue3)create-vue 组合式APIsetup、ref、watch,通信

优势&#xff1a; 更易维护&#xff1a;组合式api&#xff0c;更好的TS支持 之前是选项式api&#xff0c;现在是组合式&#xff0c;把同功能的api集合式管理 复用功能封装成一整个函数 更快的速度 更小的体积 更优的数据响应式&#xff1a;Proxy create-vue 新的脚手架工…

vue前端项目中添加独立的静态资源

如果想要在vue项目中放一些独立的静态资源&#xff0c;比如html文件或者用于下载的业务模板或其他文件等&#xff0c;需要在vue打包的时候指定一下静态资源的位置和打包后的目标位置。 使用的是 copy-webpack-plugin 插件&#xff0c;如果没有安装则需要先安装一下&#xff0c;…

Pikachu靶场——PHP反序列化漏洞

文章目录 1. PHP反序列化1.1 反序列化代码审计1.2 漏洞防御 1. PHP反序列化 可参考我写的另一篇博客&#xff1a;反序列化漏洞及漏洞复现。 序列化serialize() 序列化说通俗点就是把一个对象变成可以传输的字符串&#xff0c;比如下面是一个对象&#xff1a; class S{publi…

Push rejected: Push to origin/master was rejected

Push rejected: Push to origin/master was rejected 原因&#xff1a;推拒绝&#xff1a;推送到起源/主人被拒绝 解决方案如下&#xff1a; 方案1&#xff1a; 1.在Idea打开终端 方案2&#xff1a; 1、在对应项目文件里打开 Git Bash 然后依次输入&#xff1a; git pull …

【数据结构--八大排序】之堆排序

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

Databend 开源周报第112期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 理解用户自定义…