C++深入学习之模板

为什么需要模板

先来看下面一段程序:

int add(int x, int y)
{return x + y;
}double add(double x, double y)
{return x + y;
}long add(long x, long y)
{return x + y;
}string add(string x, string y)
{return x + y;
}//T1 = T2 = T3
T3 add(T1 x, T2 y)
{return x + y;
}

可以看到重复率很高,代码很冗余。如果此时可以有一种通用的符号可以代替上述各种类型的话,那这么多的函数我们不就可以用一个函数来代替了吗?

这就是模板出现的原因,它的出现可以简化代码,让程序员少写代码。

还可以解决严格性与灵活性的冲突:

这是因为C++是强类型语言,其声明变量时需要有严格的类型声明,如 int a = 10;
这很严格,却丧失了一种灵活性(如上面说的多个不同类型变量是否可以通过一套模板来完成的例子)。

模板语法形式

//例子:函数模板
template <模板参数列表>
函数的返回类型 函数名字(函数的参数列表)
{}//模板的第一种声明形式
template <typename T1, typename T2...>
//模板的第二种声明形式
template <class T1, class T2...>
//注意:模板参数列表中typename与class的含义是完全一样(除非用的编译器是2003年以前的,那么typename可能编译器会识别不了)

模板类型

模板就两种类型:函数模板与类模板;

在刚刚的 模板语法形式 一节中提到的例子就是函数模板的形式。

函数模板

template<typename T>
T add(T x,T y){}

add函数是前文所提到的例子,使用该模板技术之后,我们的 T 类型就可以用来代替上述一大串add函数中的各种类型啦。

函数模板示例 以及 实例化、特化概念

#include <cstddef>
#include <iostream>
#include <string>
#include <string.h>
using namespace std;//函数模板示例
//template<typename T>//模板参数列表
template<class T> //使用class的效果和typename是一样的
T add(T x, T y){cout << "T add(T , T) " << endl;return x+y;
}//函数模板与函数模板之间也是可以进行重载的
template<class T> //使用class的效果和typename是一样的
T add(T x, T y, T z){cout << "T add(T , T, T) " << endl;return x+y+z;
}//当我们发现有些时候我们的函数模板并不适用
//需要将函数特例化写出来的时候,就需要采用下面这种形式
//表示这个函数是我们函数模板的一个特化
//模板特化又分为模板的全特化与偏特化(部分特化)
//   全特化:将模板的参数列表中的参数全部以特殊版本的形式写出来(如下面的const char* add函数);
//   偏特化(部分特化):将模板参数列表中的参数类型,至少有一个没有特化出来
template<>
const char* add(const char* ps1,const char* ps2){cout << "const char* add(const char*,const char*)" << endl;size_t len1 = strlen(ps1);size_t len2 = strlen(ps2);size_t len = len1+len2+1;char* pstr = new char[len]();strcpy(pstr,ps1);strcat(pstr,ps2);return pstr;
}//上面的函数模板在 经过模板参数列表的推导之后 成为下面的函数,被称为模板函数
//也可以说是从抽象到具象的一种 实例化
//实例化可被区分为 隐式实例化和显式实例化
int add(int x,int y){cout << "int ad(int , int) " << endl;return x+y;
}/*不难发现上面两个函数(add(int x,int y) 与 add(T x, T y))之间发生了重载关系* 即普通函数与函数模板之间可以进行重载* 经测试可以发现 普通函数 是优先于 函数模板 被调用的* */void test(){int ia = 3, ib = 4, ic = 5;double da = 3.3, db = 8.8;string s1 = "hello",s2 = "world";//add(ia,ib)这句代码我们并未显式声明ia和ib的类型,是靠编译器进行隐式推导出来的//因此这是一种隐式实例化cout << "add(ia,ib) = " << add(ia,ib) << endl;//而add<double>(da,db)这句代码我们显式声明了da和db的类型//因此这是一种显式实例化cout << "add(da,db) = " << add<double>(da,db) << endl;cout << "add(s1,s2) = " << add(s1,s2) << endl;cout << "add(ia,ib,ic) = " << add(ia,ib,ic) << endl;//模板的特化示例const char* str1 = "hebei";const char* str2 = "wuhan";//如果不进行模板的特化,下面这行代码将报错,因为两个const char*无法进行相加cout << "add(str1,str2) = "  <<add(str1,str2) << endl;
}int main(){test();return 0;
}

模板参数列表参数类型的剖析

在这里插入图片描述

函数模板被分为 头文件 与 实现文件 的情况分析

问题研究的就是把函数模板的声明写到头文件中,把其实现写到另外一个文件里去,然后在测试文件里面进行测试一下来分析这种情况。

头文件中声明函数模板:
在这里插入图片描述
在实现文件中实现该函数模板:
在这里插入图片描述
测试文件中进行测试:
在这里插入图片描述
编译运行:
在这里插入图片描述
可以发现在函数模板声明与实现分文件存放时编译会出现问题,编译器找不到经过模板参数列表推导后的模板函数的定义。

所以得出一条重要结论:

对于模板而言,不能将头文件与实现文件分开(不能将声明与实现分开,否则会报错)。

但是如果非要将函数模板的头文件与实现文件进行分开编写的话,可以在头文件中去include实现文件:
在这里插入图片描述
注意要删去实现文件中的头文件引入嗷,否则会报重定义的问题:
在这里插入图片描述
编译运行,此时就没有问题了:
在这里插入图片描述
这与inline内敛函数是类似的。

成员函数的函数模板

上面聊的都是非类中成员函数的普通函数模板,现在我们来聊聊类中成员函数的函数模板。
直接看代码示例以及注释解析:

#include <iostream>using namespace std;class Point{public:Point(double dx = 0.0,double dy = 0.0):_dx(dx),_dy(dy){cout << "Point(double =0.0,double =0.0)" << endl;}//成员函数也是可以设置为模板形式的//给模板参数列表设置默认参数longtemplate<typename T=long>T func(){return _dx;}~Point(){cout << "~Point()" << endl;}private:double _dx;double _dy;
};void test(){Point pt(1,2);//调用时通过<>传递给func函数的模板参数列表告知其T类型为int//或者也开以给模板参数列表设置默认类型参数//这样才能正常调用cout << "pt.func() = " << pt.func<int>();
}int main(){test();return 0;
}

可变模板参数 – C++11新特性

可变模板参数是C++11新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数。

基本形式:

template<typename ...Args>//这里的Args被称为模板参数包,表示这里面有N个typename参数void func(Args...args){ //这里的args被称为函数参数包,功能同上}

代码示例:

#include <iostream>using namespace std;//可变模板参数,传0到任意个参数,参数类型与个数都不确定
//...后面不一定要写Args,只是习惯上那么写
template <typename ...T> //T是模板参数包
void print(T ...t){ //t是函数参数包//打印模板参数个数和函数参数个数cout << "sizeof...(T) = " << sizeof...(T) << endl;cout << "sizeof...(t) = " << sizeof...(t) << endl;
}//上面的show方法使用到了递归却没有退出条件
//因此我们要手动给一个空的退出条件
void show(){cout << endl;
}//打印参数包中的数据的打印方法
template <typename T,typename ...Args>
void show(T t,Args ...args){cout << t << " ";//相当于...在args前面时是一个打包参数的过程,即打包操作//...在args后面时则变成了一个拆解参数的过程,即解包操作show(args...);//递归遍历
}void test(){//通过下面的调用可以发现//此时我们可以传递任意多的参数进去print();print(1,"hello");//int stringprint(1,true,3.3,"helloworld");//int bool double string
}void test2(){show(1,3.3);//递归//调用过程://1、第一次调用show时,1是第一个参数,所以被打印,然后后面一坨被打包进下一个show函数中当作参数//    cout << 1 << " ";//    show(3.3);//2、第二次调用时,3.3是第一个参数,所以被打印,后面已经没有参数了,所以调用了空参的show//    cout << 3.3 << " ";//    show();//3、第三次调用时,因为没有参数了,而函数模板的第一个参数是必须要有的,所以这里的show因为//   没有参数于是只能去调用无参的同名show函数//   cout << endl;//   最终打印换行结束递归}int main(){test2();return 0;
}

类模板

形式如下:

template<typename T>
class Stack{private:T* data;
};

类模板示例

其实基本和函数模板没有什么太大分别,只要前面的函数模板研究清楚基本上就没有问题,一点点小的区别直接看代码示例即可:

#include <iostream>using namespace std;//类模板
template<typename T,size_t kSize = 10>
class Example{public:Example():_data(new T[kSize]()){cout << "Example()" << endl;}//析构函数我们选择在类外进行实现~Example();
private:T* _data;
};//类模板和函数模板的用法基本没有区别,只要注意一下一个点即可
//就是在类外进行成员函数实现时,必须要声明模板嗷
//因为Example是一个类模板,属于一种抽象类型,T是不确定的
//所以我们在类外写其函数实现时也要带上模板参数列表
template<typename T,size_t kSize>
Example<T,kSize>::~Example(){if(_data){delete[] _data;_data = nullptr;}
}void test(){Example<int,20> example;
}int main(){test();return 0;
}

模板的嵌套

类模板与类模板之间可以嵌套,类模板与函数模板之间也可以嵌套:

template<typename T> //类模板
class A{template<typename K> //类模板class B{template<typename ...Args> //函数模板T func(Args ...args){}}
}

模板也可以作参数

就是将模板作为参数放入模板参数列表中:

template<template<class T1> class T2, class T3,int num>

模板注意事项

成员函数模板不能被设计为virtual

成员函数模板不能声明为virtual的原因主要是因为C++中的虚函数机制和模板机制在实现上有一些根本性的差异。

首先,理解一下虚函数的工作原理。在C++中,虚函数用于支持动态绑定,使得在基类中声明的虚函数在派生类中可以被重写。当通过基类指针或引用调用一个虚函数时,运行时系统会根据对象的实际类型确定要调用的函数版本。

然而,模板并不是在编译时实例化,而是在运行时根据实际参数类型进行实例化。这意味着模板实例化的代码通常在编译时就确定了,而不是在运行时。

由于虚函数和模板的这种根本性差异,将成员函数模板声明为virtual会导致一些问题。例如,当你在派生类中为成员函数模板提供一个新的实现时,由于虚函数的动态绑定特性,你可能会覆盖基类中的实现,而不是添加一个新的实现。这显然不是你想要的结果。

因此,为了避免这种混淆和潜在的错误,C++标准规定成员函数模板不能被声明为virtual。如果你需要为不同的类型提供不同的行为,你应该使用模板特化和条件编译来实现,而不是尝试将模板和虚函数结合使用。

小结

模板是C++引入的新特性,也是标准模板库STL的基础,模板有函数模板和类模板之分,两种应用有很多相似之处。
学习模板,最重要的是理解模板定义(函数模板定义、类模板定义)与具体定义(函数定义和类定义)的不同,模板不是定义,要通过实例化(通过模板)或者特化(避开模板)来生成具体的函数或者类定义,再调用函数或者创建类的对象。

模板支持嵌套,这就是说可以在一个模板里面定义另一个模板。以模板(类,或者函数)作为另一个模板(类,或者函数)的成员,也称为成员模板。同时,模板也可以作为另一个模板的参数,出现在类型参数表中。

模板的使用在日常工作当中使用较少,除非是专门做一些库组件开发的会用的比较多,掌握本文的内容应付日常的工作学习已经足够(若想把所有的模板内容全部学会的话那比C++语言本身都还要繁杂),因此能够在看一些开源代码时知道其代码用到模板的时候在干嘛即可,比如每个C++er都会学习的 STL 的源码就应用了大量的模板相关的知识。

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

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

相关文章

基于OpenCV的图像缩放

基础概念 缩放是将图像的尺寸变小或变大的过程&#xff0c;即减少或增加原图像数据的像素个数&#xff0c;或者说通过增加或删除像素点来改变图像的尺寸&#xff1b; 基本原理&#xff1a;将分辨率&#xff08;图片尺寸&#xff09;为(w,h)的图像&#xff0c;缩放后其图像分辨…

jvm虚拟机初识

JVM Java虚拟机就是二进制字节码的运行环境&#xff0c;负责装载字节码到其内部&#xff0c;解释/编译为对应平台上的机器指令执行。每一条Java指令&#xff0c;Java虚拟机规范中都有详细定义&#xff0c;如怎么取操作数&#xff0c;怎么处理操作数&#xff0c;处理结果放在哪…

服务器监控软件夜莺使用(二)

文章目录 一、采集器安装1. Categraf简介2. Categraf部署3. 测试服务器部署4. 系统监控插件5. 显卡监控插件6. 服务监控插件 二、监控仪表盘1. 机器列表2. 系统监控3. 服务监控 三、告警配置1. 邮件通知2. 告警规则3. 告警自愈 一、采集器安装 1. Categraf简介 Categraf 需要…

聚道云软件连接器助力某贸易公司实现付款流程自动化

客户介绍&#xff1a; 某贸易公司是一家集进出口贸易、国内贸易、电子商务等业务于一体的综合性贸易企业。公司业务遍及全球多个国家和地区&#xff0c;拥有庞大的供应商网络和采购需求。 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 客户痛点&#…

CAN转RS232学习笔记

2024-1-9 用keil打开工程后&#xff0c;打开main.c文件 报错&#xff1a;error in include chian 网络解决方法&#xff1a; KEIL消除警告&#xff1a;error in include chain(cmsis_armcc.h):expected identifier or ‘(‘-CSDN博客 上文链接包含的链接&#xff08;套娃&am…

前端JS加密对抗由浅入深-2

前言&#xff1a; 本文主要讲解&#xff0c;针对前端非对称、多段加密数据传输站点&#xff0c;如何进行动态调试&#xff0c;如何进行安全测试。本次讲解不涉及任何漏洞方面&#xff0c;仅为学习探讨&#xff0c;该站点现已经更改加密方式&#xff0c;严禁非法测试&#xff0…

如何使用内网穿透实现iStoreOS软路由公网远程访问局域网电脑桌面

文章目录 简介一、配置远程桌面公网地址二、家中使用永久固定地址 访问公司电脑**具体操作方法是&#xff1a;** 简介 软路由是PC的硬件加上路由系统来实现路由器的功能&#xff0c;也可以说是使用软件达成路由功能的路由器。 使用软路由控制局域网内计算机的好处&#xff1a…

一文弄懂SpringCloud Stream

目录 SpringCloud StreamSpringCloud Stream相关概念SpringCloud Stream使用 SpringCloud Stream Spring Cloud Stream 是一个构建消息驱动微服务的框架&#xff0c;Spring Cloud Stream 提供了一个抽象层&#xff0c;屏蔽了不同消息中间件之间的差异&#xff0c;使得开发人员…

Java学习笔记-day05-响应式编程初探-自定义实现Reactive Streams规范

最近在学响应式编程&#xff0c;这里先记录下&#xff0c;响应式编程的一些基础内容 1.名词解释 Reactive Streams、Reactor、WebFlux以及响应式编程之间存在密切的关系&#xff0c;它们共同构成了在Java生态系统中处理异步和响应式编程的一系列工具和框架。 Reactive Streams…

3D人体姿态估计

3D人体姿态估计是指通过算法对输入的图像或视频进行分析&#xff0c;推断出人体的三维姿态信息。该技术可以应用于许多领域&#xff0c;如虚拟现实、运动分析、人机交互等。 1. 算法原理&#xff1a; 3D人体姿态估计利用深度学习模型作为算法的核心&#xff0c;通过网络学习人…

html js加载本地文件报错处理,跨域问题

这个问题是怎么来的&#xff1f;我写了一个本地html文件&#xff0c;里面通过three.js加载并显示一个本地三维模型&#xff0c;结果报错了。 报错如下&#xff1a; Access to XMLHttpRequest at file:///C:/model/quater.mtl from origin null has been blocked by CORS poli…

是面试官放水,还是公司实在是太缺人?这都没挂,字节原来这么容易进....

“字节是大企业&#xff0c;是不是很难进去啊&#xff1f;” “在字节做软件测试&#xff0c;能得到很好的发展吗&#xff1f; 一进去就有11.5K&#xff0c;其实也没有想的那么难” 直到现在&#xff0c;心情都还是无比激动&#xff01; 本人211非科班&#xff0c;之前在字节和…

uni-app发版及分包要求

uni-app发版及分包要求 发版 注意&#xff0c;小程序的接口不允许http&#xff0c;只支持https。仅仅是https还不够&#xff0c;正式版和体验版上的接口功能实现还需要将接口地址添加到开发管理——开发设置——服务器域名——request合法域名中去。否则&#xff0c;手机预览…

Spark---RDD(双值类型转换算子)

文章目录 1.RDD双值类型算子1.1 intersection1.2 union1.3 subtract1.4 zip 1.RDD双值类型算子 RDD双Value算子就是对两个RDD进行操作或行动&#xff0c;生成一个新的RDD。 1.1 intersection 对源 RDD 和参数 RDD 求交集后返回一个新的 RDD 函数定义&#xff1a; def inters…

解读 Sobit v2:铭文资产跨链更注重安全、易用性

铭文市场的发展正在从早期的“无序”进入到“有序”阶段&#xff0c;我们看到从 12 月份以来&#xff0c;比特币生态内的多个应用纷纷宣布获得融资。这表明&#xff0c;目前仍旧有大量的资金有意向铭文领域&#xff0c;同样铭文赛道新一轮浪潮或许正在酝酿。 另一方面&#xff…

【设计模式-01】Singleton单利模式

一、方式1(最常用&#xff0c;推荐使用) 单例实现方式一: 饿汉式 类加载到内存后&#xff0c;就实例化一个单例&#xff0c;JVM保证线程安全 简单实用&#xff0c;推荐使用。 唯一缺点: 不管用到与否&#xff0c;类装载时就完成加载。 /*** description: 单例实现方式一: 饿汉…

Java 求2个整数,3个整数 的 10等分比例值

10等份取整比 比如 1.5 &#xff1a; 4 &#xff1a; 4.5 会变成 1&#xff1a;4&#xff1a;5 &#xff0c;当然小数后一位的四舍五入是向上还是向下去整&#xff0c;这个根据自己需要调整即可。 代码 &#xff1a; public static Integer getIntTenPerNum(Integer nu…

YOLOv8改进 | Neck篇 | 利用ASF-YOLO改进特征融合层(适用于分割和目标检测)

一、本文介绍 本文给大家带来的改进机制是ASF-YOLO(发布于2023.12月份的最新机制),其是特别设计用于细胞实例分割。这个模型通过结合空间和尺度特征,提高了在处理细胞图像时的准确性和速度。在实验中,ASF-YOLO在2018年数据科学竞赛数据集上取得了卓越的分割准确性和速度,…

Java项目:115SSM宿舍管理系统

博主主页&#xff1a;Java旅途 简介&#xff1a;分享计算机知识、学习路线、系统源码及教程 文末获取源码 一、项目介绍 宿舍管理系统基于SpringSpringMVCMybatis开发&#xff0c;系统主要功能如下&#xff1a; 学生管理班级管理宿舍管理卫生管理维修登记访客管理 二、技术框…

网络安全新形势下的动态防御体系研究(上)

文章目录 前言一、网络安全的趋势二、网络安全背景&#xff08;一&#xff09;整体形势对网络安全防护提出新挑战&#xff08;二&#xff09;发展对网络安全防护提出新目标 三、网络安全现状分析&#xff08;一&#xff09;国外网络安全现状分析&#xff08;二&#xff09;国内…