C++---模板进阶(非类型模板参数,模板的特化,模板分离编译)

news2024/11/24 20:05:25

我们都学习和使用过模板,而这篇文章我们来将一些更深入的知识。在此之前,我们在使用C++编程时可以看到模板是随处可见的,它能支持泛型编程。模板包括函数模板和类模板,我们有的人可能会说是模板函数和模板类,但严格讲这样说是错误的。而在我们实际使用中,类模板用的场景是比函数模板多的,如STL中vector,list等都是类模板,而算法中sort,find等是函数模板。

非类型模板参数

模板参数分为类型模板形参,非类型形参。

类型形参:出现在模板参数列表中,跟在class或typename关键字之后的参数类型名称。(我们之前一直使用的)

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

举个例子:

我们想要创建一个大小固定,可以存储不同类型元素的数组类,我们可以这样写:

#include<iostream>
using namespace std;
#define N 10

template<class T>
class Array
{
private:
	int _a[N];	
};
int main()
{
	Array<int> arr1;
	Array<int> arr2;
	return 0;
}

这时实例化出来的arr1,arr2都是大小为10的定长数组。但我们假如说想改变一个数组的长度呢?例如我们想将数组长度改为20,怎么办呢?有同学说我们可以将N define为20呀。但是假如我们想要一个数组长为10,另一个长为20呢?

这时候我们的非类型模板参数就有作用了,我们可以将Array定义为下面的形式:

#include<iostream>
using namespace std;

//这里的N叫做非类型模板参数,它是一个常量 
template<class T, size_t N>
class Array
{
private:
	int _a[N];
};
int main()
{
	Array<int, 10> arr1;
	Array<int, 20> arr2;
	int n = 10;
	Array<int, n> arr3;//error 这里非类型模板参数是常量,这里会报错
	return 0;
}

对于非类型模板参数的使用场景,STL中的deque和array容器中都用了。deque中的非类型模板参数用来传一个一个常量来控制buff的大小。

我们下面介绍一下array容器:

C++11支持array,它的结构类似于vector,只不过vector是动态数组,而array是静态的。它不提供头插,头插,尾插,尾删,任意位置插入删除这种操作,因为它根本不存在这种说法,而且它可以直接使用operator[ ] 访问修改任意位置的数据。

array的缺陷:

我们不推荐使用array,array底层是在栈上开辟空间的,而栈空间又是很有限的,例如:在32位的linux下栈空间只有8M,所以一般开大空间时极不推荐使用array,相比之下vector就很有优势。而且在知道要开多大空间的情况下,vector 也可以通过reserve一次性开好空间,在后续的使用中还可以自动增容,而array空间是固定的,不灵活。所以我们一般不使用array。

注意:

  • 非类型模板参数只允许使用整形家族(int,short,long,long long,char),而浮点型,类对象,字符串是不允许作为非类型模板参数的
  • 非类型模板参数需要在编译的时候就确认结果,因为编译器在编译阶段就需要根据传入的非类型模板参数生成对应的类或函数

模板的特化

我们先举个例子,理解一下模板的特化,看下面的代码:

template<class T>
bool IsEqual(const T& left, const T& right)
{	
	return left == right;
}
int main()
{
	cout << IsEqual(1, 2) << endl;
	char p1[] = "hello";
	char p2[] = "world";
	cout << IsEqual(p1, p2) << endl;
	return 0; 
}

我们实现了IsEqual函数用来判断两个参数是否相等。我们总共调用了两次,第一次比较的是两个整数,是没问题的;而第二次实际上是有问题的,我们本意是想比较两个字符串是否相等,可是我们仔细看看:我们实际上比较的p1,p2两个指针。我们可能会想先判断一下传过来的参数的类型是什么,再进行不同的操作,就像下面这样:

template<class T>
bool IsEqual(const T& left, const T& right)
{
	if(T == char*)//error,C++中不支持类型比较
	{
        return *left==*right;
    }
	else
	{...}
	
	return left == right;
}
int main()
{
	cout << IsEqual(1, 2) << endl;//ok
	char* p1 = "hello";
	char* p2 = "world";
	cout << IsEqual(p1, p2) << endl;//err
	return 0; 
}

但是很可惜,C++中不支持类型比较,所以上面写的是错误的。

那么这里就需要我们的模板特化出手了,模板特化的目的就是在原模板类的基础上,针对某些类型进行特殊化处理。模板特化又分为函数模板特化和类模板特化。

函数模板的特化

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

我们上面的代码就可以改为:

#include<iostream>
using namespace std;

template<class T>
bool IsEqual(const T& left, const T& right)
{
	return left == right;
}

template<>
bool IsEqual<const char *&>(const char*& left,const char*& right)
{
	return strcmp(left, right)==0;
}
int main()
{
	cout << IsEqual(1, 2) << endl;
	const char* p1 = "hello";
	const char* p2 = "hello";
	cout << IsEqual(p1, p2) << endl;
	return 0;
}

说明:其实一般情况下,如果函数模板遇到不能处理或者处理有误的类型,可以直接将该函数直接给出,这叫做模板的匹配原则:有现成的完全匹配的,就直接调用,没有现成调用的,实例化模板生成。上面的代码就可以这样写:

#include<iostream>
using namespace std;

template<class T>
bool IsEqual(const T& left, const T& right)
{
	return left == right;
}

bool IsEqual(const char*& left,const char*& right)
{
	return strcmp(left, right)==0;
}
int main()
{
	cout << IsEqual(1, 2) << endl;
	const char* p1 = "hello";
	const char* p2 = "hello";
	cout << IsEqual(p1, p2) << endl;
	return 0;
}

对于模板匹配原则和函数模板特化,两者底层没有任何差别,如果能使用函数模板特化的时候更推荐使用函数模板特化。

类模板特化

1.全特化

就是将模板参数列表中所有参数确定化,例如:

#include<iostream>
using namespace std;

template<class T1,class T2>
class A
{
public:
	A()
	{
		cout << "T" << endl;
	}
private:
	T1 a1;
	T2 a2;
};

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

int main()
{
	A<double, double>a1;
	A<int, int>a2;
	return 0;
}
2.偏特化

也叫半特化:即对模板参数进行一定的确定化,例如:

template<class T1>//第二个参数是int类型就会调用这个
class A<T1, int>
{
public:
	A()
	{
		cout << "T1,int" << endl;
	}
private:
};

template<class T2>//第一个参数是int类型就会调用这个
class A<int, T2>
{
public:
	A()
	{
		cout << "int,T2" << endl;
	}
private:
};

template<class T1,class T2>//两个参数都是指针类型就会调用这个
class A<T1*, T2*>
{
public:
	A()
	{
		cout << "T1*,T2*" << endl;
	}
private:
};

template<class T1, class T2>//两个参数都是引用类型就会调用这个
class A<T1&, T2&>
{
public:
	A()
	{
		cout << "T1&,T2&" << endl;
	}
private:
};

template<class T1, class T2>//第一个参数是指针类型,第二个参数是引用类型就会调用这个
class A<T1*, T2&>
{
public:
	A()
	{
		cout << "T1*,T2&" << endl;
	}
private:
};

int main()
{
	A<double, int>a;
	A<int, char>b;
	A<double*, int*>c;
	A<char&, int&>d;
	A<double*, char&>e;

	return 0;
}

上面这几种特化版本都是偏特化。

偏特化并不仅仅是指特化,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本,例如我们可以限制他的类型是引用,指针之类的。

关于特化的场景我们等到哈希表的时候会给大家介绍。

模板分离编译

我们在工作的时候写的项目都会由若干个源文件共同实现,每个源文件独立编译生成目标文件,最后将所有目标我呢见链接起来形成单一的可执行文件的过程称为分离编译模式

模板的分离编译

在前面我们模拟实现string,vector,list等STL容器的时候,都没有将声明和定义分开。我们实际上是习惯将声明放在头文件中,将定义放在.cpp文件中的。我们没有分离的原因就是C++的模板不支持分离编译。

我们以这段代码为例:

//Func.h代码:
#pragma once
#include<iostream>
using namespace std;

void Print();

template<class T>
void F(T a);

//Func.cpp代码
#include"Func.h"

void Print()
{
	cout << "print" << endl;
}

template<class T>
void F(T a)
{
	cout << "F(T a)" << endl;
}

//test.cpp代码
#include"Func.h"

int main()
{
	Print();
	F(10);//这里编不过
	return 0;
}

不支持分离编译的原因:
我们都知道:程序的编译过程分为:预处理,编译,汇编,链接四个步骤

其中预处理会做的事情就是:头文件展开,宏替换,条件编译,去掉注释,在linux环境下就生成了.i文件

编译:检查语法错误后,生成汇编代码,在linux环境下生成.s文件

汇编:将汇编代码转成二进制机器码,在linux环境下生成.o文件

链接:会把.o文件里F或Print这样没有地址的地方,用被修饰过的函数名去Func.o中的符号表找对应的地址,然后填上地址就像上图中的地址一样。然后再把目标文件合并成可执行程序。

而问题就出现在链接的时候,编译器拿着我们的函数名去符号表中找时,能找到Print()函数,但是却找不到F()函数,这是因为在编译阶段,Print()函数有定义,可以生成。但是F()函数是一个函数模板,他不能生成,因为我们不知道T是什么类型。模板其实是调用时生成的。

如何解决

一、

这种方法实际上是不可行的,就是让编译器在编译的时候去各个地方查找实例化,例如我们在Func.i中看到一个模板,我们就去Test.i中找实例化,但是这样的话,如果是一个大项目的化,有几十几百个文件,对于编译器的实现就复杂了。所以实际在链接前,各文件间是不会交互的

二、

显示指定实例化,编译器看到后就知道要把这个模板参数T实例化成什么类型。但是这样做的问题也很大:就是我换个类型就又链接不上了,那我们就只能使用一种类型就实例化一次,这样就很麻烦。

三、

最后这种方法就很粗暴,STL中用的也是这种方法,就是不分离编译,声明和定义都放在一个.h文件里。这样的话,.h文件中就包含了模板的定义,就不需要链接的时候去查找了,在编译的阶段就能直接填地址了。而我们在项目中一般把这种文件命名为后缀为.hpp的文件,也就是说它即是.h文件,又是.cpp文件。

同样我们的类模板也不支持分离编译,最好的办法也是不分离,写在同一个文件中。

我在这里还要在解释我上文中的一句话:模板其实是调用时实例化生成的。我们在一个类模板或者函数模板中,假如写出了语法错误,假如我们没有实例化运行的话,编译器是检查不出错误的,这就是因为模板是调用时实例化的,没有实例化的时候,编译器不会去检查函数模板或类模板内部的语法错误。

总结

模板优点:

1.模板复用了代码,节约资源,更快的迭代开发,有了模板才有了C++的标准模板库STL的诞生

2.增强了代码的灵活性

模板缺点:

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

2.假如我们使用模板时出现错误,编译器提示的错误信息会非常凌乱,而且准确度不高,我们不能盲目地相信模板的报错。可能只是一点小错误,最后却报出了一大堆错误,这时候我们要优先看第一个错误。

以上就是本章的全部内容,谢谢大家!

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

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

相关文章

【Python】把xmind转换为指定格式txt文本

人工智能训练通常需要使用文本格式&#xff0c;xmind作为一种常规格式不好进行解析&#xff0c;那如何把xmind转换为txt格式呢&#xff1f; 软件信息 python python -v Python 3.9.13 (tags/v3.9.13:6de2ca5, May 17 2022, 16:36:42) [MSC v.1929 64 bit (AMD64)] on win32…

集成学习笔记

集成学习 简介 决策树 GBDT 拟合残差 一般 GBDT XGBOOST 弓 1 能表达样本落入的子节点&#xff0c;但是不能把表示结构 2 3.正则项 – 惩罚 防止过拟合&#xff0c;比如一个值总共有10颗树都是由同一颗树决定的&#xff0c;过拟合 5 找到一种方式不依赖于损失函数 …

【Modelground】个人AI产品MVP迭代平台(3)——工程化架构设计

文章目录 背景monorepo多项目调试/打包公共静态资源服务公共模型拷贝入项目的public文件夹总结 背景 Modelground中的项目&#xff0c;基本都依赖Mediapipe模型&#xff0c;因此&#xff0c;有很强的需要对Mediapipe进行封装&#xff0c;其余项目都调用这个封装库。从架构上&a…

文字生成视频!又一王炸!!!(且免费使用!)

VIVA王炸 开场 “ 生成令人惊叹的AI视频&#xff0c;再加上4K视频增强和初学者友好的自动提示优化&#xff0c;为您提供无与伦比的视频创作体验。” 直抒胸臆 自从sora的出现&#xff0c;开启了人工智能的有一个阶段。VIVA是现在唯数不多的与OpenAI的sora互相抗衡。也是为数…

Orange Pi AI Pro 开箱 记录

香橙派 AIpro&#xff08;OrangePi AIpro&#xff09;是一款面向AI开发的强大开发板&#xff0c;提供了高性能和多功能的开发环境。我将结合自己的开发经验&#xff0c;详细介绍这款开发板的性能、适用场景及使用体验。 一、产品概述 香橙派 AIpro配备了强大的硬件配置&#…

String类知识

目录 一、String存在意义 二、字符串为何不可变 三、String类常用方法 1、字符串构造 2、String对象的比较 3、字符串查找 4、转化 &#xff08;1&#xff09;数值和字符转化 &#xff08;2&#xff09;大小写转换 &#xff08;3&#xff09;字符串转数组 &#xff08;4&…

不同类型红酒的保存期限与品质变化

云仓酒庄雷盛红酒&#xff0c;以其多样的品种和与众不同的风味吸引了无数葡萄酒爱好者。然而&#xff0c;不同类型和风格的红酒在保存过程中&#xff0c;其期限和品质变化也各不相同。本文将深入探讨这个问题&#xff0c;以帮助消费者更好地理解和欣赏云仓酒庄雷盛红酒的多样性…

智慧互联网医院系统开发指南:从源码到在线问诊APP

近期&#xff0c;互联网医院系统的热度非常高&#xff0c;很多人跟小编提问如何开发&#xff0c;今天小编将从零开始为大家详解互联网医院系统源码&#xff0c;以及在线问诊APP开发技术。 一、需求分析与系统设计 1.1 需求分析 用户管理 预约挂号 在线问诊 电子病历 药品…

0606 作业

#include <stdio.h> #include <string.h>typedef struct usr{char unm[21];char pwd[21]; }user;int main(int argc, const char *argv[]) {FILE* userfilefopen("./user_tible.txt","r");printf("输入username:");user u;scanf(&qu…

主流的单片机语言是 C 吗?是的话为啥不是 C++?

是c&#xff0c;而且可以预见在很长很长一段时间&#xff0c;没有巨大变革的情况下都会是c 商业项目开发光讨论语言特性优劣问题&#xff0c;是非常片面的&#xff0c;所以要看待为什么是c&#xff0c;最主要仍然是从收益和成本上来看。 刚好我有一些资料&#xff0c;是我根据…

【postgresql初级使用】初识触发器,在数据行发生变化时自动执行用户行为,也可以SQL级别触发,特别是视图上可以有触发器了

初识触发器 ​专栏内容&#xff1a; postgresql使用入门基础手写数据库toadb并发编程 个人主页&#xff1a;我的主页 管理社区&#xff1a;开源数据库 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物. 文章目录 初识触发器概…

深入解析手机信息恢复,2个技巧,做数据安全守护者

在现代社会&#xff0c;手机就像我们的第六感一样&#xff0c;随时能够帮助我们搞定难题。但是&#xff0c;有时候手机也会闹个小脾气&#xff0c;比如误删信息、系统崩溃和硬件故障等&#xff0c;这些问题可了不得&#xff01;它们会让我们无法访问那些重要的数据&#xff0c;…

【机器学习】逻辑回归:原理、应用与实践

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 逻辑回归&#xff1a;原理、应用与实践引言1. 逻辑回归基础1.1 基本概念1.2 Sig…

Mysql8安装教程与配置(超详细图文)

MySQL 8.0 是 MySQL 数据库的一个重大更新版本&#xff0c;它引入了许多新特性和改进&#xff0c;旨在提高性能、安全性和易用性。 1.下载MySQL 安装包 注&#xff1a;本文使用的是压缩版进行安装。 &#xff08;1&#xff09;从网盘下载安装文件 点击此处直接下载 &#…

网络层-IP协议 二

一、网段划分 为了进行组网,把一个IP地址,分成了两个部分: 网络号 主机号 例如:192.168.2.100 这个IP地址中,前面一部分 : 192.168.2就是我们的网络号 后面一部分 100就是我们的主机号. 家用宽带来说,一般默认就是前面三个字节是网络号,主机号的范围就表示局域网中可以有…

开机弹窗找不到opencl.dll怎么办,教你几种有效的修复方法

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“找不到opencl.dll文件”。这个问题可能会影响到我们的正常使用&#xff0c;因此了解其原因和解决方法是非常必要的。本文将从多个方面对“找不到opencl.dll文件”这一问题进行详细分析和解…

某h5st逆向分析

具体网址经过了base64处理 aHR0cHM6Ly9zby5tLmpkLmNvbS93YXJlL3NlYXJjaC5hY3Rpb24/a2V5d29yZD0lRTklOTklQTQlRTYlQjklQkYlRTYlOUMlQkEmc2VhcmNoRnJvbT1ob21lJnNmPTE1JmFzPTA 要做的是一个搜索的功能具体如图所示。 这里发现携带的参数中存在一个token还有一个加密参数&#x…

【网络安全的神秘世界】Kali火狐浏览器汉化教程

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 打开火狐浏览器 进入设置后&#xff0c;搜索language 打开之后选择添加其他语言&#xff0c;简体中文在最后一个 Add后点击ok即可

代码签名证书:软件安全的守护神

在数字化日益普及的今天&#xff0c;软件安全问题愈发受到人们的关注。而在这其中&#xff0c;一个常被提及但可能不为大众所熟知的名词——“代码签名证书”&#xff0c;实际上在软件安全领域扮演着举足轻重的角色。今天&#xff0c;我们就来聊聊代码签名证书对软件安全到底有…

SVM模型实现城镇居民月平均消费数据分类

SVM模型实现城镇居民月平均消费数据分类 一、SVM支持向量机简介二、数据集介绍三、SVM建模流程及分析一、SVM支持向量机简介 支持向量机是由感知机发展而来的机器学习算法,属于监督学习算法。支持向量机具有完备的理论基础,算法通过对样本进行求解,得到最大边距的超平面,并…