当前位置: 首页 > news >正文

CPP初级:模板的运用!

目录

一.泛型编程

二.函数模板

1.函数模板概念

2.函数模板格式

3.函数模板的原理

三.函数模板的实例化

1.隐式实例化

2.显式实例化

3.模板参数的匹配原则

四.类模板

1.类模板的定义格式

2.类模板的实例化


一.泛型编程

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。

泛型编程是一种编程范式,它允许程序员编写不依赖于特定数据类型的代码。

在泛型编程中,程序员可以定义一些通用的算法和数据结构,这些可以在不同的数据类型中使用。

比如交换函数,如果我们没有学习泛型编程,则我们就需要根据类型的交换,造出多个轮子:

typedef int Type;
void Swap(Type& left, Type& right)
{Type temp = left;left = right;right = temp;}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;
}

使用函数重载固然可以实现这一问题,但是有几个不好的地方:

  •  代码空间会变大
  • 重载的函数只是类型不同,代码的复用率较低
  • 只要有新的类型需要使用这个函数,就需要重载新的函数
  • 代码的可维护性较低,一个出错可能全部的重载都出错

因为函数重载存在上述缺点,因此我们提出了”函数模板“

在现实生活中,我们可以通过往模具中填充不同的材料生成不同的铸件。

C++的开发者受到了启发,发明了模板。

模板:告诉编译器一个模子,让编译器根据不同的类型利用该模子生成代码。

模板可以分为函数模板和类模板:

模板是泛型编程的基础。

二.函数模板

1.函数模板概念

函数模板代表了一个函数家族,该家族模板与类型无关。

函数模板在使用时被参数化,根据实参类型产生的特定类型版本。

2.函数模板格式

我们用templata关键字来声明模板:

template <typename T1, typename T2,......typename Tn>
返回值类型  函数名(参数列表)
{//函数体
}

这里需要大家注意的是,我们的第一行后面并没有分号,也就代表着它并不是一条语句。

 现在我们举出一个实例:

template <typename T>//函数模板的声明
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}

此外,我们也可以使用class取代typename

template <class T>//函数模板的声明
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}

 现在我们使用一下我们定义的函数模板

#include <iostream>
using namespace std;
template <class T>//函数模板的声明
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}
int main()
{int a = 1, b = 2;Swap(a, b);cout << a << ' ' << b << endl;double c = 1.3, d = 2.5;Swap(c, d);cout << c << ' ' <<d << endl;return 0;
}

可以看到,这里圆满的完成了交换逻辑。 

3.函数模板的原理

那么,上述问题是如何解决的呢?

大家都知道,瓦特改良蒸汽机,人类开始了工业革命,解放了生产力。

机器生产淘汰掉了很多手工产品。

本质是什么,重复的工作交给了机器去完成。

因此有人给出了论调:懒人创造世界。

函数模板的本质也是如此

现在我们进入汇编来看一下上述代码运行的过程中编译器都干了什么事

 

可以看到,这里调用函数时,显式的规定了参数的类型。 

因此我们可以得到结论:函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。

我们的编译器根据这个模具帮我们做了这个事情。

注意:Swap在调用时,调用的不是void Swap(T& left, T& right),而是编译器预先根据要调用的类型进行推演。

编译器负责在编译时分析模板定义,并在需要时生成特定类型的代码,之后编译器会检查模板的语法,并确保模板的使用是合法的,之后编译器会根据实际使用的类型参数生成相应的函数或类的实现。

例如上图中的这两行代码:

00007FF6E7122423  call        Swap<int> (07FF6E7121352h)
00007FF6E7122480  call        Swap<double> (07FF6E7121398h) 

这两个函数模板就是编译器生成的。

在编译器的编译阶段,编译器就会根据传入的实参类型来推演生成对应类型的函数以供调用。

就比如上图: 当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码。

三.函数模板的实例化

用不同类型的参数使用函数模板称为函数模板的实例化

模板参数的实例化可以分为:隐式实例化和显式实例化

1.隐式实例化

隐式实例化即我们刚刚实例化的方法,这里不再过多赘述。

#include <iostream>
using namespace std;
template <class T>//函数模板的声明
T Add(const T& left, const T& right)
{return left + right;
}
int main()
{int a1 = 10, a2 = 20;Add(a1, a2);cout << Add(a1,a2) << endl;return 0;
}

 

 现在我们来看一下这段代码:

T Add(const T& left, const T& right)
{return left + right;
}
int main()
{int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.2;Add(a1, d2);cout << Add(a1, d2) << endl;return 0;
}

这段代码在大部分编译器下是无法运行的,在VS2022中爆出了如下警告:

 

 为什么在大部分编译器下无法通过编译呢?

这是因为在编译期间,当编译器看到该实例化后,会去推演其实参的类型。

通过实参a1将T推演为了int通过实参d1将T推演为double类型

但是模板参数列表中只有一个T,编译器就无法判断T在这里是int还是double。

为什么在vs2022中可以编译成功呢?

这是因为编译器进行了类型转换操作,但是类型转换操作的风险是极大的,因为不知道此处你想要的是double还是int。

那么我们应该处理这个问题呢?

处理方式1:用户自己强制转换

Add(a1, (int)d2);//想要int类型的,我们直接将d2强转为int

 

处理方式2:采用显式实例化

那么,如何显式实例化呢?

2.显式实例化

显式实例化:在函数名的后面,参数列表的前面加一对尖括号<>,尖括号内部指定模板参数的实际类型。如下:

Add<int>(a, b);

 

如果参数类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

3.模板参数的匹配原则

  • 在我们的程序中,一个非模板函数是可以和一个同名的函数模板同时存在的,而且该函数模板还可以被实例化为这个非模板函数
T Add(const T& left, const T& right)
{cout << "T Add(T& left,T& right)" << endl;return left + right;
}
int Add(const int& left, const int& right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}
int main()
{Add(1, 2);Add<int>(1, 2);return 0;
}

我们运行之后,可以看到如下的结果: 

 

我们发现,第一个Add函数调用了专门处理int类型的加法函数,而第二个Add函数调用了模板。

那么,为什么第一个Add函数不调用函数模板呢?

这是因为如下内容:

  • 对于非模板函数和同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数而不会从该模板产生出一个实例。 
  • 如果条件不同的话,则会选择模板。
  • 也就是说,编译器会优先调用更加匹配的版本调用!

我们可以看一下下面的这段代码:

int Add(int left, int right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}
// 通用加法函数
template<class T1, class T2>//注意这里有两个类型
T1 Add(T1 left, T2 right)
{cout << "T1 Add(T1 left, T2 right)" << endl;return left + right;
}
int main()
{Add(1, 2);// 与非函数模板类型完全匹配,不需要函数模板实例化Add(1, 2.0);// 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数return 0;
}

 

可以看到,第一个调用了非模板函数,第二个调用了模板函数。

 

  •  模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。

对于模版T1 Add(T1 left, T2 right)不知道返回值是T1或T2,可以选择auto,auto虽然不太适合做返回值,但是对于简单普通函数操作,可以进行自动类型转换。

int Add(int left, int right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}//auto可作简单处理的函数返回值
template<class T1, class T2>
auto Add(const T1& left, const T2& right)
{cout << "auto Add(const T1& left, const T2& right)" << endl;return left + right;
}
int main()
{Add(1, 2);// 与非函数模板类型完全匹配,不需要函数模板实例化cout << Add(1, 2) << endl;Add(1, 2.0);// 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数cout << Add(1, 2.0) << endl;return 0;
}

四.类模板

1.类模板的定义格式

template<class T1, class T2, ..., class Tn>
class 类模板名
{// 类内成员定义
};
// 动态顺序表
// 注意:Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具
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中只是提供了一个模具,具体印刷出什么模型,是由编译器最终实例化决定的。

注意:模版不建议声明和定义分离到.h 和.cpp,会出现链接错误,要分离也分离在.h。

2.类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字和变量名字中间加一个尖括号<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

Vector<int> intvector;
Vector<string> stringvector;

码字不易,如果你觉得博主写的不错的话,可以关注一下博主哦。

相关文章:

CPP初级:模板的运用!

目录 一.泛型编程 二.函数模板 1.函数模板概念 2.函数模板格式 3.函数模板的原理 三.函数模板的实例化 1.隐式实例化 2.显式实例化 3.模板参数的匹配原则 四.类模板 1.类模板的定义格式 2.类模板的实例化 一.泛型编程 泛型编程&#xff1a;编写与类型无关的通用代码…...

排序---基数排序

前言 个人小记 一、简介 基数排序是一种非比较排序&#xff0c;所以排序速度较快&#xff0c;当为32位int整数排序时&#xff0c;可以将数分为个位十位分别为2^16,使得拷贝只需要两轮&#xff0c;从而达到2*n&#xff0c;然后给一个偏移量&#xff0c;使得可以对负数排序。以…...

“新高考”下分班怎么分?

来自安徽的张女士告诉我&#xff1a;上一年孩子升入了高中&#xff0c;但没想到才高一&#xff0c;孩子就面临了一个困难的挑选&#xff1a;312”分班&#xff01; 什么是312”分班呢&#xff1f;许多人或许不明白&#xff0c;便是要求学生在高一入学时&#xff0c;针对于3门必…...

二叉树的层序遍历-力扣

本题是二叉树的层序遍历&#xff0c;通过一个队列来控制遍历的节点&#xff0c;二叉树每层的节点和上一层入队的节点个数是相同的&#xff0c;根据这一点编写循环条件。 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* …...

N32G45XVL-STB之移植LVGL(lvgl-8.2.0)

目录 概述 1 软硬件介绍 1.1 软件版本信息 1.2 ST7796-LCD 1.3 MCU IO与LCD PIN对应关系 2 认识LVGL 2.1 LVGL官网 2.2 LVGL库文件下载 3 移植LVGL 3.1 准备移植文件 3.2 添加lvgl库文件到项目 3.2.1 src下的文件 3.2.2 examples下的文件 3.2.3 配置文件路径 3.2…...

【设计模式】创建型设计模式之 原型模式

介绍 原型模式是一种创建型设计模式&#xff0c;主要用于创建重复的对象&#xff0c;而无需重新初始化它们&#xff0c;从而提高效率并简化对象的创建过程。此模式的核心思想是利用已存在的对象实例&#xff0c;通过复制&#xff08;克隆&#xff09;的方式来生成新的对象&…...

【类型商店】字符字符串(下)

啊&#xff0c;哈喽&#xff0c;小伙伴们大家好。我是#Y清墨&#xff0c;今天呐&#xff0c;我要介绍的是字符与字符串。 导语 前两期&#xff0c;我们已经懂得了概念&#xff0c;今天来看些函数。 正题 一.增加或连接 &#xff08;1) 后面增加() string s1,s2; //定义 s…...

『 Linux 』内存管理与文件系统

文章目录 交换分区页与页框(页帧)交换分区与内存之间的交换操作系统如何管理内存物理地址转换页号与页内偏移量 内存管理,文件系统与文件管理之间的联系 交换分区 在Linux的安装过程中,用户将会被提示创建一个交换分区; 这是一个特殊的分区,其大小可以由用户根据系统内存需求和…...

线性代数|机器学习-P8矩阵低秩近似eckart-young

文章目录 1. SVD奇异值分解2. Eckart-Young2.1 范数 3. Q A Q U Σ V T QAQU\Sigma V^T QAQUΣVT4. 主成分分析图像表示 1. SVD奇异值分解 我们知道&#xff0c;对于任意矩阵A来说&#xff0c;我们可以将其通过SVD奇异值分解得到 A U Σ V T AU\Sigma V^T AUΣVT&#xff0…...

平面设计神器CorelDRAW2021精简版,你值得拥有!

亲爱的设计师小伙伴们&#xff0c;今天我要为大家种草一款神奇的软件——CorelDRAW平面设计软件2021精简版&#xff01;&#x1f929;✨作为一名专业的图形设计师&#xff0c;我深知一个好工具对于我们的工作有多么重要。而这款软件简直就是我们设计师的救星&#xff01;&#…...

kafka是什么?

Kafka是一个由Apache软件基金会开发的开源流处理平台&#xff0c;最初由LinkedIn公司开发&#xff0c;使用Scala和Java编写。它是一个高吞吐量的分布式发布订阅消息系统&#xff0c;可以处理消费者在网站中的所有动作流数据&#xff0c;如网页浏览、搜索和其他用户行为等。Kafk…...

ABC351

C 栈的应用 #include<bits/stdc.h>using namespace std;stack<int>stk;int main() {int n;cin>>n;for(int i1;i<n;i){int a;cin>>a;while(!stk.empty()&&astk.top()){stk.pop();a;}stk.push(a);}cout<<stk.size()<<endl;retur…...

base上海,数据科学,数据挖掘,数据分析等岗位求收留

裁员了&#xff0c;base上海&#xff0c;数据科学&#xff0c;数据挖掘&#xff0c;数据分析等岗位&#xff0c;期望30k~40k&#xff0c;求推荐求收留 1&#xff0c;6年数据算法工作&#xff0c;做过指标体系搭建&#xff0c;用户画像&#xff0c;货品定价&#xff0c;社区分析…...

IC元器件

1.电阻&#xff1a; 电阻的作用&#xff1a; 1.与负载串联&#xff1a;做限流分压 2.电阻并联&#xff1a;将小功率电阻并联成大功率&#xff0c;防烧毁 2.电容&#xff1a; 电容就是两块金属板&#xff0b;中间的介质&#xff08;相当于两个人坐在一起加上中间的空气…...

SQL159 每个创作者每月的涨粉率及截止当前的总粉丝量

描述 用户-视频互动表tb_user_video_log iduidvideo_idstart_timeend_timeif_followif_likeif_retweetcomment_id110120012021-09-01 10:00:002021-09-01 10:00:20011NULL210520022021-09-10 11:00:002021-09-10 11:00:30101NULL310120012021-10-01 10:00:002021-10-01 10:00…...

Linux安装MySQL教程【带图文命令巨详细】

巨详细Linux安装MySQL 1、查看是否有自带数据库或残留数据库信息1.1检查残留mysql1.2检查并删除残留mysql依赖1.3检查是否自带mariadb库 2、下载所需MySQL版本&#xff0c;上传至系统指定位置2.1创建目录2.2下载MySQL压缩包 3、安装MySQL3.1创建目录3.2解压mysql压缩包3.3安装解…...

外部排序快速入门详解:基本原理,败者树,置换-选择排序,最佳归并树

文章目录 外部排序1.最基本的外部排序原理2.外部排序的优化2.1 败者树优化方法2.2 置换-选择排序优化方法2.3 最佳归并树 外部排序 为什么要学习外部排序&#xff1f; 答&#xff1a; 在处理数据的过程中&#xff0c;我们需要把磁盘(外存&#xff09;中存储的数据拿到内存中处理…...

人工智能和物联网如何结合

欢迎来到 Papicatch的博客 目录 ​ &#x1f349;引言 &#x1f349;AI与IoT的结合方式 &#x1f348;数据处理和分析 &#x1f34d;实例 &#x1f348;边缘计算 &#x1f34d;实例 &#x1f348;自动化和自主操作 &#x1f34d;实例 &#x1f348;安全和隐私保护 &…...

【JAVASE】JAVA应用案例(下)

一&#xff1a;抢红包 一个大V直播时&#xff0c;发起了抢红包活动&#xff0c;分别有9,666,188,520,99999五个红包。请模拟粉丝来抽奖&#xff0c;按照先来先得&#xff0c;随机抽取&#xff0c;抽完即止&#xff0c;注意&#xff1a;一个红包只能被抽一次&#xff0c;先抽或…...

【面试干货】 B 树与 B+ 树的区别

【面试干货】 B 树与 B 树的区别 1、B 树2、 B 树3、 区别与优缺点比较4、 总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在数据库系统中&#xff0c;B 树和 B 树是常见的索引结构&#xff0c;它们在存储和组织数据方面有着不同的设计…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南

1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发&#xff0c;使用DevEco Studio作为开发工具&#xff0c;采用Java语言实现&#xff0c;包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

Python ROS2【机器人中间件框架】 简介

销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

Yolov8 目标检测蒸馏学习记录

yolov8系列模型蒸馏基本流程&#xff0c;代码下载&#xff1a;这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中&#xff0c;**知识蒸馏&#xff08;Knowledge Distillation&#xff09;**被广泛应用&#xff0c;作为提升模型…...

4. TypeScript 类型推断与类型组合

一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式&#xff0c;自动确定它们的类型。 这一特性减少了显式类型注解的需要&#xff0c;在保持类型安全的同时简化了代码。通过分析上下文和初始值&#xff0c;TypeSc…...

day36-多路IO复用

一、基本概念 &#xff08;服务器多客户端模型&#xff09; 定义&#xff1a;单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用&#xff1a;应用程序通常需要处理来自多条事件流中的事件&#xff0c;比如我现在用的电脑&#xff0c;需要同时处理键盘鼠标…...

Python 实现 Web 静态服务器(HTTP 协议)

目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1&#xff09;下载安装包2&#xff09;配置环境变量3&#xff09;安装镜像4&#xff09;node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1&#xff09;使用 http-server2&#xff09;详解 …...

篇章二 论坛系统——系统设计

目录 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 1. 数据库设计 1.1 数据库名: forum db 1.2 表的设计 1.3 编写SQL 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 通过需求分析获得概念类并结合业务实现过程中的技术需要&#x…...

LangChain 中的文档加载器(Loader)与文本切分器(Splitter)详解《二》

&#x1f9e0; LangChain 中 TextSplitter 的使用详解&#xff1a;从基础到进阶&#xff08;附代码&#xff09; 一、前言 在处理大规模文本数据时&#xff0c;特别是在构建知识库或进行大模型训练与推理时&#xff0c;文本切分&#xff08;Text Splitting&#xff09; 是一个…...