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

【C++初阶】模版入门看这一篇就够了

文章目录

  • 1. 泛型编程
  • 2. 函数模板
    • 2. 1 函数模板概念
    • 2. 2 函数模板格式
    • 2. 3 函数模板的原理
    • 2. 4 函数模板的实例化
    • 2. 5 模板参数的匹配原则
    • 2. 6 补充:使用调试功能观察函数调用
  • 3. 类模板
    • 3 .1 类模板的定义格式
    • 3. 2 类模板的实例化


1. 泛型编程

在C语言中,如果我们要实现交换函数swap,为了让这个函数能兼容所有的类型,我们需要swap_intswap_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;
}
//....

使用函数重载虽然可以实现需求,但是依然有一下几个不好的地方:

  1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错

那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?

工业上生产不同颜色的同一种金属制品,会先制作一个模具,然后通过往模具中加入实现准备好的不同颜色的液态金属就可以制作出不同的成品。

如果在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 函数模板的实例化

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

  1. 隐式实例化:让编译器根据实参推演模板参数的实际类型。
#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;
}

该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型通过实参aT推演为int,通过实参bT推演为char类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 还是char类型而报错。
注意在模板中,编译器一般不会进行类型转换操作。
这时候有两种解决办法:

  1. 手动进行强制类型转换
Add(a, (int)b);

这样就可以了,形参接收到的是强制类型转换后的数值。

  1. 显式实例化:在函数名后的<>中指定模板参数的实际类型。
int main()
{int a = 10;char b = 'a';cout << Add<int>(a, b) << endl;return 0;
}

这个写法可以先给Add的模板的Tint,使其生成对应的函数后再接收参数,那么如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

但是要注意,对于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 模板参数的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,即使该函数模板可以被实例化为这个非模板函数。
#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函数,而不会使用函数模板去重新生成一个。

  1. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
#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;
}

这种情况下,编译器就不会使用上面显式写出来的函数,而是使用函数模板实例化出来一个新的函数。

  1. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
    这一条实际上在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)中,会导致编译错误。
原因有二:

  1. 多重定义错误
    如果将模板的定义放在.cpp文件中,并且不在头文件中声明这些成员函数,则在每个包含该头文件的编译单元中都需要重新定义这些成员函数。这会导致链接错误,因为每个翻译单元都会看到一个不同的定义。
  2. 编译时实例化需求
    当编译器遇到模板类的使用时,它需要知道整个模板类的定义,以便它可以为特定的模板参数实例化模板。如果成员函数的定义不在同一个文件中,编译器就无法生成正确的代码。

3. 2 类模板的实例化

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

也就是说

Stack<int> a;

类模板创建类的时候,<int>是必须的,而模板函数在一些情况下不是必须的。

谢谢你的阅读,喜欢的话来个点赞收藏评论关注吧!
我会持续更新更多优质文章

相关文章:

【C++初阶】模版入门看这一篇就够了

文章目录 1. 泛型编程2. 函数模板2. 1 函数模板概念2. 2 函数模板格式2. 3 函数模板的原理2. 4 函数模板的实例化2. 5 模板参数的匹配原则2. 6 补充&#xff1a;使用调试功能观察函数调用 3. 类模板3 .1 类模板的定义格式3. 2 类模板的实例化 1. 泛型编程 在C语言中&#xff0…...

Spring Bean创建流程

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

重学SpringBoot3-怎样优雅停机

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

【数据结构】顺序表和链表

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

Training language models to follow instructions with human feedback解读

前置知识方法数据集结论 前置知识 GPT的全称是Generative Pre-Trained Transformer&#xff0c;预训练模型自诞生之始&#xff0c;一个备受诟病的问题就是预训练模型的偏见性。因为预训练模型都是通过海量数据在超大参数量级的模型上训练出来的&#xff0c;对比完全由人工规则…...

线性回归矩阵求解和梯度求解

正规方程求解线性回归 首先正规方程如下&#xff1a; Θ ( X T X ) − 1 X T y \begin{equation} \Theta (X^T X)^{-1} X^T y \end{equation} Θ(XTX)−1XTy​​ 接下来通过线性代数的角度理解这个问题。 二维空间 在二维空间上&#xff0c;有两个向量 a a a和 b b b&…...

M3U8不知道如何转MP4?包能学会的4种格式转换教学!

在流媒体视频大量生产的今天&#xff0c;M3U8作为一种基于HTTP Live Streaming&#xff08;HLS&#xff09;协议的播放列表格式&#xff0c;广泛应用于网络视频直播和点播中。它包含了媒体播放列表的信息&#xff0c;指向了视频文件被分割成的多个TS&#xff08;Transport Stre…...

C++第4课——swap、switch-case-for循环(含视频讲解)

文章目录 1、课程代码2、课程视频 1、课程代码 #include<iostream> using namespace std; int main(){/* //第一个任务&#xff1a;学会swap int a,b,c;//从小到大排序输出 升序 cin>>a>>b>>c;//5 4 3if(a>b)swap(a,b);//4 5 3 swap()函数是用于交…...

大数据新视界 -- 大数据大厂之大数据重塑影视娱乐产业的未来(4 - 4)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...

在Java中,需要每120分钟刷新一次的`assetoken`,并且你想使用Redis作为缓存来存储和管理这个令牌

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA编程利器技巧(编写中……&#xff09; 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权限后&#xff0c;输入命令 lsblk 查看所有磁盘和分区&#xff0c;找到想要替换用户可使用文件夹内存的磁盘和分区。若没有进行分区&#xff0c;并转为所需要的分区数据类型&#xff0c;先进行分区与格式化&#xff0c;过程自行查阅。 扩充替换过程&#xff0c;例如…...

基于ArcMap中Python 批量处理栅格数据(以按掩膜提取为例)

注&#xff1a;图片来源于公众号&#xff0c;公众号也是我自己的。 ArcMap中的python编辑器是很多本科生使用ArcMap时容易忽略的一个工具&#xff0c;本人最近正在读一本书《ArcGIS Python 编程基础与应用》&#xff0c;在此和大家分享、交流一些相关的知识。 这篇文章主要分享…...

【flink】之集成mybatis对mysql进行读写

背景&#xff1a; 在现代大数据应用中&#xff0c;数据的高效处理和存储是核心需求之一。Flink作为一款强大的流处理框架&#xff0c;能够处理大规模的实时数据流&#xff0c;提供丰富的数据处理功能&#xff0c;如窗口操作、连接操作、聚合操作等。而MyBatis则是一款优秀的持…...

Java设计模式—观察者模式详解

引言 模式角色 UML图 示例代码 应用场景 优点 缺点 结论 引言 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为设计模式&#xff0c;它定义了对象之间的一对多依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都会得到通知…...

【Cri-Dockerd】安装cri-dockerd

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

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)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...

数据结构——基础知识补充

1.队列 1.普通队列 queue.Queue 是 Python 标准库 queue 模块中的一个类&#xff0c;适用于多线程环境。它实现了线程安全的 FIFO&#xff08;先进先出&#xff09;队列。 2.双端队列 双端队列&#xff08;Deque&#xff0c;Double-Ended Queue&#xff09;是一种具有队列和…...

只有.git文件夹时如何恢复项目

有时候误删文件但由于.git是隐藏文件夹而幸存&#xff0c;或者项目太大&#xff0c;单单甩给你一个.git文件夹让你自己恢复整个项目&#xff0c;该怎么办呢&#xff1f; 不用担心&#xff0c;只要进行以下步骤&#xff0c;即可把原项目重新搭建起来&#xff1a; 创建一个文件…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)

0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述&#xff0c;后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作&#xff0c;其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

相机从app启动流程

一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信

文章目录 Linux C语言网络编程详细入门教程&#xff1a;如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket&#xff08;服务端和客户端都要&#xff09;2. 绑定本地地址和端口&#x…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用

在工业制造领域&#xff0c;无损检测&#xff08;NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统&#xff0c;以非接触式光学麦克风技术为核心&#xff0c;打破传统检测瓶颈&#xff0c;为半导体、航空航天、汽车制造等行业提供了高灵敏…...

适应性Java用于现代 API:REST、GraphQL 和事件驱动

在快速发展的软件开发领域&#xff0c;REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名&#xff0c;不断适应这些现代范式的需求。随着不断发展的生态系统&#xff0c;Java 在现代 API 方…...