【与C++的邂逅】--- 模板初阶

news2024/12/25 8:59:27

 Welcome to 9ilk's Code World

       

(๑•́ ₃ •̀๑) 个人主页:         9ilk

(๑•́ ₃ •̀๑) 文章专栏:      与C++的邂逅


本篇博客我们将了解C++中泛型编程体现的一大利器 --- 模板,有了模板可以帮我们用户省力。


🏠 泛型编程

如何实现一个通用的交换函数呢?
void Swap(int& left, int& right)
{
 int temp = left;
 left = right;
 right = temp;
}
void Swap(double& left, double& right)
{
 double temp = left;
 left = right;
 right = temp;
}
void Swap(char& left, char& right)
{
 char temp = left;
 left = right;
 right = temp;
}

//...
使用函数重载虽然可以实现,但是有一下几个不好的地方:
1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数。
2. 代码的可维护性比较低,一个出错可能所有的重载均出错。

那能否 告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码 呢?
如果在 C++ 中,也能够存在这样一个 模具 ,通过给这个模具中 填充不同材料 ( 类型 ) ,来 获得不同材料的铸件 ( 即生成具体类型的代码) ,那将会节省许多头发。巧的是前人早已将树栽好,我们只需在此乘凉。
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

🏠 函数模板

📌 概念

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定 类型版本。
  • 格式
template<typename T1,typename T2,...,typename Tn>
返回值类型  函数名(参数列表)
{}

:typename是用来定义模板参数的关键字,也可以使用class(但是不能用struct替换class)

我们可以用函数模板解决C语言中不同类型swap函数复用率低,代码量大的问题。

template<typename T>
void Swap(T& left , T& right)
{
  T temp =  left;
    left = right;
   right = temp; 
}

int main()
{
  int a = 1;
  int b = 2;
  double c = 1.1;
  double d = 2.2;
  Swap(a,b); 
  Swap(c,d);
  return 0;
}

📌 原理

对于上述的函数模板,我们传不同类型参数时,它们调用的是同一个函数吗?

通过汇编,我们可以看到两次调用函数,call的函数地址都不同。这说明传不同的类型调用的是不同的函数。函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具,其实是编译器帮我们完成了我们原本需要干的事。

在编译器编译阶段 对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。
注:对于像Swap这样的函数是我们在实践中经常需要用的所以库里面提供了swap函数模板。

📌 实例化

用不同类型的参数使用函数模板时 称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化
  • 隐式实例化

隐式实例化:让编译器根据实参推演模板参数的实际类型。

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

template<class T1,class T2>
auto Add(const T1& left, const T2& right)
{
	return left + right;
}


int main()
{
 int a1 = 10, a2 = 20;
 double d1 = 10.0, d2 = 20.0;
 Add(a1, a2);
 Add(d1,d2);
 Add(a1,d2);//T1推演出int,T2推演出double
 return 0;
}

如果函数模板参数个数只有一个,然后调用Add(a1,d2)呢?

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

int main()
{
 int a1 = 10, a2 = 20;
 double d1 = 10.0, d2 = 20.0;
 Add(a1,d2);
 return 0;
}

此时该语句不同通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型,通过实参a1将T推演成int,通过实参d2将T推演成double类型,但模板参数列表中只有一个T,编译器无法确定此处到底应该将T确定为int或者double类型而报错。

注:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出现问题,编译器就需要背黑锅。

对于这种推演歧义问题,我们第一种解决方法是强转所传参数,第二种方式就是显示实例化。

Add(a1,d2);//编译器不会对其进行强转
Add(a1,(int)d2);
Add((double)a1,d2);

  • 显式实例化

显式实例化:在函数名后的<>中指定模板参数的实际类型,不再让你去推演参数。

Add<int>(a1,d2);
Add<double>(a1,d2);
注:如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。


显式实例化使用场景:
template<class T>
T* func(int a)
{
  T* p = (T*)operator new(sizeof(T));
  new(p)T(a);
  return p;
}

int main()
{
  //int * ret1 = func(1);//返回值不能推演 因为我可能不接收返回值
  int* ret1 = func<int>(1);
  A* ret2  = func<A>(1);
  return 0;
}

📌 模板参数匹配原则

// 专门处理int的加法函数  --- 1号
int Add(int left, int right)
{
	cout << "int Add(int left, int right)" << endl;
	return left + right;
}

 //通用加法函数          --- 2号
template<class T>
T Add(T left, T right)
{
	cout << "T Add(T left, T right) " << endl;
	return left + right;
}

template<class T1,class T2>   --- 3号
auto Add( T1 left,  T2 right)
{
	cout << " auto Add( T1 left,  T2 right) " << endl;
	return left + right;
}

int main()
{
   Add(1,2); //匹配1号
   Add(1.1,2.2); //匹配2号
   Add(1,1.2); //匹配3号
}

说明:

1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化成这个非模板函数。

2.在普通函数(如1号)和函数模板(2,3号)都存在的情况下,会优先匹配普通函数+参数类型匹配的那一个,也就是有现成品+符合参数口味。比如,当调用Add(1,2)时,函数模板推演出来两个模板参数都是int,但是由于有现成的1号函数所以会调用1号

3.没有普通函数时,会优先匹配函数模板+参数类型匹配。比如调用Add(1.1,2.2),没有现成两个参数都是double的普通函数,于是去看函数模板,2号模板和3号模板虽然都能推演出是double,但是3号其实是表示两个不同模板参数,所以更匹配2号。再比如调用Add(1,1.2)时,只能匹配3号了,因为只有它能推演出是两个不同类型参数。

int Add(int left, int right)
{
	cout << "int Add(int left, int right)" << endl;
	return left + right;
}

int main()
{
    Add(1,3);
    Add(1.1,2.2);
	Add(1.1, 2.2);
	return 0;
}

输出:

int Add(int left, int right)

int Add(int left, int right)

int Add(int left, int right)

template<class T>
T Add(T left, T right)
{
	cout << "T Add(T left, T right) " << endl;
	return left + right;
}

int main()
{
    Add(1,3);
    Add(1.1,2.2);
	Add(1.1, 2.2);
	return 0;
}

输出结果:

屏蔽第三个函数调用,前两个分别是:

T Add(T left, T right)

T Add(T left, T right)

第三个编译报错。

template<class T1,class T2>
auto Add( T1 left,  T2 right)
{
	cout << " auto Add( T1 left,  T2 right) " << endl;
	return left + right;
}

int main()
{
    Add(1,3);
    Add(1.1,2.2);
	Add(1.1, 2.2);
	return 0;
}

输出结果:

auto Add( T1 left,  T2 right)

auto Add( T1 left,  T2 right)

auto Add( T1 left,  T2 right)

因此得出结论:

1. 当只有一个函数时,类型转换一下也能用,也可以匹配调用(口味不对但将就用)。

2. 模板参数不允许自动类型转换,但普通函数可以进行自动类型转换。(比如只有1号函数时,对于调用Add(1,1.2)发生了类型转换,而对于2号函数模板则会发生歧义不能强转)

🏠 类模板

typedef int DataType;
struct Stack
{
 void Init(size_t capacity)
 {
 _array = (DataType*)malloc(sizeof(DataType) * capacity);
  if (nullptr == _array)
  {
  perror("malloc申请空间失败");
  return;
  }
  _capacity = capacity;
  _size = 0;
 }

 void Push(const DataType& data)
 {
  // 扩容
  _array[_size] = data;
  ++_size;
 }

 DataType Top()
 {
  return _array[_size - 1];
 }

 void Destroy()
 {
  if (_array)
  {
   free(_array);
   _array = nullptr;
   _capacity = 0;
   _size = 0;
  }
 }

 DataType* _array;
 size_t _capacity;
 size_t _size;
};

在C语言阶段我们实现的栈数据结构无法做到一个栈存int类型数据,一个栈存double类型数据,要的话只能写多个这样的类,但是这些类的接口一致,只是存储的数据类型不同,这时就得引出我们的类模板了。

📌 定义格式

template<class T1,class T2,...,class Tn>
class 类模板名
{
  ///...
};
注意:类模板中函数放在类外进行定义时,需要加模板参数列表。
template<class T>
class Vector
{ 
public :
 Vector(size_t capacity = 10)
 : _pData(new T[capacity])
 , _size(0)
 , _capacity(capacity)
 {}
 
 // 使用析构函数演示:在类中声明,在类外定义。
 ~Vector();
 
 void PushBack(const T& data);
 void PopBack();
 // ...
 
 size_t Size() {return _size;}
 
 T& operator[](size_t pos)
 {
 assert(pos < _size);
 return _pData[pos];
 }
 
private:
 T* _pData;
 size_t _size;
 size_t _capacity;
};

template <class T>
Vector<T>::~Vector()
{
 if(_pData)
 delete[] _pData;
 _size = _capacity = 0;
}

📌 类模板实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类, 而实例化的结果才是真正的类
// Vector类名,Vector<int>才是类型
Vector<int> s1;
Vector<double> s2;

🏠 辨析总结

模板分为函数模板和类模板,他们不是具体的一个类或函数,而是代表一个家族。

  • 类模板 vs 模板类

类模板是一个类家族,而模板类是通过类模板实例化出来的具体类。

注:类模板中成员函数全是函数模板,因为所有类模板的成员函数,放在类外定义时,需要在函数名前加类名,而类名实际为ClassName<T>,所以定义时还需加模板参数列表。

  • 函数模板 vs 模板函数

函数模板不是一个具体函数,而是一个函数家族,模板函数是根据函数模板实例化出来的函数。


完。

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

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

相关文章

二叉树的三个简单题

1、二叉树的第k个结点 思路解析 由题可知这是一棵二叉搜索树 它或者是一棵空树&#xff0c;或者是具有下列性质的二叉树&#xff1a; 1. 若它的左子树不空&#xff0c;则左子树上所有结点的值均小于它的根结点的值&#xff1b; 2. 若它的右子树不空&#xff0c;则右子树…

LSTM唐诗生成

LSTM唐诗生成 1课程简介1-2递归神经网络RNN1-3RNN网络细节1-4LSTM网络架构2-1处理Minist数据集2-2RNN网络模型及训练3-1任务概述与环境配置3-2参数配置3-3数据预处理模块3-4batch数据制作3-5RNN模型定义3-8测试唐诗生成效果 1课程简介 使用深度网络模型 写首歌 写个剧本等 原…

openshift node NotReady kubelet http: TLS handshake error

文章目录 问题现象解决方法 问题现象 openshift 集群 node 节点 notready $ oc get node NAME STATUS ROLES AGE VERSION master1.ocp4.demo.com Ready control-plane,master 4d14h v1.29.76abe8a1 master2.ocp4…

折腾 Quickwit,Rust 编写的分布式搜索引擎-官方教程

快速上手 在本快速入门指南中&#xff0c;我们将安装 Quickwit&#xff0c;创建一个索引&#xff0c;添加文档&#xff0c;最后执行搜索查询。本指南中使用的所有 Quickwit 命令都在 CLI 参考文档 中进行了记录。 https://quickwit.io/docs/main-branch/reference/cli 使用 Qui…

光庭信息半年报:营收利润「双」下降,汽车软件业务竞争加剧

「软件定义汽车」概念&#xff0c;不可否认强化了软件在整车价值的权重、带动更多供应商争夺软件业务的同时&#xff0c;也同样埋下了不小的风险。 比如&#xff0c;在汽车行业&#xff0c;常见的软件业务有两种&#xff1a;1、软件许可/IP&#xff0c;这类产品服务主要集中于…

MacOS 本地打开android模拟器

MacOS 本地打开android模拟器 查看本地安卓模拟器&#xff1a;emulator -list-avds 注意&#x1f4e2;&#xff1a;这里会提示你找不到 emulator 命令&#xff0c;此时我们需要进入 cd ~/Library/Android/sdk/tools/ 查看模拟器列表&#xff1a;./emulator -list-avds 启动…

PHP概述-特点-应用领域-如何学习

老师建议注册使用百度文心一言&#xff1b;讯飞星火大模型-AI大语言模型-星火大模型-科大讯飞&#xff1b;Kimi.ai - 帮你看更大的世界 等人工智能工具软件的一个到两个&#xff0c;也可下载文心一言、讯飞星火、kimi等APP软件使用&#xff0c;对于我们在读的大二学生来说有什么…

需方软件供应链安全保障要求及开源场景对照自评表(上)

国标《信息安全技术 软件供应链安全要求》确立了软件供应链安全目标&#xff0c;规定了软件供应链安全风险管理要求和供需双方的组织管理和供应活动管理安全要求。 开源软件供应链作为软件供应链的一种特殊形式&#xff0c;该国标亦适用于指导开源软件供应链中的供需双方开展组…

BaseCTF [Week2] 最简单的编码

前言&#xff1a;做题笔记。 下载解压 查壳。 64ida打开。 查找字符串。 跟进。 逆着向前看。 说明是密文。 里面是base64的变异加密。 原base64关键加密&#xff1a; &#xff08;看BaseCTF week1 [第一周]BasePlus 官方WP&#xff09; 变种后&#xff1a; 在此基础上加上了…

安卓系统 XBL阶段详解

在安卓系统的启动流程中&#xff0c;XBL&#xff08;eXtensible Boot Loader 或 Secondary Bootloader&#xff09;是一个关键阶段&#xff0c;特别是在使用QualComm&#xff08;高通&#xff09;等SOC&#xff08;System on Chip&#xff09;的设备上。以下是对XBL阶段的详细解…

Yololov5+Pyqt5+Opencv 实时城市积水报警系统

在现代城市生活中&#xff0c;积水问题不仅影响交通和人们的日常生活&#xff0c;还可能对城市基础设施造成潜在的威胁。为了快速、准确地识别和应对积水问题&#xff0c;使用计算机视觉技术进行智能积水检测成为一个重要的解决方案。在这篇博客中&#xff0c;我将带你一步步实…

数据结构(邓俊辉)学习笔记】串 08——KMP算法:再改进

文章目录 1. 美中不足2. 以卵击石3. 前车之覆4. 后车之鉴5. 可视对比 1. 美中不足 以上&#xff0c;我们不仅给出了 KMP 算法&#xff0c;同时也证明它的时间复杂度已经达到了渐进意义上的最优&#xff0c;也就是最坏情况也不超过 O(n)。而该算法目前这个版本也绝非完美无缺&am…

005、架构_数据节点

​DN组件总览 ​ DN节点包含进程 dbagent进程:主要提供数据节点高可用、数据导入导出、数据备份恢复、事务一致性、运维类功能、集群的扩缩容、卸数等功能;MySQL进程:主要提供数据一致性、分组管理、快同步复制、高低水位等;

机械学习—零基础学习日志(如何理解概率论10)

数理统计 这里X为总体。x1,x2,x3为样本。具体的取值为样本值。 抽样分布 来一道习题&#xff1a; 回答&#xff1a; 上一道题解析&#xff1a; 《概率论与数理统计期末不挂科|考研零基础入门4小时完整版&#xff08;王志超&#xff09;》学习笔记 王志超老师 &#xff08;UP…

一张图认识视频中间件

之前我们有介绍了很多关于视频中间件的技术资料&#xff1a; 超视网络视频中间件平台详解 视频中间件&#xff1a;海康E-home私有协议接入&#xff0c;并输出标准FLV/HLS/RTSP流 视频中间件&#xff1a;大华IPC/NVR 主动注册协议接入并输出标准FLV/RTSP/HLS流 视频中间件&#…

Meta AI动画生成功能的规模化部署与优化策略

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

SQL 注入之报错注入、延时注入、布尔盲注

在 SQL 注入攻击中&#xff0c;报错注入、延时注入和布尔盲注是常见的攻击手段。这些攻击方式利用了数据库系统在处理用户输入时的漏洞&#xff0c;从而获取敏感信息或者执行恶意操作。本文将详细介绍这三种 SQL 注入攻击方式的原理和实现方法。 一、报错注入 报错注入是利用…

【区块链 + 司法存证】易保全区块链电子数据存证保全系统 | FISCO BCOS应用案例

电子数据在司法领域存在诸多痛点&#xff0c;如数据存储成本高、安全性低&#xff1b;数据控制权分离带来的举证责任分配困难&#xff1b; 读取难度大、证据展示困难&#xff1b;数据独立、无法在司法机构间进行协同共享等。易保全首创“区块链 司法 应 用”的模式&#xff0…

如何提高OZON电商店铺的客户满意度

以下是提高 OZON 电商店铺客户满意度的方法&#xff1a; 一、产品质量与描述相符 严格选品把控&#xff1a;花费 1-2 小时深入研究供应商和产品质量。在选择产品时&#xff0c;要与可靠的供应商合作&#xff0c;确保产品的质量稳定。例如&#xff0c;对于电子产品&#xff0c…

第二证券:股指预计保持震荡格局 关注消费电子、汽车等板块

2024年1—7月&#xff0c;全国一般公共预算收入135663亿元&#xff0c;同比下降2.6%&#xff0c;扣除上一年同期中小微企业缓税入库抬高基数、上一年年中出台的减税政策翘尾减收等特殊因素影响后&#xff0c;可比添加1.2%左右。综合考虑超长时间特别国债年内相对滑润发行、8月份…