【C++】模版进阶

news2025/2/9 7:11:09

目录

  • 一、非类型模版参数
  • 二、模板的特化
    • 1、概念
    • 2、函数模版特化
    • 3、类模板特化
      • 1.全特化
      • 2.偏特化
      • 3.类模板特化应用示例
  • 三、模版分离编译
    • 1、什么是分离编译
    • 2、模板的分离编译
    • 3、模板的优缺点

一、非类型模版参数

模版参数分为类型模版参数与非类型模版参数

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

举个例子

// 定义一个模板类型的静态数组
template<class T, size_t N = 10>
class array
{
public:
    T& operator[](size_t index)
    {
        return _array[index];
    }
    const T& operator[](size_t index)const
    {
        return _array[index];
    }

    size_t size()const
    {
        return _size;
    }
    bool empty()const
    {
        return 0 == _size;
    }

private:
    T _array[N];
    size_t _size;
};

注意:

  1. 其中N只能是整形常量,浮点数、类对象以及字符串都不允许作为非类型模板参数的。
  2. 非类型的模版参数必须在编译期就能确定结果。
  3. 类模版参数解决了typedef只能重定义一种类型的问题。

二、模板的特化

1、概念

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

class Date
{
public:
	Date(int year = 1900, 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);
	}

	friend ostream& operator<<(ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}

private:
	int _year;
	int _month;
	int _day;
};

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}
int main()
{
	cout << Less(1, 2) << endl; // 可以比较,结果正确

	Date d1(2023, 6, 15);
	Date d2(2023, 6, 14);
	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指针的地址,这就无法达到预期的结果。

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

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(2023, 6, 15);
	Date d2(2023, 6, 14);
	cout << Less(d1, d2) << endl; // 可以比较,结果正确

	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl; // 可以比较,结果错误
	return 0;
}

运行结果:
在这里插入图片描述
注意:一般情况下如果函数模版遇到不能处理或者处理有误的类型,为了实现简单,通常都是将该函数直接给出。

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

这种现实简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化

3、类模板特化

1.全特化

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

template<class T1, class T2>
class Data
{
public:
	Data() 
	{ 
		cout << "Data<T1, T2>" << endl; 
	}
private:
	T1 _d1;
	T2 _d2;
};
template<>
class Data<int, char>
{
public:
	Data() 
	{ 
		cout << "Data<int, char>" << endl; 
	}
private:
	int _d1;
	char _d2;
};
void Test()
{
	Data<int, int> d1;
	Data<int, char> d2;
}
int main()
{
	Test();
	return 0;
}

运行结果:
在这里插入图片描述

2.偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。
比如对于以下模板类:

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

void Test2()
{
	Data<int, double> d2; // 调用基础的模板 
	Data<double, int> d1; // 调用特化的int版本
	Data<int*, int*> d3; // 调用特化的指针版本
	Data<int&, int&> d4(1, 2); // 调用特化的引用版本
}

int main()
{
	Test2();
	return 0;
}

运行结果:
在这里插入图片描述

3.类模板特化应用示例

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

class Date
{
public:
	Date(int year = 1900, 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);
	}

	friend ostream& operator<<(ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}

private:
	int _year;
	int _month;
	int _day;
};
template<class T>
struct Less
{
	bool operator()(const T& x, const T& y) const
	{
		return x < y;
	}
};
int main()
{
	Date d1(2023, 6, 14);
	Date d2(2023, 6, 15);
	Date d3(2023, 6, 12);
	vector<Date> v1;
	v1.push_back(d1);
	v1.push_back(d2);
	v1.push_back(d3);
	// 可以直接排序,结果是日期升序
	sort(v1.begin(), v1.end(), Less<Date>());
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	vector<Date*> v2;
	v2.push_back(&d1);
	v2.push_back(&d2);
	v2.push_back(&d3);
	sort(v2.begin(), v2.end(), Less<Date*>());
	// 可以直接排序,结果错误,日期还不是升序,而v2中放的地址是升序
	// 此处需要在排序过程中,让sort比较v2中存放地址指向的日期对象
	// 但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期
	for (auto e : v2)
	{
		cout << *e << " ";
	}
	cout << endl;
	return 0;
}

此时v2中存放的地址:
在这里插入图片描述

运行结果:
在这里插入图片描述
通过观察上述过程的结果会发现,对于日期对象可以直接排序,并且结果是正确的。但是如果待排序元素是指针,结果就不一定正确了。因为,sort最终是按照Less模版中的方式比较的,所以只会比较指针,而不是比较指针指向的空间中内容。此时就可以使用类模版特化来处理此类问题:

//对Less类模板按照指针方式特化
template<>
struct Less<Date*>
{
	bool operator()(Date* x, Date* y) const
	{
		return *x < *y;
	}
};

运行结果:
在这里插入图片描述
特化后,在运行上述代码,就会得到正确结果了。

三、模版分离编译

1、什么是分离编译

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

2、模板的分离编译

我们先来看看以下场景,模版的声明和定义分开,在头文件中进行声明,源文件中完成定义:

// a.h
template<class T>
T Add(const T& left, const T& right);
void func();

//a.cpp
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
void func()
{
	cout << "void func()" << endl;
}

//main.cpp
int main()
{
	//Add(1, 2);
	//Add(1.0, 2.0);
	func();
	return 0;
}

分析:
在这里插入图片描述
我们从汇编角度看:
在这里插入图片描述
func会被编译成一堆指令,所以在a.obj中有func函数的地址,但是没有Add的地址,因为Add没有实例化,所以没办法确定T。
在这里插入图片描述

解决方法:

  1. 将声明和定义放到一个文件 “xxx.cpp” 里面或者xxx.h其实也是可以的。推荐。
  2. 模板定义的位置显式实例化。这种方法不实用,不推荐。

3、模板的优缺点

优点:

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

缺点:

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

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

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

相关文章

一定要看!带你选择适合自己的测试工具

目录 前言&#xff1a; Jmeter实现接口请求JSON断言 Postman接口请求断言 前言&#xff1a; 选择适合的测试工具对于测试人员和测试项目的成功非常重要。不同的测试工具都有其独特的优缺点&#xff0c;而且每个项目的需求也不尽相同。因此&#xff0c;在选择测试工具时&#…

使用vitepress快速搭建个人网站或官方文档网站

使用vitepress快速搭建个人网站或官方文档网站 1. vitepress是什么&#xff1f; 官方首页的介绍&#xff0c; 翻译过来就是&#xff0c;vite和vue组成的强大的静态网站构造器。简单、强大和快速&#xff0c;是你一直想要的SSG(Static Site Generator)框架。 官网地址&#…

python mitmproxy抓包库

一.简介 mitmproxy是一款用Python编写的支持HTTP(S)的中间人代理工具。它可以拦截、查看、修改、重放和保存HTTP/HTTPS流量 &#xff0c;支持命令行界面和图形界面&#xff0c;可用于安全测试、网络调试、API开发和反向工程等场景。mitmproxy具有很高的灵活性和扩展性&#xf…

GUT|IF30+的联合分析文章:宏基因加代谢组

● 代谢组学是基于LC-MS/MS液质联用技术对生物样本中的小分子代谢物进行定性和相对定量分析&#xff1b; ● 宏基因组-代谢组的联合分析可以用来解释差异菌群与差异代谢物的关联性&#xff1b; ● 从而帮助建立微生物-代谢物-表型之间的逻辑关系。 凌恩生物的宏基因组学引入了…

JS:yFiles for HTML Complete 2.5.0 Crack

yFiles for HTML Complete 是市场上最先进、最完整的图表解决方案。我们强大而灵活的 API 提供了广泛的功能——开箱即用。只需选择最符合您需求的那些。 适用于每个用例的布局 从大量预定义的布局中进行选择并配置它们以完美地适应手头的任务。 yFiles 提供业内最广泛的高质量…

Sentinel与Hystix的线程隔离有什么差别

线程隔离有两种方式实现&#xff1a; 线程池隔离&#xff08;hystix 默认采用&#xff09; 优点&#xff1a;支持主动超时&#xff0c;支持异步调用 缺点&#xff1a; 线程的额外开销比较大 场景&#xff1a; 低扇出&#xff08;服务A调用服务B这种简单的调用&#xff09;信号…

新能源共享叉车充电桩管理系统设计思路

一、充电桩系统设计思路 1、总后台端&#xff0c;电脑版 1.1运营商管理。后台可以添加运营商&#xff0c;运营商可以添加无限添加充电站、充电桩、站点合伙人、充电操作员等&#xff0c;运营商、站点合伙人均有独立的后台入口&#xff0c;可以管理和查看与当前运营商相关充电站…

测试行业干了6年,从只会点点点到了现在的测试开发,总算是证明了自己

测试不止是点点点 我感觉我是一个比较有发言权的人吧&#xff0c;我在测试行业摸爬滚打6年&#xff0c;以前经常听到开发对我说&#xff0c;天天的点点点有意思没&#xff1f; 和IT圈外的同学、朋友聊起自己的工作&#xff0c;往往一说自己是测试&#xff0c;无形中也会被大家…

如何优雅的给SpringBoot部署的jar包瘦身?

一、需求背景 我们知道Spring Boot项目&#xff0c;是可以通过java -jar 包名 启动的。 那为什么Spring Boot项目可以通过上述命令启动&#xff0c;而其它普通的项目却不可以呢&#xff1f; 原因在于我们在通过以下命令打包时 mvn clean package一般的maven项目的打包命令&…

k8s kubeadm高可用集群证书续期

1.查看证书期限 kubeadm certs check-expiration 2.更改系统时间为证书过期的时间 因为要保持集群的时间一直&#xff0c;使用xshell对多个会话同时执行以下命令 systemctl stop chrondy #停止时间同步工具 date -s 06/15/2024 #更改系统时间为证书过期后的时间 date …

2023年JDK要升级到多少?看看七家大模型给的答案

前言 在2023年很多公司应该还在用JDK8&#xff0c;目前JDK已经更新到JDK20&#xff0c;JDK21也将在2023年9月发布&#xff0c;那么在2023年如果我们要升级JDK&#xff0c;到底升级到哪个版本比较合适呢&#xff1f;这个问题我们可以交给大模型&#xff0c;看看各家大模型是怎么…

使用Jemeter对HTTP接口压测

我们不应该仅仅局限于某一种工具&#xff0c;性能测试能使用的工具非常多&#xff0c;选择适合的就是最好的。笔者已经使用Loadrunner进行多年的项目性能测试实战经验&#xff0c;也算略有小成&#xff0c;任何性能测试&#xff08;如压力测试、负载测试、疲劳强度测试等&#…

MySQL表CRUD

目录 一、Create 1.1 单行数据全列插入 1.2 多行数据指定列插入 1.3 插入否则更新 1.4 替换数据 二、Retrieve 2.1 SELECT列 2.2 WHERE条件 2.3 结果排序 2.4 筛选分页结果 三、Update 四、Delete 4.1 删除数据 4.2 截断表 五、插入查询结果 六、聚合函数 七…

IM相关技术

messages表 保存的消息记录(Saved Messages) bff,session TON以及tdlib 官方版设置中文 tg://setlanguage?langclassic-zh-cn https://web.telegram.org/k/ https://web.telegram.org/a/ https://github.com/TGX-Android https://github.com/NekoX-Dev/NekoX, 内置公共代理不…

C++类与对象(上)

类与对象&#xff08;上&#xff09; 1.面向过程与面向对象初步认识2.类的引入3.类的定义4.类的访问限定符以及封装4.1访问限定符4.2 封装 5.类的作用域6.类的实例化7.类对象模型7.1计算类对象的大小7.2类对象的存储方式的猜想 8.this指针8.2this指针的特性 1.面向过程与面向对…

JavaWeb之JSP

文章目录 JSP的基本介绍JSP的本质JSP的三种语法JSP头部的page指令language属性contentType属性image.pngpageEncoding属性import属性autoFlush属性 - 给out输出流使用buffer属性 - 给out输出流使用errorPage属性isErrorPage属性session属性extends属性 JSP中的常用脚本声明脚本…

贸易企业缺进项严重,如何减轻13%的增值税税负?

贸易企业缺进项严重&#xff0c;如何减轻13%的增值税税负&#xff1f; 《税筹顾问》专注于园区招商&#xff0c;您的贴身节税小能手&#xff0c;合理合规节税&#xff01; 贸易企业的增值税税负很重&#xff0c;这不仅是因为13%的高额增值税税率&#xff0c;也因为贸易企业缺进…

K8s 部署 Apache Kudu 集群

一、K8s 部署 Apache Kudu 集群 安装规划 组件replicaskudu-master3kudu-tserver3 1. 创建命名空间 vi kudu-ns.yamlapiVersion: v1 kind: Namespace metadata:name: apache-kudulabels:name: apache-kudukubectl apply -f kudu-ns.yaml查看命名空间&#xff1a; kubectl …

傻白入门芯片设计,形式化验证方法学——AveMC工具学习(二十)

一、形式验证方法学 &#xff08;一&#xff09;什么是形式化验证&#xff1f; 形式化验证方法学是使用数学证明的方法&#xff0c;分析设计中所有可能的状态空间来验证设计是否符合预期。形式化验证方法主要有三个方面的应用&#xff1a;定理证明、模型检验和等价性检查。 …

让你不再疑惑语音翻译怎么弄

语音是人类交流的一种最基本的方式&#xff0c;但是当我们需要和来自不同国家或地区的人交流时&#xff0c;语言的限制往往让我们感到无力。然而&#xff0c;如今的语音翻译技术正在以惊人的速度发展&#xff0c;使得我们的声音可以轻松地跨越语言的界限。那么&#xff0c;你知…