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

【C++】泛型编程 | 函数模板 | 类模板

一、泛型编程

泛型编程是啥?

编写一种一般化的、可通用的算法出来,是代码复用的一种手段。

类似写一个模板出来,不同的情况,我们都可以往这个模板上去套。

举个例子:

void Swap(int& a, int& b)
{int tmp = a;a = b;b = tmp;
}
int main()
{int a = 1, b = 2;Swap(a, b);cout << a << b<<endl;return 0;
}

这是一个交换函数。如果很多不同类型的数据需要交换,咋办?

🤔函数重载?

函数重载的确可以解决,但是每多一种数据,都要实现对应的重载函数。实在太麻烦了。

我们想要的是:有一个一般化的模板,不管是什么类型,往这个模板函数上套用就行。这就是泛型编程的思想。

当用上泛型编程:

template<typename T>
void Swap(T& a, T& b)
{T tmp = a;a = b;b = tmp;
}
int main()
{char a = 'a', b = 'b';Swap(a, b);cout << a << b<<endl;int c = 1, d = 2;Swap(c, d);cout << c << d << endl;
​return 0;
}

结果:

 

接下来我们具体介绍如何使用泛型编程。

二、函数模板

泛型编程思想下得到的函数,就像是过了模具得到的。这些“模具”,被称作函数模板。

函数模板不是一个函数,而是一个模板。

函数模板的参数是一个模板,可以包含多个类型,返回值也是一个模板,可以包含多个类型。

格式

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

注:

1.typename是用来定义模板参数关键字,也可以使用class。

2.tyname后面的类型名可以自己定,我们常常取为T(type)、Ty、K、V等,一般是大写字母or单词首字母大写。

运用起来:

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

template<class T>
……

原理

❓Q:这俩调用的是同一个Swap函数吗?

char a = 'a', b = 'b';
Swap(a, b);int c = 1, d = 2;
Swap(c, d);

不是的!在函数栈帧里,要给形参开空间。这俩形参的类型不一样,自然不会是同一块空间。

不信我们看看汇编代码:

 

可见,调用的是两个不同的函数。

实际上,编译器会根据传入的实参类型来推演,把函数模板中的 T 换成相应类型,从而生成对应的函数。

如:当用double类型使用函数模板时,编译器会通过推演,将T替换成double,然后产生一份专门处理double类型的代码。

如果是int,那通过推演、替换,生成一份处理int的代码。

所以,在使用函数模板时,编译常常会慢上一点,因为它正在后台默默处理这些工作。

如图:

函数模板的实例化

什么叫”函数模板的实例化“?

是指:在调用函数模板时,根据传递的实参推导出函数模板的具体实现,生成一个特定类型的函数。

模板参数实例化分为:隐式实例化 和 显式实例化。

隐式实例化

隐式实例化,就是不指定类型,让编译器 自己去推演 模板参数的类型。

例:

template<class T>
T Add(const T& a, const T& b)return a + b;
}
int main()
{cout << Add(1, 2) << endl;      cout << Add(1.1, 2.0) << endl;  
​return 0;
}

这里补充一个点(可跳过不看):

当Add的实参是数字时,那一定要加const修饰形参。如果实参是变量,const就不是一定要加。

这样写会报错:

template<class T>
T Add( T& a,  T& b)     //不加const会报错return a + b;
}
int main()
{cout << Add(1, 2) << endl;    //当实参是数字cout << Add(1.1, 2.0) << endl;  
​return 0;
}

这是因为:编译器会做一个强校验,当实参是数字时,它本身就是不能被修改的,此时必须加const才能通过编译。

如果这样写,加const就只是锦上添花,不是必须要的:

template<class T>
T Add( T& a,  T& b)   //不加const也能通过编译
{return a + b;
}
int main()
{int a = 1, b = 2;cout << Add(a, b) << endl;  //当实参是变量
​
​return 0;
}

然而,下面这种情况却编译不通过:

 cout << Add(1.1, 2) << endl;   

 

这是因为:编译器根据实参1.1将 T推为double,根据实参2又将 T推为int,这样T就不知道自己到底是int还是double,矛盾了。

此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化

用强制转换的方式:

template<class T>
T Add(const T& a, const T& b)  
{return a + b;
}
int main()
{cout << Add(1, (int)2.1) << endl;      cout << Add((double)1, 2.1) << endl;
​return 0;
}

用显示实例化的方式:

cout << Add<int>(1, 2.1) << endl;   //指定实例化成int类型
​
cout << Add<double>(1, 2.1) << endl;   //指定实例化成double类型

显式实例化

显示实例化,就是显示地指定函数模板的实参,从而生成一个特定类型的函数。

格式:在函数名后的<>中指定模板参数的实际类型。

函数名 <类型> (参数列表);

如:

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

如果类型不匹配,如b是bouble类型,但实例化类型指定为int,此时 编译器会尝试进行隐式类型转换。

如果转换失败 编译器将会报错。

模板参数的匹配原则

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

int Add(int a, int b)
{return a + b;
}
​
template<class T>
T Add(const T& a, const T& b)
{return a + b;
}
int main()
{cout << Add(1,2) << endl;   //调用非模板函数cout << Add<int>(1,2) << endl;   //调用 模板显示实例化出的函数return 0;
}

来看看怎么调用的:

❓为什么两者调用的函数不同呢?

当已经有现成的,专门处理int的函数Add存在时,Add(1,2)会优先调用现成的,这样效率更高,省去了模板实例化的时间。

而下面的Add<int>(1,2),则指定了编译器去显示实例化模板,生成int类型的Add函数。

➡️2.对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数。

如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。

例:

int Add(int a, int b)
{return a + b;
}
​
template<class T1,class T2>
T1 Add(const T1& a, const T2& b)
{return a + b;
}
int main()
{cout << Add(1,2) << endl;  cout << Add(1,2.0) << endl;   //模板会隐式实例化,使T1为int,T2为double,更匹配return 0;
}

来看看怎么调用的:

三、类模板

其实相比函数模板,后面用到 类模板的场景要更多。

为什么需要类模板

在没有类模板的时代,我们用typedef。typedef的问题有哪些呢?

typedef int STDateType;
class Stack
{
private:STDateType* _a;int _top;int _capacity;
};
int main()
{Stack s1;  //s1是int类型Stack s2;   //s2是char类型return 0;
}

如上,如果我们想要s1是int而s2是char,咋整?

解决办法:将int重命名为STDateType1,char重命名为STDateType2:

typedef int STDateType1;
typedef char STDateType2;
class Stack
{
private:STDateType1* _a;int _top;int _capacity;
};
class Stack
{
private:STDateType2* _a;  int _top;             int _capacity;
};
int main()
{Stack s1;  //s1是int类型Stack s2;   //s2是char类型return 0;
}

两段Stack代码重复度极高,可见这个办法很多余。这暴露了typedef所不能解决的问题。

这种场景下,就需要用到类模板了。

类模板

格式:

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

实例化:

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可。

 // Vector类名,Vector<int>才是类型Vector<int> s1;Vector<double> s2;

注意,类模板名字不是真正的类,而实例化的结果才是真正的类。

例:

Stack<int> s1;  //s1是int类型
Stack<char> s2;   //s2是char类型

这种场景下,编译器会根据<int>、<char>分别生成对应的class Stack{};。

所以说,即使用了模板,T为 int和char时,依旧是两种不同的类,只不过不由我们手动实现,而是交给编译器去做了。

补充说明:

模板不支持分离编译,不能把声明写在.h文件,定义写在.cpp文件中。如果非要分离的话,模板是支持写在同一个文件里的。

可以把声明留在类里,定义写在类外(同一个文件里)。

用下面的例子展示下 声明与定义分离的写法:

class Stack
{
public:void Push(const T& x);  //声明写在类里
private:T* _a;  int _top;             int _capacity;
};
​
//定义在类外
// 注意:类模板中函数放在类外进行定义时,需要加模板参数列表
template<typename T>
void Stack<T>::Push(const T& x)
{if (_top == _capacity){……}_a[_top] = x;_top++;
}

当然,都写在类里面也是可以的。

应用类模板

回到刚刚的那种场景,我们用类模板处理一下:

template<typename T>
class Stack
{
public:Stack(int capacity = 4)  :_a(nullptr), _top(0)  , _capacity(0)   {if (capacity > 0) {_a = new T[capacity];  _capacity = capacity;_top = 0;}}
private:T* _a;          //用模板,不同的类型都可以套int _top;             int _capacity;
};
int main()
{Stack<int> s1;  //s1是int类型Stack<char> s2;   //s2是char类型return 0;
}

相关文章:

【C++】泛型编程 | 函数模板 | 类模板

一、泛型编程 泛型编程是啥&#xff1f; 编写一种一般化的、可通用的算法出来&#xff0c;是代码复用的一种手段。 类似写一个模板出来&#xff0c;不同的情况&#xff0c;我们都可以往这个模板上去套。 举个例子&#xff1a; void Swap(int& a, int& b) {int tmp …...

web前端——简单的网页布局案列

✨博主&#xff1a;命运之光 &#x1f338;专栏&#xff1a;Python星辰秘典 &#x1f433;专栏&#xff1a;web开发&#xff08;简单好用又好看&#xff09; ❤️专栏&#xff1a;Java经典程序设计 ☀️博主的其他文章&#xff1a;点击进入博主的主页 目录 问题背景 解决样例 …...

线程安全问题(3)--- wait(),notify()

前言 在多线程的环境下&#xff0c;我们常常要协调多个线程之间的执行顺序&#xff0c;而为了实现这一点&#xff0c;Java提供了一些方法来帮助我们完成这一点。 一&#xff0c;wait() 作用&#xff1a; 使当前线程进入等待状态 释放当前的锁 (即该方法必须和 synchrnized 关键…...

【Android知识笔记】进程通信(一)

一、Android Framework 用到了哪些 IPC 方式 Linux 的 IPC 方式有: 管道Socket共享内存信号信号量消息队列管道通信 管道是基于pipefs文件系统实现的,也就是多个进程通过对同一个文件进行读写来实现进程间通信。半双工,单向的,通过 pipe(fds) 系统函数调用可得到一对文件描…...

存储空间压缩6倍 ,多点DMALL零售SaaS场景降本实践

&#x1f9d1;‍&#x1f4bc; 作者简介 冯光普&#xff1a;多点 DMALL 数据库团队负责人&#xff0c;负责数据库稳定性建设与 DB PaaS 平台建设&#xff0c;在多活数据库架构、数据同步方案等方面拥有丰富经验。 杨家鑫&#xff1a;多点高级 DBA&#xff0c;擅长故障分析与性能…...

BGP路由属性

任何一条BGP路由都拥有多个路径属性&#xff08;Path Attributes&#xff09;&#xff0c;当路由器通告BGP路由给它的对等体时&#xff0c;该路由将会携带多个路径属性&#xff0c;这些属性描述了BGP路由的各项特征&#xff0c;同时在某些场景下也会影响BGP路由优选的决策。 一…...

Java面试常用函数

1. charAt() 方法用于返回字符串指定索引处的字符。索引范围为从 0 到 length() - 1。 map.getOrDefault(num, 0) :如果map存在num这个key&#xff0c;则返回num对应的value&#xff0c;否则返回0. Arrays.sort(nums); 数组排序 Arrays.asList("a","b",&q…...

linux编译curl库(支持https)

openssl下载和编译 https://www.openssl.org/source/old/ 解压 tar -xvf openssl-3.0.1.tar.gz cd openssl-3.0.1/配置 ./config如果是编译静态库加入 -fPIC no-shared 如果指定安装路径,使用 --prefix=/usr/local/openssl/选项指定特定目录 编译和安装 make sodu make i…...

Ei Scopus检索 | 2024年第三届能源与环境工程国际会议(CFEEE 2024)

会议简介 Brief Introduction 2024年第三届能源与环境工程国际会议(CFEEE 2024) 会议时间&#xff1a;2024年9月1日-3日 召开地点&#xff1a;新西兰奥克兰 大会官网&#xff1a;https://www.cfeee.org/ 2024年第三届能源与环境工程国际会议(CFEEE 2024) 将于2024年12月12日至1…...

thinkphp6(tp6)创建定时任务

使用 thinkphp6 框架中提供的命令行形式实现定时任务 一、创建一个自定义命令类文件 php think make:command Hello 会生成一个 app\command\Hello.php 命令行指令类&#xff0c;我们修改内容如下&#xff1a; <?php declare (strict_types1);namespace app\command;use …...

【学习笔记】C++ 中 static 关键字的作用

目录 前言static 作用在变量上static 作用在全局变量上static 作用在局部变量上static 作用在成员变量上 static 作用在函数上static 作用在函数上static 作用在成员函数上 前言 在 C/C 中&#xff0c;关键字 static 在不同的应用场景下&#xff0c;有不同的作用&#xff0c;这…...

攻防世界-web-file_include

1. 题目描述 打开界面&#xff0c;如下代码&#xff1a; 代码很简单&#xff0c;从参数中获取到filename然后include这个filename 2. 思路分析 2.1 首先参考常见做法&#xff0c;将参数设置为php://filter/readconvert.base64-encode/resourceflag.php&#xff0c;看是否有…...

C语言的函数指针、指针函数, 函数数组

函数指针 是指向函数的指针&#xff0c;它允许您在程序运行时动态选择要调用的函数。函数指针可以像普通变量一样传递、存储和使用&#xff0c;这使得它们在许多编程场景中非常有用&#xff0c;如回调函数、函数表、插件架构等。 以下是一个简单的例子来说明函数指针的概念&a…...

笔记本开启WiFi

笔记本开启WiFi 为了节省流量&#xff1a;笔记本开启WiFi 条件 支持热点的电脑&#xff1b;我的是华硕飞行堡垒7。 注意事项 笔记本连接公司网络&#xff0c;公司网络通常都在监管下的&#xff0c;手机连接wifi后&#xff0c;刷抖音、购物网站&#xff0c;公司后台会捕获你…...

力扣第37天----第322题、第279题

力扣第37天----第322题、第279题 文章目录 力扣第37天----第322题、第279题一、第322题--零钱兑换二、第279题--组合总和 Ⅳ 一、第322题–零钱兑换 ​ 整体思路&#xff0c;跟前面的几道完全背包差不多&#xff0c;就不具体解释了。有一些细节要注意&#xff0c;见代码注释。…...

【ArcGIS Pro二次开发】(67):处理面要素空洞

这个一个简单的小功能。 有些面要素可能会存在空洞&#xff0c;这个工具的目的就是获取面要素的空洞&#xff0c;或者去除空洞获取要素的边界。 这个功能其实在之前做拓扑功能的时候就已经有了&#xff0c;这次只是单独把它提取出来。因为有时候会单独用到这个功能。 一、要实…...

FPGA-结合协议时序实现UART收发器(一):UART协议、架构规划、框图

FPGA-结合协议时序实现UART收发器&#xff08;一&#xff09;&#xff1a;UART协议、架构规划、框图 记录FPGA的UART学习笔记&#xff0c;以及一些细节处理&#xff0c;主要参考奇哥fpga学习资料。 本次UART主要采用计数器方法实现&#xff0c;实现uart的稳定性发送和接收功能…...

web请求cookie中expires总结

用意 cookie 有失效日期 "expires"&#xff0c;如果还没有过失效期&#xff0c;即使重新启动电脑&#xff0c;cookie 仍然不会丢失 注意&#xff1a;如果没有指定 expires 值&#xff0c;那么在关闭浏览器时&#xff0c;cookie 即失效。 设置 如果cookie存储时间大…...

如何学习Java核心知识

Java作为一门广泛应用于软件开发的编程语言&#xff0c;拥有着强大的生态系统和丰富的资源&#xff0c;是值得投入时间和精力去学习的。以下是一些建议&#xff0c;帮助你系统地学习Java核心知识。 1. 学习Java语言基础&#xff1a; 学习Java语言基础是学习Java的第一步&…...

【AWS】如何用SSH连接aws上的EC2实例(虚拟机)?

目录 0.环境 1.连接结果示例 2.SSH连接思路 3.具体步骤 1&#xff09;安装并运行ssh服务 2&#xff09;启动ssh服务 3&#xff09;在AWS上找到正在运行的EC2实例&#xff0c;并且根据提供的ssh连接语句进行连接 0.环境 windows 11 64位 前提&#xff1a; 有aws账户&…...

数据结构——看完这篇保证你学会队列

数据结构——队列 一、队列的概念二、队列的实现方式三、队列所需要的接口四、接口的详细实现4.1初始化4.2销毁4.3入队4.5出队4.6获取队头元素4.7获取队尾元素4.8获取队列元素个数4.9判空 五、完整代码5.1Queue.h5.2Queue.c5.3test.c 一、队列的概念 队列&#xff1a;只允许在…...

开源免费缺陷管理工具:对比6款

在软件开发环境中&#xff0c;缺陷管理工具是关键的基础设施。例如&#xff0c;在构建一个电商平台时&#xff0c;这些工具能系统地跟踪从发现到解决的各个问题阶段。它们支持多用户协作&#xff0c;实现信息和状态的实时共享。通过数据分析&#xff0c;这些工具还能帮助团队识…...

Weblogic反序列化漏洞

文章目录 1、搭建环境2、漏洞特征3、漏洞利用1)获取用户名密码2)后台上传shell 4、检测工具 1、搭建环境 漏洞环境基于vulhub搭建–进入weak_password的docker环境 sudo docker-compose up -d拉取靶场 2、漏洞特征 404特征Weblogic常用端口&#xff1a;7001 3、漏洞利用…...

element-ui el-table 滚动到底部,进行加载下一页

使用element-ui 自带的InfiniteScroll 无限滚动组件无法使用在table里面&#xff0c;所以项目只能组件写一个 俺的方法是写了一个自定义组件&#xff0c;进行监听滚动条是否拉到最底部进行一个处理。方法如下 直接复制完事了&#xff0c; loadTableMore: { bind(el, binding…...

线性代数的学习和整理19,特征值,特征向量,以及引入的正交化矩阵概念(草稿)

目录 1 什么是特征值和特征向量&#xff1f; 1.1 特征值和特征向量这2个概念先放后 1.2 直观定义 1.3 严格定义 2 如何求特征值和特征向量 2.1 方法1&#xff1a;结合图形看&#xff0c;直观方法求 2.1.1 单位矩阵的特征值和特征向量 2.1.2 旋转矩阵 2.2 根据严格定义…...

初步了解android如何锁键

百年三万六千日&#xff0c;光阴只有瞬息间。 手机下面的三个图形&#xff0c;正方形&#xff0c;园形&#xff0c;三角形分别的什么建&#xff1f;都起到什么功能&#xff1f; 三角形的那个叫返回键&#xff0c;就是可以返回你的上一个操作; 圆形是HOME键&#xff0c;按一下可…...

行业追踪,2023-09-13

自动复盘 2023-09-13 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…...

$nextTick和setTimeout区别(宏任务微任务)

nextTick 在vue 源码中是利用 Promise.resolve()实现的。该问题实际就是Promise与setTimeout的区别&#xff0c;本质是Event Loop中微任务与宏任务的区别。 nextTick:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法&#xff0c;获取更新后的 DOM。…...

Linux内核及可加载内核模块编程

图1 Linux系统整体结构 图2 Linux的源代码结构 下面显示一段内核模块代码案例&#xff1a; #include <linux/moduLe.h> #include <linux/kernel.h #include <linux/intt.h> /*模块的初始化函数lkp_ init()_init是用于初始化的修饰符 */ static int __init lk…...

软件设计师_备考笔记

考试介绍及考点分布情况 考试要求&#xff1a; &#xff08;1&#xff09;掌握数据表示、算术和逻辑运算&#xff1b; &#xff08;2&#xff09;掌握相关的应用数学、离散数学的基础知识&#xff1b; &#xff08;3&#xff09;掌握计算机体系结构以及各主要部件的性能和基…...

做网站开发要注册/3天引流800个人技巧

1. 学习了关于深搜&#xff0c;二叉树&#xff0c;图论之后发现对于理解清楚递归来说是非常重要的&#xff0c;递归能够帮助我们干很多事情&#xff0c;而且在很多情况下只能使用递归来解决&#xff0c;比如涉及到不确定的情况下往往需要使用到递归&#xff0c;这也叫做深搜&am…...

东莞做营销网站建设/我赢网提供的高水平网页设计师

各种云计算虚拟机大比拼 我们一直想对国内能用的公有云计算平台做一个性能上的比较&#xff0c;我很高兴看到我们的团队在这方面已经做出了许多重要的工作。今天先把我们对国内几种IaaS平台选择的测试机器拿出来做一个最基本的比较。我们都选择了2核的机器&#xff0c;他们在最…...

兼职 做网站/公司企业员工培训

主要说fdisk命令如何分区加硬盘分区硬盘给硬盘分区#fdisk /dev/sdb#n #新建分区p #主分区e #扩展分区分区要选起始磁盘位置建立完成后要保存保存用w建立分区完成后要格式化#mkfs.ext3 /dev/sdb1也可以加参数mkfs.ext3 -p 4096 /dev/sdb2格式化完成后要挂载先建立一…...

东莞市有几个区/北京seoqq群

ps: 本文只介绍ajax与后台进行数据交互时编码的情况, 并不倾向于解决中文乱码问题! 1.后台获取前台提交的数据 1.前台get方式进行请求 -> 请求参数有中文, 则后台获取到为乱码! 2.前台post方式进行请求 -> 请求参数有中文, 后台获取参数无乱码! 2.页面获取后台响应的数据…...

做网站建设怎么介绍自己/徐州seo招聘

前言&#xff1a; 现在有许许多多的人都爱上了神舟电脑的“物美价廉”&#xff0c;那么在使用神舟电脑的时候&#xff0c;我们有哪些值得知道的事情&#xff1f;我认为用神舟电脑而不知道&#xff0c;神舟电脑上的控制中心“control center 管理软件”&#xff0c;将没有比这个…...

用图片做简单网站/如何实现网站的快速排名

转载自http://www.cnblogs.com/venow/archive/2012/11/03/2752431.html 策略模式定义&#xff1a; 定义了一系列的算法&#xff0c;并将每一个算法封装起来&#xff0c;而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。 策略模式解析&#xff1a; 策略…...