C++模板——泛型编程

news2024/11/13 9:03:41

目录

1. 什么是泛型编程

2. 函数模板

2.1 定义格式

2.2 实例化及原理 

2.3 参数匹配原则

3. 类模板 

3.1 定义格式

3.2 实例化 

4. 非类型模板参数 

5. 模板的特化 

5.1 概念

5.2 函数模板和类模板特化

6. 模板的分离编译 


1. 什么是泛型编程

  如何实现一个通用的加法函数呢?

//内置类型
int Add(int& x, int& y) { return x + y; }
double Add(double& x, double& y) { return x + y; }
//......

//自定义类型
Date Add(Date& x, Date& y) { return x + y; }
//......

  像上面这样,使用函数重载虽然可以实现,但是有以下几个不好的地方

      A. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数。

      B. 代码的可维护性比较低,一个出错可能所有的重载均出错。

  那能否告诉编译器一个模板,让编译器根据不同的类型利用该模子来自己生成代码呢?

  答案肯定是可以的。

  而这种编程思想就是 泛型编程 :一种编程范式,编写与类型无关的通用代码,是代码复用的一种手段;模板是泛型编程的基础,包含函数模板和类模板。

2. 函数模板

2.1 定义格式

/*
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
*/
//eg:
template<typename T>
T Add(T& x, T& y) { return x + y; }

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

2.2 实例化及原理 

  即,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用,叫做:隐式实例化 

  但是,对于以下情况语句:

int a = 10;
double b = 1.34;
Add(a, b);

  该语句不能通过编译,因为在编译期间,需要推演其实参类型,通过实参a将T推演为int,通过实参b将T推演为double类型,但模板参数列表中只有一个T, 编译器无法确定此处到底该将T确定为int 或者 double类型而报错; 因为:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅 。

  此时有两种处理方式:

        1. 用户自己来强制转化

Add(a, (int)b) 或 Add((double)a, b);

        2. 显式实例化 在函数名后的<>中指定模板参数的实际类型

Add<int>(a, b) 或 Add<double>(a, b>;

  此时的模板修改为:

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

  因为,显示实例化进行了类型转换,生成临时对象,需要const修饰。

  小细节: 此时,隐式和显式调用的是同一个函数,更准确的说是:编译器先生成了显式实例化的函数,可供隐式推演直接使用。

  原理是:const形参既可以接受const形参,也可以接受非const形参;但非const形参只能接受非const形参。也就是小编之前总结的一句话:权限只能缩写,不能放大! 

  如下示图:

2.3 参数匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。

  2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例;如果模板可以产生一个具有更好匹配的函数,那么将选择模板。  

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

  总之就是:永远选择当前情况下有的且最合适的;如果只有且允许类型转换的,就退而求其次;如果还没有,就模板生成!

3. 类模板 

3.1 定义格式

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

//eg:
template<class T>
class stack
{
public:
    //方法......
private:
    T* _arry;
    int _top;
    int _capacity;
};

3.2 实例化 

  类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的模板参数类型放在<> 中即可。

//stack是类名,stack<T>才是类型
stack<int> sta1;
stack<double> sta2;
stack<stack<int>> sta3;
//......

4. 非类型模板参数 

  前面我们看的都是 类型形参,即: 跟在class/typename之后的T1, T2, ... 代表具体的数据类型。

  而非类型形参,是用一个常量作为函数/类模板的一个参数,在编译期就能确认结果,所以在函数/类模板中可将该参数当成常量来使用;但是只支持整形常量:int, unsigned int, size_t

  如下示例:

// 定义一个模板类型的静态数组
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; }

    //函数模板
    template<size_t N = 20>
    void func() { cout << N << endl; }

private:
    T _array[N];
    size_t _size;
};

int main()
{
    Array<int, 30> a1;
    a1.func<40>();
    return 0;
}

5. 模板的特化 

5.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;
}

  可以看到,Less绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示例中,p1指向的d1显然小于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内容,而比较的是p1和p2里的地址,这就无法达到预期而错误。 

  此时,就需要对模板进行特化,即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化分为 函数模板特化 类模板特化同一模板特化又可分为 全特化 和 偏特化。

5.2 特化的实现

   函数模板的特化步骤:

        1. 必须要先有一个基础的函数模板

        2. 关键字template/class后面接一对空的尖括号<>

        3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型

  所以,为了解决5.1中的示例问题,需要在原基础上增加特化版本的函数,如下:

template<>
bool Less<Date*>(Date* left, Date* right) { return *left < *right; }

  【 但是,一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出:bool Less(Date* left, Date* right) { return *left < *right; } 】

  类模板的特化步骤和函数模板的特化是一样的,如下示例:

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;
};

int main()
{
	Data<int, int> d1;
	Data<int, char> d2;
}

  输出:

 

  像上面这样,将模板参数列表中所有的参数都确定化就是全特化。包括前面 2.2函数模板的显示实例化 和 3.2类模板的实例化 所有演示示例 都是全特化

   而 偏特化 并不是全特化的对立面,而是: 任何针对模版参数进一步进行条件限制设计的特化版本;有以下两种表现形式:

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


//表现形式1:部分特化:将模板参数类表中的一部分参数特化
//将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
 Data() {cout<<"Data<T1, int>" <<endl;}
private:
 T1 _d1;
 int _d2;
}; 

//表现形式2:参数更进一步的限制
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{ 
public:
 Data() {cout<<"Data<T1*, T2*>" <<endl;}
 
private:
T1 _d1;
 T2 _d2;
};

int main()
{
 Data<double , int> d1; // 调用特化的int版本
 Data<int , double> d2; // 调用基础的模板 
 Data<int *, int*> d3; // 调用特化的指针版本
}

 输出:

 

6. 模板的分离编译 

  一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链 接起来形成单一的可执行文件的过程称为分离编译模式。 

  而对于模板来说,如果出现下面的情况,以类模板为例: 

  单独的函数模板也是同样的道理。

  最好的解决办法就是把声明和定义放到同一个头文件中;当然也可以在模板定义的位置显式实例化一下,但这种方法不实用,所以不推荐使用。 

(如果你对上述的编译链接有疑问,可点击前往小编的另一篇文章《程序环境和预处理详解》 )

  最后我们简单总结一下:模板的优点:复用了代码,增强了代码的灵活性,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。

                                          模板的缺陷:可能会导致代码膨胀问题 和 编译时间变长 ;出现模板编译错误时,错误信息非常凌乱,不易定位错误。

 

  本篇分享到这就结束了,但对你我来说只是学习的又一个新起点;尽管上述的知识点都比较简单,可 “不积硅步,无以至千里”,只有打好基础,才能在未来的更多实际使用场景中游刃有余地解决问题。

  当然,如果本篇分享对你有所帮助的话,就是对小编最大的鼓励啦,可以的话,点赞+收藏+评论并分享给你的小伙伴一起学习吧,关注小编,持续更新中!

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

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

相关文章

Windows下Pytorch入门深度学习环境安装与配置(CPU版本)

Windows下Pytorch入门深度学习环境安装与配置&#xff08;CPU版本&#xff09; 一、安装过程中各个软件的作用&#xff08;一&#xff09;Python&#xff08;二&#xff09;库 / 包 / package / library&#xff08;三&#xff09;PyTorch / Tensorflow&#xff08;四&#xff…

详解yolov5和yolov8以及目标检测相关面试

一、与yoloV4相比&#xff0c;yoloV5的改进 输入端&#xff1a;在模型训练阶段&#xff0c;使用了Mosaic数据增强、自适应锚框计算、自适应图片缩放基准网络&#xff1a;使用了FOCUS结构和CSP结构Neck网络&#xff1a;在Backbone和最后的Head输出层之间插入FPN_PAN结构Head输出…

计算机二级题--文件 章节

之前写的有文件全部的知识点&#xff0c;这一篇主要针对计算机二级真题的整理。 需要备考计算机二级的小伙伴们先收藏起来吧。整理不易&#xff0c;不过有帮助记得点赞哦 1.相关概念考点&#xff1a; 1.文件指针指向的是文件缓冲区的位置&#xff0c;不是文件在读位置。 2.…

儿童洗衣机哪个牌子好?五大爆款机型倾情分享

在当今繁忙的生活中&#xff0c;儿童洗衣机已成为我们日常生活中不可或缺的家电。但是&#xff0c;面对市场上众多品牌的儿童洗衣机&#xff0c;那么&#xff0c;到底儿童洗衣机哪个牌子好&#xff1f;本次我将在这篇文章中探讨儿童洗衣机的选购策略&#xff0c;以帮助大家找到…

记录unraid docker更新的域名

背景&#xff1a;级联 一、安装内容 unraid更新docker&#xff0c;之前一直失败&#xff0c;修改网络后可以进行安装。 二、查看域名 查看域名&#xff0c;发现是走github的&#xff0c;怪不得有一些docker无法正常更新 三、解决方法 更改代理&#xff0c;这里为unraid的…

智慧教育解决方案

1. 智慧教育解决方案概述 智慧教育解决方案旨在通过教育改革实现四个转变&#xff1a;从以教为主到以学为主&#xff0c;从专业教育到通识与专业教育结合&#xff0c;从课堂教育到课内外结合&#xff0c;以及从结果评价到过程与结果评价结合。 2. 教育三维目标 教育三维目标…

避免工件精度受损:精准调整滚珠丝杆状态!

滚珠丝杆作为一种常用的传动装置&#xff0c;经过长期使用或者维护不当的情况下有时会发生弯曲或变形。弯曲不仅会对滚珠丝杆的精度和寿命造成影响&#xff0c;而且还会对整个机器系统的工作效率和安全性产生影响。那么&#xff0c;应该如何要避免滚珠丝杆弯曲对工件的影响呢&a…

Java数据结构与算法--链表(Linked List)

博客主页&#xff1a;誓则盟约系列专栏&#xff1a;Java SE关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 深入了解链表&#xff1a; 链表是一种常见的数据结构&#xff0c;它由一系列节点…

【SOC 芯片设计 DFT 学习专栏 -- DFT DRC规则检查】

请阅读【嵌入式及芯片开发学必备专栏】 请阅读【芯片设计 DFT 学习系列 】 如有侵权&#xff0c;请联系删除 转自&#xff1a; 芯爵ChipLord 2024年07月10日 12:00 浙江 文章目录 概述DRC的概念Tessent DRC检查的概述时钟相关检查扫描相关检查BIST规则检查预DFT时钟规则检查 …

zh echarts样式

记录一下&#xff1a; 一个图的配置 在echarts官网demo界面 option {title: {text: },legend: {data: [xxx前, xxx后]},radar: {// shape: circle,name: {// 雷达图各类别名称文本颜色textStyle: {color: #000,fontSize: 16}},indicator: [{ name: 完整性, max: 1 },{ name:…

ByteBuffer调试工具类

一个可以形象展示ByteBuffer内容的方法&#xff0c;便于调试 package com.example.netty;import java.nio.ByteBuffer;public class ByteBufferUtil {/*** 打印ByteBuffer的内容&#xff0c;以十六进制和ASCII字符的形式展示。** param buffer 要展示的ByteBuffer*/public sta…

Java全栈课程之Linux——用户组管理

每个用户都有一个用户组&#xff0c;系统可以对一个用户组中的所有用户进行集中管理。不同Linux 系统对用户组的规定有所不同&#xff0c;如Linux下的用户属于与它同名的用户组&#xff0c;这个用户组在创建用户时同时创建。 用户组的管理涉及用户组的添加、删除和修改。组的增…

Jenkins持续集成软件

1.什么是jenkins? jenkins是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;提供一个开放易用的软件平台&#xff0c;时软件项目可以进行持续集成。 通俗来说&#xff1a;Jenkins软件就是自动拉取git远程仓库所…

03--KVM虚拟化

前言&#xff1a;这里开始涉及到云计算内容&#xff0c;虚拟化使云计算发展&#xff0c;云计算推动虚拟化进步&#xff0c;两者相辅相成&#xff0c;这一章总结一下kvm虚拟化的解决方案。 1、基础概念 1.1、云计算 以前要完成信息处理, 是需要在一个客观存在的计算机上完成的…

永劫无间游戏辅助攻略:2024阵容搭配攻略大全!云手机辅助!

《永劫无间》是一款备受玩家喜爱的动作类游戏&#xff0c;其丰富的角色选择和多样的技能搭配让玩家在战斗中体验到了极大的乐趣。然而&#xff0c;要在竞争激烈的战场上脱颖而出&#xff0c;仅仅依靠基础的游戏理解是远远不够的。为了帮助广大玩家提升战斗力&#xff0c;本文将…

安防巡检机器人:守护安全的智能卫士

安防巡检机器人&#xff0c;作为机器人技术在安防领域的杰出应用&#xff0c;是一种集自主导航、智能巡检、环境监测、远程监控等多功能于一体的智能装备。这些机器人通过集成先进的传感器、高清摄像头、智能算法和导航系统等模块&#xff0c;实现了全天候、全方位、自主化的安…

芒果TV大模型来袭 | AI如何重塑微短剧制作未来?

在数字化浪潮推动下&#xff0c;7月23日&#xff0c;芒果TV大模型正式通过生成式人工智能大语言模型备案审核&#xff0c;并预计2024年内可应用于微短剧生产&#xff0c;可支撑节目创意策划、内容创作和生成、角色拟人对话、生成式内容推荐等行业应用场景。 市场对高质量内容的…

33.【C语言】实践扫雷游戏

预备知识&#xff1a; 第13篇 一维数组 第13.5篇 二维数组 第28篇 库函数 第29篇 自定义函数 第30篇 函数补充 0x1游戏的运行&#xff1a; 1.随机布置雷 2.排雷 基本规则&#xff1a; 点开一个格子后&#xff0c;显示1&#xff0c;对于9*9&#xff0c;代表以1为中心的去…

vue element-ui日期控件传参

前端&#xff1a;Vue element-ui <el-form-item label"过期时间" :rules"[ { required: true, message: 请选择过期时间, trigger: blur }]"><el-date-picker v-model"form.expireTime" type"date" format"yyyy-MM-dd&…