【C++修炼之路 第四章】模板 初阶

在这里插入图片描述


引入:

我们平时需要调用这样的函数时,往往需要写不同类型的函数用于匹配操作不同类型变量

void Swap(int& a, int& b) {// ...
}
void Swap(double& a, double& b) {// ...
}
//......

像上面代码中函数重载有一下几个不好的地方:

1、重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数

2、代码的可维护性比较低,一个出错可能所有的重载均出错


这样显然局限性较大,如何实现一个通用的交换函数呢?

答: C++ 创造了 ”模板“:泛型编程

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

只有类型不同,函数的其他逻辑都相同



这里是 根据 广泛的类型 ,写的代码:因此称为泛型编程

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

template<typename T>
void Swap(T& a, T& b) {//...
}

1. 函数模板

1.1 函数模板概念

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化根据实参类型产生函数的特定 类型版本

1.2 函数模板格式

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

注意:typename 是用来定义模板参数关键字,也可以使用 class (切记:不能使用struct代替class)

使用 模板写 Swap 函数:

template<typename T>
void Swap(T& a, T& b) {T t = a;a = b; b = t;
}
int main()
{int a = 10, b = 20; Swap(a, b);double c = 1.1, d = 2.2;Swap(c, d);int* p1 = &a, *p2 = &b;Swap(p1, p2);return 0;
}

1.3 模板的原理:

从底层汇编来看,实际上这几个调用的并不是同一个函数

(调试时,编译器演示出来也可能是 假象)

在底层,编译器会识别你参数的类型,根据 template 模板的逻辑,生成一个函数

然后根据参数类型调用对应的函数

在这里插入图片描述



需要什么,编译器生成的函数:叫做模板的示例化

因此,本质没变:该写多少个函数就写多少个,只是编译器帮你写了,你自己不用写,半自动化了


在这里插入图片描述



在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供 调用。比如:当用 double 类型使用函数模板时,编译器通过对实参类型的推演,将 T 确定为 double 类型,然后产生一份专门处理 double 类型的代码,对于其他类型也是如此。



1.4 函数模板的实例化(进一步讲解)

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


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

在模板的使用中,一个 T 对应一种类型,不能混着用,会报错

举例:

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, a2);Add(d1, d2);// //return 0;
}

在以上代码中写入下面这个操作:

Add(a1, d1);

该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型

通过实参 a1 将 T 推演为 int,通过实参 d1 将 T 推演为 double 类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将 T 确定为 int 还是 double 类型,从而报错

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



此时有两种处理方式:

1、用户自己来强制转化 :

Add(a, (int)d);
Add((double)a, (int)d);

2、使用显式实例化


1.4.2 显式实例化:在函数名后的<>中指定模板参数的实际类型

就是不让 编译器推演,直接显式指定参数类型,

int main(void)
{int a = 10;double b = 20.0;// 显式实例化Add<int>(a, b);Add<double>(a, b);return 0;
}

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

或者两两匹配

Add<int, double>(a, b);

有时候一定要 显式实例化,如返回值不确定的情况:编译器自己推不出来

前面的函数模板是通过传递的函数参数推演类型,而返回值推不出来

template<class T>
T* func(int a)
{T* p = (T*)operator new(sizeof(T));new(p)T(a);return p;
}int main()
{func(10); // 报错return 0;
}

解决办法:显式实例化

func<int>(10);
func<A>(10); // A 是类名

从这里可以看出,显式实例化的匹配,应该是 和这里匹配 template < class T >



1.5 模板参数的匹配原则


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

即 两者可以同时存在,模板也还可以生成一个相同的

// 专门处理int的加法函数
int Add(int left, int right)
{return left + right;
}// 通用加法函数
template<class T>
T Add(T left, T right)
{return left + right;
}void Test()
{Add(1, 2); // 与非模板函数匹配,编译器不需要特化Add<int>(1, 2); // 调用编译器特化的Add版本
}

2、对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板

即:优先选 非模板函数,如果模板可以生成一个更加匹配的,就选模板这个

有现成的蛋糕就吃现成的,不吃半成品;如果半成品造出来的蛋糕更好吃,那就选半成品这个

// 专门处理int的加法函数
int Add(int left, int right)
{return left + right;
}// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{return left + right;
}void Test()
{Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的 Add 函数
}

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

上面第 二 点:会自动匹配更加适合的版本(模板),但不代表不能使用 非函数模板,这个本身就能类型转换

// 专门处理int的加法函数
int Add(int left, int right)
{return left + right;
}void main()
{Add(1, 2.2);
}

4、关于返回值的问题

(1)返回 T1 :int 类型(导致精度丢失)

// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{return left + right;
}int main()
{cout << Add(1, 2) << '\n';cout << Add(1, 2.2) << '\n';return 0;
}

(2)返回 T2 :double 类型

(3)返回 auto :让编译器自己判断类型,避免了精度丢失的问题

// 通用加法函数
template<class T1, class T2>
auto Add(T1 left, T2 right)
{return left + right;
}


2. 类模板(重要)

类模板和函数模板在用法上没有实质性的区别

2.1 类模板的定义格式

template<class T1, class T2, ..., class Tn> 
class 类模板名
{// 类内成员定义
}; 


对于下面这段代码:我们可以在 typedef 这里自定义想要的栈元素类型

typedef double DataType;
typedef double DataType;
struct Stack
{Stack(size_t capacity = 4){_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* _array;size_t _capacity;size_t _size;
};int main()
{Stack st1; // intStack st2; // doublereturn 0;
}

但是,我们定义的 st1 和 st2 不能直接指定要 int 还是 double

即不能同时存在不同的类型,如果要有 int 也要 double 就要写两个类

这两个类,只有元素类型不同,类中各种函数逻辑都一样,刚好适合写成 模板

此时,类模板就派上用场了

我们将 上述 的栈类,写成模板形式

// 类模版
template<typename T>
class Stack
{
public:Stack(size_t capacity = 4){_array = (T*)malloc(sizeof(T) * capacity);if (nullptr == _array){perror("malloc申请空间失败");return;}_capacity = capacity;_size = 0;}void Push(const T& data);
private:T* _array;size_t _capacity;size_t _size;
};

声明和定义分离:直接写会报错,因为编译器不知道 T 是什么类型的

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

需要加上 模板“声明”,以及类型换成 stack< T >

template<class T>
void Stack<T>::Push(const T& data)
{// 扩容_array[_size] = data;++_size;
}

注意:

模版不建议声明和定义分离到.h 和.cpp会出现链接错误(可能会使编译的时间暴增!!不信自己试试),具体原因后面会讲

要分离也分离在.h(在同一个 头文件中)



2.2 类模板的实例化

​ 类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类(好好领会)。

int main()
{// Stack类名,Stack<int> 才是类型Stack<int> st1;    // intStack<double> st2; // doublereturn 0;
}

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

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

相关文章

filebeat,kafka,clickhouse,ClickVisual搭建轻量级日志平台

springboot集成链路追踪 springboot版本 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.3</version><relativePath/> <!-- lookup parent from…

Stateflow中的状态转换表

状态转换表是表达顺序模态逻辑的另一种方式。不要在Stateflow图表中以图形方式绘制状态和转换&#xff0c;而是使用状态转换表以表格格式表示模态逻辑。 使用状态转换表的好处包括&#xff1a; 易于对类列车状态机进行建模&#xff0c;其中模态逻辑涉及从一个状态到其邻居的转换…

处理uniapp刷新后,点击返回按钮跳转到登录页的问题

在使用uniapp的原生返回的按钮时&#xff0c;如果没有刷新会正常返回到对应的页面&#xff0c;如果刷新后会在当前页反复横跳&#xff0c;或者跳转到登录页。那个时候我第一个想法时&#xff1a;使用浏览器的history.back()方法。因为浏览器刷新后还是可以通过右上角的返回按钮…

SpringBoot限制请求访问次数

本篇文章的主要内容是SpringBoot怎么限制请求访问次数。 当我们的服务端程序部署到服务器上后&#xff0c;就要考虑很多关于安全的问题。总会有坏人来攻击你的服务&#xff0c;比如说会窃取你的数据或者给你的服务器上强度。关于给服务器上强度&#xff0c;往往就有高强度给服务…

​1:1公有云能力整体输出,腾讯云“七剑”下云端

【全球云观察 &#xff5c; 科技热点关注】 曾几何时&#xff0c;云计算技术的兴起&#xff0c;为千行万业的数字化创新带来了诸多新机遇&#xff0c;同时也催生了新产业新业态新模式&#xff0c;激发出高质量发展的科技新动能。很显然&#xff0c;如今的云创新已成为高质量发…

[React 进阶系列] useSyncExternalStore hook

[React 进阶系列] useSyncExternalStore hook 前情提要&#xff0c;包括 yup 的实现在这里&#xff1a;yup 基础使用以及 jest 测试 简单的提一下&#xff0c;需要实现的功能是&#xff1a; yup schema 需要访问外部的 storage外部的 storage 是可变的React 内部也需要访问同…

VulnHub:CK00

靶场搭建 靶机下载地址&#xff1a;CK: 00 ~ VulnHub 下载后&#xff0c;在vmware中打开靶机。 修改网络配置为NAT 处理器修改为2 启动靶机 靶机ip扫描不到的解决办法 靶机开机时一直按shift或者esc直到进入GRUB界面。 按e进入编辑模式&#xff0c;找到ro&#xff0c;修…

【devops】ttyd 一个web版本的shell工具 | web版本shell工具 | web shell

一、什么是 TTYD ttyd是在web端一个简单的服务器命令行工具 类似我们在云厂商上直接ssh链接我们的服务器输入指令一样 二、安装ttyd 1、macOS Install with Homebrew: brew install ttydInstall with MacPorts: sudo port install ttyd 2、linux Binary version (recommend…

Android10.0 锁屏分析-KeyguardPatternView图案锁分析

首先一起看看下面这张图&#xff1a; 通过前面锁屏加载流程可以知道在KeyguardSecurityContainer中使用getSecurityView()根据不同的securityMode inflate出来&#xff0c;并添加到界面上的。 我们知道&#xff0c;Pattern锁所使用的layout是 R.layout.keyguard_pattern_view&a…

Mysql基础与安装

一、数据库的概念和相关的语法和规范 1、数据库的概念 数据库&#xff1a;组织&#xff0c;存储&#xff0c;管理数据的仓库。 数据库的管理系统&#xff08;DBMS&#xff09;&#xff1a;实现对数据有效组织&#xff0c;管理和存取的系统软件。 数据库的种类&#xff1a; m…

QT 多线程 QThread

继承QThread的线程 继承 QThread 是创建线程的一个普通方法。其中创建的线程只有 run() 方法在线程里的。其他类内定义的方法都在主线程内。 通过上面的图我们可以看到&#xff0c;主线程内有很多方法在主线程内&#xff0c;但是子线程&#xff0c;只有 run() 方法是在子线…

Python | Leetcode Python题解之第236题二叉树的最近公共祖先

题目&#xff1a; 题解&#xff1a; # Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val x # self.left None # self.right Noneclass Solution:def lowestCommonAncestor(self, root: TreeNode, p…

实战篇(十一) : 拥抱交互的三维世界:利用 Processing 和 OpenGL 实现炫彩粒子系统

🌌 拥抱交互的三维世界:利用 Processing 和 OpenGL 实现炫彩粒子系统 在现代计算机图形学中,三维粒子系统是一个激动人心的领域。它不仅可以用来模拟自然现象,如烟雾、火焰和水流,还可以用来创造出令人叹为观止的视觉效果。在这篇文章中,我们将深入探讨如何使用 Proces…

第四届中国移动“梧桐杯”大数据创新大赛正式启动报名!

“梧桐杯”大赛是中国移动面向海内外高校青年学生打造的年度大数据创新赛事&#xff0c;以“竞逐数海&#xff0c;领航未来”为主题&#xff0c;携手政府、高校和行业企业通过比赛发掘高校优秀人才&#xff0c;孵化投资优秀项目。大赛设置“企业导师校内导师”双轨导师制&#…

Linux_线程的同步与互斥

目录 1、互斥相关概念 2、代码体现互斥重要性 3、互斥锁 3.1 初始化锁 3.2 申请、释放锁 3.3 加锁的思想 3.4 实现加锁 3.5 锁的原子性 4、线程安全 4.1 可重入函数 4.2 死锁 5、线程同步 5.1 条件变量初始化 5.2 条件变量等待队列 5.3 唤醒等待队列…

MySQL学习记录 —— 이십이 MySQL服务器文件系统(2)

文章目录 1、日志文件的整体简介2、一般、慢查询日志1、一般查询日志2、慢查询日志FILE格式TABLE格式 3、错误日志4、二进制日志5、日志维护 1、日志文件的整体简介 中继服务器的数据来源于集群中的主服务。每次做一些操作时&#xff0c;把操作保存到重做日志&#xff0c;这样崩…

JAVASE-医疗管理系统项目总结

文章目录 项目功能架构运行截图数据库设计设计模式应用单列设计模式JDBC模板模板设计模式策略模式工厂设计模式事务控制代理模式注解开发优化工厂模式 页面跳转ThreadLocal分页查询实现统计模块聊天 项目功能架构 传统的MVC架构&#xff0c;JavaFX桌面端项目&#xff0c;前端用…

R语言进行集成学习算法:随机森林

# 10.4 集成学习及随机森林 # 导入car数据集 car <- read.table("data/car.data",sep ",") # 对变量重命名 colnames(car) <- c("buy","main","doors","capacity","lug_boot","safety"…

ARM体系结构和接口技术(五)封装RCC和GPIO库

文章目录 一、RCC&#xff08;一&#xff09;思路1. 找到时钟基地址2. 找到总线的地址偏移&#xff08;1&#xff09;AHB4总线&#xff08;2&#xff09;定义不同GPIO组的使能宏函数&#xff08;3&#xff09;APB1总线&#xff08;4&#xff09;定义使能宏函数 二、GPIO&#x…

基于Java的汽车租赁管理系统设计(含文档、源码)

本篇文章论述的是基于Java的汽车租赁管理系统设计的详情介绍&#xff0c;如果对您有帮助的话&#xff0c;还请关注一下哦&#xff0c;如果有资源方面的需要可以联系我。 目录 摘 要 系统运行截图 系统总体设计 系统论文 资源下载 摘 要 近年来&#xff0c;随着改革开放…