【C++】模板(函数模板与类模板)讲解

news2024/12/23 19:11:27

  

  本篇文章会对C++中的模板进行讲解,其中会对函数模板和类模板进行讲解。希望本篇文章会对你有所帮助。

文章目录

一、函数模板

1、1 模板的引入

1、2 函数模板举例讲解

1、2、1 函数模板的概念

1、2、2 函数模板格式

1、2、3 函数模板实例化

1、2、4 模板参数的匹配原则

二、类模板

2、1 类模板的格式

2、2 类模板的实例化

三、非类型模板参数

四、模板的特化

4、1 模板的特化的概念

4、2 函数模板特化

4、3 类模板特化

4、3、1 全特化

4、3、2 偏特化

模板总结


🙋‍♂️ 作者:@Ggggggtm 🙋‍♂️

👀 专栏:C++ 👀

💥 标题:模板讲解 💥

 ❣️ 寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景 ❣️

一、函数模板

1、1 模板的引入

  在平常写代码中,经常会遇到对两个变量的只进行交换。为了代码的整洁,阅读性高,我们将此功能封装成为函数。但是我们对不同类型的交换,我们就需要写出不同的交换函数。具体代码如下:

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++就引入了 模板。我们接下来看函数模板是什么。

1、2 函数模板举例讲解

1、2、1 函数模板的概念

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

1、2、2 函数模板格式

  只有概念我们并不能很好的理解,我们看一下函数模板的格式,结合着理解一下:

template<typename T>
void Swap( T& left, T& right)
{
 T temp = left;
 left = right;
 right = temp;
}

  函数模板有固定格式的,格式如下:

        template<typename T1, typename T2,......,typename Tn>

        返回值类型 函数名(参数列表){}

  注意:typename是用来定义模板参数关键字也可以使用class(切记:不能使用struct代替class)

1、2、3 函数模板实例化

  函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。

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

  模板参数实例化分为:隐式实例化和显式实例化。我们看看隐式实例化和显式实例化有什么区别。

  • 隐式实例化。让编译器根据实参推演模板参数的实际类型。
    template<class T>
    T Add(const T& left, const T& right)
    {
     return left + right;
    }
    
    int main()
    {
     int a1 = 10, a2 = 20;
     double d1 = 10.0, d2 = 20.0;
     Add(a1, a2);
     Add(d1, d2);
     return 0;
    }
    
  • 显示实例化。在函数名后的<>中指定模板参数的实际类型。
    template<class T>
    T Add(const T& left, const T& right)
    {
     return left + right;
    }
    
    int main(void)
    {
     int a = 10;
     double b = 20.0;
     
     // 显式实例化
     Add<int>(a, b);
     return 0;
    }

  但是隐式实例化是有坑的。我们看如下代码:

template<class T>
T Add(const T& left, const T& right)
{
 return left + right;
}

int main()
{
 int a1 = 10, a2 = 20;
 double d1 = 10.0, d2 = 20.0;
 Add(a1, d1);
 return 0;
}

  上述代码不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型 通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错。

  此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化。

1、2、4 模板参数的匹配原则

  模板参数有如下匹配原则:

  • 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
  • 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。
  • 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。

二、类模板

  类模板与函数模板大致相同。我们这里就讲解类模板的格式和实例化。

2、1 类模板的格式

  类模板允许我们定义一种通用的类,并在编译时生成具体的类代码,以适应不同的类型。类模板的定义同样由关键字"template"、模板参数列表和类的定义组成。我们看一下类模板的格式:

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

template <typename T>
class Stack {
private:
    T* stackArray;
    int top;
    int capacity;
public:
    Stack(int size) {
        capacity = size;
        stackArray = new T[size];
        top = -1;
    }
    void push(T element) {
        stackArray[++top] = element;
    }
    T pop() {
        return stackArray[top--];
    }
};

2、2 类模板的实例化

   类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。具体如下:

Stack<int> intStack(10); // 使用类模板,实例化为Stack<int>
Stack<double> doubleStack(10); // 使用类模板,实例化为Stack<double>

三、非类型模板参数

  模板参数分类:类型形参与非类型形参

  类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。

  非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

  具体我们看如下例子:


 // 定义一个模板类型的静态数组
 template<class T, size_t N = 10>
 class array
 {
 public:
 T& operator[](size_t index){return _array[index];}
 const T& operator[](size_t index)const{return _array[index];}
 
 size_t size()const{return _size;}
 bool empty()const{return 0 == _size;}
 
 private:
 T _array[N];
 size_t _size;
 };
注意:
  1.  浮点数、类对象以及字符串是不允许作为非类型模板参数的非类型模板参数只能是整数。
  2.  非类型的模板参数必须在编译期就能确认结果

四、模板的特化

4、1 模板的特化的概念

   通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板,代码如下:

template<class T>
bool Less(T left, T right)
{
 return left < right;
}
int main()
{
 cout << Less(1, 2) << endl; // 可以比较,结果正确
 Date d1(2022, 7, 7);
 Date d2(2022, 7, 8);
 cout << Less(d1, d2) << endl; // 可以比较,结果正确
 Date* p1 = &d1;
 Date* p2 = &d2;
 cout << Less(p1, p2) << endl; // 可以比较,结果错误
 return 0;
}

  注:上述代码中的 Date 为自定义类型。此处就不再给出自定义类型 Date 的代码,需要的可以找我要。我们在这里理解意思即可。

  可以看到,Less绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示例中,p1指向的d1显然小于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内容,而比较的是p1和p2指针的地址,这就无法达到预期而错误。
  此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化类模板特化。

4、2 函数模板特化

  针对上述问题,我们这里引出函数模板的特化。函数模板的特化步骤如下:

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

   我们结合4、1 中的例子,对其函数进行特化。代码如下:

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
 return left < right;
}
// 对Less函数模板进行特化
template<>
bool Less<Date*>(Date* left, Date* right)
{
 return *left < *right;
}
int main()
{
 cout << Less(1, 2) << endl;
 Date d1(2022, 7, 7);
 Date d2(2022, 7, 8);
 cout << Less(d1, d2) << endl;
 Date* p1 = &d1;
 Date* p2 = &d2;
 cout << Less(p1, p2) << endl; // 调用特化之后的版本,而不走模板生成了
 return 0;
}

  其实特化有固定格式,如下:

template <typename T>
class TemplateClass {
  // 通用实现
};

// 对于特定类型int的完全特化
template <>
class TemplateClass<int> {
  // int类型的特定实现
};

  上述的特化类型不仅仅是 int 型,可以是任意类型。但是需要注意的是,一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。

4、3 类模板特化

  类模板的特化又分为全特化和偏特化。我们看看它们之间有什么区别。

4、3、1 全特化

  全特化即是将模板参数列表中所有的参数都确定化。具体实例如下:

template<class T1, class T2>
class Data
{
public:
 Data() {cout<<"Data<T1, T2>" <<endl;}
private:
 T1 _d1;
 T2 _d2;
};

//全特化
template<>
class Data<int, char>
{
public:
 Data() {cout<<"Data<int, char>" <<endl;}
private:
 int _d1;
 char _d2;
};

4、3、2 偏特化

  偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类:

template<class T1, class T2>
class Data
{
public:
 Data() {cout<<"Data<T1, T2>" <<endl;}
private:
 T1 _d1;
 T2 _d2;
};

  偏特化有以下两种表现方式:
  • 部分特化将。                                                                                                                  模板参数类表中的一部分参数特化。
    // 将第二个参数特化为int
    template <class T1>
    class Data<T1, int>
    {
    public:
     Data() {cout<<"Data<T1, int>" <<endl;}
    private:
     T1 _d1;
     int _d2;
    };
  • 参数更进一步的限制。
    偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。   

模板总结

  模板是C++中强大的泛型编程(编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础)工具,可以用于函数和类。通过使用模板,我们可以编写出更通用、灵活的代码,以适应不同类型的需求。 

  模板是一个很好的工具,但是再有优点的同时,也有缺点:

【优点】
  1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生;
  2. 增强了代码的灵活性。
【缺陷】
  1. 模板会导致代码膨胀问题,也会导致编译时间变长;
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误。

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

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

相关文章

2023下半年软考高级系统架构设计师怎么报名?

软考高级系统架构设计师报名时间&#xff1a; 广西2023下半年软考高级系统架构设计师报名时间&#xff1a;8月15日8:00至8月24日17:00 广东2023下半年软考高级系统架构设计师报名时间&#xff1a;8月16日9:00-8月24日17:00 甘肃2023下半年软考高级系统架构设计师报名时间&am…

恢复idea删除的git本地文件

idea中删除git本地文件无法远程拉取pull已删除文件的问题 当前本地库处于另一个分支中&#xff0c;需将本分支Head重置&#xff0c;git 强行pull并覆盖本地文件 解决方式一&#xff1a; git fetch --all git reset --hard origin/master git pull解决方式二&#xff1a; git…

Docker基础——基础详解

仓库&#xff0c;镜像&#xff0c;容器的关系 Docker镜像 当运行容器时&#xff0c;使用的镜像如果在本地中不存在&#xff0c;docker 就会自动从 docker 镜像仓库中下载&#xff0c;默认是从 Docker Hub 公共镜像源下载 镜像列表 可以使用 docker images 来列出本地主机上的…

vue3,elementPlus和自己封装,点击 新增添加表单,删除表单,提交数据

ElementPlus下的form也有新增表单 如果你写H5等没找到合适的 自己也可以进行封装 实现3个代码讲解:1&#xff1a;ElementPlus的代码 2&#xff1a;自己书写的代码 3&#xff1a;自己把2的代码进行封装 1&#xff1a;ElementPlus的运行效果 点击提交 1&#xff1a;ElementPlus…

jieba和WordCloud

词云图像 import wordcloud import jieba import matplotlib.pyplot as plttext"中华人民共和国是伟大的国家,我最敬爱的祖国啊&#xff0c;你是美丽的花园" textjieba.lcut_for_search(text) text" ".join(text) wwordcloud.WordCloud(width1000,height8…

05 Docker 安装常用软件 (mongoDB)

目录 1. mongoDB简介 1.1 mongodb的优势 2. mongodb的安装 2.1 创建数据文件夹 2.2 备份日志 2.3 配置文件夹 2.4 创建两个文件 ---> 2.4.1 配置如下: 2.5 拉取mongodb 2.6 运行容器 2.7 进入mongodb容器 ---> 2.7.0 高版本(6.0)以上是这样的 , 旧版的没研究 …

我爱学QT--qt的网络编程

学习地址&#xff1a; QT网络编程之TCP通信_哔哩哔哩_bilibili QT网络编程有TCP和UDP。 TCP编程需要用到两个类&#xff1a;QTcpServer和QTcpSocket 本节课目标&#xff1a; 完成一个服务器和一个客户端 首先是经典的几步 先设计ui再设计逻辑实现 多看看写的文件理解吧

10.5.2 【Linux】命令执行的判断依据, ,||

cmd ; cmd &#xff08;不考虑指令相关性的连续指令下达&#xff09; 在某些时候&#xff0c;我们希望可以一次执行多个指令&#xff0c;例如在关机的时候我希望可以先执行两次sync 同步化写入磁盘后才 shutdown 计算机&#xff0c;那么可以这样作&#xff1a; [rootstudy ~]…

【信号去噪和分类】基于小波的隐马尔可夫模型统计信号处理(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

java学习路程之篇二、知识点、配置JAVA_HOME、跨平台、JVM、JRE、JDK

文章目录 1、Java背景介绍2、Java跨平台性3、JDK的下载和安装4、第一个Java程序5、HelloWorld案例详解6、JVM、JRE和JDK7、配置JAVA_HOME 1、Java背景介绍 2、Java跨平台性 3、JDK的下载和安装 4、第一个Java程序 5、HelloWorld案例详解 6、JVM、JRE和JDK 7、配置JAVA_HOME

MySQL表的约束与内置函数

目录 MySQL表的约束 空属性 默认值 列描述comment zerofill 主键 复合主键 自增长 唯一键 外键 MySQL的内置函数 日期函数 字符串函数 ​编辑数学函数 其他函数 MySQL表的约束 MySQL表的约束是用于规定表中数据的完整性和一致性的规则。 约束可以在创建表时定义&…

使用.LayUI实现动态选项卡Tab的强大功能

标题 正文&#xff1a;1.引入.LayUI框架文件2.创建选项卡容器3.初始化.LayUI组件和创建选项卡项以及获取数据准备3.1 Permission实体类3.2 PermissionDao类3.3 TreeVo类3.4 PermissionAction类 4.监听选项卡的切换事件5.运行和测试6.总结&#xff1a; 正文&#xff1a; 在Web界…

【导航地图DB】

地图DB概述 车载导航地图DB 主要侧重道路交通网&#xff0c;并从实用性、加快检索速度和减少数据容量的角度来考虑&#xff0c;有自己特有格式的电子地图。 含有空间位置地理坐标&#xff0c;能够与空间定位系统结合&#xff0c;准确引导人或交通工具从出发地到达目的地的电子…

ylb-接口10用户登录

总览&#xff1a; 在api模块下的service包&#xff0c;补充用户接口&#xff08;UserService&#xff09;&#xff1a;用户登录 package com.bjpowernode.api.service;import com.bjpowernode.api.model.User; import com.bjpowernode.api.pojo.UserAccountInfo;public inte…

运维小知识(三)——BIOS密码忘记怎么办(一键清除)

&#x1f349;&#x1f349;问题 为了防止恶意更改BIOS程序&#xff0c;一些人会设置BIOS密码&#xff0c;但这个密码若不常用的话会被人遗忘&#xff0c;或者一台旧电脑&#xff0c;需要重装系统&#xff0c;没有BIOS密码怎么办&#xff1f;有没有童靴遇到过&#xff0c;网上找…

bash脚本文件windows上的和linux上格式不同问题

脚本执行时报&#xff1a; syntax error: unexpected end of file&#xff0c;由于脚本是给定的。之前没有考虑这个问题&#xff0c;后查阅资料发现问题如下&#xff1a; 可能发生场景&#xff1a;windows环境编辑/生成的shell脚本&#xff0c;在linux系统上运行时报错 编辑和…

备战秋招009(20230714)

文章目录 前言一、Java内存区域1、JVM组成部分2、运行时数据区域01、基础02、程序计数器03、虚拟机栈04、本地方法栈05、堆06、方法区07、直接内存 3、HotSpot虚拟机对象01、对象的创建02、内存分配03、内存布局04、访问定位 二、垃圾回收1、堆空间01、空间结构02、GC 分类03、…

linux之vscod编译源代码

vscode想要编译c/c程序 在本地的linux上首先要安装gcc与g编译器&#xff0c;因为这个后面会写到配置文件里面去 先在这个软件里面安装上c/c的扩展插件&#xff0c;然后当我们开始编译c/c程序的时候 会自动生成一个.vscode目录 然后先来看一个配置文件launch.json的配置文件…

【观察】杉数科技:释放智能决策“乘数效应”,驱动智能制造高质量增长

毫无疑问&#xff0c;中国制造业在时代背景与国家政策的双重驱动下&#xff0c;正加速向数智化转型&#xff0c;而以数据驱动的智能决策也正成为制造业资源优化配置的“利器”&#xff0c;通过端到端的数据深度感知与决策优化&#xff0c;显然能够将工业和制造业的数据价值发挥…

波奇学Linux: sudo提权,gcc编译,动静态链接,debug版本

sudo给普通用户提权 在root用户下输入 vim /etc/sudoers 添加白名单&#xff0c;名单上的用户可以用root权限运行 gcc和g编译c和c文件 g可以编译c和c文件 gcc只能编译c文件 ./a.out 执行a.out可执行文件 通过g编译器展示预处理->编译->汇编->链接的文件 预处理&am…