【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>是必须的,而模板函数在一些情况下不是必须的。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/58265.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

动态规划-子序列问题——1027.最长等差数列

1.题目解析 题目来源&#xff1a;1027.最长递增子序列——力扣 测试用例 2.算法原理 1.状态表示 等差数列至少是三个数&#xff0c;所以一维数组显然无法满足状态表示&#xff0c;所以需要开辟一个二维数组来表示&#xff0c;二维数组的两个下标分别是倒数第一与倒数第二个数 …

【JSON相关漏洞(Hijacking+Injection)挖掘技巧及实战案例全汇总】

JSON相关漏洞&#xff08;HijackingInjection&#xff09;挖掘技巧及实战案例全汇总 本文一是在为测试过程中遇到json返回格式时提供测试思路&#xff0c;二是几乎所有国内的资料都混淆了json和jsonp的区别——这是两种技术&#xff1b;以及json和jsonp hijacking的区别——这…

基于知识图谱的设备问答系统

你是不是还在为毕业设计苦恼&#xff1f;今天我就来给大家分享一款基于知识图谱的设备问答系统&#xff0c;不仅技术领先&#xff0c;而且非常适合用作毕业设计项目&#xff0c;帮助你轻松通过&#xff01; 项目简介 这个设备问答系统基于Django&#xff08;后端框架&#xf…

从零开始的 vue项目部署到服务器详细步骤(vue项目build打包+nginx部署+配置ssl证书)

从零开始的 vue项目部署到服务器详细步骤&#xff08;vue项目build打包nginx部署配置ssl证书&#xff09; 文章目录 从零开始的 vue项目部署到服务器详细步骤&#xff08;vue项目build打包nginx部署配置ssl证书&#xff09;一、前言二、vue项目部署前配置1、vite.config.js 增加…

ubuntu内核更新导致显卡驱动掉的解决办法

方法1&#xff0c;DKMS指定内核版本 用第一个就行 1&#xff0c;借鉴别人博客解决方法 2&#xff0c;借鉴别人博客解决方法 方法2&#xff0c;删除多于内核的方法 系统版本&#xff1a;ubuntu20.24 这个方法是下下策&#xff0c;如果重装驱动还是不行&#xff0c;就删内核在…

豆瓣同城活动采集-过去一年到未来已定档活动

通过采集豆瓣同城活动&#xff0c;来辅助分析一个城市的文商旅体活跃繁荣程度。 以成都为例&#xff0c;2023年10月30日到2024年11月未来已定档活动期间&#xff0c;豆瓣同城活动共有975场。 示例数据&#xff1a; 活动网址&#xff1a;有&#xff0c;此处不显示 封图网址&…

海量数据面试题

⭐️前言⭐️ 本篇文章主要针对在面试时可能涉及到的海量数据的面试题&#xff0c;该类型面试题常常考虑通过位图、布隆过滤器或者哈希的方式来解决。 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f349;博主将持续更新学习记录收获&#xff0c;友友们有任何…

pytorh学习笔记——cifar10(九)使用torhvision的标准resnet模型

之前的demo都是模仿和简化了已有的模型&#xff0c;也可以直接调用orhvision的标准模型&#xff0c;代码将更加简单。 新建resnet18.py import torch.nn as nn from torchvision import modelsclass ResNet18(nn.Module):def __init__(self, num_classes10):super(ResNet18, …

json与python中字典的互相转化

json的定义 JSON &#xff08;JavaScript Object Notation&#xff09; &#xff0c;是一种轻量级的数据交换格式。它的使用范围很广&#xff0c;并成为 ECMA 标准&#xff0c;可以被使用在多种编程语言中&#xff0c;用于前后端之间的数据传输、存储和交换数据。可以说是“用…

如何快速分析音频中的各种频率成分

从视频中提取音频 from moviepy.editor import VideoFileClip# Load the video file and extract audio video_path "/mnt/data/WeChat_20241026235630.mp4" video_clip VideoFileClip(video_path)# Extract audio and save as a temporary file for further anal…

Rust 力扣 - 5. 最长回文子串

文章目录 题目描述题解思路题解代码题解链接 题目描述 题解思路 从中心点先寻找和中心点相等的左右端点&#xff0c;在基于左右端点进行往外扩散&#xff0c;直至左右端点不相等或者越界&#xff0c;然后左右端点这个范围内就是我们找寻的回文串&#xff0c;我们遍历中心点&am…

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

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA编程利器技巧(编写中……&#xff09; 5、面经吐血整理的 面试技…

macOS 15 Sequoia dmg格式转用于虚拟机的iso格式教程

想要把dmg格式转成iso格式&#xff0c;然后能在虚拟机上用&#xff0c;最起码新版的macOS镜像是不能用UltraISO&#xff0c;dmg2iso这种软件了&#xff0c;你直接转放到VMware里绝对读不出来&#xff0c;办法就是&#xff0c;在Mac系统中转换为cdr&#xff0c;然后再转成iso&am…

Unity3D学习FPS游戏(3)玩家第一人称视角转动和移动

前言&#xff1a;上一篇实现了角色简单的移动控制&#xff0c;但是实际游戏中玩家的视角是可以转动的&#xff0c;并根据转动后视角调整移动正前方。本篇实现玩家第一人称视角转动和移动&#xff0c;觉得有帮助的话可以点赞收藏支持一下&#xff01; 玩家第一人称视角 修复小问…

NAT技术和代理服务器

NAT IP原理 之前我们讨论了, IPv4协议中, IP地址数量不充足的问题 NAT技术当前解决IP地址不够用的主要手段, 是路由器的一个重要功能;NAT能够将私有IP对外通信时转为全局IP. 也就是就是一种将私有IP和全局IP相互转化的技术方法:很多学校, 家庭, 公司内部采用每个终端设置私有…

批处理操作的优化

原来的代码 Override Transactional(rollbackFor Exception.class) public void batchAddQuestionsToBank(List<Long> questionIdList, Long questionBankId, User loginUser) {// 参数校验ThrowUtils.throwIf(CollUtil.isEmpty(questionIdList), ErrorCode.PARAMS_ERR…

2023IKCEST第五届“一带一路”国际大数据竞赛--社交网络中多模态虚假 媒体内容核查top11

比赛链接&#xff1a;https://aistudio.baidu.com/competition/detail/1030/0/introduction PPT链接&#xff1a;https://www.ikcest.org/bigdata2024/zlxz/list/page.html 赛题 社交网络中多模态虚假媒体内容核查 背景 随着新媒体时代信息媒介的多元化发展&#xff0c;各种内容…

GitHub Star 数量前 5 的开源应用程序生成器

欢迎来的 GitHub Star 数量排名系列文章的第 7 篇——最受欢迎的应用程序生成器。 之前我们已经详细探讨过&#xff1a;在 GitHub 上最受欢迎的——无代码工具、低代码项目、内部工具、CRUD项目、自部署项目和 Airtable 开源替代品。累计超过 50 个优质项目&#xff01;&#…

橘子多开同步器 v6.0 免费版

下载&#xff1a; 【1】https://drive.uc.cn/s/ddb0774e92924?public1 【2】https://pan.quark.cn/s/b5b1aae8c331 橘子多开同步器是一款专门为了游戏工作室而打造的免费游戏客户端多开同步工具&#xff0c;涵盖了包括客户端多开、客户端键鼠同步、智能防封等功能。 功能介…

Linux 进程优先级 进程切换

目录 优先级 概念 为什么优先级要限制在一定范围内 进程切换 方式 EIP寄存器(程序计数器) 进程在运行时会使用寄存器来保存临时数据 进程的上下文是什么&#xff1f; 进程的上下文保存到哪&#xff1f; 内核栈或专门的上下文结构也在内核空间&#xff1f;那为什么不直…