深入计算机语言之C++:模板初阶

 🔑🔑博客主页:阿客不是客

🍓🍓系列专栏:从C语言到C++语言的渐深学习

欢迎来到泊舟小课堂

😘博客制作不易欢迎各位👍点赞+⭐收藏+➕关注

一、泛型编程

1.1 引入

C 语言中实现两数交换,中规中矩的写法是通过 tmp 交换。比如我们这里想交换 变量a 和 变量b 的值,我们可以写一个 Swap 函数:

void Swap(int* px, int* py)
{int tmp = *px;*px = *py; *py = tmp; 
}int main(void)
{int a = 0, b = 1;Swap(&a, &b);   // 传址return 0;
}

变量a 和 变量b 是整型,如果现在有了是浮点型的 变量c 和 变量d。还可以用我们这个整型的 Swap 函数交换吗?

void Swap(int* px, int* py)
{int tmp = *px;*px = *py;*py = tmp;
}int main(void)
{int a = 0, b = 1;double c = 1.1, d = 1.2;Swap(&a, &b);   // 传址Swap(&c, &d);return 0;
}

❓ 数据的类型是多种多样的,那我们能不能实现一个通用的 Swap 函数呢?

那我们不用C语言了!我们用 C++,C++ 里面不是有函数重载嘛!用 C++ 我们还能用引用的方法交换呢,直接传引用,取地址符号都不用打了,多好!

💬 test.cpp: 于是改成了C++之后 ——

void Swap(int& rx, int& ry) {int tmp = rx;rx = ry;ry = tmp;
}
void Swap(double& rx, double& ry) {double tmp = rx;rx = ry;ry = tmp;
}
void Swap(char& rx, char& ry) {char tmp = rx;rx = ry;ry = tmp;
}int main(void)
{int a = 0, b = 1;double c = 1.1, d = 1.2;Swap(a, b);   // 传址Swap(c, d);return 0;
}

好像靠函数重载来调用不同类型的 Swap,只是表面上看起来 "通用" 了 ,实际上问题还是没有解决,有新的类型,还是要添加对应的函数,看起来并没有比C语言好到哪去…… 

❌ 用函数重载解决的缺陷:

① 重载的函数仅仅是类型不同,代码的复用率很低,只要有新类型出现就需要增加对应的函数。

② 代码的可维护性比较低,一个出错可能导致所有重载均出错。

正好,在C++中有一种被称为模板的东西可以巧妙解决我们的问题。那什么叫模板呢?在生活中我们制作一样东西可以在一定基础的模板上进行改造,就像下列表情包所为:

下面让我们开始函数模板的学习!在这之前我们再来科普一下什么是泛型编程。

1.2 什么是泛性编程

泛型编程是一种编程风格,其中算法以尽可能抽象的方式编写,而不依赖于将在其上执行这些算法的数据形式。这个概念在 1989 年由 David Musser 和 Alexander A. Stepanov 首次提出。

泛型,就是针对广泛的类型的意思。

泛型编程: 编写与类型无关的调用代码,是代码复用的一种手段。 模板是泛型编程的基础。

二、函数模板

2.1 函数模板的概念

在C++中,模板(template)是一种通用的编程工具,允许程序员编写通用代码以处理多种数据类型或数据结构,而不需要为每种特定类型编写重复的代码,通过模板,可以实现代码的复用和泛化,提高代码的灵活性和可维护性———简而言之就是可以写一份通用的模板,便于阅读和管理

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。

2.2 函数模板的格式

template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}

  1. template是声明模板的关键字,告诉编译器开始泛型编程。
  2. 尖括号<>中的typename是定义形参的关键字,用来说明其后的形参名为类型 参数,(模板形参)。Typename(建议用)可以用class关键字代替,两者没有区别。
  3. T1, T2, ..., Tn 表示的是函数名,可以理解为模板的名字,名字你可以自己取。

💬 解决刚才的问题,构造一个通用的Swap函数:

template<class T>
void Swap(T& rx, T& ry)
{T tmp = rx;rx = ry;ry = tmp;
}

2.3 函数模板的原理

学会了如何使用函数模版之后,我们就可能会有一个疑问:那就是不同的类型调用的函数模版是同一个函数吗?这时我们继续利用上面代码,通过调用汇编来观察一下:

img

img

通过反汇编观察我们可以知道,当调用实参类型不同时,调用的函数也不同。当然也是符合逻辑的,不同类型的大小不同,调用的函数栈帧大小也就不同,自然也不可能调用同一个函数。

那么函数模版到底是如何调用的呢?其实也非常简单,函数模版就是一个蓝图,根据不同的参数类型生成对应的函数,只不过这件事我们将它交给了编译器来做了。

img

编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于其他类型也是如此 

2.4、函数模板实例化

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

2.4.1 模板的隐式实例化

我们刚才讲的 Swap 其实都是隐式实例化,就是让编译器自己去推。

💬 现在我们再举一个 Add 函数模板做参考:

template<class T>
T Add(const T& x, const T& y)
{return x + y;
}int main()
{int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.2;cout << Add(a1, a2) << endl;cout << Add(d1, d2) << endl;return 0;
}

 

❓ 现在思考一个问题,如果出现 a1 d2 这种情况呢?实例化能成功吗?

这必然是失败的, 因为会出现冲突。一个要把它实例化成 int ,一个要把它实例成 double,

① 传参之前先进行强制类型转换:

template<class T>
T Add(const T& x, const T& y)
{return x + y;
}int main()
{int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.2;cout << Add(a1, a2) << endl;cout << Add(d1, d2) << endl;cout << Add((double)a1, d2) << endl;return 0;
}

② 写两个参数,那么返回的参数类型就会起决定性作用:

#include <iostream>
using namespace std;template<class T1, class T2>
T1 Add(const T1& x, const T2& y)   // 那么T1就是int,T2就是double
{   return x + y;   // 范围小的会像范围大的提升,int会像double "妥协"
}                   // 最后表达式会是一个double,但是最后返回值又是T1,是int,又会发生强制类型转换int main()
{int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.2;cout << Add(a1, d2) << endl; return 0;
}

但是这种方法的类型非常混乱,很可能写着写着就不知道输出的是什么类型了,而且强制类型转换还会发生精度丢失,出现一些奇奇怪怪的bug,所以我们还有更好的写法:

③ 我们还可以使用 "显式实例化" 来解决:

Add<int>(a1, d2);     // 指定实例化成int
Add<double>(a1, d2)   // 指定实例化成double

2.4.2 模板的显式实例化

定义:在函数名后的 < > 里指定模板参数的实际类型。

函数名 <类型> (参数列表);

简单来说,显式实例化就是在中间加一个尖括号 < > 去指定你要实例化的类型。(在函数名和参数列表中间加尖括号)

  • 如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错
template<class T>
T Add(const T& x, const T& y)
{return x + y;
}int main()
{int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.2;cout << Add(a1, a2) << endl;cout << Add(d1, d2) << endl;cout << Add<int>(a1, d2) << endl;     // 指定T用int类型cout << Add<double>(a1, d2) << endl;  // 指定T用double类型return 0;
}

除了以上场景需要显式实例化外,还有一种场景也需要我们显式实例化:

template <class T1, class T2>
void Add(T1 x)
{T2 y = 10;cout << a + b<< endl;
}

存在模板无法推演的情况,因此必须要显示实例化

2.4.3 模板参数的匹配

我们还是用刚才的 Add 函数模板来举例,现在我需要对整型的 a1 和 a2 进行加法操作,

💬 如果我们有一个现成的、专门用来处理 int 类型加法的函数,但同时有 Add 函数模板,生成 int 类型的加法函数的:

匹配原则:

  • 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
int Add(int left, int right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}template<class T>
T Add(T left, T right)
{cout << "T Add(T left, T right)" << endl;return left + right;
}int main()
{Add(1, 2); // 与非模板函数匹配,编译器不需要特化Add<int>(1, 2); // 调用编译器特化的Add版本
}

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

四、类模板 

4.1 引入

💬 就比如 Stack,如果我们定它是 int,那么它就是存整型的栈:

class Stack
{
public:Stack(int capacity = 4) : _top(0) , _capacity(capacity){_arr = new int[capacity];}~Stack(){delete[] _arr;_arr = nullptr;_capacity = _top = 0;}
private:int* _arr;int _top;int _capacity;
};

❓ 如果我想改成存 double 类型的栈呢?

当时我们在讲解数据结构的时候,是用 typedef 来解决的,如果需要改变栈的数据类型,直接改 typedef 那里就可以了。

但问题是我需要同时存两种不同的栈呢?这是改也无法解决的。

这和文章开头提到的问题(Swap)本质上是一个问题,就是不支持泛型。它们类里面的代码几乎是完全一样的,只是类型的不同。函数我们可以使用模板,类也是可以的,我们下面就来讲解一下类模板。

4.2 类模板的定义格式

定义:和函数模板的定义方式是一样的,template 后面跟的是尖括号 < > 

template<class T1, class T2, ..., class Tn>
class 类模板名

{
    类内成员定义
}

💬 代码演示:解决刚才的问题

template<class T>
class Stack
{
public:Stack(T capacity = 4) : _top(0) , _capacity(capacity){_arr = new T[capacity];}~Stack(){delete[] _arr;_arr = nullptr;_capacity = _top = 0;}
private:T* _arr;int _top;int _capacity;
};

但是由于类模板没有传参的功能,所以无法像普通函数模板一样,根据传入的实参来推测对应类型的函数以供使用:

所以,类模板只支持显示实例化

4.3 类模板的显示实例化

基于上面的原因,我们想要对类模板实例化,我们可以使用显示实例化。类模板实例化在类模板名字后跟 < >,然后将实例化的类型放在 < > 中即可。

类名 <类型> 变量名;

📌 注意事项:

  1. Stack 不是具体的类,是编译器根据被实例化的类型生成具体类的模具。类模板名字不是真正的类,而实例化的结果才是真正的类。
  2. Stack 是类名,Stack<int> 才是类型。
  3. 类模板中的函数在类外定义,没加 "模板参数列表" ,编译器不认识这个 T 。类模板中函数放在类外进行定义时,需要加模板参数列表。
template<class T>
class Stack {
public:Stack(T capacity = 4) : _top(0) , _capacity(capacity) {_arr = new T[capacity];}// 这里我们让析构函数放在类外定义,在类中只进行声明~Stack();
private:T* _arr;int _top;int _capacity;
};// 类模板中函数放在类外进行定义时,需要加模板参数列表
template <class T>    👈 必须要加!!!
Stack<T>::~Stack()  // Stack是类名,不是类型! Stack<T> 才是类型,
{delete[] _arr;_arr = nullptr;_capacity = _top = 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

O-RAN前传Spilt Option 7-2x

Spilt Option 7-2x 下行比特处理上行比特处理相关文章&#xff1a; Open Fronthaul wrt ORAN 联盟被称为下层拆分(LLS)&#xff0c;其目标是提高电信市场的灵活性和竞争力。下层拆分是指无线电单元(RU) 和分布式单元(DU) 之间的拆分。 O-RAN前传接口可以在 eCPRI 上传输。eCPR…

硬件---2电阻---精度、阻值识别、电阻功率、电阻限流、电阻分流、电阻分压

一电阻的阻值识别 1电阻精度问题 电阻精度是指实际电阻值与标称值之间的允许偏差&#xff0c;用于表示电阻的制造误差范围。简单来说&#xff0c;精度越高&#xff0c;实际电阻值越接近标称值。 <1>理解电阻精度 标称值是电阻器上标注的理想数值&#xff0c;比如100Ω。…

软件测试学习笔记丨Vue常用指令-条件渲染(v-if)

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/23462 v-if 条件渲染 用于返回表达式为true的值渲染多个标签可以使用<template> <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8&…

Linux基础(十一)——内存交换空间swap

内存交换空间swap 1.swap的作用2.使用实体分区创建swap3.使用文件创建swap 1.swap的作用 如果突然间某支程序用掉你大部分的内存&#xff0c; 那你的系统恐怕有损毁的情况发生喔&#xff01; 所以&#xff0c; 早期在安装 Linux 之前&#xff0c; 大家常常会告诉你&#xff1a…

注册登录学生管理系统小项目

头文件 #ifndef _LOGINLINK_H_ #define _LOGINLINK_H_ #include<myhead.h> typedef struct {int id;char name[20];int age; }stu,*Pstu; typedef struct node {union{int len;stu data;};struct node *next; }node,*Pnode; int regist(); int login(); Pnode create()…

atoi函数学习

文章目录 一、atoi函数1、函数原型2、函数功能3、函数返回 二、atoi使用三、atoi函数模拟实现 一、atoi函数 1、函数原型 atoi函数参数为一个字符指针&#xff0c;返回类型是int&#xff0c;作用将字符串转换为整型。使用函数需要包含头文件stdlib.h 2、函数功能 解析c字符串…

前端学习-盒子模型(十八)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 盒子模型组成 边框 语法 边框简写 代码示例 表格的细线边框 语法 内边距 内边距复合写法 外边距 外边距典型应用 外边距合并 清除内外边距 总结 前…

红队-linux基础

声明 通过学习 泷羽sec的个人空间-泷羽sec个人主页-哔哩哔哩视频,做出的文章如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 一.openssl 1、openssl passwd -1 123 openssl是一个开源的…

ThingsBoard规则链节点:Save Attributes 节点详解

引言 1. Save Attributes 节点简介 2. 节点配置 2.1 基本配置示例 3. 使用场景 3.1 设备状态管理 3.2 数据统计 3.3 业务逻辑处理 4. 实际项目中的应用 4.1 项目背景 4.2 项目需求 4.3 实现步骤 5. 总结 引言 ThingsBoard 是一个开源的物联网平台&#xff0c;提供了…

光缆车间三维可视化系统

通过图扑可视化平台整合实时生产数据与流程监控&#xff0c;并呈现光缆制造流程&#xff0c;提升生产透明度与效率。支持异常检测、资源管理与智能决策&#xff0c;助力车间运营的智能化与自动化升级。

强化学习这么做绝绝子!最新idea登顶Science!

强化学习&#xff08;RL&#xff09;全新里程碑&#xff01;RL之父Richard Stutton团队&#xff0c;提出一种奖励聚中思想&#xff0c;能大幅增强所有RL算法&#xff01;也即&#xff1a;通过从观察到的奖励中减去平均奖励&#xff0c;来提高连续强化学习问题中折扣方法的性能&…

[含文档+PPT+源码等]精品基于springboot实现的原生Andriod广告播放系统

基于Spring Boot实现的原生Android广告播放系统背景&#xff0c;主要可以从以下几个方面进行阐述&#xff1a; 一、市场需求与背景 移动互联网的快速发展&#xff1a; 随着移动互联网技术的不断进步&#xff0c;智能手机已成为人们日常生活中不可或缺的一部分。人们越来越多地…

2023上半年下午3,4,5

文中的英文很可能是类名。没有英文的段落可以不看 如图&#xff0c;C1和C5应该是父类&#xff0c;有234和678等多个箭头指向他们 所以C2,C3,C4应该是C1的子类&#xff0c;C5同理 聚合表示部分可以脱离整体而存在&#xff0c;整体消失部分也能存在 组合则部分不能脱离整体&…

【C/C++】字符/字符串函数(0)(补充)——由ctype.h提供

零.导言 除了字符分类函数&#xff0c;字符转换函数也是一类字符/字符串函数。 C语言提供了两种字符转换函数&#xff0c;分别是 toupper &#xff0c; tolower。 一.什么是字符转换函数&#xff1f; 顾名思义&#xff0c;即转换字符的函数&#xff0c;如大写字母转小写字母&am…

华为eNSP实验:QINQ技术

QinQ技术是一种扩展VLAN空间的技术&#xff0c;通过在802.1Q标签报文的基础上再增加一层802.1Q的Tag来达到扩展VLAN空间的功能。 QinQ技术允许私网VLAN透传公网&#xff0c;使得在骨干网中传递的报文有两层802.1Q Tag&#xff08;一层公网Tag&#xff0c;一层私网Tag&#xff…

YOLOv5之Common.py

文章目录 1.学习目的2.网络模型![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/67b8dbd00c9b4034ba370fc8b8a6031a.jpeg)3.common.py分析 1.学习目的 YOLOv5中最关键一个模型类 2.网络模型 3.common.py分析 # Ultralytics YOLOv5 &#x1f680;, AGPL-3.0 license…

5G时代,国产化融合通信行业的新机遇

在5G时代&#xff0c;国产化融合通信行业正在经历重要的转型与崛起&#xff0c;国产化融合通信行业正肩负着重要的社会责任&#xff0c;成为了推动我们社会发展的重要力量。5G技术的高速发展以及大规模的商业应用&#xff0c;使国产化融合通信行业迎来了前所未有的发展机遇。 5…

Spring WebFlux 核心原理(2-2)

1、Project Reactor 核心 1.1、新建项目 新建maven项目&#xff0c;将Project Reactor作为依赖项添加到应用程序中&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:x…

sublime Text的提取查找结果功能

notePad中是 sublime Text是快捷键 ctrlshiftF 点击find就行了&#xff0c;会新建一个文件里面是提取的内容 勾选展示上下文的情况

基于Jeecgboot3.6.3vue3的flowable流程online表单的审批使用介绍

更多技术支持与服务请加入我的知识星球或加我微信&#xff0c;名称:亿事达nbcio技术交流社区https://t.zsxq.com/iPi8F 今天介绍一下基于jeecgboot3.6.3的flowable流程使用online表单进行审批的情况 1、首先建立一个online应用类型的流程&#xff0c;如下&#xff1a; 2、进行…