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

news2024/11/25 0:24:28

🚀 作者简介:一名在后端领域学习,并渴望能够学有所成的追梦人。
🚁 个人主页:不 良
🔥 系列专栏:🛸C++  🛹Linux
📕 学习格言:博观而约取,厚积而薄发
🌹 欢迎进来的小伙伴,如果小伙伴们在学习的过程中,发现有需要纠正的地方,烦请指正,希望能够与诸君一同成长! 🌹


文章目录

  • 非类型模板参数
  • 模板特化
    • 函数模板特化
    • 类模板特化
      • 全特化
      • 偏特化
  • 模板的分离编译
  • 模板总结

非类型模板参数

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

我们可以使用define定义一个宏常量,使用宏常量定义一个定长数组:

#include <iostream>
using namespace std;
#define N 10
template<class T>
class Arr {
private:
	T _arr[N];
};
int main()
{
	Arr<int> a1;
	Arr<double> d1;
	return 0;
}

但是当我们想让a1的空间为10,d1的空间为20的时候,依靠宏常量是无法办到的,这个和之前typedef重定义类型一样,只能typedef一次,所以我们可以使用非类型模板参数来达到需求。

#include <iostream>
using namespace std;
template<class T,size_t N = 10>
class Arr {
private:
	T _arr[N];
};
int main()
{
	Arr<int,10> a1;
	Arr<double,20> d1;
	return 0;
}

同样的,非类型模板参数也可以给缺省值:template<class T, size_t N = 10>

非类型模板参数只能是整形常量(char、bool和int等)。常量就是不可修改,主要是想让他做数组的大小或者是简单的标识。

template <class T ,bool flag = true>
void func(const T& a)
{

}

注意:浮点数、类对象以及字符串是不允许作为非类型模板参数的;非类型的模板参数必须在编译期就能确认结果。

C++11里搞了一个新容器array:一个静态数组(固定大小的顺序容器),包含在头文件<array>中。

image-20230705212753677

对标的是C语言的静态数组。这个容器没有初始化(随机值),他真正的优势是越界的检查更严格,C语言中的静态数组是抽查,而对于array容器来说,读写全面检查,访问的位置必须合法。

#include <iostream>
#include <array>
using namespace std;
int main()
{
	int a1[10];
	array<int, 10> a2;
	a2[5]++;

	a1[10];//不报错
	//a1[10] = 10;//报错
	a1[15] = 10;//不报错

	a1[10];//报错
	a1[15] = 10;//报错
    return 0;
}

但是平时不会去用array容器,因为有vector容器。相比array是在栈上,而vector是在堆上。

模板特化

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板:

// 函数模板 -- 参数匹配
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指针的地址,这就无法达到预期而错误。此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化

函数模板特化

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板即在特化之前已经定义了一个函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
//模板参数匹配
//函数模板:参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}

//函数模板特化
//对Less函数进行函数模板特化
template<>
bool Less<int*>(int* left, int* right)
{
	return *left < *right;
}

当我们实现了函数模板特化之后,直接调用函数模板特化,就不用再进行函数模板生成了。

#include <iostream>
using namespace std;
//函数模板:参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}
//函数模板特化
//对Less函数进行函数模板特化,特化为int*
template<>
bool Less<int*>(int* left, int* right)
{
	return *left < *right;
}
int main()
{
	cout << Less(1, 2) << endl;//输出1
	int a = 20;
	int b = 10;
	cout << Less(&a, &b) << endl;//调用特化函数模板
}

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

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

使用函数实现简单明了,代码的可读性高,容易书写,对于一些参数类型复杂的函数模板,特化时特别给出(或者通过函数实现),因此函数模板不建议特化。

类模板特化

全特化

全特化即是将模板参数列表中所有的参数都确定化,全特化必须在原模板的基础之上。

实现全特化后,有和全特化类型匹配的优先调用全特化类模板。

template<class T1, class T2>
class Data
{
public:
    Data() {cout<<"Data<T1, T2>" <<endl;}
private:
    T1 _d1;
    T2 _d2;
};

如下我们将上面的类全特化为两个int类型:

#include <iostream>
using namespace std;
//类模板
template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

//全特化-特化为int
template<>
class Data<int,int>
{
public:
	Data() { cout << "Data<int, int>" << endl; }
private:
	int _d1;
	int _d2;
};

//全特化-特化为int和double
template<>
class Data<int, double>
{
public:
	Data() { cout << "Data<int, double>" << endl; }
private:
	int _d1;
	double _d2;
};

int main()
{
	Data<int, int> d1;//调用全特化为int的类模板
	Data<int, double> d2;//调用全特化为int和double的类模板
	Data<int,char> d2;//调用未特化的类模板,类模板实例化
    return 0;
}

打印结果:

image-20230706095454444

偏特化

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

偏特化不针对具体的类型,针对泛类。对于以下模板类:

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

测试:

int main()
{
	Data<int, char> d1;//调用未特化的类模板,类模板实例化
	Data<char,int> d2;//调用偏特化第二个参数为int的类
	Data<int*, int*> d3;//调用偏特化进一步限制为指针的类
	Data<int&, int&> d4(4,5);//调用偏特化进一步限制为引用的类
}

打印结果:

image-20230706101649458

类实例化时优先级:全特化 > 偏特化 > 类模板参数。

模板的分离编译

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

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

image-20230706103943398

但是当我们运行的时候报错,发生链接错误:

image-20230706104016445

我们知道程序要运行起来要经过下面四个步骤:

预处理:头文件的展开,宏替换,条件的编译,去掉注释…… 生成Func.i和main.i文件,.h文件被展开了,所以此时没有.h文件

编译:检查语法,生成汇编代码…… 生成Func.s和main.s文件

汇编:将汇编代码转换二进制机器码 生成Func.o和main.o

链接:合并生成可执行文件。

Func.i生成Func.o的时候,生成一堆汇编指令,但是只能生成func函数的指令,生不成Add函数的指令。

func会被编译成一堆指令,所以在Func.o中有func函数的地址,但是没有Add的地址,因为Add没有实例化(编译器没有看到对Add模板函数的实例化,因此不会生成具体的加法函数),没办法确定T。

main.cpp文件中有声明,所以能够通过编译,但是生成的符号表中Add函数的地址未给出,而Func.cpp生成的符号表中有func函数的地址,但Add函数的地址也未给出,编译器在链接时才会从两个文件的符号表中找对应的地址,但是这个模板函数没有实例化没有生成具体的代码,因此找不到Add的地址,所以链接时报错。

解决方法:

1、可以在.cpp定义的时候显示示例化,但是只能在int类型时使用,显示实例化带有局限性一般不采用。

//Func.cpp文件
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
//显式实例化
template
int Add(const int& left, const int& right);

2、可以声明和定义分离,但是要在同一个文件中

//Func.h文件
//声明
template<class T>
T Add(const T& left, const T& right);

//定义
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

在库里面比较短小的函数就在类里面定义,因为在类里可能被识别成内联函数。短小的函数适合作为内联函数,大的函数就函数和声明定义分离。

声明和定义放到一起直接就可以实例化,编译时就有地址,在当前函数就生成地址,不需要再去链接。

.hpp.h都是头文件的文件后缀名。

模板总结

优点:

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

缺点:

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

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

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

相关文章

jar包发版至服务器Linux命令

maven -> clean compile install / package 使用WinSCP 或者其他软件在target下 找到jar包 传至服务器 找到原先jar包存放位置 cd /usr/local/xxx 通过线上端口号找到线程数 netstat -tunlp | grep 8080 杀死线程 kill -9 线程数 运行新jar包 nohup java -jar jar包名…

利用 jenkins 关联 Job 方式完善 RobotFramework 测试 Setup 以及 Teardown 后操作

目录 1.前言 2.Jekins 关联 Job 方式 1.前言 Jenkins是一个流行的持续集成和交付工具&#xff0c;它可以帮助自动化构建、测试和部署软件。与Robot Framework结合使用&#xff0c;可以实现更高效的测试工作流程。 在Robot Framework中&#xff0c;Setup和Teardown是测试用例…

文件IO_文件同步(附Linux-5.15.10内核源码分析)

目录 1.为什么要进行文件同步&#xff1f; 2.fsync函数介绍 2.1 fsync函数 2.2 fsync函数内核源码分析 2.3 fsync函数使用示例 3.fdatasync函数介绍 3.1 fdatasync函数 3.2 fdatasync函数内核源码分析 3.3 fdatasync函数使用示例 4.sync函数介绍 4.1 sync函数 4.2 …

第一阶段-第十一章 Python基础的综合案例(数据可视化-地图可视化)

目录 一、基础地图使用  1.学习目标  2.视觉映射器  3.本节的演示二、疫情地图-国内疫情地图  1.案例效果  2.函数的语法  3.本节的代码演示三、疫情地图-省级疫情地图  1.案例效果  2.本节的代码演示 说明&#xff1a;该文章是学习 黑马程序员在B站上分享的视…

SPRINGBOOT部署安装hello world

1.安装JAVA环境&#xff0c;设置为全局变量 用以下方法检查&#xff0c;安装是否正确 2.maven安装&#xff0c;并且修改setting中的镜像设置&#xff0c;改为国内阿里云镜像 3.idea中设置JDK版本号&#xff0c;IDEA中springboot不要选择3.0版本&#xff0c;会出现与jdk不匹…

如何在照片上添加水印?这三个方法让你轻松实现

我有个朋友他是一名摄影爱好者&#xff0c;他在旅行中经常能捕捉到一些绝美的照片。他为了分享这份美丽&#xff0c;决定将它们上传到社交媒体上。但是&#xff0c;他很担心别人未经许可就盗用了他的作品。于是他来想我请教这个问题。我就给他推荐了几款加水印软件&#xff0c;…

(学习笔记-TCP基础知识)TCP与UDP区别

UDP UDP不提供复杂的控制机制&#xff0c;利用IP提供面向[无连接]的通信服务。 UDP协议非常简单&#xff0c;头部只有8个字节(位)&#xff0c;UDP的头部格式如下&#xff1a; 目标和源端口&#xff1a;主要是告诉UDP协议应该把报文发给哪个进程包长度&#xff1a;该字段保存了…

CentOS目录详解

在centos中&#xff0c;最顶层的目录称作根目录&#xff0c; 用/表示。/目录下用户可以再创建目录&#xff0c;但是有一些目录随着系统创建就已经存在&#xff0c;接下来重点介绍几个常用目录。 /bin&#xff08;binary&#xff09;包含了许多所有用户都可以访问的可执行文件&a…

轻松实现金蝶云星空与赛意SMOM系统的全面集成

1. 金蝶云星空&#xff1a;为运营协同与管控型企业提供通用ERP服务平台 金蝶云星空是基于当今先进管理理论和数十万家国内客户最佳应用实践开发的ERP服务平台。它针对事业部制、多地点、多工厂等企业和集团公司&#xff0c;提供了通用的企业资源计划&#xff08;ERP&#xff0…

win键无效,键盘Win组合键突然不不能用如何解决?

电脑win键失效怎么办&#xff1f; 在使用windows系统的时候&#xff0c;发现一个问题&#xff0c;就是win键失效了&#xff0c;怎么按都没有反应&#xff0c;该怎么办呢&#xff1f; 键盘方面的原因 此时之是键盘的原因与系统本身没有关系&#xff0c;键盘屏蔽热键主要目标是…

【技能实训】DMS数据挖掘项目-Day13

文章目录 任务15【任务15.1】ClientFrame.java【任务15.2】ClientFrame.java【任务15.3】实现匹配日志信息或物流数据的数据保存功能&#xff08;保存到本地文件&#xff09;&#xff0c;将15.2中&#xff0c;返回的匹配数据&#xff0c;保存到客户端文件中【任务15.4】实现物流…

恢复软件哪些好?推荐3款,亲试好用!

“想问下朋友们有什么好的恢复软件推荐吗&#xff1f;我的电脑数据经常都莫名其妙就找不到了&#xff0c;我也不敢随意进行操作。如果有好的数据恢复软件&#xff0c;快给我推荐推荐吧&#xff01;” 电脑数据很多都是比较重要的&#xff0c;如果经常丢失数据&#xff0c;会对我…

DV SSL证书

一、DV SSL证书是什么&#xff1f; DV SSL&#xff08;又称域名验证型证书&#xff09;是便宜又快速实现网站HTTPS加密、有效防劫持的SSL证书。购买DV证书仅需百十元起&#xff0c;只需验证域名的所有权&#xff0c;3-5分钟极速签发。因此&#xff0c;DV SSL证书成为众多个人网…

探索新机遇,助力娱乐社交新增长丨网易云信亮相 PMTalk 北京产品运营大会

近日&#xff0c;在 PMTalk 联合网易易盾、网易云信主办的北京产品运营大会上&#xff0c;网易云信娱乐社交产品经理聂夏军介绍了 AIGC 与娱乐社交行业融合并助力业务新增长的思路&#xff0c;并分享了网易云信在帮助开发者业务增长方面的探索和实践经验。 由于国内娱乐社交市场…

nginx推流环境搭建

目录 1、创建安装文件夹2、安装编译 nginx 所需要的库3、下载 nginx-1.21.6.tar.gz下载 nginx-rtmp-module4、解压解压nginx文件解压rtmp模块5、编译6、安装7、启动nginx,检测nginx是否能成功运行8、配置nginx使用RTMP9、重启nginx服务器1、创建安装文件夹 cd ~ mkdir nginx …

无参数读文件和RCE总结

什么是无参数&#xff1f; 顾名思义&#xff0c;就是只使用函数&#xff0c;且函数不能带有参数&#xff0c;这里有种种限制&#xff1a;比如我们选择的函数必须能接受其括号内函数的返回值&#xff1b;使用的函数规定必须参数为空或者为一个参数等 接下来&#xff0c;从代码…

基于51单片机和proteus的温室大棚系统

此系统是基于51单片机和proteus的仿真设计&#xff0c;功能如下&#xff1a; 1. LCD1602实时显示光照/土壤湿度/温度值及设定值。 2. 按键可增减光照/土壤湿度/温度设定阈值。 3. 获取到的温度低于设定温度则打开加热设备。 4. 获取到的光照值低于设定光照值则打开补光灯。…

「车型分析」控制系统典型应用车型 —— 停车机器人

如今&#xff0c;城市可用土地的日益稀缺&#xff08;城市化&#xff09;和汽车使用数量的增加&#xff08;机动化&#xff09;,为了可持续性发展和其他生活质量问题相结合&#xff0c;由此孕育出来了一种自动停车系统。停车机器人凭借其灵活、高效、标准化的停车模式&#xff…

高时空分辨率、高精度一体化预测技术之风、光、水能源自动化预测教程

详情点击链接&#xff1a;高时空分辨率、高精度一体化预测技术之风、光、水能源自动化预测 第一&#xff1a;预测平台及安装 一、高精度气象预测基础 综合气象观测数值模拟模式&#xff1b; 全球预测模式、中尺度数值模式&#xff1b; 二、自动化预测平台 Linux系统 Crontab…

直播软件源码开发搭建提高安全性方案

随着互联网技术的发展与普及&#xff0c;直播软件源码平台早已深入到大家生活当中&#xff0c;人们常常在直播软件源码平台中去获取资讯、发布资讯或是去寻找想要的物品。但是&#xff0c;直播软件源码平台虽是一款放松娱乐、获取资讯等作用的软件&#xff0c;但也包含了用户们…