【C++初阶】模版入门看这一篇就够了
文章目录
- 1. 泛型编程
- 2. 函数模板
- 2. 1 函数模板概念
- 2. 2 函数模板格式
- 2. 3 函数模板的原理
- 2. 4 函数模板的实例化
- 2. 5 模板参数的匹配原则
- 2. 6 补充:使用调试功能观察函数调用
- 3. 类模板
- 3 .1 类模板的定义格式
- 3. 2 类模板的实例化
1. 泛型编程
在C语言中,如果我们要实现交换函数swap
,为了让这个函数能兼容所有的类型,我们需要swap_int
,swap_double
等等,为每个类型单独实现一个对应的交换函数,还要起不同的名称防止冲突。
而在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;
}
//....
使用函数重载虽然可以实现需求,但是依然有一下几个不好的地方:
- 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数。
- 代码的可维护性比较低,一个出错可能所有的重载均出错。
那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?
工业上生产不同颜色的同一种金属制品,会先制作一个模具,然后通过往模具中加入实现准备好的不同颜色的液态金属就可以制作出不同的成品。
如果在C++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同材料的铸件(即生成具体类型的代码),那将会节省许多不必要的代码。
巧的是前人早已将树栽好,我们只需在此乘凉,这便是泛型编程和模板。
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。
模板是泛型编程的基础。
模板有两种:
我们来一一介绍。
2. 函数模板
2. 1 函数模板概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
2. 2 函数模板格式
template<typename T1, typename T2,..,typename Tn>
返回值类型 函数名(参数列表)
{}
以swap
函数为例:
template<typename T>
void swap(T& a, T& b)
{T temp = a;a = b;a = temp;
}
注意:typename
是用来定义模板参数的关键字,也可以使用class
(不能使用struct
)。
2. 3 函数模板的原理
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。
所以其实模板就是将本来应该我们做的重复的事情交给了编译器。
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double
类型使用函数模板时,编译器通过对实参类型的推演将T
确定为double
类型,然后产生一份专门处理double
类型的代码,对于字符类型也是如此。
2. 4 函数模板的实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。板参数实例化分为隐式实例化和显式实例化。
- 隐式实例化:让编译器根据实参推演模板参数的实际类型。
#include<iostream>
using namespace std;
template<class T>
T Add(T A, T B)
{return A + B;
}int main()
{int a = 10;char b = 'a';cout << Add(a, b) << endl;return 0;
}
该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型通过实参a
将T
推演为int
,通过实参b
将T
推演为char
类型,但模板参数列表中只有一个T
,编译器无法确定此处到底该将T
确定为int
还是char
类型而报错。
注意在模板中,编译器一般不会进行类型转换操作。
这时候有两种解决办法:
- 手动进行强制类型转换
Add(a, (int)b);
这样就可以了,形参接收到的是强制类型转换后的数值。
- 显式实例化:在函数名后的
<>
中指定模板参数的实际类型。
int main()
{int a = 10;char b = 'a';cout << Add<int>(a, b) << endl;return 0;
}
这个写法可以先给Add
的模板的T
传int
,使其生成对应的函数后再接收参数,那么如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。
但是要注意,对于swap
函数来说,上面的两种做法都不能使swap
的两个参数不同,第一个做法中,强制类型转换产生的是一个临时对象,具有常性,不可修改;第二个中隐式类型转换产生的也是一个临时对象,不可修改。除非写一个两个实参类型不同的函数模板,比如:
#include<iostream>
using namespace std;template<class T,class Y>
void swap(T& a, Y& b)
{//交换函数的实现
}
因为这样的函数模板没有什么实际意义,所以就不实现了。
不过顺带一提,对于这样的函数,如果我们想在调用时指定其两个参数的类型,可以这么写:
#include<iostream>
//using namespace std; //这里注意:std命名空间中有一个swap函数模版template<class T,class Y>
void swap(T& a, Y& b)
{//交换函数的实现
}int main()
{int a = 10;char b = 'a';swap<int, char>(a, b); //<>中不同的类型名用 , 隔开std::cout << a << ' ' << b << std::endl;return 0;
}
代码中也提到了全局的swap
函数模板,所以在实践中我们不需要手动去实现swap
函数模板,直接使用库里的就行了(库函数的swap
不支持不同类型变量的交换)。
2. 5 模板参数的匹配原则
- 一个非模板函数可以和一个同名的函数模板同时存在,即使该函数模板可以被实例化为这个非模板函数。
#include<iostream>
using namespace std;// 显式写出 int 的Add函数
int Add(const int& a, const int& b)
{return a + b;
}// 这个函数模板也可以实例化出 int 的Add函数
template<class T>
T Add(const T& a, const T& b)
{return a + b;
}int main()
{cout << Add(1, 2) << " " << Add(10.2, 15.0) << endl;return 0;
}
对于这种情况,编译器会优先使用显式写出来的那个int类型的Add函数,而不会使用函数模板去重新生成一个。
- 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
#include<iostream>
using namespace std;// 显式写出 int 的 Add 函数
int Add(const int& a, const int& b)
{return a + b;
}//这个函数模板也能实例化出 int 的 Add 函数
template<class T,class Y>
T Add(const T& a, const Y& b)
{return a + b;
}int main()
{cout << Add(1, 2.0) << endl;return 0;
}
这种情况下,编译器就不会使用上面显式写出来的函数,而是使用函数模板实例化出来一个新的函数。
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
这一条实际上在2.4中已经介绍过了,不再赘述。
2. 6 补充:使用调试功能观察函数调用
我们怎么能知道函数调用的是哪个模板,或者说哪个显式写出来的函数呢?
虽然可以通过在函数内部写一条输出语句输出不同的内容实现,但这在实际开发中显得有些麻烦,要给所有的可能的模板都加上输出,所以一般不采用。
我们可以使用调试功能来观察,这里以VS2022的调试功能为例:
当程序运行到函数调用的这一行时,按F11(逐语句),就可以进入调用的函数内部:
可以发现,这个函数调用的就是这个函数模板(实例化出来的函数),如果是调用的其他模板或函数,程序就会执行到相应的位置。
3. 类模板
3 .1 类模板的定义格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{// 类内成员定义
};
举例:
#include<iostream>
using namespace std;
template<typename T>
class Stack
{
public:Stack(size_t capacity = 4):_size(0),_capacity(capacity)//,_data(new T[capacity]) //注意不能这么写,详情请看类和对象(下){_data = new T[_capacity];}// 析构函数略void push_back(const T& temp){if (_size == _capacity){T* newdata = new int[2 * _capacity];for (int i = 0; i < _size; i++){newdata[i] = _data[i];}delete[] _data;_data = newdata;}_data[_size++] = temp;}void Print(){for (int i = 0; i < _size; i++){cout << _data[i] << " ";}cout << endl;}private:T* _data;size_t _size;size_t _capacity;
};int main()
{Stack<int> a;a.push_back(1);a.push_back(2);a.push_back(3);a.push_back(4);a.push_back(5);a.Print();return 0;
}
对于Stack
类中的_data
数组,它的类型完全是由模板参数控制的,在创建时可以根据需要自由选择种类。
同时,对Print
函数来说,cout
这一非格式化输出也是很有意义的,因为它不可能使用printf
函数去输出。
另外,对于类模板,不建议把成员函数的声明和定义拆分到不同的文件(.h和.cpp)中,会导致编译错误。
原因有二:
- 多重定义错误:
如果将模板的定义放在.cpp文件中,并且不在头文件中声明这些成员函数,则在每个包含该头文件的编译单元中都需要重新定义这些成员函数。这会导致链接错误,因为每个翻译单元都会看到一个不同的定义。 - 编译时实例化需求:
当编译器遇到模板类的使用时,它需要知道整个模板类的定义,以便它可以为特定的模板参数实例化模板。如果成员函数的定义不在同一个文件中,编译器就无法生成正确的代码。
3. 2 类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>
,然后将实例化的类型放在<>
中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
也就是说
Stack<int> a;
类模板创建类的时候,<int>
是必须的,而模板函数在一些情况下不是必须的。
谢谢你的阅读,喜欢的话来个点赞收藏评论关注吧!
我会持续更新更多优质文章
相关文章:

【C++初阶】模版入门看这一篇就够了
文章目录 1. 泛型编程2. 函数模板2. 1 函数模板概念2. 2 函数模板格式2. 3 函数模板的原理2. 4 函数模板的实例化2. 5 模板参数的匹配原则2. 6 补充:使用调试功能观察函数调用 3. 类模板3 .1 类模板的定义格式3. 2 类模板的实例化 1. 泛型编程 在C语言中࿰…...

Spring Bean创建流程
Spring Bean 创建流程图 大家总是会错误的理解Bean的“实例化”和“初始化”过程,总会以为初始化就是对象执行构造函数生成对象实例的过程,其实不然,在初始化阶段实际对象已经实例化出来了,初始化阶段进行的是依赖的注入和执行一…...

重学SpringBoot3-怎样优雅停机
更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-怎样优雅停机 1. 什么是优雅停机?2. Spring Boot 3 优雅停机的配置3. Tomcat 和 Reactor Netty 的优雅停机机制3.1 Tomcat 优雅停机3.2 Reac…...

【数据结构】顺序表和链表
1.线性表 我们在C语言当中学过数组,其实呢,数组可以实现线性表,线性表理解上类似于数组,那么什么是线性表呢?线性表是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使 用的数据结构,常见…...

Training language models to follow instructions with human feedback解读
前置知识方法数据集结论 前置知识 GPT的全称是Generative Pre-Trained Transformer,预训练模型自诞生之始,一个备受诟病的问题就是预训练模型的偏见性。因为预训练模型都是通过海量数据在超大参数量级的模型上训练出来的,对比完全由人工规则…...
线性回归矩阵求解和梯度求解
正规方程求解线性回归 首先正规方程如下: Θ ( X T X ) − 1 X T y \begin{equation} \Theta (X^T X)^{-1} X^T y \end{equation} Θ(XTX)−1XTy 接下来通过线性代数的角度理解这个问题。 二维空间 在二维空间上,有两个向量 a a a和 b b b&…...

M3U8不知道如何转MP4?包能学会的4种格式转换教学!
在流媒体视频大量生产的今天,M3U8作为一种基于HTTP Live Streaming(HLS)协议的播放列表格式,广泛应用于网络视频直播和点播中。它包含了媒体播放列表的信息,指向了视频文件被分割成的多个TS(Transport Stre…...
C++第4课——swap、switch-case-for循环(含视频讲解)
文章目录 1、课程代码2、课程视频 1、课程代码 #include<iostream> using namespace std; int main(){/* //第一个任务:学会swap int a,b,c;//从小到大排序输出 升序 cin>>a>>b>>c;//5 4 3if(a>b)swap(a,b);//4 5 3 swap()函数是用于交…...

大数据新视界 -- 大数据大厂之大数据重塑影视娱乐产业的未来(4 - 4)
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...

在Java中,需要每120分钟刷新一次的`assetoken`,并且你想使用Redis作为缓存来存储和管理这个令牌
学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……) 2、学会Oracle数据库入门到入土用法(创作中……) 3、手把手教你开发炫酷的vbs脚本制作(完善中……) 4、牛逼哄哄的 IDEA编程利器技巧(编写中……) 5、面经吐血整理的 面试技…...
linux网络编程7——协程设计原理与汇编实现
文章目录 协程设计原理与汇编实现1. 协程概念2. 协程的实现2.1 setjmp2.2 ucontext2.3 汇编实现2.4 优缺点2.5 实现协程原语2.5.1 create()2.5.2 yield()2.5.3 resume()2.5.4 exit()2.5.5 switch()2.5.6 sleep() 2.6 协程调度器 3. 利用hook使用协程版本的库函数学习参考 协程设…...

Ubuntu22.04版本左右,扩充用户可使用内存
1 取得root权限后,输入命令 lsblk 查看所有磁盘和分区,找到想要替换用户可使用文件夹内存的磁盘和分区。若没有进行分区,并转为所需要的分区数据类型,先进行分区与格式化,过程自行查阅。 扩充替换过程,例如…...

基于ArcMap中Python 批量处理栅格数据(以按掩膜提取为例)
注:图片来源于公众号,公众号也是我自己的。 ArcMap中的python编辑器是很多本科生使用ArcMap时容易忽略的一个工具,本人最近正在读一本书《ArcGIS Python 编程基础与应用》,在此和大家分享、交流一些相关的知识。 这篇文章主要分享…...
【flink】之集成mybatis对mysql进行读写
背景: 在现代大数据应用中,数据的高效处理和存储是核心需求之一。Flink作为一款强大的流处理框架,能够处理大规模的实时数据流,提供丰富的数据处理功能,如窗口操作、连接操作、聚合操作等。而MyBatis则是一款优秀的持…...
Java设计模式—观察者模式详解
引言 模式角色 UML图 示例代码 应用场景 优点 缺点 结论 引言 观察者模式(Observer Pattern)是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知…...

【Cri-Dockerd】安装cri-dockerd
cri-dockerd的作用: 在k8s1.24之前。k8s会通过dockershim来调用docker进行容器运行时containerd,并且会自动安装dockershim,但是从1.24版本之前k8s为了降低容器运行时的调用的复杂度和效率,直接调用containerd了,并且…...

GCC及GDB的使用
参考视频及博客 https://www.bilibili.com/video/BV1EK411g7Li/?spm_id_from333.999.0.0&vd_sourceb3723521e243814388688d813c9d475f https://www.bilibili.com/video/BV1ei4y1V758/?buvidXU932919AEC08339E30CE57D39A2BABF6A44F&from_spmidsearch.search-result.0…...

大数据新视界 -- 大数据大厂之大数据重塑影视娱乐产业的未来(4 - 3)
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...

数据结构——基础知识补充
1.队列 1.普通队列 queue.Queue 是 Python 标准库 queue 模块中的一个类,适用于多线程环境。它实现了线程安全的 FIFO(先进先出)队列。 2.双端队列 双端队列(Deque,Double-Ended Queue)是一种具有队列和…...
只有.git文件夹时如何恢复项目
有时候误删文件但由于.git是隐藏文件夹而幸存,或者项目太大,单单甩给你一个.git文件夹让你自己恢复整个项目,该怎么办呢? 不用担心,只要进行以下步骤,即可把原项目重新搭建起来: 创建一个文件…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...

【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...

c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...

android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...
libfmt: 现代C++的格式化工具库介绍与酷炫功能
libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库,提供了高效、安全的文本格式化功能,是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全:…...

tauri项目,如何在rust端读取电脑环境变量
如果想在前端通过调用来获取环境变量的值,可以通过标准的依赖: std::env::var(name).ok() 想在前端通过调用来获取,可以写一个command函数: #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...
《Offer来了:Java面试核心知识点精讲》大纲
文章目录 一、《Offer来了:Java面试核心知识点精讲》的典型大纲框架Java基础并发编程JVM原理数据库与缓存分布式架构系统设计二、《Offer来了:Java面试核心知识点精讲(原理篇)》技术文章大纲核心主题:Java基础原理与面试高频考点Java虚拟机(JVM)原理Java并发编程原理Jav…...

Copilot for Xcode (iOS的 AI辅助编程)
Copilot for Xcode 简介Copilot下载与安装 体验环境要求下载最新的安装包安装登录系统权限设置 AI辅助编程生成注释代码补全简单需求代码生成辅助编程行间代码生成注释联想 代码生成 总结 简介 尝试使用了Copilot,它能根据上下文补全代码,快速生成常用…...
LTR-381RGB-01RGB+环境光检测应用场景及客户类型主要有哪些?
RGB环境光检测 功能,在应用场景及客户类型: 1. 可应用的儿童玩具类型 (1) 智能互动玩具 功能:通过检测环境光或物体颜色触发互动(如颜色识别积木、光感音乐盒)。 客户参考: LEGO(乐高&#x…...