[c++高阶]模版进阶

news2024/12/23 7:02:43

1.前言

在我们学习c++的时候,常常会遇见要使用函数重载的情况。而当使用函数重载时,通常会使得我们编写很多重复的代码,这样就显得非常臃肿,并且效率非常的低下

重载的函数仅仅只是类型不同,代码的复用率比较低,只要有新类型出现时,就需要增加对应的函数。此外,代码的可维护性比较低,一个出错可能会导致所有的重载均出错。

        所以为了解决上述的问题,c++中专门提出了模版来解决这个问题。在前面已经讲解过一篇关于模版的初步知识,本篇博客主要讲解c++的模版进阶的知识。

2.什么是c++模版

     程序设计中经常会用到一些程序实体:它们的实现和所完成的功能基本相同,不同的仅 仅是所涉及的数据类型不同。而模板是一种专门处理不同数据类型机制


       模板------是泛型程序设计的基础(泛型generic type——通用类型之意)。
 

        函数、类以及类继承为程序的代码复用提供了基本手段,还有一种代码复用途径——类属类型(泛型),利用它可以给一段代码设置一些取值为类型的参数(注意:这些参数 的值是类型,而不是某类型的数据),通过给这些参数提供一些类型来得到针对不同类 型的代码。

2.1  范型编程的思想

范型编程:主要就是你刻画一个模子,然后其他人利用这个模子,生成出符合自己的,且是自己想要的东西。

2.2 c++模版的分类

c++模版主要分为函数模版和类模版

对于泛型编程和c++模版的分类的相关知识不了解的阅读下面文章:【c++基础(六)】模版初阶--泛型编程,类模板-CSDN博客

本章着重讲解的是模版的高阶操作::非类型模板参数、全特化、偏特化等,以及关于模板声明与定义不能分离(在两个不同的文件中)的问题。

3.非类型模版参数

3.1 什么是非类型模版参数

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

因此简单的说非类型模版参数就是指完成一些与类型无关的,与结构大小有关的模版参数。

3.2 问题的引入

   假设我现在自定义了一个静态栈,栈的大小设置为100。然后我构建了一个int 的类型的栈st1,和一个double 类型的栈st2。那么我希望stl 的大小为100,st2 的大小为500,能不能实现呢?

#define N 100
 
// 静态栈
template<class T>
class Stack
{
private:
	int _a[N];
	int _top;
};
 
int main()
{
	Stack<int> st1;
	Stack<double> st2;
 
	return 0;
}

显然目前是不能的,因为静态栈的大小已经确定了,无法被更改。那么有没有什么办法可以解决这个问题呢?--这个时候就要用到非类型的模版参数了。

3.3 非类型模版的使用

  • 非类型模板形参 : 就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
  • template <size_t N> // N 为模板参数中的 ------- 非类型模板形参

非类型模版参数的定义

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

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

3.4 解决上述问题

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

 // 静态栈
template<class T,size_t N=100>
class Stack
{
private:
	int _a[N];
	int _top;
};
 
int main()
{
	Stack<int> st1;
	Stack<double,200> st2;
 
	return 0;
}

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

template<class T, size_t N>
class Stack
{
public:
    T& operator[](size_t pos)
	{
		assert(pos >= 0 && pos < N);
		return _arr[pos];
	}
	size_t size() const
	{
		return N;
	}
 
private:
	int _arr[N];	//创建大小为 N 的整型数组
};
 
int main()
{
	Stack<int , 10> s1;   // 大小为 10
	Stack<double , 20> s2;   // 大小为 20
	Stack<char , 100> s3;  // 大小为 100
 
	// 输出它们的 类型
	cout << typeid(s1).name() << endl;
	cout << typeid(s2).name() << endl;
	cout << typeid(s3).name() << endl;
}

这样就完美的解决了上面无法自己调整大小的问题。

非类型模版参数也支持缺省值

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

3.5 非类型模版的使用规则

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

举个例子

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

这里使用的是非整型家族,这样会出现问题。

到此就可以总结出来非类型模版参数的使用规律了:

1.只能将整型家族作为非类型的模版参数

2.非类型的模版参数必须为常量,因为这个参数在编译阶段就要确定下来

整型家族:charshortboolintlonglong long 等

4.模版的特化

其实特化很好理解,就是把一个函数的模版,特化成针对某一类型或者某一具体需要的函数的使用。

举个例子来帮助理解:

一个函数比较指针类型

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}
 
int main()
{
 
	Date* p1 = new Date(2024, 7, 6);
	Date* p2 = new Date(2024, 7, 8);
 
	cout << Less(p1, p2) << endl; 
 
	return 0;
}

比较完了之后发现这样是会出现问题的。

  •  也就是说,Less 绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果

分析上述问题出现的原因

Less只是比较了指针地址,而Less 内部并没有比较p1和p2指向的对象内容,而比较的是pl和p2指针地址,这就无法达到预期,而错误。 

如何解决这个问题呢?

此时,就需要对 -------------- 模板进行特化处理

即 : 在原模板类的基础上 , 针对特殊类型所进行特殊化的实现方式。

模板特化中分为 函数模板特化 与 类模板特化。 

4.1 函数模版特化

函数模版特化也很好理解,就是把从 模版中复制一份出来,并给出实际的类型。

举个例子:

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}
 
// 对Less函数模板进行特化
template<>
bool Less<int*>(int* left, int* right)
{
	return *left < *right;
}
 
int main()
{
	int* p1 = new int (6);
	int* p2 = new int(8);
	cout << Less(p1, p2) << endl;
 
	return 0;
}

输出结果为:1

一般来说对less进行特化的话也可以这样写

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

4.2 类模板的特化

类模板的特化也很简单,也是从类模板中生成出一份具体类型的代码,用来专门处理这一个类型。类模板特化又分为全特化和偏特化

全特化

全特化指 将所有的模板参数特化为具体类型,将模板全特化后,调用时,会优先选择更为匹配的模板类。
简单一点来说:全特化 就是将模板参数列表中 所有的参数都确定话

举个例子:

 假设有下面这样一个 Data 类,我希望 构造函数 打印出来的 d2 对象面 Tl 是 int T2 是 double,有什么办法吗?

先使用类模板---然后再从类模板复制一份给出具体类型

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

我们实例化 dl 和 d2 对象时,编译器会自动调用其默认构造函数,当我们打印的时候,可以看到实际上d2 对象里面还是 T1 和 T2  并不是我们想要的 int  double。  

这个时候对T1,T2进行特化

template<class T1, class T2>
class Data
{
public:
	Data()
	{
		cout << "Data<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};
 
 
// 全特化
template<>
class Data<int, double>
{
public:
	Data()
	{
		cout << "Data<int, double>" << endl;
	}
private:
	int _d1;
	double _d2;
};
 
int main()
{
	Data<int, int> d1;
	Data<int, double> d2;
 
	return 0;
}

这样打印出来的结果就对了。

总结:

对模板进行全特化处理后,实际调用时,会优先选择已经特化并且类型符合的模板。因为不用再重复生成而是直接选择最合适的。

偏特化

偏特化简单来说就是特化一部分,还有一部分使用模版。

也可以是多个部分全部给定具体的参数类型。

举个例子:

部分特化

template<class T1, class T2>
class Data
{
public:
	Data()
	{
		cout << "Data<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};
 
// 部分特化 -- 将第一个参数特化为double
template<class T2>
class Data<double, T2>
{
public:
	Data()
	{
		cout << "Data<double, T2>" << endl;
	}
private:
	double _d1;
	T2 _d2;
};
 
int main()
{
	Data<int, int> _d1;
	Data<double, double> _d2;
	Data<double, char> _d3;
 
	return 0;
}

参数全部给定,来使其只针对某一类型

// 基础模板
template<class T1, class T2>
class Data
{
public:
	Data()
	{
		cout << "Data<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};
 
// 部分特化 -- 将第一个参数特化为double
template<class T2>
class Data<double, T2>
{
public:
	Data()
	{
		cout << "Data<double, T2>" << endl;
	}
private:
	double _d1;
	T2 _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, int> d1; // 调用基础的版本
 
	Data<double, double> d2; // 调用部分特化的double版本
 
	Data<int*, int*> d3; // 调用特化的指针版本
 
	Data<int&, int&> d4(2, 4); // 调用特化的引用版本
 
	return 0;
}

5.总结

模板是 STL 的基础支撑,假若没有模板、没有泛型编程思想,那么恐怕 "STL" 会变得非常大 。可以说模版在c++中是重中之重,希望各位小伙伴好好理解一下。

模板的优点 

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

模板的缺点 

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

6.共勉

以下就是我对 【模板进阶】 的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对 C++ 的理解,请持续关注我,谢谢大家!!! 

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

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

相关文章

浮点数的这些特性你了解吗

问题1:下面的代码&#xff0c;输出结果是什么&#xff1a; public class CaclTest{public void test1(){float f 1.0F / 0.0F;System.out.println("f:" f)}public static void main(String[] args){CaclTest ct new CaclTest();ct.test1();}} A. 运行抛出异常:j…

7.数据结构与算法-循环链表

如果经常对首位元素进行操作&#xff0c;用尾元素更方便更快捷 两个循环链表合并

信息安全工程师(21)安全协议

前言 安全协议是建立在密码体制基础上的一种交互通信协议&#xff0c;它运用密码算法和协议逻辑来实现认证、密钥分配、数据机密性、完整性和抗否认性等安全目标。 一、定义与目的 安全协议旨在确保网络环境中信息交换的安全性&#xff0c;通过密码技术和协议逻辑来保护数据的机…

第八届蓝桥杯嵌入式省赛程序设计题解析(基于HAL库)

一.题目分析 &#xff08;1&#xff09;.题目 &#xff08;2&#xff09;.题目分析 1.按键功能分析----过程控制 a. 选择按键按下的个数和目标层数&#xff08;每个按键都要在一秒之内按下&#xff0c;否则就结束&#xff09; b. 当升降机到达目标平台&#xff0c;LED灯熄灭 c.…

负载均衡(Load Balancing)是一种计算机技术,用于在网络应用中分配工作负载,以优化资源使用、最大化吞吐量、减少响应时间以及避免过载。

负载均衡&#xff08;Load Balancing&#xff09;是一种计算机技术&#xff0c;用于在网络应用中分配工作负载&#xff0c;以优化资源使用、最大化吞吐量、减少响应时间以及避免过载。通过将任务均匀地分布在多个组件上&#xff0c;如服务器、网络链接、CPU、硬盘等&#xff0c…

【AG 创新工坊】探索存内计算的未来,共话 AGI 时代

目录 ⚛️1. 会议详情 ☪️2. 会议回顾 ♋2.1 多模态时代&#xff0c;存内计算架构的应用与发展 ♏2.2 分布式环境下深度学习任务的高效可靠执行研究 ♐2.3 IGZO在后道单片三维集成中的机遇与挑战 ♑2.4 witin-nn:神经网络算法模型在存内开发板上的应用开发 ♉2.5 茶歇交…

讯飞星火编排创建智能体学习(一)最简单的智能体构建

目录 开篇 智能体的概念 编排创建智能体 创建第一个智能体 ​编辑 大模型节点 测试与调试 开篇 前段时间在华为全联接大会上看到讯飞星火企业级智能体平台的演示&#xff0c;对于拖放的可视化设计非常喜欢&#xff0c;刚开始以为是企业用户才有的&#xff0c;回来之后查…

X86架构(九)——保护模式的进入

全局描述符表 全局描述符表(Global Descriptor Table,GDT)是保护模式下非常重要的一个数据结构。 在保护模式下&#xff0c;对内存的访问仍然使用段地址和偏移地址&#xff0c;在每个段能够访问之前&#xff0c;必须先行设置好 GDT 的地址&#xff0c;并加载全局描述符表寄存…

推荐4款2024年大家都在用的高质量翻译器。

翻译器在我们的生活中有着很重要的作用&#xff0c;不管是我们在学习还是工作&#xff0c;生活娱乐&#xff0c;出国旅游等场合都会派上用场&#xff0c;它是我们解决沟通的障碍&#xff0c;提高阅读效率的好帮手。我自己使用的翻译器有很多&#xff0c;可以给大家列举几款特别…

依赖倒置原则(学习笔记)

抽象不应该依赖细节&#xff0c;细节应该依赖抽象。简单的说就是要求对抽象进行编程&#xff0c;不要对实现进行编程&#xff0c;这样就降低了客户与实现模块间的耦合。 依赖倒转原则是基于这样的设计理念&#xff1a;相对于细节的多变性&#xff0c;抽象的东西要稳定的多。 以…

了解输出电源优先级

主要又SUB&#xff0c;SBU以及USB三种模式。 调试10kW逆变器存在的输出电源优先级的问题&#xff0c;当优先级为SUB时&#xff0c;利用电压源模拟电池&#xff0c;当电池电压超过58.4V&#xff0c;即过压&#xff0c;在接入市电&#xff0c;市电继电器仍然闭合&#xff0c;仍然…

pyboard405意外故障,micropython OLED例程无法运行,折腾了大半天。

thonny报告&#xff1a; Traceback (most recent call last): File "<stdin>", line 3, in <module> RuntimeError: name too mode # main.py -- put your code here! from machine import I2C,Pin #从machine模块导入I2C、Pin子模块 from ss…

javascript:监听浏览器页签切换

监听页面的可见性变化&#xff0c;在很多场景下非常实用&#xff0c;比如跟踪用户行为、节省资源、优化性能等。 1 代码示例 document.addEventListener("visibilitychange", () > {if (document.visibilityState "visible") {alert("当前页面已…

上交所系统被股民买崩了?原因竟然是...

在A股市场迎来久违的牛市之际&#xff0c;上海证券交易所&#xff08;上交所&#xff09;却在关键时刻遭遇了技术挑战。9月27日&#xff0c;上交所的交易系统出现了罕见的宕机现象&#xff0c;持续时间长达两个小时&#xff0c;导致投资者在火热的交易期间无法正常进行交易操作…

通信工程学习:什么是OFDM正交频分复用

OFDM&#xff1a;正交频分复用 OFDM&#xff08;Orthogonal Frequency Division Multiplexing&#xff0c;正交频分复用&#xff09;是一种在通信领域中广泛应用的多载波调制技术。该技术通过将高速数据流分割成多个低速子流&#xff0c;并在不同频率上同时传输这些子流&#x…

论文笔记:Gradient Episodic Memory for Continual Learning

1. Contribution 提出了一组指标来评估模型在连续数据上的学习情况。这些指标不仅通过测试准确性来表征模型&#xff0c;还通过其跨任务迁移知识的能力来表征模型。针对持续学习任务&#xff0c;提出了GEM模型&#xff08;Gradient Episodic Memory&#xff09;&#xff0c;它…

【预备理论知识——1】深度学习:概率论概述

简单地说&#xff0c;机器学习就是做出预测。 概率论 掷骰子 假设我们掷骰子&#xff0c;想知道看到1的几率有多大&#xff0c;而不是看到另一个数字。 如果骰子是公平的&#xff0c;那么所有六个结果{1,…, 6}都有相同的可能发生&#xff0c; 因此我们可以说 1 发生的概率为1…

Linux date命令(用于显示和设置系统的日期和时间,不仅可以显示时间,还能进行复杂的时间计算和格式化)

文章目录 深入探讨 Linux Date 命令1. Date 命令详细功能解析1.1 命令概述1.2 命令语法 2. 时间显示与格式化2.1 标准时间输出2.2 自定义格式输出 3. 设置系统日期和时间3.1 基本用法3.2 注意事项 4. 实用示例与脚本应用4.1 生成时间戳秒级时间戳毫秒时间戳 4.2 时间戳转换4.3 …

什么是后仿

什么是后仿 参考 查了一圈发现网上对post netlist simulation 介绍的比较少&#xff0c;今天和大家聊聊post netlist simulation。 首先什么是post netlist simulation(后面简称 postsim )&#xff1f; Netlist simulation 有些公司也叫gate level simulation&#xff0c;是指将…

问:全国产业园数量增长,对中小企业意味着什么?

随着全国产业园数量的持续增长&#xff0c;这一趋势无疑为中小企业带来了前所未有的机遇与可能。产业园作为产业集聚的重要载体&#xff0c;不仅为中小企业提供了更广阔的发展空间&#xff0c;还通过资源共享、成本降低、创新协同等方式&#xff0c;助力企业快速成长。 对于中…