【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++】泛型编程 | 函数模板 | 类模板
一、泛型编程 泛型编程是啥? 编写一种一般化的、可通用的算法出来,是代码复用的一种手段。 类似写一个模板出来,不同的情况,我们都可以往这个模板上去套。 举个例子: void Swap(int& a, int& b) {int tmp …...
web前端——简单的网页布局案列
✨博主:命运之光 🌸专栏:Python星辰秘典 🐳专栏:web开发(简单好用又好看) ❤️专栏:Java经典程序设计 ☀️博主的其他文章:点击进入博主的主页 目录 问题背景 解决样例 …...
线程安全问题(3)--- wait(),notify()
前言 在多线程的环境下,我们常常要协调多个线程之间的执行顺序,而为了实现这一点,Java提供了一些方法来帮助我们完成这一点。 一,wait() 作用: 使当前线程进入等待状态 释放当前的锁 (即该方法必须和 synchrnized 关键…...
【Android知识笔记】进程通信(一)
一、Android Framework 用到了哪些 IPC 方式 Linux 的 IPC 方式有: 管道Socket共享内存信号信号量消息队列管道通信 管道是基于pipefs文件系统实现的,也就是多个进程通过对同一个文件进行读写来实现进程间通信。半双工,单向的,通过 pipe(fds) 系统函数调用可得到一对文件描…...
存储空间压缩6倍 ,多点DMALL零售SaaS场景降本实践
🧑💼 作者简介 冯光普:多点 DMALL 数据库团队负责人,负责数据库稳定性建设与 DB PaaS 平台建设,在多活数据库架构、数据同步方案等方面拥有丰富经验。 杨家鑫:多点高级 DBA,擅长故障分析与性能…...
BGP路由属性
任何一条BGP路由都拥有多个路径属性(Path Attributes),当路由器通告BGP路由给它的对等体时,该路由将会携带多个路径属性,这些属性描述了BGP路由的各项特征,同时在某些场景下也会影响BGP路由优选的决策。 一…...
Java面试常用函数
1. charAt() 方法用于返回字符串指定索引处的字符。索引范围为从 0 到 length() - 1。 map.getOrDefault(num, 0) :如果map存在num这个key,则返回num对应的value,否则返回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) 会议时间:2024年9月1日-3日 召开地点:新西兰奥克兰 大会官网:https://www.cfeee.org/ 2024年第三届能源与环境工程国际会议(CFEEE 2024) 将于2024年12月12日至1…...
thinkphp6(tp6)创建定时任务
使用 thinkphp6 框架中提供的命令行形式实现定时任务 一、创建一个自定义命令类文件 php think make:command Hello 会生成一个 app\command\Hello.php 命令行指令类,我们修改内容如下: <?php declare (strict_types1);namespace app\command;use …...
【学习笔记】C++ 中 static 关键字的作用
目录 前言static 作用在变量上static 作用在全局变量上static 作用在局部变量上static 作用在成员变量上 static 作用在函数上static 作用在函数上static 作用在成员函数上 前言 在 C/C 中,关键字 static 在不同的应用场景下,有不同的作用,这…...
攻防世界-web-file_include
1. 题目描述 打开界面,如下代码: 代码很简单,从参数中获取到filename然后include这个filename 2. 思路分析 2.1 首先参考常见做法,将参数设置为php://filter/readconvert.base64-encode/resourceflag.php,看是否有…...
C语言的函数指针、指针函数, 函数数组
函数指针 是指向函数的指针,它允许您在程序运行时动态选择要调用的函数。函数指针可以像普通变量一样传递、存储和使用,这使得它们在许多编程场景中非常有用,如回调函数、函数表、插件架构等。 以下是一个简单的例子来说明函数指针的概念&a…...
笔记本开启WiFi
笔记本开启WiFi 为了节省流量:笔记本开启WiFi 条件 支持热点的电脑;我的是华硕飞行堡垒7。 注意事项 笔记本连接公司网络,公司网络通常都在监管下的,手机连接wifi后,刷抖音、购物网站,公司后台会捕获你…...
力扣第37天----第322题、第279题
力扣第37天----第322题、第279题 文章目录 力扣第37天----第322题、第279题一、第322题--零钱兑换二、第279题--组合总和 Ⅳ 一、第322题–零钱兑换 整体思路,跟前面的几道完全背包差不多,就不具体解释了。有一些细节要注意,见代码注释。…...
【ArcGIS Pro二次开发】(67):处理面要素空洞
这个一个简单的小功能。 有些面要素可能会存在空洞,这个工具的目的就是获取面要素的空洞,或者去除空洞获取要素的边界。 这个功能其实在之前做拓扑功能的时候就已经有了,这次只是单独把它提取出来。因为有时候会单独用到这个功能。 一、要实…...
FPGA-结合协议时序实现UART收发器(一):UART协议、架构规划、框图
FPGA-结合协议时序实现UART收发器(一):UART协议、架构规划、框图 记录FPGA的UART学习笔记,以及一些细节处理,主要参考奇哥fpga学习资料。 本次UART主要采用计数器方法实现,实现uart的稳定性发送和接收功能…...
web请求cookie中expires总结
用意 cookie 有失效日期 "expires",如果还没有过失效期,即使重新启动电脑,cookie 仍然不会丢失 注意:如果没有指定 expires 值,那么在关闭浏览器时,cookie 即失效。 设置 如果cookie存储时间大…...
如何学习Java核心知识
Java作为一门广泛应用于软件开发的编程语言,拥有着强大的生态系统和丰富的资源,是值得投入时间和精力去学习的。以下是一些建议,帮助你系统地学习Java核心知识。 1. 学习Java语言基础: 学习Java语言基础是学习Java的第一步&…...
【AWS】如何用SSH连接aws上的EC2实例(虚拟机)?
目录 0.环境 1.连接结果示例 2.SSH连接思路 3.具体步骤 1)安装并运行ssh服务 2)启动ssh服务 3)在AWS上找到正在运行的EC2实例,并且根据提供的ssh连接语句进行连接 0.环境 windows 11 64位 前提: 有aws账户&…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
基于鸿蒙(HarmonyOS5)的打车小程序
1. 开发环境准备 安装DevEco Studio (鸿蒙官方IDE)配置HarmonyOS SDK申请开发者账号和必要的API密钥 2. 项目结构设计 ├── entry │ ├── src │ │ ├── main │ │ │ ├── ets │ │ │ │ ├── pages │ │ │ │ │ ├── H…...
Sklearn 机器学习 缺失值处理 获取填充失值的统计值
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 使用 Scikit-learn 处理缺失值并提取填充统计信息的完整指南 在机器学习项目中,数据清…...
FFmpeg avformat_open_input函数分析
函数内部的总体流程如下: avformat_open_input 精简后的代码如下: int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…...

