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

注意:

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

模板的特化

概念

这里举一个简单的例子来说明什么是特化,下面是用于比较两个任意相同类型的数据是否相等的函数模板。

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

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

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

这样使用是没有问题的,但是我们也有可能会按照下面这样使用模板

char a1[] = "hello 2023";
char a2[] = "hello 2023";
cout << IsEqual(a1, a2) << endl;

判断结果是这两个字符串不相等,这是因为我们希望的是该函数能够判断两个字符串的内容是否相等,而该函数实际上是判断这两个字符串的首元素的地址(函数传入的参数为数组名,数组名为数组首元素的地址)是否相同,这是两个存在于栈区的字符串,其地址显然是不同的。
对于上述实例,使用模板可以实现一些与类型无关的代码,但对于一些特殊的类型可能会得到一些错误的结果,此时就需要对模板进行特化,即在原模板的基础上,针对特殊类型进行特殊化的实现方式.

函数模板特化

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

函数模板的特化步骤

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

对于上述实例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 A
{
public:
	A()
	{
		cout << "A<T1, T2>" << endl;
	}
private:
	T1 _D1;
	T2 _D2;
};

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

函数模板的特化步骤:

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

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

template<class T1, class T2>
class A
{
public:
	A()
	{
		cout << "A<T1, T2>" << endl;
	}
private:
	T1 _D1;
	T2 _D2;
};

template<>
class A<double, int>
{
public:
	A()
	{
		cout << "A<double, int>" << endl;
	}
private:
	double _D1;
	int _D2;
};

那么如何证明当T1是double,T2是int时,使用的就是我们自己特化的类模板呢?

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

偏特化

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

例如,对于以下类模板:

template<class T1, class T2>
class A
{
public:
	A()
	{
		cout << "A<T1, T2>" << endl;
	}
private:
	T1 _D1;
	T2 _D2;
};

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

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

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

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

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

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

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

模板的分离编译

什么是分离编译

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

模板的分离编译

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

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

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

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

以上代码在预处理阶段需要进行头文件的包含以及去注释操作。

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

预处理后就需要进行编译,虽然在 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)没有模板函数的定义,无法进行实例化。

解决方法

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

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

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

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

模板总结

优点:

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

缺陷:

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

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

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

相关文章

【 书生·浦语大模型实战营】学习笔记(一):全链路开源体系介绍

&#x1f389;AI学习星球推荐&#xff1a; GoAI的学习社区 知识星球是一个致力于提供《机器学习 | 深度学习 | CV | NLP | 大模型 | 多模态 | AIGC 》各个最新AI方向综述、论文等成体系的学习资料&#xff0c;配有全面而有深度的专栏内容&#xff0c;包括不限于 前沿论文解读、…

汇编语言——用INT 21H 的A号功能,输入一个字符串存放在内存,倒序输出

用INT 21H 的A号功能&#xff0c;输入一个字符串“Hello, world!”&#xff0c;存放在内存&#xff0c;然 后倒序输出。 在DOS中断中&#xff0c;INT 21H是一个常用的系统功能调用中断&#xff0c;它提供了多种功能&#xff0c;其中A号功能用于字符串的输入。 在使用这个功能时…

OSCP靶场--Internal

OSCP靶场–Internal 考点(CVE-2009-3103) 1.nmap扫描 ## ┌──(root㉿kali)-[~/Desktop] └─# nmap 192.168.216.40 -sV -sC -Pn --min-rate 2500 -p- Starting Nmap 7.92 ( https://nmap.org ) at 2024-03-31 07:00 EDT Nmap scan report for 192.168.216.40 Host is up…

pymysql进行数据库各项基础操作

目录 1、mysql数据库简介2、基于mysql数据库的准备工作3、通过pymysql进行表的创建4、通过pymysql进行数据插入5、通过pymysql进行数据修改6、通过pymysql进行数据查询7、通过pymysql进行数据删除 本文内容是通过pymysql来进行时数据库的各项基础操作。 1、mysql数据库简介 …

西南交大swjtu算法实验4.2|分治

1. 实验目的 编写一个分治算法来搜索 m*n 矩阵 matrix 中的一个目标值 target&#xff0c;该矩阵 具有以下特性:每行的元素从左到右升序排列。每列的元素从上到下升序排列。 通过该实例熟悉分治算法的分析求解过程&#xff0c;时间复杂度分析方法&#xff0c;以及如何设计 分治…

基于深度学习的图书管理推荐系统(python版)

基于深度学习的图书管理推荐系统 1、效果图 1/1 [] - 0s 270ms/step [13 11 4 19 16 18 8 6 9 0] [0.1780757 0.17474999 0.17390694 0.17207369 0.17157653 0.168248440.1668652 0.16665359 0.16656876 0.16519257] keras_recommended_book_ids深度学习推荐列表 [9137…

ES6 学习(一)-- 基础知识

文章目录 1. 初识 ES62. let 声明变量3. const 声明常量4. 解构赋值 1. 初识 ES6 ECMAScript6.0(以下简称ES6)是JavaScript语言的下一代标准&#xff0c;已经在2015年6月正式发布了。它的目标&#xff0c;是使得」JavaScript语言可以用来编写复杂的大型应用程序&#xff0c;成为…

C之易错注意点转义字符,sizeof,scanf,printf

目录 前言 一&#xff1a;转义字符 1.转义字符顾名思义就是转换原来意思的字符 2.常见的转义字符 1.特殊\b 2. 特殊\ddd和\xdd 3.转义字符常错点----计算字符串长度 注意 &#xff1a; 如果出现\890,\921这些的不是属于\ddd类型的&#xff0c;&#xff0c;不是一个字符…

车载电子电器架构 —— 局部网络管理汇总

车载电子电器架构 —— 局部网络管理汇总 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明…

在 Linux(红帽系列) 中使用 yum 工具安装 Nginx 及 Nginx 的常用命令与 Nginx 服务的启动和停止

官方文档&#xff1a;https://nginx.org/en/linux_packages.html 在红帽系列的 Linux 发行版中&#xff0c;使用 yum 工具帮助我们管理和下载安装 rpm 软件包&#xff0c;并帮助我们自动解决 rpm 软件包之间的依赖关系。 关于 yum 可以参考&#xff1a;https://www.yuque.com/u…

ROS2 学习(一)ROS2 简介与基本使用

参考引用 动手学 ROS2 1. ROS2 介绍与安装 1.1 ROS2 的历史 ROS&#xff08;Robot Operating System&#xff0c;机器人操作系统&#xff09;&#xff0c;但 ROS 本身并不是一个操作系统&#xff0c;而是可以安装在现在已有的操作系统上&#xff08;Linux、Windows、Mac&…

无需mac系统申请ios证书的傻瓜式教程

在hbuilderx云打包&#xff0c;无论是开发测试还是打生产包&#xff0c;都需要p12格式的私钥证书、证书密码和证书profile文件。这三样东西都是必须的&#xff0c;点击hbuilderx的官网链接&#xff0c;它创建证书的第一步&#xff0c;就需要使用mac系统的钥匙串访问去生成一个c…

设置asp.net core WebApi函数请求参数可空的两种方式

以下面定义的asp.net core WebApi函数为例&#xff0c;客户端发送申请时&#xff0c;默认三个参数均为必填项&#xff0c;不填会报错&#xff0c;如下图所示&#xff1a; [HttpGet] public string GetSpecifyValue(string param1,string param2,string param3) {return $"…

【Qt】窗口

目录 一、概述二、菜单栏&#xff08;QMenuBar&#xff09;三、工具栏&#xff08;QToolBar&#xff09;四、状态栏&#xff08;QStatusBar&#xff09;五、浮动窗口六、对话框 一、概述 Qt窗口是通过QMainWindow类来实现的。 QMainWindow是一个为用户提供主窗口程序的类&…

用1/10的成本为节点运营者启用零认证下载

在Sui网络上运行的验证节点和完整节点需要具有最高水平的可靠性和运行时间&#xff0c;以便提供高吞吐量及区块链的可扩展性。可靠地运行有状态应用的关键部分&#xff0c;确保可以相对轻松地进行硬件故障转移。如果磁盘故障或其他类型的故障影响到运行验证节点的机器&#xff…

最新2024年增强现实(AR)营销指南(完整版)

AR营销是新的最好的东西&#xff0c;就像元宇宙和VR营销一样。利用AR技术开展营销活动可以带来广泛的利润优势。更不用说&#xff0c;客户也喜欢AR营销&#xff01; 如果企业使用AR&#xff0c;71%的买家会更多地购物。40%的购物者准备在他们可以在AR定制的产品上花更多的钱。…

记录实现水平垂直居中的5种方法

记录块级元素实现水平垂直居中的方法&#xff0c;效果如图。 <div class"parent"><div class"child">居中元素</div> </div><style> .parent {position: relative;width: 600px;height: 300px;background-color: #679389; …

每日一练 找无重复字符的最长子串

我们来看下这个题目&#xff0c;我们要统计的是不重复的子串&#xff0c;我们可以使用“滑动窗口法”&#xff0c;其实我们很容易就能想到思路。 我们的左窗代表我们目前遍历的开始&#xff0c;即我们遍历的子串的开头&#xff0c;右窗从左窗开始进行遍历&#xff0c;每次遍历…

【Redis持久化】RDB、ROB介绍和使用

RDB、ROB介绍和使用 引言ROB介绍配置指令介绍使用指令&#xff1a;dump文件修复指令快照禁用 AOF工作流程&#xff1a;文件重写&#xff1a;三种写回策略&#xff1a; 混合使用 引言 持久化的目的&#xff0c;其实就是在Redis重启或者中途崩溃的时候能够依靠自身恢复数据&…

Electron 读取本地配置 增加缩放功能(ctrl+scroll)

最近&#xff0c;一个之前做的electron桌面应用&#xff0c;需要增加两个功能&#xff1b;第一是读取本地的配置文件&#xff0c;然后记载配置文件中的ip地址&#xff1b;第二就是增加缩放功能&#xff1b; 第一&#xff0c;配置本地文件 首先需要在vue工程根目录中&#xff0…