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

  

  本篇文章会对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.mfbz.cn/a/37344.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

栈--C语言实现数据结构

本期带大家一起用C语言实现栈&#x1f308;&#x1f308;&#x1f308; 一、栈的概念&#x1f30e; 栈是一种常见的数据结构&#xff0c;它遵循后进先出&#xff08;Last In, First Out&#xff09;的原则。可以将其类比为现实生活中的一摞书或者一叠盘子。 栈由一个连续的内…

Mac环境下安装nginx并本地部署项目

1、前提 必须安装了homebrew&#xff0c;可在终端输入命令brew -v查看是否已经安装&#xff0c;如果输入指令出现版本号说明已经安装成功 如果未安装先安装&#xff08;homebrew官网地址&#xff09; /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/H…

神经网络之VGG

目录 1.VGG的简单介绍 1.2结构图 3.参考代码 VGGNet-16 架构&#xff1a;完整指南 |卡格尔 (kaggle.com) 1.VGG的简单介绍 经典卷积神经网络的基本组成部分是下面的这个序列&#xff1a; 带填充以保持分辨率的卷积层&#xff1b; 非线性激活函数&#xff0c;如ReLU&a…

1、Redis入门与应用

Redis入门与应用 Redis的技术全景 Redis一个开源的基于键值对&#xff08;Key-Value&#xff09;NoSQL数据库。使用ANSI C语言编写、支持网络、基于内存但支持持久化。性能优秀&#xff0c;并提供多种语言的API。 我们要首先理解一点&#xff0c;我们把Redis称为KV数据库&am…

优化SQL查询实现高效数据检索(一)

大家好&#xff0c;SQL&#xff08;结构化查询语言&#xff09;可以帮助大家从数据库中收集数据&#xff0c;它是专为此而设计的&#xff0c;换句话说&#xff0c;它使用行和列来处理数据&#xff0c;让使用者能够使用SQL查询来操作数据库中的数据。 SQL查询 SQL查询是一系列…

Nginx Linux安装

参考 : http://test.runoob.com/w3cnote/nginx-install-and-config.html 点击跳转 下载安装包 - 这里选择的是 nginx-1.6.3 pgp 网址 : http://nginx.org/en/download.html 点击跳转 2. 上传Linux - 这里新建了临时文件夹 mkdir /usr/local/tmp 3. 解压 tar -zxvf nginx-1.6.…

Springcloud基础(4)-Ribbon负载均衡

负载均衡 1. Ribbon简单描述2. 在SpringCloud中查看相关处理源码3. ribbon的默认策略&#xff0c;懒加载3. 实操中的相关问题 1. Ribbon简单描述 Spring Cloud Ribbon 是一套基于 Netflix Ribbon 实现的客户端负载均衡和服务调用工具。Ribbon是Netflix发布的开源项目&#xff0…

手机快充协议

高通:QC2.0、QC3.0、QC3.5、QC4.0、QC5.0、 FCP、SCP、AFC、SFCP、 MTKPE1.1/PE2.0/PE3.0、TYPEC、PD2.0、PD3.0/3.1、VOOC 支持 PD3.0/PD2.0 支持 QC3.0/QC2.0 支持 AFC 支持 FCP 支持 PE2.0/PE1.1 联发科的PE&#xff08;Pump Express&#xff09;/PE 支持 SFCP 在PP…

火车头小发猫AI伪原创[php源码]

对于大多数站长来说&#xff0c;有点困难&#xff0c;但是如果他们不知道如何原创&#xff0c;我们不知道如何伪原创吗&#xff1f;我把我常用的伪原创的方法列出来&#xff0c;希望对大家有所帮助。 使用教程&#xff1a;火车头采集器AI伪原创 <?php header("Conte…

【面试】Hbase

逻辑模型 1 NameSpace 命名空间&#xff0c;类似于关系型数据库的database概念&#xff0c;每个命名空间下有多个表。Hbase有两个自带的命名空间,分别是hbase和default, hbase中存放的是HBase内置的表, default表是用户默认使用的命名空间。 2 Region 类似于关系型数据库的表…

资深测试整理,APP专项测试方法总结,看这篇就够了...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 APP专项测试 1、…

Python启动TCP服务并监听连接,从客户端发送消息

下面是一个简单的例子&#xff0c;演示如何在Python中启动TCP服务并监听连接&#xff0c;以及如何从客户端发送消息&#xff1a; TCP服务端代码&#xff1a; import socketHOST 192.168.6.211 PORT 8888server_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM) …

【QT】QT搭建OpenCV环境

QT/OpenCV 01、开始之前02、QT03、CMake04、OpenCV05、配置06、测试 01、开始之前 本文版本&#xff1a; 1、QT&#xff1a;Based on Qt 5.12.2 (MSVC 2017, 32 bit)&#xff0c;编译方式是MinGW 2、CMake&#xff1a;cmake-3.27.0-rc4-windows-x86_64.msi 3、OpenCV&#xff1…

深度学习——优化器Optimizer

代码以及详细注释&#xff1a; import torch import torch.utils.data as Data import torch.nn.functional as F import matplotlib.pyplot as plt# torch.manual_seed(1) # reproducible """超参数 """ # 学习率 LR 0.01 # 批大小 BATCH_…

什么是RPC并实现一个简单的RPC

1. 基本的RPC模型 主要介绍RPC是什么&#xff0c;基本的RPC代码&#xff0c;RPC与REST的区别&#xff0c;gRPC的使用 1.1 基本概念 RPC&#xff08;Remote Procedure Call&#xff09;远程过程调用&#xff0c;简单的理解是一个节点请求另一个节点提供的服务本地过程调用&am…

详解Jenkins配置邮件通知

前言 这几天Darren洋在使用Jenkins定时构建jmeter脚本中&#xff0c;要用到邮箱配置&#xff0c;故记录之。 一、Jenkins默认邮箱通知 这里填好smtp服务器地址和邮箱后缀&#xff0c;这样下面的账号就不用加邮箱后缀了。 网易邮箱设置以下我就不说废话文学了&#xff0c;直接上…

智能优化算法——哈里鹰算法(Matlab实现)

目录 1 算法简介 2 算法数学模型 2.1.全局探索阶段 2.2 过渡阶段 2.3.局部开采阶段 3 求解步骤与程序框图 3.1 步骤 3.2 程序框图 4 matlab代码及结果 4.1 代码 4.2 结果 1 算法简介 哈里斯鹰算法(Harris Hawks Optimization&#xff0c;HHO)&#xff0c;是由Ali Asghar Heid…

【深度剖析】 快速排序为什么不稳定?!

文章目录 零、前言一、快速排序的步骤原理二、什么是稳定性&#xff1f;三、不稳定的地方在哪里&#xff1f;四、怎么让快速排序变得稳定&#xff1f;1、采用双指针的快速排序A 思路简述B 参考代码 :C 算法分析 2、基于递归的快速排序A 思路简述B 参考代码C 算法分析 3、采用归…

【K8S系列】深入解析K8S调度

序言 做一件事并不难&#xff0c;难的是在于坚持。坚持一下也不难&#xff0c;难的是坚持到底。 文章标记颜色说明&#xff1a; 黄色&#xff1a;重要标题红色&#xff1a;用来标记结论绿色&#xff1a;用来标记论点蓝色&#xff1a;用来标记论点 Kubernetes (k8s) 是一个容器编…

使用docker部署rancher并导入k8s集群

前言&#xff1a;鉴于我已经部署了k8s集群&#xff0c;那就在部署rancher一台用于管理k8s&#xff0c;这是一台单独的虚拟环境&#xff0c;之前在k8s的master节点上进行部署并未成功&#xff0c;有可能端口冲突了&#xff0c;这个问题我并没有深究&#xff0c;如果非要通过修改…