【C++深入浅出】模版初识

 


目录

一. 前言

二. 泛型编程

三. 函数模版 

3.1 函数模版的概念

3.2 函数模版的格式

3.3 函数模版的原理

3.4 函数模板的实例化

3.5 模板参数的匹配原则

四. 类模版

4.1 类模版的定义

4.2 类模版的实例化


一. 前言

        本期我们要介绍的是C++的又一大重要功能----模版。通过模版,我们可以很轻松的进行泛型编程,大大简化我们编程时的代码。

        本文的目标是让读者对模版有一定程度上的了解,以便后续STL的学习,对于模版更深层次的内容,我们放到以后再进行拓展。

        话不多说,开启我们今日的学习叭

二. 泛型编程

        假如现在有个需求,要求我们实现一个swap函数用于数据的交换,数据类型可能是整形浮点型字符型等等,通过我们之前学习的知识,你会如何进行实现呢?

        聪明的你可能会这样实现

//利用函数重载实现三种不同类型的swap函数
void Swap(int& x, int& y) //引用传参
{int tmp = x;x = y;y = tmp;
}void Swap(double& x, double& y)
{double tmp = x;x = y;y = tmp;
}void Swap(char& x, char& y)
{double tmp = x;x = y;y = tmp;
}

        是的,上面的代码确实实现了我们的需求,但还是存在一些不足,体现在如下两个方面:

  1. 几个重载函数仅仅是类型不同,代码逻辑都是一样的,代码的复用率比较低。每当有新类型出现时,用户就需要再自行添加一个函数
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错(想必你写的时候也是CV的叭)

        依照以上两点不足,那我们能不能只给编译器提供一个模板,到时候让编译器根据我们指定的类型自动生成所需要的函数呢?

        答案是有的。C++为我们提供了模版的概念,支持我们进行泛型编程。模版就好比一个模具,我们可以从倒入任何种类(类型)的东西到模具中,最终都会形成我们想要的对应图案(即生成相应的代码)

        泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模版是泛型编程的基础。模板分为函数模版类模版

三. 函数模版 

3.1 函数模版的概念

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

3.2 函数模版的格式

        函数模版的定义格式如下所示:

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

        例如,我们将上面的swap函数定义为函数模版如下:

template<typename T> //T是类型名
void swap(T& x, T& y)
{T tmp = x;x = y;y = tmp;
}

其中,typename是定义模版参数的关键字,也可以使用class关键字替代。

3.3 函数模版的原理

        我们可以把函数模板看做一个蓝图,它本身并不是一个函数,而是编译器根据调用函数时传入实参产生特定类型函数的一个模具。

        例如,当我们给swap函数传入double类型的参数,编译器就会自动根据模板生成一份参数为double类型的swap函数;而如果我们传入的是int类型的参数,编译器就会生成一份参数为int类型的swap函数。

        由此可见,模板就是将本来应该我们做的重复的事情交给了编译器去做,将手动敲出来的代码变为编译器自动生成。

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

3.4 函数模板的实例化

        当不同类型的参数使用函数模板时,编译器根据模版为这个类型生成一份函数代码,这个过程称为函数模板的实例化。模板参数实例化分为:隐式实例化显式实例化

        隐式实例化

        即编译器根据实参的类型自动推导模版参数的类型:

template<class T>
T Add(const T& left, const T& right)
{return left + right;
}int main()
{double d1 = 2.0;double d2 = 5.0;Add(d1, d2); //d1和d2是double类型的,编译时编译器推导T为double类型,实例化double类型的Add函数int i1 = 0;Add(i1, d2); //此处编译会报错,因为Add只有一个模板参数T,而实参有两种类型,编译器推导时不知道将T推导为int还是doublereturn 0;
}

         可以看到,编译器进行隐式实例化也不是胡乱实例化的,必须要有唯一的结果编译器才会进行实例化,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅。

        要解决上面Add函数的问题,我们有两个解决方法:

        1、用户自行进行强制类型转换

Add(i1, (int)d2); //将两个实参都变为int类型Add((double)i1, d2); //将两个实参都变为double类型

        2、使用下面的显式实例化

        显式实例化

        调用函数时我们可以在函数名后使用<>来指定模板参数的实际类型,如下:

int main()
{int a = 10;double b = 20.0;double c = 30.0;// 显式实例化Add<int>(a, b);  //显式指定T实例化为intAdd<double>(b, c);  //显式指定T实例化为doublereturn 0;
}

        当实例化的函数形参和实参的类型不匹配时,编译器就会尝试进行隐式类型转换,如果无法转换成功编译器将会报错

template<typename T>
void Swap(T& x, T& y)
{T tmp = x;x = y;y = tmp;
}
int main()
{int i = 10;double d = 10.0;Swap<int>(i, d); //尽管我们显式进行实例化了,但这里编译器仍然会报错,原因是形参和实参无法进行隐式类型转换return 0;
}

3.5 模板参数的匹配原则

        1、 一个非模板函数可以和一个同名的函数模板同时存在,并且该函数模板还可以被实例化为这个非模板函数。换句话说,我们既可以使用非模板函数,也可以使用编译器特化的模板函数。

//Swap函数模板
template<class T>
void Swap(T& x, T& y)
{T tmp = x;x = y;y = tmp;
}//处理int类型的Swap函数
void Swap(int& x, int& y)
{int tmp = x;x = y;y = tmp;
}int main()
{int a = 10, b = 20;Swap(a, b); //使用下面专门处理int的Swap函数Swap<int>(a, b); //使用上面编译器特化后的Swap函数return 0;
}

        2、对于非模板函数和同名函数模板,调用时会调用最匹配的那个函数。在其他条件相同的情况下,编译器会优先调用非模板函数。如果模板可以产生一个更匹配的函数,则将选择模板。
 

//Add函数模板
template<class T1,class T2>
T1 Add(T1 x, T2 y)
{return x + y;
}//处理int类型的Add函数
int Add(int x, int y)
{return x + y;
}int main()
{Add(10, 20); //虽然可以通过模板生成<int,int>的模板函数,但编译器并不会去生成,而是去调用已经存在的非模板函数Add(10, 20.0); //通过函数模板生成的函数会更加匹配,故调用函数模版生成的Add函数return 0;
}

         3、模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

//Add函数模板
template<class T>
T Add(T x, T y)
{return x + y;
}int Swap(int x, int y)
{int tmp = x;x = y;y = tmp;
}int main()
{int a = 20;double d = 10.0;Add(d, a); //这里会报错,Add是模板函数,不支持自动类型转换Swap(d, a); //这里编译通过,d会发生隐式类型转换为int类型return 0;
}


四. 类模版

4.1 类模版的定义

        在C++中,除了普通函数支持模版,类也支持模版,我们把这样的类称为类模版。下面给出一个类模版的定义格式:

template<class T1, class T2, ..., class Tn> //模板参数
class 类模板名
{// 类内成员定义
};

        下面我们用vector类来演示一下类模板的定义方法以及注意事项(只是演示噢,并不是真正的vector模拟实现)

//vector类
template<class T>
class Vector
{
public://构造时使用new动态申请空间Vector(int capacity = 10):_p(new T[capacity])_capacity(capacity),_size(0){}void puch_back(const T& x); //尾插void pop_back(); //尾删~Vector(); //析构时需要调用delete释放空间
private:T* _p;int _capacity;int _size;
};//在类模板外定义成员函数,需要加上模版参数列表
template<class T>
Vector<T>::~Vector()
{if (_p != nullptr){delete[] _p;_capacity = _size = 0;}
}

值得注意的是:和模板函数一样,类模板并不是一个具体的类,它只是一个模具,只有进行实例化后才会变成一个具体的类。

4.2 类模版的实例化

        与函数模板实例化不同,类模板不支持隐式类型推导,类模板实例化需要在类模板名字后用<>显式指定模板参数的实际类型。注意:类模板的名字不是真正的类,只有指定类型实例化后才是真正的类。

//模板的实例化
int main()
{//Vector是类名,Vector<int>才是类型名,才能用来定义对象Vector<int> vi(20); //用参数为int的Vector类定义对象Vector<double> vd(10); //用参数为double的Vector类定义对象return 0;
}

        类模版经过类型实例化后得到的类我们称作模板类,通过模板类我们就可以定义许多相应的类对象,三者的关系可用下图表示: 


 以上,就是本期的全部内容啦🌸

制作不易,能否点个赞再走呢🙏

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

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

相关文章

【Unity ShaderGraph】| 制作一个 高级流体水球效果

前言 【Unity ShaderGraph】| 快速制作一个 流体水球效果一、效果展示二、简易流体水球效果三、进阶流体水球效果四、应用实例 前言 本文将使用ShaderGraph制作一个 高级流体水球 &#xff0c;可以直接拿到项目中使用。对ShaderGraph还不了解的小伙伴可以参考这篇文章&#xf…

微服务初始和Nacos安装

一)初始微服务: 微服务是将一个大型的&#xff0c;单一的应用程序拆分成多个小型服务&#xff0c;每一个服务负责于特定的业务功能&#xff0c;并且可以通过网络来和其他服务进行通讯&#xff0c;是一个思想&#xff0c;将一个大的项目拆分成多个小的项目&#xff0c;多个小的项…

Go语言标准输入

文章目录 Go语言标准输入函数使用 Go语言标准输入 函数 Scan // 使用stdin读取内容&#xff0c;读取的内容以空白&#xff08;换行也属于空白&#xff09;分隔&#xff0c;赋值给函数参数。返回读取的个数和错误 func Scan(a ...interface{}) (n int, err error)Scanf // 和…

C++进阶篇3---二叉搜索树(Binary Search Tree)

一、二叉搜索树的概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树: 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值 若它的右子树不为空&#xff0c;则右子树上所有节点的值都大于根节点的值 它的…

基于jquery+html开发的json格式校验工具

json简介 JSON是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。 它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集。 JSON采用完全独立于语言的文本格式&#xff0c;但是也使用了类似于C语言家族…

Django 尝试SSE报错 AssertionError: Hop-by-hop headers not allowed 的分析

情况描述 近期计划测试一下django对日志打印的支持&#xff0c;一般都是用websocket的方式&#xff0c;想测试一下SSE (Server-sent events)的服务端推送&#xff0c;发现过程中存在报错&#xff1a; Traceback (most recent call last):File "D:\Software\Anaconda3\li…

shell中的运算

目录 1.运算符号 2.运算指令 练习 1.运算符号 运算符号意义加法-减法*乘法/除法%除法后的余数**乘方自加一- -自减一<小于<小于等于>大于>大于等于等于ji ->jji*j*i->jj*i/j/i->jj/i%j%i->jj%i 2.运算指令 (()) //((a12))let //let a12 …

[动态规划] (一) LeetCode 1137.第N个泰波那契数

[动态规划] (一) LeetCode 1137.第N个泰波那契数 文章目录 [动态规划] (一) LeetCode 1137.第N个泰波那契数题目解析解题思路状态表示状态转移方程初始化和填表顺序返回值 代码实现总结空间优化代码实现 总结 1137. 第 N 个泰波那契数 题目解析 解题思路 状态表示 (1) 题目要…

正点原子嵌入式linux驱动开发——Linux 串口RS232/485/GPS 驱动

串口是很常用的一个外设&#xff0c;在Linux下通常通过串口和其他设备或传感器进行通信&#xff0c;根据 电平的不同&#xff0c;串口分为TTL和RS232。不管是什么样的接口电平&#xff0c;其驱动程序都是一样的&#xff0c;通过外接RS485这样的芯片就可以将串口转换为RS485信号…

97. 交错字符串

题目链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 解题思路&#xff1a;动态规划。 如果s1.length()s2.length ! s3.length()&#xff0c;直接返回false&#xff0c;否则使用动态规划求解。定义状态&#xff1a;dp[i][i]&#xff…

解决找不到vcruntime140.dll,无法继续执行代码方法

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“找不到vcruntime140.dll”。这个错误通常发生在运行某些程序或游戏时&#xff0c;它会导致程序无法正常启动或运行。那么&#xff0c;找不到vcruntime140.dll&#xff0c;无法继续执行代码…

word中批注内容显示设置

在修改他人word时&#xff0c;有时候保存为pdf需要有选择性的显示&#xff0c;如下图&#xff0c;原始修改方式包括三种&#xff1a;批注、格式修改、删除添加&#xff0c;如下图所示&#xff1a; 有时候不想要格式设置说明&#xff0c;则只需要进行在审阅--显示标志--不勾选设…

【Java 进阶篇】解决Java Web应用中请求参数中文乱码问题

在Java Web应用开发中&#xff0c;处理请求参数时经常会遇到中文乱码的问题。当浏览器向服务器发送包含中文字符的请求参数时&#xff0c;如果不正确处理&#xff0c;可能会导致乱码问题&#xff0c;使得参数无法正确解析和显示。本文将详细探讨Java Web应用中请求参数中文乱码…

qt高精度定时器的使用停止线程应用

##线程停止 //线程停止应用 public: explicit WorkerThread(QObject *parent 0) :QThread(parent), m_bStopped(false){qDebug() << "Worker Thread : " << QThread::currentThreadId();}~WorkerThread(){stop();quit();wait();}void stop() {qDebug()…

共用体开发案例

有若干个人员的数据,其中有学生和教师。学生的数据中包括:姓名、号码性别、职业、班级。教师的数据包括:姓名、号码、性别、职业、职务。要求用同一个表格来处理。 #include <stdio.h>struct Person {char name[32];int age;char zhiYe;char addr[32];union {int class;…

JDBC与MySql数据库

一、系统开发前的环境准备 1.下载Mysql 下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/ 文件解压缩到本地 在此路径下新增my.ini文件以及新建data文件夹 编辑my.ini文件 配置环境变量 注意是编辑系统变量的Path 以管理员身份运行cmd 输入命令&#xff1a…

VDA到Excel方案介绍之自定义邮件接收主题

VDA标准是德国汽车工业协会&#xff08;Verband der Automobilindustrie&#xff0c;简称VDA&#xff09;制定的一系列汽车行业标准。这些标准包括了汽车生产、质量管理、供应链管理、环境保护、安全性能等方面的规范和指南。VDA标准通常被德国和国际上的汽车制造商采用&#x…

【机器学习】sklearn特征值选取与处理

sklearn特征值选取与处理 文章目录 sklearn特征值选取与处理1. 调用数据集与数据集的划分2. 字典特征选取3. 英文文本特征值选取4. 中文特征值选取5. 中文分词文本特征抽取6. TfidfVectorizer特征抽取7. 归一化处理8. 标准化处理9. 过滤低方差特征10. 主成分分析11. 案例&#…

制作自己的前端组件库并上传到npm上

最近实现了自己的一个前端组件库demo&#xff0c;目前只上传了几个小组件。话不多说&#xff0c;上图&#xff1a; 我分了三个项目&#xff1a;yt-ui组件库、使用文档、demo。线上地址如下&#xff1a; [yt-ui组件库](mhfwork/yt-ui - npm) [组件库使用文档](介绍 | mhfwork/y…

docker应用部署---Tomcat的部署配置

1. 搜索tomcat镜像 docker search tomcat2. 拉取tomcat镜像 docker pull tomcat3. 创建容器&#xff0c;设置端口映射、目录映射 # 在/root目录下创建tomcat目录用于存储tomcat数据信息 mkdir ~/tomcat cd ~/tomcatdocker run -id --namec_tomcat \ -p 8080:8080 \ -v $PWD:…