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

 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推演出doublereturn 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.rhkb.cn/news/410196.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python数据采集与网络爬虫技术实训室解决方案

在大数据与人工智能时代&#xff0c;数据采集与分析已成为企业决策、市场洞察、产品创新等领域不可或缺的一环。而Python&#xff0c;作为一门高效、易学的编程语言&#xff0c;凭借其强大的库支持和广泛的应用场景&#xff0c;在数据采集与网络爬虫领域展现出了非凡的潜力。唯…

聚鼎科技:新人开一家装饰画店铺怎么快速起店

在当下这个看重审美和个性表达的时代&#xff0c;开设一家装饰画店铺无疑是迎合市场的明智选择。对于新人来说&#xff0c;快速且有效地启动一家装饰画店铺并非易事&#xff0c;但通过遵循一些关键步骤&#xff0c;可以大大缩短起步时间并提高成功率。 进行市场调研&#xff0c…

用序列模型(GPT Bert Transformer等)进行图像处理的调研记录

Visual Autoregressive Modeling: Scalable Image Generation via Next-Scale Prediction 北大和字节团队的一篇VLM&#xff0c;在生成任务上&#xff0c;用GPT范式&#xff0c;声称在FID上超过了DIT&#xff0c;SD3和SORA。开源。首先是multi-scale的VQVAE&#xff0c;然后是…

足球联赛|基于SprinBoot+vue的足球联赛管理系统(源码+数据库+文档)

足球联赛管理系统 目录 基于SprinBootvue的足球联赛管理系统 一、前言 二、系统设计 三、系统功能设计 5.1 系统前台功能实现 5.2 后台功能模块实现 5.2.1 管理员模块实现 5.2.2 用户后台模块实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选…

Linux离线安装fontconfig

Linux离线下载yum包&#xff0c;安装字体库 一、下载安装包 以CentOS Linux release 7.9.2009下载fontconfig的rpm包的为例 http://mirror.centos.org/centos/7/按提示跳转历史库 找到对应版本的centos https://vault.centos.org/7.9.2009/os/x86_64/Packages/在Packages目…

Level3 — PART 4 机器学习算法 — 决策树

目录 引言 信息量 信息熵 案例 ID3 属性选择—信息增益 决策树生成 Python实现ID3 C4.5 属性选择—信息增益率 连续型属性 缺失值 剪枝 CART 分类树属性选择—基尼系数 回归树属性选择—方差 剪枝 Python实现CART CHAID GBRT 决策树对比 模拟题 CDA L…

集团数字化转型方案(十六)

为了全面推进集团的数字化转型&#xff0c;我们将实施一系列战略举措&#xff0c;包括整合最新的人工智能、大数据分析和云计算技术&#xff0c;升级企业资源规划&#xff08;ERP&#xff09;系统&#xff0c;实现业务流程的自动化与优化&#xff1b;同时&#xff0c;建立全方位…

在银河麒麟服务器V10上源码编译安装mysql-5.7.42-linux-glibc2.12-x86_64

在银河麒麟服务器V10上源码编译安装mysql-5.7.42-linux-glibc2.12-x86_64 一、卸载MariaDB&#xff08;如果已安装&#xff09;二、下载MySQL源码包并解压三、安装编译所需的工具和库四、创建MySQL的安装目录及数据库存放目录五、编译安装MySQL六、配置MySQL七、设置环境变量八…

用EA和SysML一步步建模的操作指南(01)

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 对于许多学习SysML和MBSE的同学来说&#xff0c;比较头痛的问题之一是&#xff1a;各种各样的教程里给出的案例&#xff0c;图都是画好了的&#xff01;如何从零开始用建模工具把模型画…

centos7.9系统安装cloudpods并使用ceph存储(二)

1.ceph安装 1.1 环境准备 配置hosts&#xff1a; $ vim /etc/hosts 10.121.x.x node01设置ssh无密码登录&#xff1a; # ssh-keygen -t rsa # ssh-copy-id -i /root/.ssh/id_rsa node01关闭selinux、firewalld # setenforce 0 # sed -i "s#SELINUXenforcing#SELINUXd…

如何使用双重IP代理实现更安全的网络访问

在进行网络爬虫或其他需要隐匿真实IP的操作时&#xff0c;单一的代理IP有时并不能完全满足我们的需求。为了进一步提高安全性和隐私保护&#xff0c;我们可以使用双重IP代理。本文将详细介绍如何使用Java实现双重IP代理&#xff0c;帮助你在网络环境中更加游刃有余。 什么是双重…

安装CUDA以及GPU版本的pytorch

使用pytorch进行深度学习的时候&#xff0c;往往想用GPU进行运算来提高速度。于是搜索便知道了CUDA。 下面给出一个自检的建议&#xff1a; 检查cuda的版本是否适配自己的GPU。 打开NVDIA控制面板&#xff0c;点击左下角“系统信息”&#xff0c;然后就可以看到NVDIA GPU的详…

深入了解搜索引擎蜘蛛:从定义到最新技术应用

撰写一篇关于搜索引擎蜘蛛的详细文章&#xff0c;需涵盖从基础概念到未来趋势的多个方面。以下是根据您提供的大纲撰写的长篇文章&#xff0c;适合用于了解搜索引擎蜘蛛的重要性及其在现代互联网中的作用。 1. 引言 在互联网的浩瀚世界中&#xff0c;搜索引擎就像是庞大的图书…

Python开发工具:VSCode+插件

本文是 Python 系列教程第 3 篇&#xff0c;完整系列请查看 Python 专栏。 Visual Studio Code的安装非常简单&#xff0c;就不放这里增加文章篇幅了。 相比PyCharm&#xff0c;VSCode更加轻量&#xff0c;启动速度快。并且搭配Python插件就能实现和Pycharm一样的代码提示、高…

基于x86 平台opencv的图像采集和seetaface6的人脸跟踪功能

目录 一、概述二、环境要求2.1 硬件环境2.2 软件环境三、开发流程3.1 编写测试3.2 配置资源文件3.3 验证功能一、概述 本文档是针对x86 平台opencv的图像采集和seetaface6的人脸跟踪功能,opencv通过摄像头采集视频图像,将采集的视频图像送给seetaface6的人脸跟踪模块从而实现…

livekit安装脚本详解

livekit安装脚本详解 在私有化部署时&#xff0c;官网是执行了一个脚本。接下来将对这个脚本进行解析。 livekit脚本解析 脚本最终地址是&#xff1a; https://raw.githubusercontent.com/livekit/livekit/master/install-livekit.sh脚本内容解析&#xff1a; # 脚本头部和…

利用机器学习推动 vSOC 检测

我们讨论了汽车 API 如何成为智能移动生态系统的主要攻击媒介之一。与此相关的风险是显而易见的。如果威胁行为者能够大规模远程利用 API,他们将有能力损害品牌或提出赎金请求。当然,Splunk 平台的强大之处在于能够从任何数据大规模创建任何用例。在本博客中,我们将深入研究…

信号与系统——定义与分类(1)

一、信号与系统 信号&#xff1a;信号是信息的表现形式或传送载体&#xff0c;例如电磁波。信号可以用一个函数 yx (t) 来表示。 系统&#xff1a;是指若干相互关联的事物组合而成,具有特定功能的整体。换句话说就是&#xff0c;系统就是对输入信号进行加工和处理&#xff0c…

通过React实现萤石摄像头rtsp地址格式的视频流的web展示

首先&#xff0c;我们需要拿到rtsp格式的流地址&#xff08;rtsp://admin:[password][ip]&#xff09;&#xff0c;其中 password:设备底下的6位数验证码 ip:设备的ipv4地址 这里拿到ip的方式可以直连网线和绑定wifi两种方式 然后下载PC端的萤石工作室&#xff08;下载中心…

Datawhale X 李宏毅苹果书 AI夏令营 Task1笔记

Datawhale X 李宏毅苹果书 向李宏毅学深度学习&#xff08;进阶&#xff09; 是 Datawhale 2024 年 AI 夏令营第五期的学习活动&#xff08;“深度学习 进阶”方向&#xff09; Datawhale官方的task1链接&#xff1a;深度学习进阶-Task1 《深度学习详解》主要内容源于《机器学…