【C++】从零开始认识泛型编程 — 模版

在这里插入图片描述

送给大家一句话:
尽管眼下十分艰难,可日后这段经历说不定就会开花结果。总有一天我们都会成为别人的回忆,所以尽力让它美好吧。 – 岩井俊二

\\\ ⱶ˝୧(๑ ⁼̴̀ᐜ⁼̴́๑)૭兯 ////
\\\ ⱶ˝୧(๑ ⁼̴̀ᐜ⁼̴́๑)૭兯 ////
\\\ ⱶ˝୧(๑ ⁼̴̀ᐜ⁼̴́๑)૭兯 ////


从零开始认识模版

  • 1 前言
  • 2 函数模板
    • 什么是函数模版
    • 如何使用函数模版
    • 调用规则
  • 2 类模板
    • 什么是类模版
    • 如何使用类模版
  • 4 特别注意
    • 4.1 非类型模板参数
    • 4.2 模版缺省值
    • 4.3 编译细节
    • 4.4 模版特化
    • 4.5 模版的分离编译
  • Thanks♪(・ω・)ノ谢谢阅读!!!
  • 下一篇文章见!!!

1 前言

泛型编程是C++中十分关键的一环,泛型编程是C++编程中的一项强大功能,它通过模板提供了类型无关的代码,使得C++程序可以更加灵活和高效,极大的简便了我们编写代码的工作量。

泛型编程作为一种编程范式的主要优点包括:

  • 代码复用:同一个算法或数据结构可以用于不同的数据类型,提高了代码的复用性。
  • 性能:由于在编译时就已经知道具体的数据类型,因此编译器可以生成针对该类型的优化代码。
  • 类型安全:泛型编程仍然可以进行类型检查,从而减少运行时错误。

泛型编程它允许开发者编写独立于数据类型的算法和函数。在C++中,泛型编程主要通过模板(Templates)来实现。模板允许编写代码时使用抽象的数据类型,这些数据类型在编译时会被具体的类型所替换。

我们来看一个简单的例子:假如我想要编写一个求和函数,那么传统的写法是:

//光是简单的这三种常见类型的自身我们都需要写许多代码!
int sum(int a, int b)
{
	return a + b;
}
float sum(float a, float b)
{
	return a + b;
}
double sum(double a, double b)
{
	return a + b;
}

而通过使用模版就可以极大的简便我们的过程:

template<class A >
A sum(A a, A b)
{
	return a + b;
}

使用一个函数就可以实现多种类型的求和,极大的提高了代码的复用率!下面我们就来学习模版!!!

C++中的模板分为两类:函数模板(Function Templates)和 类模板(Class Templates);

2 函数模板

什么是函数模版

函数模板(Function Templates):允许定义一个函数,它可以接受任何类型的参数。编译器会根据传递给函数的实际参数类型来实例化函数的特定版本。
上面的函数就是使用的函数模版。

template<class A >
A sum(A a, A b)
{
  return a + b;
}

在这个例子中,sum 函数可以接受任何类型的参数(包括自定义类型),只要该类型支持比较操作。

如何使用函数模版

函数模版的格式是:

//需要几个模版就使用几个
template<typename T1, typename T2,......,typename Tn>
//写入对应函数即可

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

函数模板是C++中的一项强大特性,它本质上并非一个具体的函数实体,而更像是编译器生成具体类型函数的蓝图。当我们定义一个函数模板时,我们实际上是在描述一个能够处理多种数据类型的算法框架。编译器会根据这个框架,在程序中使用模板的具体实例时,自动生成对应的具体类型函数。只有使用了才会生成实例化函数哦!!!!

这样的设计理念,使得模板成为了一种将重复性的工作抽象化、自动化的工具,从而极大地提高了代码的复用性和开发效率。简而言之,函数模板让编译器承担了生成多样化函数实例的职责,让程序员能够专注于逻辑和结构,而不是繁琐的细节。

ps: 函数模版就像是让编译器干苦力,从而减去我们的工作量。

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将A确定为double类型,然
后产生一份专门处理double类型的代码:
在这里插入图片描述
就这样编译器生成一个个函数,将模版实例化,这是一种隐式实例化。
我们在使用过程中可以通过显示实例化与隐式实例化来进行实例化

  1. 显示实例化:在函数名后的<>中指定模板参数的实际类型sum<int>(a,b) ,直接表明想要进行什么数据类型的函数即可。
  2. 隐式实例化:让编译器根据实参推演模板参数的实际类型,也就是正常使用函数,让编译器去处理类型(可能会发生类型转换,存在隐患)。

调用规则

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

2 类模板

什么是类模版

类模板(Class Templates):允许定义一个类,其成员函数和方法可以操作任何类型的数据。与函数模板类似,编译器会根据使用时指定的类型来实例化类的特定版本。我们之前实现的vector等各种容器都使用到了类模版,通过类模版我们可以适配各种数据类型,省去重复造轮子的过程。

template <typename T>
class Stack {
public:
    void push(T value);
    T pop();
    bool isEmpty();
private:
    std::vector<T> elems;
};

在这个例子中,Stack 类可以被用来创建任何类型数据的堆栈。

如何使用类模版

与函数模版类似,我们在类声明的前面加上:

//需要几个模版就使用几个
template<typename T1, typename T2,......,typename Tn>
class ClassName
{

};

然后在类声明里面就可以直接使用我们的模版类型。

对于类模版的实例化是很关键的:

vector<int> num;
stack<string> st;
queue<char> q;
//在迭代器中更是好用
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;

我们加入对应的模版参数即可!!!

C++标准模板库(Standard Template Library,STL)是泛型编程在C++中的一个典型应用,它提供了一系列模板化的数据结构和算法,如向量(vector)、列表(list)、队列(queue)、栈(stack)、排序算法等,这些都可以用于任何符合特定要求的类型。

4 特别注意

通过上述的介绍,就可以进行使用模版来进行代码的编写了。但是仍然有一些注意事项!!!

4.1 非类型模板参数

模板参数分类类型形参与非类型形参
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用

就比如STL 中有一个这样的容器array(很鸡肋,一般不使用,而且由于是静态数组,直接开在栈区,容易造成栈溢出),如果我们想要一个静态数组,就可以通过它来创建:

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

int main()
{
	array<int, 10> arr1;
	array<int, 1000> arr2;
	return 0;
}

通过传入的参数,编译器会生成两个不同的类,类的模版参数就是给定参数。
在C++20之前,只支持整型作为非类型模版参数(char , short , int , long long… )

4.2 模版缺省值

像函数参数一样,模版参数也支持使用缺省值!!!
使用缺省值就可以方便我们传入参数了:

//这里就是使用了缺省值
template<class T, size_t N = 10>
class array
{

};

另外再优先队列里也有很重要的使用:

//    默认底层容器是vector<T> , 默认用来比较的仿函数是 less<T>
template<class T , class Container = vector<T> , class compare = less<T> >
class priority_queue
{
};

4.3 编译细节

注意看下面的代码,我们在[ ] 重载中加入了一个size(1),明显不和语法规范,但是我们来看编译会出现什么现象:

template<class T, size_t N = 10>
class array
{
public:
	T& operator[](size_t index) { 
		assert(index < N);
		//明显错误
		size(1);
		
		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;
};

int main()
{
	array<int, 10> arr1;
	return 0;
}

来看:
在这里插入图片描述
我们的代码居然可以正常运行!!!这是怎么回事儿???

因为编译器在遇到模版时会进行下面操作:

  1. 根据模版的实例化形成模版半成品
  2. 实例化成具体的类/函数
  3. 进行语法编译

但是这里又增加了一个新的概念:按需实例化!!!没有实例化之前只会进行简单的框架检查。
也就是只有使用对应函数才会进行函数的实例化,才会进行语法编译,才会报错!!!
没有调用operator[ ],所以operator[ ] 有调用参数不匹配,就没有检查出来。
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/8789d512205f4ae1b2336702d071f9aa.png
所以只有我们使用[ ] 重载函数时,才会进行检查!!!

4.4 模版特化

模版特化就是指把模版的参数进行确定,就进行了特殊化:
来看一段代码:

#include<iostream>

using namespace std;


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

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

int main()
{
	TestVector();
	return 0;
}

我们运行看看:
在这里插入图片描述

得到的是这样的结果。因为如果类的模版参数与模版特化一致,那么就会进行特化的模版来进行实例化。

比较复杂一点点的用法是指针特化

class Data
{
public:
	Data(int a, char b) 
	{  
		_d1 = a;
		_d2 = b;
	}
	bool operator<(Data d)
	{
		return _d1 < d._d1;
	}
private:
	int _d1;
	char _d2;
};

template<class T>
bool Less(T left, T right)
{
	cout << "Less(T left, T right)" << endl;
	return left < right;

}

template<>
bool Less<Data*>(Data* left, Data* right)
{
	cout << "Less<Data*>(Data* left, Data* right)" << endl;
	return *left < *right;
}

void TestVector()
{
	Data* d1 = new Data(1,'c');
	Data* d2 = new Data(2,'c');
	Less(d1,  d2);
}

int main()
{
	TestVector();
	return 0;
}

这样就是对指针进行特化
在这里插入图片描述
如果加上这个:


template<class T>
bool Less(T* left, T* right)
{
	cout << " Less(T* left, T* right)" << endl;
	return *left < *right;
}

那么就会优先执行这个指针模版。
总的来说,函数模版真不如直接使用函数重载!!!

特化分为:全特化与偏特化

  1. 全特化即是将模板参数列表中所有的参数都确定化
  2. 偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类:
    • 部分特化将模板参数类表中的一部分参数特化,如上面的例子。
    • 参数更进一步的限制,偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。如上面的例子

类特化的是使用场景主要是在仿函数中进行使用,比如我们之前实现优先队列,在里面我们直接使用:

template<class T>
bool Less(T* left, T* right)
{
	cout << " Less(T* left, T* right)" << endl;
	return *left < *right;
}

可以适配更多的类型指针。

4.5 模版的分离编译

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

模版不支持分离编译,如果声明与定义写到两个文件里,就会报错。

// a.h
template<class T>
T Add(const T& left, const T& right);
// a.cpp
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

// main.cpp
#include"a.h"
int main()
{
	Add(1, 2);
	Add(1.0, 2.0);
	return 0;
}

这样就会发生报错!!!链接错误
链接错误:是在语法没问题情况下,链接的时候,一个函数声明去其他文件寻找函数定义,找不到就会发生链接错误。

那为什么寻找不到呢???明明我们写了函数定义。
因为 a.cpp下的函数定义没有实例化,调用函数时仅仅是声明知道了使用什么模版类型,而函数定义不知道使用什么模版参数,那自然无法实例化!!!

解决方法很简单:

  1. 将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h就很好的。推荐使用这种。因为.h文件预处理展开后,实例化模版时,既有声明又有定义,直接就实例化了,就有函数地址了。不需要链接时再去找。
  2. 模板定义的位置显式实例化。这种方法真不实用,真不推荐使用。这样模版还有什么意义!?
// a.cpp

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

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

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

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

相关文章

pycharm安装AI写代码插件

在IDE安装特慢&#xff08;可能找不到插件&#xff09; 去官网搜一下 对应安装包 下载zip在IDE解压 插件--已安装齿轮图标--从磁盘里安装 选择下载的插件 应用 --重启OK

3gp转MP4怎么转?简单的3个方法~

3gp最初是由第三代合作伙伴计划&#xff08;3rd Generation Partnership Project&#xff09;设计的&#xff0c;旨在满足移动设备对高效传输音频和视频的需求。它的起源可以追溯到20世纪初&#xff0c;当时移动通信技术的飞速发展使得人们对更加高效的多媒体文件格式有了迫切需…

几种免费SSL证书申请方式

目录 DV单域名免费证书的获取渠道&#xff1a; DV多域名免费证书获取渠道&#xff1a; DV通配符免费证书获取渠道&#xff1a; 随着现在网络安全意识的逐渐提升&#xff0c;越来越多的网站都在相继配对部署SSL证书&#xff0c;用以实现https访问。 大家都知道SSL证书好&…

【linux】基础IO(软硬链接)

上一节我们已经搞懂了已经被打开的文件&#xff0c;还有没有被打开的文件都是怎样被管理起来的&#xff0c;同样&#xff0c;路径的重要性也不言而喻&#xff0c;是确定文件在那个分区&#xff0c;进而可以解析到目标文件与目录内容的关系&#xff0c;从而找到inode&#xff0c…

微带线设计细节的模拟仿真分析

微带线设计在很多PCB设计场景中被应用&#xff0c;但是&#xff0c;有些工程师往往并不注重设计细节&#xff0c;导致最后的设计指标与预期相差甚远&#xff0c;比如设计中&#xff0c;会将丝印、散热金属等放在走线的上方&#xff0c;设计检查时会有人产生质疑&#xff0c;至于…

3DTiles生产流程与规范

一篇19年整理的比较老的笔记了。更多精彩内容尽在数字孪生平台。 瓦片切分 标准的四叉树切分对于均匀分布的地理数据切片非常有效&#xff0c;但是这样均等的切分不适用于随机分布、不均匀分布的地理数据&#xff0c;当地理数据稀疏分布的时候&#xff0c;均等的四叉树就不再高…

java-spring-mybatis -学习第一天-基础知识讲解

目录 前置条件(创建一个项目) Mybatis 定义 可能出现的问题 这边如果连接不上数据库 ​编辑 Dao接口设计 Mybatis流程 创建实体类 User 和其属性 创建Mapper的接口类 测试类测试 实例数据库数据的更新 实例数据库数值的删除 最重要的是有一个原始的数据库 -我这边…

使用 vllm 本地部署 cohere 的 command-r

使用 vllm 本地部署 cohere 的 command-r 0. 引言1. 安装 vllm2. 本地部署 cohere 的 command-r3. 使用 cohere 的 command-r 0. 引言 此文章主要介绍使用 使用 vllm 本地部署 cohere 的 command-r。 1. 安装 vllm 创建虚拟环境&#xff0c; conda create -n myvllm python…

Oracle Linux 8.8 一键安装 Oracle 11GR2 RAC(231017)

前言 Oracle 一键安装脚本&#xff0c;演示 Oracle Linux 8.8 一键安装 Oracle 11GR2 RAC&#xff08;231017&#xff09;过程&#xff08;全程无需人工干预&#xff09;&#xff1a;&#xff08;脚本包括 ORALCE PSU/OJVM 等补丁自动安装&#xff09; ⭐️ 脚本下载地址&…

kafka大数据采集技术实验(未完待续)

Kafka环境搭建 下载地址&#xff1a;https://link.zhihu.com/?targethttps%3A//kafka.apache.org/downloads解压启动zookeeper bin/zookeeper-server-start.sh config/zookeeper.properties需要注意的是 : " c o n f i g / z o o k e e p e r . p r o p e r t i e s &q…

维态思(上海)环保科技有限公司 | 2024全国水科技大会暨技术装备成果展览会

嘉宾简介 胡建龙 维态思&#xff08;上海&#xff09;环保科技有限公司 总经理 报告题目&#xff1a;微生态滤床 植物工厂——小城镇生活污水生态净化及零排放案例分享 国家注册设备工程师&#xff08;给排水&#xff09;、上海市&#xff08;合作交流&#xff09;五四青年…

BUUCTF---misc---[ACTF新生赛2020]outguess

1、下载附件&#xff0c;解压之后得到下面信息 2、查看图片属性&#xff0c;发现有个核心价值观编码&#xff1b;解码为abc 3、flag.txt提示 4、结合题目&#xff0c;这是一个outguess隐写 5、用kali先下载安装隐写库 6、使用命令-k(密钥)&#xff1b;-r(将图片里面的隐写信息…

InstantMesh:利用稀疏视图大规模重建模型从单张图像高效生成3D网格

作者&#xff1a;Jiale Xu&#xff0c;Weihao Cheng&#xff0c;Yiming Gao等 编译&#xff1a;东岸因为一点人工一点智能 InstantMesh&#xff1a;利用稀疏视图大规模重建模型从单张图像高效生成3D网格在这项工作中&#xff0c;我们提出了InstantMesh&#xff0c;一个开源的…

免费在英伟达官网使用多个开源AI大模型

英伟达官网能体验到多个聊天AI和图片生成AI&#xff0c;不废话直接上链接 AI开源大模型&#xff08;https://build.nvidia.com/explore/discover?api-keytrue&#xff09; 开源的AI大模型有meta的llama3-8b和llama3-70b、snowflake的arctic、microsoft的phi-3-mini、mistral…

【Linux系统编程】第九弹---权限管理操作(下)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、目录权限 2、粘滞位 总结 1、目录权限 首先提出一个问题&#xff0c;删除一个文件需要什么权限呢&#xff1f;&#xff1f…

虚拟机软件哪个好用 虚拟机软件哪个可以玩暗区突围 虚拟机软件排名 PD19虚拟机 Mac类虚拟机运行Windows程序 CrossOver支持的热门游戏

随着跨系统互联的需求不断增长&#xff0c;越来越多的用户会选择在电脑系统中安装虚拟机软件&#xff0c;进而更加便捷地访问和操作其他系统。一款好用的虚拟机软件能够提高系统互联的效率&#xff0c;进而实现了资源共享、测试环境搭建等多种用途。而在众多的虚拟机软件当中&a…

张驰咨询:降本增效企业突破市场重围的制胜法宝

企业在快速发展的过程中&#xff0c;降本增效是永恒不变的主题。毕竟&#xff0c;在竞争激烈的市场环境中&#xff0c;只有不断提高效率和降低成本&#xff0c;才能在竞争中立于不败之地。那么&#xff0c;为什么企业需要降本增效呢&#xff1f; 首先&#xff0c;降本增效是企业…

vue+springboot的登录图片验证码(前端对接报错)

tip:这个只是一个效果实际要运用&#xff0c;还是需要改改滴&#xff01; 后台Java自带的 本来我是打算用第三方库的&#xff0c;没有整出来&#xff0c;就跟沈某人说不会来着&#xff0c;他说最好用Java自带的&#xff0c; 不然换个系统第三方的就不能用了&#xff0c;大概…

不可以论文查重,也包含了查AI率吗?

临近毕业&#xff0c;完成一篇符合学术规范的毕业论文是一项繁琐又具挑战性的任务。撰写完论文后&#xff0c;反复的查重降重已让人心身疲累。今年&#xff0c;学校又提出了新要求&#xff0c;论文还需要通过AIGC检测系统&#xff08;www.checkaigc.com&#xff09;才行&#x…

Vue2学习笔记(尚硅谷天禹老师)

目录 一、入门案例 二、模板语法 三、数据绑定 四、el和data的两种写法 五、MVVM模型 六、Object.defineproperty方法 七、Vue中响应式原理 八、数据代理 九、methods配置项 十、Vue中的事件处理 十一、Vue中的键盘事件 十二、计算属性 十三、监视属性watch 十四、绑定Class样式…