【C++】:模板初阶—函数模板|类模板


✨ Blog’s 主页: 白乐天_ξ( ✿>◡❛)
🌈 个人Motto:他强任他强,清风拂山岗!
💫 欢迎来到我的学习笔记!

本文参考博客:一同感受C++模版的所带来的魅力
一、泛型编程思想
- 首先我们来实现一个
swap交换函数。如果学过了C++的函数重载和引用的话,就可以写出swap函数不同参数类型的重载函数。(注意: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;
}
根据上面的代码,我们就可以感觉到,swap交换函数仅仅只是实现一个交换功能,却需要根据参数的类型写出几个很相似的函数,重复相同或者相近的代码。如果又增加了其他类型数据需要进行交换呢?继续重复相同的操作再去实现这个函数吗?
这样操作虽然可行,但是它存在几处缺陷:
- 重载的函数仅仅只是类型不同,代码复用吕比较低,只要有新的类型出现,就需要自己再次实现所需类型的相近函数。
- 代码的可维护性比较低,一个出错可能所有的重载均出错。
那么既然重载函数的重复率比较高,能否只给出一个标准,然后让他们自己根据这个标准去实现所需要的东西呢?
这个标准就像做月饼用的模具一样:我们只需要放入材料,就可以利用模具做出形状相同的月饼。

后来C++就生成一个类似于模具的东西,将其交给编译器,让编译器根据这个模具自行生成所需代码。这就是泛型编程思想:编写与类型无关的通用代码,是代码复用的一种手段。模板,是泛型编程的基础。
二、函数模板–函数的模板
2.1 概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
通过函数模板,可以编写一种通用的函数定义,使其能够是用于多种数据类型,从而提高代码的服用行和灵活性。
2.2 格式
- 声明一个函数模板需要关键字
template,<>内部是模板参数,可以使用class或者typename来进行类型的声明(不能用struct)。 - 然后开始使用模板参数Tn,
template<typename T1,typename T2,typename T3,……,typename Tn>
返回值类型 函数名(参数列表)
{ }
- 注意:这里的函数模板的参数和普通的函数参数不一样。函数模板参数定义的是
<u>类型</u>,而普通函数参数定义的是<u>对象</u>。
返回值类型 函数名(参数列表){ }
- 在了解上面的函数模板后,我们就可以为
Swap()函数写一个通用的函数模板了。
//模板类型
template<typename T>
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}
- 运用:
int main()
{int a1 = 10, a2 = 20;double d1 = 1.1, d2 = 2.2;cout << "a1:" << a1 << " a2:" << a2 << endl;cout << "d1:" << d1 << " d2:" << d2 << endl;Swap(a1, a2);swap(d1, d2);cout << "a1:" << a1 << " a2:" << a2 << endl;cout << "d1:" << d1 << " d2:" << d2 << endl;return 0;
}
运行结果:
调试就可以发现:
Swap函数的函数模板可以自己推导传入参数的类型。
- 那如果给是
Swap函数传入不同的参数类型呢?
//模板类型
template<typename T>
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}template<class T1,class T2>
void func(const T1& x,const T2& y)//不同的类类型
{ }
int main()
{int a1 = 10, a2 = 20;double d1 = 1.1, d2 = 2.2;//传入不同的类型:Swap(a1, d1); //error: message : “void Swap(T &,T &)”: 模板 参数“T”不明确// message : 可能是“double”或 “int”//message: “void Swap(T&, T&)” : 无法从“double”推导出“T & ”的 模板 参数func(a1, d1); //rightreturn 0;
}
message : “void Swap(T &,T &)”: 无法从“double”推导出“T &”的 模板参数
- 那为什么
func()函数传入不同的类型的参数却没有问题呢?- 原因:传入
func()函数的参数并没有用来做编译器无法推导参数类型的操作。Swap()函数是因为编译器无法推导出参数类型。(一个T类型,结果参数是两个类型)
- 原因:传入
- 其实在C++中早就已经定义好了
swap()这个函数,头文件<utility>,我们可以直接使用。

2.3 原理
2.4 实例化
- 用不同类型的参数使用函数模板,称为函数模板的实例化。现在我们利用下面这个函数模板来理解实例化概念。
//用函数模板生成对应的函数-->模板的实例化
template<class T>//一个模板参数T
T Add(const T& left, const T& right)
{return left + right;
}
2.4.1 隐式实例化
- 概念:
- 隐式实例化(推导实例化):让编译器根据实参推演模板参数的实际类型,再返回不同类型的数据。
int main()
{int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.2;//推导实例化:实参传递给形参,编译器推导出T的类型cout << Add(a1, a2) << endl;cout << Add(d1, d1) << endl;return 0;
}
运行结果:
- 缺陷:传入参数类型不同,编译器左右为难、骑虎难下。
int main()
{int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.2;cout << Add(a1, d1) << endl;//message : “T Add(const T &,const T &)”: 无法从“double”推导出“const T &”的 模板 参数return 0;
}
①d1强制类型转换为int类型,解决类型冲突问题。
cout << Add(a1, (int)d1) << endl;
②a1强制类型转换为double类型,解决类型冲突问题。
cout << Add((double)a1, d1) << endl;
③ 如果非要传入不同类型的参数,就应该用不同的模板参数重新定义一个函数模板。
template<class T1& left,class T2& right>
T1 Add(const T1& left,const T2& right)//两个模板参数
{return left + right;
}
2.4.2 显示实例化
- 概念
- 显示实例化:在函数名后面紧跟
<>并在其中指定模板参数的实际类型。
//显示实例化:用指定类型来实例化
cout << Add<int>(a1, d1) << endl;
cout << Add<double>(a1, d1) << endl;
- 缺陷:其实在某些场景下面我们只能选择显示实例化。
- 形参部分不是模板参数,而是普通的自定义类型,返回值才是。此时我们无法通过传参来指定这个
T的类型,只有外部在调用这个模板时显示指定。
template<class T>
T* Alloc(int n)
{return new T[n];
}
// 有些函数无法自动推,只能显示实例化
int main()
{// 有些函数无法自动推,只能显示实例化double* p1 = Alloc<double>(10);float* p1 = Alloc<float>(20);int* p2 = Alloc<int>(30);return 0;
}
2.5 模板参数的匹配原则
- 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
//普通函数
int Add(int left, int right)
{return left + right;
}
//函数模板
template<class T>
T Add(T left, T right)
{return left + right;
}
int main()
{Add(1, 2);//调用了普通传参的函数Add<int>(1, 2);//调用了模板函数让其实生成对应的函数return 0;
}
- 根据调试可以发现:普通函数和函数模板是可以共存的。在进行普通传参时调用的是普通函数;显示指定了类型时,就会调用函数模板生成对应的函数。
- 对于非模板函数和同名函数模板,如果其他条件相同,在调用时会优先调用非模板函数而不会从该模板产生一个实例。如果模板可以产生一个更好匹配的函数,那么将会选择模板。
// 普通函数
int Add(int left, int right)
{return left + right;
}// 函数模板
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{return left + right;
}int main()
{Add(1, 2);//调用普通函数,因为参数匹配Add(1, 2.2);///调用函数模板,因为参数不匹配,普通函数不接收此参数。//函数模板可以根据这个类型自动推导return 0;
}
- 模板函数不允许自动类型转换,但是普通函数可以进行自动类型转换。
- 普通函数传入的参数可以发生隐式类型转换,又称【自动类型转换】,下面代码中浮点数自动类型转换为整型数。
// 普通函数,允许自动类型转换
void print(int value)
{std::cout << "Integer: " << value << std::endl;
}int main()
{print(3); print(3.14); // 在这里发生了隐式类型转换,浮点数自动类型转换为整型数return 0;
}
- 对于函数模板来说是不能进行自动类型转换的。a和b是两个不同的类型,并没有发生自动类型转换,一个模板参数T就使得编译器无法进行自动推导。解决办法就是上面提到的
隐式/显示类型转换。第三种方法就是增加模板参数。
template <class T>
void print(T a, T b) {cout << a << " " << b << endl;
}
int main()
{int a = 1;double b = 1.11;print(a, b);//error---message : “void print(T,T)”: 模板 参数“T”不明确return 0;
}
三、类模板–类的模板
3.1 类模板的定义格式
- 函数模板是加在函数上,类模板是加在类上。
template<class T1, class T2, ..., class Tn>
class 类模板名
{// 类内成员定义
};
- 类模板都是显示实例化。
template<class T>
class Stack
{
public:Stack(int n = 4)//写构造:_array(new T[n])//不用检查是否失败,失败了直接抛异常,_size(0),_capacity(n){}~Stack(){delete[] _array;_size = _capacity = 0;_array = nullptr;}void Push(const T& x)//传引用传参,避免多余拷贝浪费资源,能用引用就尽量使用引用{//空间不够用:扩容if (_size == _capacity){//不能使用realloc,无构造//C++无自动扩容的概念,扩容需要手动T* temp = new T[_capacity * 2];//扩容两倍,size不变memcpy(temp, _array, sizeof(T) * _size);//拷贝数据delete[] _array;//delete一次即可:释放旧空间_array = temp;//指向新空间_capacity *= 2;}_array[_size++] = x;}
//其他功能…………
private:T* _array;size_t _capacity;size_t _size;
};
int main()
{//类模板都是显示实例化Stack<int> st1;// intst1.Push(1);st1.Push(2);st1.Push(3);Stack<double> st2;// doublest2.Push(1.1);st2.Push(2.2);st2.Push(3.3);return 0;
}
- 我们在C语言中学习栈的时候就有一个
typedef的操作,功能类似于这里的模板。既然如此,那类模板的意义是什么呢?- C语言中的栈只能实现某一种类型,而C++可以一个栈存入int类型,另一个栈存入double类型。
- 就像在这里一样,实现的栈的结构基本都是一样的,不一样的只是存入栈的数据类型是不一样的。
- 如果在这里进行声明和定义的分离?
- 定义的函数模板只能给当前的函数或者当前的类使用,每个函数模板都需要定义自己的模板参数,按需定义。当进行声明和定义分离时,需要在函数前面单独声明类模板,并指定类域。下面就是类模板中的成员函数在类外面实现所需要变化成的模板函数。
template<class T>
class Stack
{
public:Stack(int n = 4):_array(new T[n]), _size(0), _capacity(n){}~Stack(){delete[] _array;_size = _capacity = 0;_array = nullptr;}void Push(const T& x);//其他功能……
private:T* _array;size_t _capacity;size_t _size;
};
//指定类域不行,还需要声明一下类模板
template<class T>
void Stack<T>::Push(const T& x)//传引用传参,避免多余拷贝浪费资源,能用引用就尽量使用引用
{//空间不够用:扩容if (_size == _capacity){T* temp = new T[_capacity * 2];memcpy(temp, _array, sizeof(T) * _size);delete[] _array;_array = temp;_capacity *= 2;}_array[_size++] = x;
}
- 如果我们将上面分离后新声明的T改为X,能否编译?
- 能的。T只是一个符号而已,我们实际上在调用这个函数时,并没有T或者X的概念。
- 注意:模板不支持声明和定义分离到两个不同文件中去。(即使可以,也相当繁琐。)
3.2 类模板的实例化
- 类模板实例化和函数模板实例化不同。类模板实例化需要在类模板名称后面跟
<>。然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
int main()
{//类模板都是显示实例化Stack<int> st1;// intst1.Push(1);st1.Push(2);st1.Push(3);Stack<double> st2;// doublest2.Push(1.1);st2.Push(2.2);st2.Push(3.3);return 0;
}
以上就是本文所要介绍的所有类容,感谢您的阅读!🌹

相关文章:
【C++】:模板初阶—函数模板|类模板
✨ Blog’s 主页: 白乐天_ξ( ✿>◡❛) 🌈 个人Motto:他强任他强,清风拂山岗! 💫 欢迎来到我的学习笔记! 本文参考博客:一同感受C模版的所带来的魅力 一、泛型编程思想 首先…...
Java 远程执行服务器上的命令
在Java中使用JSch库执行远程服务器上的命令是一种常见的做法,特别是在需要自动化运维任务或者进行远程文件操作时。以下是基于Codekru网站提供的示例,展示如何使用JSch库在远程服务器上执行单个或多个命令。 准备工作 首先,确保您的项目中已…...
3DMax基础- 创建基础模型
目录 零.软件简介 一. 标准基本型 长方体 圆锥体 球体 圆柱体 管状体 圆环 四棱锥 茶壶 平面编辑 加强型文本 二. 扩展基本体 三.复合对象 变形 散布 一致 连接 图形合并 布尔 并集 合并 交集 差集 四.门和窗 门 窗 植物,栏杆,墙 零.软件简介 3…...
JavaScript 知识点(从基础到进阶)
🌏个人博客主页:心.c 前言:JavaScript已经学完了,和大家分享一下我的笔记,希望大家可以有所收获,花不多说,开干!!! 🔥🔥ǵ…...
计算机网络知识点复习——TCP协议的三次握手与四次挥手(连接与释放)
TCP协议的三次握手与四次挥手(连接与释放) 一、前言二、简单的知识准备1. TCP协议的主要特点2. TCP报文段 三、TCP连接的建立(三次握手)四、TCP连接的释放(四次挥手)五、TCP连接与释放的总结六、结束语 一、…...
SpringDataJPA系列(7)Jackson注解在实体中应用
SpringDataJPA系列(7)Jackson注解在实体中应用 常用的Jackson注解 Springboot中默认集成的是Jackson,我们可以在jackson依赖包下看到Jackson有多个注解 一般常用的有下面这些: 一个实体的示例 测试方法如下: 按照上述图片中的序号做个简…...
【Spring Boot 3】【Web】统一封装 HTTP 响应体
【Spring Boot 3】【Web】统一封装 HTTP 响应体 背景介绍开发环境开发步骤及源码工程目录结构总结背景 软件开发是一门实践性科学,对大多数人来说,学习一种新技术不是一开始就去深究其原理,而是先从做出一个可工作的DEMO入手。但在我个人学习和工作经历中,每次学习新技术总…...
Linux如何做ssh反向代理
SSH反向代理是一种通过SSH协议实现的安全远程访问方式,它允许客户端通过SSH连接到一台具有公网IP的代理服务器,然后这台代理服务器再将请求转发给内部网络中的目标主机。以下是实现SSH反向代理的步骤: 一、准备工作 确保服务器配置ÿ…...
Verilog语法+:和-:有什么用?
Verilog语法:和-:主要用于位选择,可以让代码更简洁。 一、位选择基础 在Verilog中,位选择可以通过直接索引来实现,例如: reg [7:0] data; wire select_a; wire [2:0] select_b; assign select_a data[3]; assign select_b …...
stm32F103 串口2 中断 无法接收指定字符串 [已解决]
stm32F103 串口2中断接收指定字符串 USART 初始化和中断配置示例中断处理函数示例关键点总结 确保在串口配置中正确使能空闲中断 ( USART_IT_IDLE) 是关键。这个中断可以帮助你在串口接收一帧数据完成后,进行相应的处理和分析。 为了确保你在串口配置时能避免类似问…...
Matlab/Simulink和AMEsim联合仿真(以PSO-PID算法为例)
目录 安装软件和配置环境变量 Matlab/Simulink和AMEsim联合仿真详细流程 非常重要的一点 Simulink模型和AMEsim模型用S-Function建立连接 从AMEsim软件打开Matlab Matlab里的设置 Matlab的.m文件修改(对于PSO-PID算法) 运行程序 我印象中好像做过…...
超声波测距模块HC-SR04(基于STM32F103C8T6HAL库)
超声波测距模块参考资料 1.电路连接及引脚配置 触发信号PA3只需要输出10us的高电平,所以直接设置成 普通的GPIO端口即可;回响信号使用外部中断,上升沿信号产生外部中断,打开定时器,下降沿再产生一次中断,读…...
Go语言结构体和元组全面解析
Go语言中的复合类型与其应用 在编程中,标准类型虽然方便,但无法满足所有需求。Go通过支持结构体和元组类型,为开发者提供了自定义数据类型的能力。本文将介绍如何定义结构体、如何使用指针操作结构体、如何通过元组返回多个值等内容…...
集成电路学习:什么是SDK软件开发工具包
SDK:软件开发工具包 SDK,即Software Development Kit(软件开发工具包),是一套由软件提供商或其他组织提供的开发工具集合。这些工具旨在帮助开发者更快速、更便捷地创建、测试和部署软件应用程序。以下是对SDK的详细解…...
java后端如何发送http请求
用java后端发送请求需要用到的一个工具包为HttpClient。HttpClient是Apache的一个子项目,是高效的、功能丰富的支持HTTP协议的客户端编程工具包。 引入依赖 <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId&…...
装WebVideoCreator记录
背景,需要在docker容器内配置WebVideoCreator环境,配置npm、node.js WebVideoCreator地址:https://github.com/Vinlic/WebVideoCreator 配置环境,使用这个教程: linux下安装node和npm_linux离线安装npm-CSDN博客 1…...
【编程底层思考】什么是GC Roots
在Java虚拟机(JVM)中,GC Roots是垃圾收集(Garbage Collection,GC)过程中的起点,用于确定对象是否可被回收。GC Roots集合是一组必须活跃的(即必须保留在内存中的)引用&am…...
[STL --stack_queue详解]stack、queue,deque,priority_queue,容器适配器
stack stack介绍 1、stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。 2、stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供…...
240907-Gradio插入Mermaid流程图并自适应浏览器高度
A. 最终效果 B. 示例代码 import gradio as grmermaid_code """ <iframe srcdoc <!DOCTYPE html> <html><head><meta charset"utf-8" /><meta name"viewport" content"widthdevice-width" />…...
ubuntu 安装python3 教程
本篇教程,主要介绍如何在Ubuntu上安装python3教程。 1、查看是否有python 在安装前,首先看看自己系统上,是否存在python环境,可能有些系统,默认就安装过python,如果已经有python了,可以直接跳过安装教程。 2、安装步骤 apt update && apt install -y python3 p…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...
MySQL 主从同步异常处理
阅读原文:https://www.xiaozaoshu.top/articles/mysql-m-s-update-pk MySQL 做双主,遇到的这个错误: Could not execute Update_rows event on table ... Error_code: 1032是 MySQL 主从复制时的经典错误之一,通常表示ÿ…...
Ubuntu系统多网卡多相机IP设置方法
目录 1、硬件情况 2、如何设置网卡和相机IP 2.1 万兆网卡连接交换机,交换机再连相机 2.1.1 网卡设置 2.1.2 相机设置 2.3 万兆网卡直连相机 1、硬件情况 2个网卡n个相机 电脑系统信息,系统版本:Ubuntu22.04.5 LTS;内核版本…...
WEB3全栈开发——面试专业技能点P7前端与链上集成
一、Next.js技术栈 ✅ 概念介绍 Next.js 是一个基于 React 的 服务端渲染(SSR)与静态网站生成(SSG) 框架,由 Vercel 开发。它简化了构建生产级 React 应用的过程,并内置了很多特性: ✅ 文件系…...
Python常用模块:time、os、shutil与flask初探
一、Flask初探 & PyCharm终端配置 目的: 快速搭建小型Web服务器以提供数据。 工具: 第三方Web框架 Flask (需 pip install flask 安装)。 安装 Flask: 建议: 使用 PyCharm 内置的 Terminal (模拟命令行) 进行安装,避免频繁切换。 PyCharm Terminal 配置建议: 打开 Py…...


