【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.hqwc.cn/news/22406.html

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

相关文章

如何将SAP数据抽取到Azure数据湖平台?

经过多年的发展&#xff0c;SNP Glue 在全球已成为值得信赖且广为人知的解决方案&#xff0c;支持客户将其 SAP 数据与现代化的平台集成。SNP Glue 打破了数据孤岛&#xff0c;向数据科学家开放了 SAP&#xff0c;支持基于企业 SAP 数据的多个新用例。 随着时间的推移&#xff…

详解 HTTPS、TLS、SSL、HTTP区别和关系

一、什么是HTTPS、TLS、SSL HTTPS&#xff0c;也称作HTTP over TLS。TLS的前身是SSL&#xff0c;TLS 1.0通常被标示为SSL 3.1&#xff0c;TLS 1.1为SSL 3.2&#xff0c;TLS 1.2为SSL 3.3。下图描述了在TCP/IP协议栈中TLS(各子协议)和HTTP的关系。 二、HTTP和HTTPS协议的区别 …

前端AES加密,后端解密,有效防止数据外泄

在工作中经常遇到密码明文传输这个问题&#xff0c;为了让密码安全些会让加密&#xff0c;现在有个比较方便的AES加密&#xff08;前端密钥可能存在泄露风险&#xff0c;应该放到配置项中&#xff09;&#xff1a; 一、前端加密 1、首先引入前端需要用到的js&#xff1a;crypt…

【Git】Git 拉取的快速方法(含项目示例)

文章目录 一、问题的提出二、问题的尝试解决 一、问题的提出 在我们之前的拉取中&#xff0c;速度可能比较慢&#xff0c;例如&#xff0c;我们要拉取CLIP的项目。 (ldm) rootI1385efcc2300601b29:/hy-tmp/latent-diffusion# pip install githttps://github.com/openai/CLIP.…

接口测试辅助,Fiddler抓取安卓手机https请求(详细)

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

Qt + QR-Code-generator 生成二维码

0.前言 之前使用 libgrencode 生成二维码&#xff0c;LGPL 协议实在不方便&#xff0c;所以需要找一个 github 星星多的&#xff0c;代码简单最好 header-only&#xff0c;协议最好是 MIT 或者兼容协议而不是 GPL 或者 LPGL。 QR-Code-generator 正好符合这个要求&#xff0c…

Stable Diffusion 用2D图片制作3D动态壁纸

如果想让我们的2D图片动起来可以使用stable-diffusion-webui-depthmap-script插件在SD中进行加工让图片动起来。 这是一个可以从单个图像创建深度图,现在也可以生成3D立体图像对的插件,无论是并排还是浮雕。生成的结果可在3D或全息设备(如VR耳机或Looking Glass显示器)上查…

git下载源码及环境搭建之前端(三)

学习目标&#xff1a; vue 新项目的 前端环境搭建 vue 项目在 使用 Visual Studio Code 开发前端项目环境的搭建及 相关文件的配置 操作步骤&#xff1a; 前端&#xff1a; 下图所示为开发时前端所用的编辑器 注意&#xff1a;在配置时 有时候 localhost 可能 不太好用&…

基于Tensorflow来重现GPT v1模型

OpenAI推出的ChatGPT模型让我们看到了通用人工智能的发展潜力&#xff0c;我也找了GPT的相关论文来进行研究。OpenAI在2017年的论文Improving Language Understanding by Generative Pre-Training提出了GPT的第一个版本&#xff0c;我也基于这个论文来用Tensorflow进行了复现。…

神经网络架构设计常见问题及解答

如果你是人工神经网络 (ANN) 的初学者&#xff0c;你可能会问一些问题。 比如要使用的隐藏层数量是多少&#xff1f; 每个隐藏层有多少个隐藏神经元&#xff1f; 使用隐藏层/神经元的目的是什么&#xff1f; 增加隐藏层/神经元的数量总是能带来更好的结果吗&#xff1f; 使用什…

信贷系统开发设计基础(二)

目录 架构演进篇 01 信贷架构演进概述 02 单体架构案例简介 03 单体系统群架构案例分析 04 微服务案例分析 架构演进篇 01 信贷架构演进概述 02 单体架构案例简介 03 单体系统群架构案例分析 04 微服务案例分析 总结&#xff1a; ---------------------------------------…

GPT-4的详细信息已经泄露

这位作者说GPT-4的详细信息已经泄露&#xff0c;不知道可信度如何。一些关键信息&#xff1a;- GPT-4的大小是GPT-3的10倍以上。我们认为它在120层中总共有大约1.8万亿个参数。- GPT-4是多个专家模型混合在一起&#xff0c;但不是之前说的8个专家&#xff0c;而是16个。研究人员…