【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.rhkb.cn/news/459507.html

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

相关文章

【React前端框架】全面解析:从入门到精通的最佳指南

&#x1f9d1;‍&#x1f4bc; 一名茫茫大海中沉浮的小小程序员&#x1f36c; &#x1f449; 你的一键四连 (关注 点赞收藏评论)是我更新的最大动力❤️&#xff01; &#x1f4d1; 目录 &#x1f53d; 前言1️⃣ React的基本概念2️⃣ React的安装与环境配置3️⃣ 组件的创建与…

Springboot项目搭建的问题

1.第一次出现这个问题是在使用postgresql进行搭建项目的时候&#xff0c;但是配置文件中的驱动一致导入不了 最后发现是meaven中依赖导入不进来&#xff08;不知道为什么&#xff09;&#xff0c;于是手动的在meaven中央仓库下载了对应的jar进行配置 然后项目可以正常启动 2.…

DBeaver如何导出insert的sql数据

前言 我们在使用DBeaver连接数据库的时候&#xff0c;有时候需要将数据库某张表的数据全部导出&#xff0c;用于导入到其他的数据库。一般导出的格式有csv&#xff0c;也要sql语句&#xff0c;今天就来介绍下如何导出sql语句。这样在其他新的数据库中&#xff0c;就能直接执行…

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

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

python之多任务爬虫——线程、进程、协程的介绍与使用(16)

文章目录 1、什么是多任务?1.1 进程和线程的概念1.2 多线程与多进程的区别1.3 并发和并行2、python中的全局解释器锁3、多线程执行机制4、python中实现多线程(threading模块)4.1 模块介绍4.2 模块的使用5、python实现多进行程(Multiprocessing模块)5.1 导入模块5.2 模块的…

【JavaEE】【多线程】定时器

目录 一、定时器简介1.1 Timer类1.2 使用案例 二、实现简易定时器2.1 MyTimerTask类2.2 实现schedule方法2.3 构造方法2.4 总代码2.5 测试 一、定时器简介 定时器&#xff1a;就相当于一个闹钟&#xff0c;当我们定的时间到了&#xff0c;那么就执行一些逻辑。 1.1 Timer类 …

深入解析HTTP与HTTPS的区别及实现原理

文章目录 引言HTTP协议基础HTTP响应 HTTPS协议SSL/TLS协议 总结参考资料 引言 HTTP&#xff08;HyperText Transfer Protocol&#xff09;超文本传输协议是用于从Web服务器传输超文本到本地浏览器的主要协议。随着网络安全意识的提高&#xff0c;HTTPS&#xff08;HTTP Secure…

鸿蒙生态开发以及技术栈介绍

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;鸿蒙开发篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来鸿蒙开发篇专栏内容: 有没有可以2小时不用手机的&#xff1f; 打开电视用什么&#xff1f; 打开空调用什么&a…

基于SpringBoot的房地产销售管理系统【附源码】

基于SpringBoot的房地产销售管理系统&#xff08;源码L文说明文档&#xff09; 目录 4 系统设计 4.1用户登录功能的详细实现 4.2管理员权限的功能实现 4.2.1客户信息管理功能的详细实现 4.2.2房产管理功能的详细实现 4.2.3预约看房功能的详细实现 4.2.4论…

微信小程序 - 动画(Animation)执行过程 / 实现过程 / 实现方式

前言 因官方文档描述不清晰,本文主要介绍微信小程序动画 实现过程 / 实现方式。 实现过程 推荐你对照 官方文档 来看本文章,这样更有利于理解。 简单来说,整个动画实现过程就三步: 创建一个动画实例 animation。调用实例的方法来描述动画。最后通过动画实例的 export 方法…

Flutter Row组件实战案例

In this section, we’ll continue our exploration by combining the Row and Container widgets to create more complex layouts. Let’s dive in! 在本节中&#xff0c;我们将继续探索&#xff0c;结合“Row”和“Container”小部件来创建更复杂的布局。让我们开始吧! Sc…

HTML5教程(一)- 网页与开发工具

1. 什么是网页 网页 基于浏览器阅读的应用程序&#xff0c;是数据&#xff08;文本、图像、视频、声音、链接等&#xff09;展示的载体常见的是以 .html 或 .htm 结尾的文件 网站 使用 HTML 等制作的用于展示特定内容相关的网页集合。 2. 网页的组成 浏览器 代替用户向服务…

剖析市面上具有代表性的四款翻译工具!

据我目前了解。市场上有多款具有代表性的翻译软件&#xff0c;它们可以各自针对不同的用户需求和使用场景提供了特色化服务。现在我将推荐剖析其中的我个人觉得比较好用的四款翻译工具&#xff0c;帮助你们能更好地理解其功能&#xff0c;选出最适合的工具来满足多样化的翻译需…

无人机之任务分配算法篇

无人机的任务分配算法是无人机系统中的重要组成部分&#xff0c;它决定了无人机如何高效、合理地执行各种任务。以下是一些常见的无人机任务分配算法&#xff1a; 一、合同网协议&#xff08;Contract Net Protocol, CNP&#xff09; 基本概念&#xff1a;CNP算法是一种分布式…

微信小程序25__实现卡片变换

先看效果图 实现代码如下&#xff1a; <view class"page" style"filter:hue-rotate({{rotation}}deg)"><view class"prev" catchtap"toPrev">《《《</view><view class"next" catchtap"toNext&q…

深入理解 SQL 中的 WITH AS 语法

在日常数据库操作中&#xff0c;SQL 语句的复杂性往往会影响到查询的可读性和维护性。为了解决这个问题&#xff0c;Oracle 提供了 WITH AS 语法&#xff0c;这一功能可以极大地简化复杂查询&#xff0c;提升代码的清晰度。本文将详细介绍 WITH AS 的基本用法、优势以及一些实际…

java项目之电影评论网站(springboot)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的电影评论网站。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 电影评论网站的主要使用者管…

Pnetlab免登录方式

Pnetlab免登录方式 文档&#xff1a;https://blog.wamoyu.com/docs/pnetlab/Installation/physicalInstallation 官方&#xff1a;https://pnetlab.com/pages/main Username:Michealzou password:ztr940407 操作系统&#xff1a;Ubuntu 20.4 1.裸机方式安装pnetlab: curl …

听书项目(一)

内容来源 尚硅谷知识星球&#xff0c;精选项目&#xff0c;只记录开发中的优点功能&#xff0c;并不涉及侵权问题&#xff0c;若有侵权联系作者删除。 项目背景 随着智能手机和高速互联网的普及&#xff0c;人们开始寻求更便捷的方式来获取信息和娱乐。有声书的出现使得人们…

部署DNS主从服务器

一。DNS主从服务器作用&#xff1a; DNS作为重要的互联网基础设施服务&#xff0c;保证DNS域名解析服务的正常运转至关重要&#xff0c;只有这样才能提供稳定、快速日不间断的域名查询服务 DNS 域名解析服务中&#xff0c;从服务器可以从主服务器上获取指定的区域数据文件&…