C++【模板进阶】

news2024/11/13 15:56:18

✨个人主页: 北 海
🎉所属专栏: C++修行之路
🎃操作环境: Visual Studio 2019 版本 16.11.17

成就一亿技术人


文章目录

  • 🌇前言
  • 🏙️正文
    • 1、非类型模板参数
      • 1.1、使用方法
      • 1.2、类型要求
      • 1.3、实际例子:array
    • 2、模板特化
      • 2.1、概念
      • 2.2、函数模板特化
      • 2.3、类模板特化
        • 2.3.1、全特化
        • 2.3.2、偏特化
    • 3、模板的分离编译问题
      • 3.1、失败原因
      • 3.2、解决方法
    • 4、模板小结
  • 🌆总结


🌇前言

模板是搭建 STL 的基本工具,同时也是泛型编程思想的代表,模板用好了可以提高程序的灵活性,以便进行更高效的迭代开发,模板除了最基本的类型替换功能外,还有更多高阶操作:非类型模板参数、全特化、偏特化等,以及关于模板声明与定义不能分离(在两个不同的文件中)的问题,都将在本文中进行介绍

活字印刷


🏙️正文

1、非类型模板参数

之前所使用的模板参数都是用来匹配不同的类型,如 intdoubleDate 等,模板参数除了可以匹配类型外,还可以匹配常量(非类型),完成如数组、位图等结构的大小确定

1.1、使用方法

在定义模板参数时,不再使用 classtypename,而是直接使用具体的类型,如 size_t,此时称为 非类型模板参数

注:非类型模板参数必须为常量,即在编译阶段确定值

利用 非类型模板参数 定义一个大小可以自由调整的 整型数组

template<size_t N>
class arr
{
public:
	int& operator[](size_t pos)
	{
		assert(pos >= 0 && pos < N);
		return _arr[pos];
	}

	size_t size() const
	{
		return N;
	}

private:
	int _arr[N];	//创建大小为 N 的整型数组
};

结果

再加入一个模板参数:类型,此时就可以得到一个 泛型、大小可自定义 的数组

template<class T, size_t N>
class arr
{
public:
	T& operator[](size_t pos)
	{
		assert(pos >= 0 && pos < N);
		return _arr[pos];
	}

	size_t size() const
	{
		return N;
	}

private:
	T _arr[N];	//创建大小为 N 的整型数组
};

结果

非类型模板参数支持缺省,因此写成这样也是合法的

template<class T, size_t N = 10>	//缺省大小为10

1.2、类型要求

非类型模板参数要求类型为 整型家族,其他类型是不行的

比如下面这些 非类型模板参数 都是标准之内的

//整型家族(部分)
template<class T, int N>
class arr1 { /*……*/ };

template<class T, long N>
class arr2 { /*……*/ };

template<class T, char N>
class arr3 { /*……*/ };

而一旦使用其他家族类型作为 非类型模板参数,就会引发报错

//浮点型,非标准
template<class T, double N>
class arr4 { /*……*/ };

结果

因此可以总结出,非类型模板参数 的使用要求为

  • 只能将 整型家族 类型作为非类型模板参数,其他类型不在标准之内
  • 非类型模板参数必须为常量(不可被修改),且需要在编译阶段确定结果

整型家族:charshortboolintlonglong long

1.3、实际例子:array

C++11 标准中,引入了一个新容器 array,它就使用了 非类型模板参数,为一个真正意义上的 泛型数组,这个数组是用来对标传统数组的

注意: 部分老编译器可能不支持使用此容器

array

array 的第二个模板参数就是 非类型模板参数

#include <iostream>
#include <cassert>
#include <array>

using namespace std;

int main()
{
	int arrOld[10] = { 0 };	//传统数组
	array<int, 10> arrNew;	//新标准中的数组

	//与传统数组一样,新数组并没有进行初始化
	//新数组对于越界读、写检查更为严格

	arrOld[15];	//老数组越界读,未报错
	arrNew[15];	//新数组则会报错

	arrOld[12] = 0;	//老数组越界写,不报错,出现严重的内存问题
	arrNew[12] = 10;	//新数组严格检查
	return 0;
}

array 是泛型编程思想中的产物,支持了许多 STL 容器的功能,比如 迭代器运算符重载 等实用功能,最主要的改进是 严格检查越界行为

实际开发中,很少使用 array,因为它对标传统数组,连初始化都没有,vector 在功能和实用性上可以全面碾压,并且 array 使用的是 栈区 上的空间,存在栈溢出问题,可以说 array 是一个鸡肋的容器

array 如何做到严格的全面检查?

  • 这个很简单,得益于类的封装,在进行下标相关操作前,先将传入的下标 pos 进行合法性检验即可,如 assert(pos >= 0 && pos < N)

2、模板特化

模板除了可以根据传入的类型进行实例化外,还可以指定实例化,这就好比普通汽车只能在公路上行驶,但我们也可以将车进行特殊改装,让其能在山川泥潭中驰骋;模板特化的用意就在于此,通过对 泛型思想的特殊化处理 ,更好的符合我们的使用需求

普拉多

2.1、概念

通常情况下,模板可以帮我们实现一些与类型无关的代码,但在某些场景中,【泛型】无法满足调用方的精准需求,此时会引发错误,比如使用 日期类对象指针 构建优先级队列后,若不编写对应的仿函数,则比较结果会变为未定义

结果
详见 《C++ STL学习之【优先级队列】》

原因:泛型思想无法满足特殊场景

解决方案:利用模板的特化制定更加精准的比较逻辑

综上所述,所谓模板的特化,就是在原模板的基础之上,对原模板进行特殊化处理,创造出另一个 “特殊” 的模板,完成需求

2.2、函数模板特化

函数也可以使用模板,因此支持 模板的特化

比如在下面这个比较函数中,假若不进行特化,则会出现错误的结果

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

int main()
{
	int x = 10;
	int y = 20;
	cout << "x == y: " << isEqual(x, y) << endl;

	char str1[] = "Haha";
	char str2[] = "Haha";

	//此时泛型比的是地址,实际内容是相等的!
	cout << "str1 == str2: " << isEqual(str1, str2) << endl;	
	return 0;
}

结果
原因:字符串比较时,比较的是地址,而非内容

解决方案:利用模板的特化,为字符串的比较构建一个特殊模板

//函数模板特殊,专为 char* 服务
template<>
bool isEqual<char*>(char* x, char* y)
{
	return strcmp(x, y) == 0;
}

结果
此时比较的结果正常,成功解决了问题

不过对于函数模板特化来说,存在一个更加方便的东西:函数重载,同样也能解决特殊需求,同时写法没这么怪,不过既然存在 函数模板特化 这个语法,那么我们还是得学习下的

结果

2.3、类模板特化

模板特化主要用在类模板中,它可以在泛型思想之上解决大部分特殊问题,并且类模板特化还可以分为:全特化和偏特化,适用于不同场景

后续举例时需要用到 Date 日期类,这里先把代码放出来

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

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

2.3.1、全特化

全特化指 将所有的模板参数特化为具体类型,将模板全特化后,调用时,会优先选择更为匹配的模板类

//原模板
template<class T1, class T2>
class Test
{
public:
	Test(const T1& t1, const T2& t2)
		:_t1(t1)
		,_t2(t2)
	{
		cout << "template<class T1, class T2>" << endl;
	}

private:
	T1 _t1;
	T2 _t2;
};

//全特化后的模板
template<>
class Test<int, char>
{
public:
	Test(const int& t1, const char& t2)
		:_t1(t1)
		, _t2(t2)
	{
		cout << "template<>" << endl;
	}

private:
	int _t1;
	char _t2;
};

int main()
{
	Test<int, int> T1(1, 2);
	Test<int, char> T2(20, 'c');
	return 0;
}

结果

对模板进行全特化处理后,实际调用时,会优先选择已经特化并且类型符合的模板,这就好比虽然你家冰箱里有菜,但你还是想点外卖,因为外卖对于你来说更加合适

可以使用全特化,解决之前优先级队列中,类型为 日期类指针 Date* 的比较问题

注:这里只是举例说明,完整代码参考优先级队列相关文章

//对比较的仿函数进行全特化处理
template<>
struct less<Date*>
{
	//比较 是否小于
	bool operator()(Date* x, Date* y)
	{
		return *x < *y;
	}
};

template<>
struct greater<Date*>
{
	//比较 是否大于
	bool operator()(Date* x, Date* y)
	{
		return *x > *y;
	}
};

结果

注意:

  • 在进行全特化前,需要存在最基本的泛型模板
  • 全特化模板中的模板参数可以不用写
  • 需要在类名之后,指明具体的参数类型,否则无法实例化出对象

2.3.2、偏特化

偏特化,指 将泛型范围进一步限制,可以限制为某种类型的指针,也可以限制为具体类型

//原模板---两个模板参数
template<class T1, class T2>
class Test
{
public:
	Test()
	{
		cout << "class Test" << endl;
	}
};

//偏特化之一:限制为某种类型
template<class T>
class Test<T, int>
{
public:
	Test()
	{
		cout << "class Test<T, int>" << endl;
	}
};

//偏特化之二:限制为不同的具体类型
template<class T>
class Test<T*, T*>
{
public:
	Test()
	{
		cout << "class Test<T*, T*>" << endl;
	}
};

int main()
{
	Test<double, double> t1;
	Test<char, int> t2;
	Test<Date*, Date*> t3;
	return 0;
}

结果

偏特化(尤其是限制为某种类型)在 泛型思想特殊情况 之间做了折中处理,使得 限制范围式的偏特化 也可以实现 泛型

  • 比如偏特化为 T*,那么传 int*char*Date* 都是可行的

借助偏特化解决指针无法正常比较问题(也是可以偏特化为引用类型的)

//原来的比较模板
template<class T>
class Less
{
public:
	bool operator()(T x, T y) const
	{
		return x < y;
	}
};

//偏特化后的比较模板
template<class T>
class Less<T*>
{
public:
	bool operator()(T* x, T* y) const
	{
		return *x < *y;
	}
};

int main()
{
	Date d1 = { 2018, 4, 10 };
	Date d2 = { 2023, 5, 10 };

	cout << "d1 < d2: " << Less<Date>()(d1, d2) << endl;
	cout << "&d1 < &d2: " << Less<Date*>()(&d1, &d2) << endl;

	int a = 1;
	int b = 2;
	cout << "&a < &b: " << Less<int*>()(&a, &b) << endl;
	return 0;
}

结果

当然也可以使用 偏特化 解决 Date* 的比较问题,这里就不再演示

注意:

  • 在进行偏特化前,需要存在最基本的泛型模板
  • 偏特化与全特化很像,注意区分

3、模板的分离编译问题

早在 模板初阶 中,我们就已经知道了 模板不能进行分离编译,会引发链接问题

结果

下面就来谈谈为什么会出现这个问题

3.1、失败原因

声明与定义分离后,在进行链接时,无法在符号表中找到目标地址进行跳转,因此链接错误

下面是 模板声明与定义写在同一个文件中时,具体的汇编代码执行步骤

Test.h

#pragma once

//声明
template<class T>
T add(const T x, const T y);

//定义
template<class T>
T add(const T x, const T y)
{
	return x + y;
}

main.cpp

#include <iostream>
#include "Test.h"

using namespace std;

int main()
{
	add(1, 2);
	return 0;
}

结果

声明与定义在同一个文件中时,可以直接找到函数的地址

代码从文本变为可执行程序所需要的步骤:

  1. 预处理:头文件展开、宏替换、条件编译、删除注释,生成纯净的C代码
  2. 编译:语法 / 词法 / 语义 分析、符号汇总,生成汇编代码
  3. 汇编:生成符号表,生成二进制指令
  4. 链接:合并段表,将符号表进行合并和重定位,生成可执行程序

当模板的 声明定义 分离时,因为是 【泛型】,所以编译器无法确定函数原型,即 无法生成函数,也就无法获得函数地址,在符号表中进行函数链接时,必然失败

错误

图解

简单举个例子:抛开模板这个东西,在头文件中声明函数,但不定义,调用函数时,报的就是链接错误

Test.h

#pragma once

//只声明,不定义
void sub(int x, int y);

main.cpp

#include <iostream>
#include "Test.h"

using namespace std;

int main()
{
	//add(1, 2);
	sub(2, 1);
	return 0;
}

结果

3.2、解决方法

解决方法有两种:

  1. 在函数定义时进行模板特化,编译时生成地址以进行链接
  2. 模板的声明和定义不要分离,直接写在同一个文件中
//定义
//解决方法一:模板特化(不推荐,如果类型多的话,需要特化很多份)
template<>
int add(const int x, const int y)
{
	return x + y;
}
//定义
//解决方法二:声明和定义写在同一个文件中
template<class T>
T add(const T x, const T y)
{
	return x + y;
}

这也就解释了为什么涉及 模板 的类,其中的函数声明和定义会写在同一个文件中 (.h),著名的 STL 库中的代码的声明和定义都是在一个 .h 文件中

结果

为了让别人一眼就看出来头文件中包含了 声明定义,可以将头文件后缀改为 .hpp,著名的 Boost 库中就有这样的命名方式

boost库


4、模板小结

模板是 STL 的基础支撑,假若没有模板、没有泛型编程思想,那么恐怕 "STL" 会变得非常大

模板的优点

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

模板的缺点

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

总之,模板 是一把双刃剑,既有优点,也有缺点,只有把它用好了,才能使代码 更灵活、更优雅


🌆总结

以上就是有关 C++【模板进阶】的全部内容了,在本文中,我们学习了非类型模板参数,认识了 C++11 中的新容器 array;然后学习了模板的特化,见识了模板特化的各种场景;最后明白了模板声明与定义不能分离的根本原因,总之,模板很强,但想要用好还得多练

C++ 初阶系列文章到此就正式结束了,后续将会继续更新 C++ 进阶内容,比如 继承多态高阶二叉树 等等高能知识点,敬请期待吧


星辰大海

相关文章推荐

STL 之 适配器

C++ STL学习之【优先级队列】

C++ STL学习之【反向迭代器】

C++ STL学习之【容器适配器】

===============

STL 之 list

C++ STL学习之【list的模拟实现】

C++ STL学习之【list的使用】

===============

STL 之 vector

C++ STL学习之【vector的模拟实现】

C++ STL学习之【vector的使用】

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

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

相关文章

详解:三子棋以及N子棋的实现

三子棋以及N子棋的实现 初始化棋盘打印棋盘玩家下棋电脑下棋判断输赢主函数的实现(test.c)game.c的实现game.h的实现 铁汁们~今天给大家分享一篇三子棋以及N子棋的实现&#xff0c;来吧&#xff0c;开造⛳️ 实现流程&#xff1a; 1.游戏不退出&#xff0c;继续玩下一把&#x…

ML之FE:基于波士顿房价数据集利用LightGBM算法进行模型预测然后通过3σ原则法(计算残差标准差)寻找测试集中的异常值/异常样本

ML之FE&#xff1a;基于波士顿房价数据集利用LightGBM算法进行模型预测然后通过3σ原则法(计算残差标准差)寻找测试集中的异常值/异常样本 目录 基于波士顿房价数据集利用LiR和LightGBM算法进行模型预测然后通过3σ原则法(计算残差标准差)寻找测试集中的异常值 # 1、定义数据…

软件架构复习笔记(张友生教材版本)

考纲(张友生版本软件架构 考试题型&#xff1a; 10*3单选 5*3简答题 5*3设计图&#xff08;含画图&#xff09; 10*2 论述题 10*2综合题 复习以课件为主&#xff0c;书为辅 第一章 (软件危机) &#xff1f; &#xff1f; 构造模型与实现 掌握软件结构体系核心模型 第二章 软件体…

K8s之Pod最小调度单元详解

文章目录 一、Pod概念1、Pod是什么&#xff1f;2、Pod网络共享实现方式3、Pod存储共享方式4、创建Pod整体流程 二、使用YAML文件定义Pod资源1、Pod资源清单YAML文件书写技巧1. YAML语法格式&#xff1a;2. 配置Linux tab缩进两个空格3. 使用kubectl explain帮助命令 2、创建Pod…

ChatGPT客服系统产品-利用chatgpt训练企业知识开发个性化客服系统

打造最前沿的AI智能客服系统&#xff0c;基于自有数据语料&#xff0c;充分运用ChatGPT的大模型自然语言生成能力&#xff0c;定制化客服系统为企业提供自主性的客服服务能力。 ChatGPT如何革新智能客服&#xff1f; 根据当前ChatGPT的使用情况&#xff0c;我们发现未来中短期内…

基于 DDR3 的串口传图帧缓存系统设计实现(fifo2mig_axi )

文章目录 前言一、接口转换模块设计二、fifo2mig_axi 模块二、接口转换模块仿真四、fifo2mig_axi_tb五、仿真展示 前言 结合串口接收模块和 tft 显示屏控制模块&#xff0c;设计一个基于 DDR3 的串口传图帧缓存系统。 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面…

次世代烘焙 法线贴图 相关知识

一般将低模 高模的法线贴图实现大量细节模型画面的游戏称为次时代游戏。 次世代常用软件 低模&#xff1a;Maya、3Dmax、Topogun 。 中模&#xff1a;Maya、3Dmax 。 高模&#xff1a;Maya、3Dmax、Zbrush。 UV&#xff1a;Maya、Zbrush、Unfold3D、Uvlayout 。 烘焙&#x…

【观察】华为重构分销伙伴体系,坚持“长期主义”做大分销市场

毫无疑问&#xff0c;随着数字化转型的加速&#xff0c;当前不同类型、不同规模的企业&#xff0c;在面临数字化转型时呈现出了不同的困境和特征&#xff0c;同时对合作伙伴也提出了更高的要求&#xff0c;因此唯有通过“精耕细作”的方式才能更好地加速企业数字化转型的步伐。…

AdaSparse: 自适应稀疏网络的多场景CTR预估建模

▐ 摘要 CTR(Click-through rate)预估一直是推荐/广告领域重要技术之一。近年来&#xff0c;通过统一模型来服务多个场景的预估建模已被证明是一种有效的手段。当前多场景预估技术面临的挑战主要来自两方面&#xff1a;1&#xff09;跨场景泛化能力&#xff1a;尤其对稀疏场景&…

【分布式锁】Redisson分布式锁的使用(推荐使用)

文章目录 前言一、常见分布式锁方案对比二、分布式锁需满足四个条件三、什么是Redisson?官网和官方文档Redisson使用 四、Redisson 分布式重入锁用法Redisson 支持单点模式、主从模式、哨兵模式、集群模式自己先思考下,如果要手写一个分布式锁组件&#xff0c;怎么做&#xff…

深入理解Java虚拟机:JVM高级特性与最佳实践-总结-1

深入理解Java虚拟机&#xff1a;JVM高级特性与最佳实践-总结-1 Java内存区域与内存溢出异常运行时数据区域程序计数器Java虚拟机栈本地方法栈Java堆方法区 OutOfMemoryError异常Java堆溢出 垃圾收集器与内存分配策略对象是否可以被回收引用计数算法可达性分析算法 Java内存区域…

力库华为机试题练习

1、两数之和 arg [2, 3, 6, 5] target 4 for i in range(len(arg)): other target - arg[i] if other in arg[i1:]: print(i, arg[i1:].index(other)i1) else: print(“输入目标数在该列表中不存在”) 2、回文数 方法一&#xff1a; class Solution: def isPalindrome(sel…

抖音小程序怎么压缩图片?教你使用抖音图片压缩助手

图片压缩是将原始图像的数据量进行减少&#xff0c;从而使其文件大小更小&#xff0c;但尽量保持原有图像质量的一种技术。通过对图片进行压缩&#xff0c;可以降低图片在传输过程中所需的带宽和存储空间&#xff0c;提高网站或应用程序的加载速度和响应速度。 此外&#xff0…

亚马逊云科技将帮助GoPlus Security,助力行业健康发展

Gartner 2022年7月发布的技术成熟度曲线分析报告显示&#xff0c;目前Web3技术已经历了第一波创新高峰期&#xff0c;正在从“创新启动阶段”向“创新泡沫阶段”过渡&#xff0c;技术体系逐步成型&#xff0c;市场热度较高&#xff0c;创业投资活跃。高速增长的背后&#xff0c…

浅谈Hutool工具类

一、Hutool简介 Hutool是一个Java工具类库&#xff0c;它封装了很多常用的Java工具类&#xff0c;如加密解密、文件操作、日期时间处理、Http客户端等。它的目标是让Java开发变得更加简单、高效。 二、Hutool的特点 高效&#xff1a;提供了很多高效的工具类和方法。 简单&…

最全的国内chatGPT大模型企业及产品整理

作者 | gongyouliu 编辑 | gongyouliu 自从去年11月30日openAI发布chatGPT以来&#xff0c;chatGPT引爆了新一轮科技革命。最近很多年都没有哪一项科技进步如chatGPT这般吸引全球的目光。除了媒体的大肆报道&#xff0c;国内外各个科技公司、科研机构、高等院校都在跟进&#x…

智能卡接口(ISO7816)

概述 智能卡接口&#xff08;7816&#xff09;是外部智能卡通过2 线交换8 位数据的串行同步通讯手段。芯片提供了2 个7816主机接口模块。 ⚫ 2路独立7816接口 ⚫ 具备卡时钟输出端口&#xff0c;输出频率在1MHz~5MHz之间可设 ⚫ 位传输方向可配置&#xff0c;支持MSB First或LS…

初识C++之C++中的IO流

目录 一、C语言中的输入与输入 二、流 三、C中的流 四、C中的文件IO流 1. 二进制文件 1.1 打开文件 1.2 向文件写入数据 1.3 从文件读取数据 1.4 关闭文件 1.5 综合使用 2. 文本读写 一、C语言中的输入与输入 在C语言中&#xff0c;我们最长使用的输入输出方式就是…

0基础小白简单入门使用emqx的webhook+规则实现Mysql数据持久化

EMQX (opens new window)是一款大规模可弹性伸缩的云原生分布式物联网 MQTT (opens new window)消息服务器。 作为全球最具扩展性的 MQTT 消息服务器&#xff0c;EMQX 提供了高效可靠海量物联网设备连接&#xff0c;能够高性能实时移动与处理消息和事件流数据&#xff0c;帮助…

EndNote X9 参考文献附录列表 格式调整

文章目录 1 参考文献附录列表 格式调整2 EndNote X9 插入参考文献常见问题总结3 EndNote X9 快速上手教程&#xff08;毕业论文参考文献管理器&#xff09; 1 参考文献附录列表 格式调整 注意&#xff1a;这里讲的是对齐格式&#xff0c; 文献规范格式参考EndNote X9 快速上手…