C++——函数模板与类模板

news2024/11/26 17:35:20

0.关注博主有更多知识

C++知识合集

目录

1.泛型编程

2.函数模板

2.1函数模板实例化

2.2函数模板参数的匹配原则

3.类模板

4.模板的分离编译

1.泛型编程

实际上泛型编程的难度是比较高的,但我们泛型编程的初学者,当然要从简单的地方开始入手。

我们可以写出很多份交换函数,这些函数之间构成函数重载,这样在调用的时候就能自动调用不同参数类型的函数:

void Swap(int &left, int &right)
{
	int tmp = left;
	left = right;
	right = tmp;
}

void Swap(char &left, char &right)
{
	char tmp = left;
	left = right;
	right = tmp;
}

void Swap(double &left, double &right)
{
	double tmp = left;
	left = right;
	right = tmp;
}

/*......*/

那么像上面这样写能不能解决我的需求呢?能!但是太挫了,原因有两个

  1.重载的函数仅仅是类型不同,函数体内容都是一样的,代码复用的率比较低;只要有新的类型出现,就需要增加对应的函数

  2.代码的可维护性比较低,一个函数出错可能所有的重载都会出错

那么我们可以试着摸一摸泛型编程的门道,向上面的Swap()函数一样,这几个Swap()函数除了参数类型不一样,其他的大部分内容都是一样,那么是不是可以将这些相同的地方集合起来,组成一个模板,然后让编译器根据不同的类型来利用该模板自动生成代码呢?

事实上在C++当中,确实存在上述这样的模板,通过给这个模板提供不同的参数类型,就可以获得不同的具体类型代码。那么泛型编程就是编写与类型无关的通用代码,是代码复用的一种手段,模板是泛型编程的基础。

2.函数模板

函数模板代表了一个函数家族,该函数模板与类型无关,在是使用时被参数化,根据实参类型产生函数的特定类型版本。也就是说函数模板不是一个具体的函数,在调用函数模板时会根据调用函数的实参生成一份特定的类型版本代码。我们以上面的Swap()函数为例:

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

int main()
{
	int x = 3, y = 5;
	Swap(x, y);

	char chl = 'a', chr = 'b';
	Swap(chl, chr);

	double d = 1.1, b = 4.3;
	Swap(d, b);
	return 0;
}

函数模板的格式为:

template <class T1,class T2,......,class Tn>
返回类型 函数名(参数列表)
{}

其中,"template"是定义模板时的关键字,其后跟上一堆尖括号,尖括号里面的内容就是模板的参数,需要注意,模板的参数与函数的参数不一样,模板的参数是定义类型,而函数的参数是定义指定类型的变量。其中模板参数的定义可以使用关键字class或者typename。函数模板的有效范围在"template"关键字之后碰到的第一个函数内有效

函数模板本身并不是一个函数。在编译器编译阶段,编译器需要根据调用函数模板时传入的实参来推演并实例化生成对应类型的函数以供正确调用。

例如在外部调用Swap()的两个实参都是double类型,在编译阶段,编译器会根据实参的类型,将函数模板的参数类型由T替换为double,然后再实例化出一份真正的函数代码:

2.1函数模板实例化

上面我们介绍了编译器是如何通过函数模板实例化出对应类型函数的大致流程,接下来介绍我们如何调用函数模板以及一些细节:

  1.隐式实例化:让编译器根据实参推演出函数模板的参数类型,然后再生成具体的函数

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

int main()
{
	/*让编译器根据实参的类型推演出函数模板的参数类型
	 *然后再生成具体的真实函数*/
	int a = 1, b = 3;
	cout << Add(a , b) << endl;

	double c = 3.1, d = 4.6;
	cout << Add(c , d) << endl;

	return 0;
}

  用法是非常简单的,但是我们需要注意,上面程序的模板参数只有一个,即T,那么这就意味着只能确定一种类型,所以在调用函数模板时需要保证两个实参的类型相同。那么我们也知道,函数调用的时候,实参与形参之间可能发生隐式类型转换,但是我们也需要知道,这种隐式类型转换发生在函数调用当中而不在函数模板调用当中

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

int main()
{
	int a = 1, b = 3;
	double c = 3.1, d = 4.6;

	/*错误调用,a的类型为int,c的类型为double
	 *函数模板只有一个参数,即只能确定一种类型
	 *而函数模板的调用不会发生隐式类型转化*/
	cout << Add(a , c) << endl;
	return 0;
}

  所以解决方案有两种,要么自己手动进行强制类型转换,要么使用显式实例化模板:

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

int main()
{
	int a = 1, b = 3;
	double c = 3.1, d = 4.6;

	/*手动强制类型转换,使得实参类型统一*/
	cout << Add(a , (int)c) << endl;
	return 0;
}

  2.显式实例化:在函数模板名称后使用<>指定模板参数的类型,这时就已经实例化出了真实的函数

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

int main()
{
	int a = 1, b = 3;
	double c = 3.1, d = 4.6;

	/*显式实例化,Add<int>确定了函数模板的参数类型
	 *所以即可生成对应的真实函数
	 *所以此时可以发生隐式类型转换,因为此时就是在调用真实的函数*/
	cout << Add<int>(a, c) << endl;
	return 0;
}

  此时实参与形参时间可以发生隐式类型转换,因为调用的时候不再是调用函数模板,而是调用真实的函数。如果不能发生隐式类型转化,那么将会报错。

2.2函数模板参数的匹配原则

如果函数与函数模板同时存在,那么在调用的时候优先选择函数而非函数模板

int Add(int x, int y)
{
	return x + y;
}

template <class T>
T Add(T x, T y)
{
	return x + y;
}
int main()
{
	/*此时函数模板与函数同时存在,但是这里调用例子中
	 *实参直接与函数的参数匹配,所以直接调用函数而非调用函数模板*/
	cout << Add(1, 2) << endl;
	return 0;
}

这个时候需要注意了,如果我们显式地实例化函数模板生成一份与已存在函数相同的函数,不会发生报错,并且正常调用:

int Add(int x, int y)
{
	return x + y;
}

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

int main()
{
	/*此时函数模板与函数同时存在,但是这里调用例子中
	*实参直接与函数的参数匹配,所以直接调用函数而非调用函数模板*/
	cout << Add(1, 2) << endl;

	/*显式实例化生成一份真实的函数
	 *看起来实例化的函数与已经存在的函数冲突
	 *实际上并没有发生冲突,还可以正常调用*/
	cout << Add<int>(1, 2) << endl;
	return 0;
}

在这个例子当中,已经存在了一份int Add(int , int),而我们显式实例化函数模板又生成了一份int Add(int , int),但是编译器没有报错,这就说明了在符号表当中,他们的函数名修饰规则一定不一样。我们在Linux环境下使用g++观察上面这个程序的汇编代码:

 

虽然说调用函数时有限使用普通函数,但如果函数模板更加匹配,那么最后调用的时候会选择函数模板

int Add(int x, int y)
{
	return x + y;
}

template <class T1,class T2>
T1 Add(T1 x, T2 y)
{
	return x + y;
}

int main()
{
	/*这两个调用都可以调用普通函数,但是要发生隐式类型转换
	 *但是上面的函数模板有两个参数,所以对应两个类型
	 *所以调用函数模板是最合适的*/
	cout << Add(1.1, 5) << endl;
	cout << Add(5, 3.4) << endl;
	return 0;
}

3.类模板

类模板的定义与函数模板的定义差不多,并且类模板不是真正的类,只有实例化出来的类才是真正的类。

我们以一个Stack类为例:

template <class T>
class Stack
{
public:
	Stack(int capacity = 4)
		:_top(0), _capacity(capacity)
	{
		_elem = new T[_capacity];
	}

	void push(const T &in)
	{
		/*不考虑扩容......*/
		_elem[_top++] = in;
	}
private:
	T *_elem;
	int _top;
	int _capacity;
};

我们Stack类作为栈,那么栈就需要存储数据,那么既然要存储数据就必然涉及到不同类型的数据,所以使用一个模板是非常有必要的。那么类模板的使用不能像函数模板那样隐式实例化,类模板的使用必须显式实例化

int main()
{
	/*实例化出不同的Stack类,用来存储不同的数据类型
	 *这几个实例化出来的类的类型是不相同的!*/
	Stack<int> st1;
	Stack<double> st2;
	Stack<char> st3;

	/*错误!他们不是相同的类型!*/
	//st1 = st2;
	return 0;
}

需要注意的是,使用不同的类型实例化出来的类,他们之间是不同的类型

4.模板的分离编译

对于函数模板来说,如果想要在一个文件下声明和定义分离是可以的,那么格式就得像下面这样:

/*函数模板的声明*/
template<class T>

T Add(const T &x, const T &y);
int main()
{
	cout << Add(1, 2) << endl;
	return 0;
}

/*函数模板的定义*/
template <class T>
T Add(const T &x, const T &y)
{
	return x + y;
}

对于类模板当中的成员函数来说,他们也是函数模板,如果这些函数模板在类模板当中声明,在类模板外定义也是可以的:

template <class T>
class Stack
{
public:
	/*类模板中只有声明*/
	Stack(int capacity = 4);
	void push(const T &in);
private:
	T *_elem;
	int _top;
	int _capacity;
};

/*声明与定义分离*/
template <class T>
Stack<T>::Stack(int capacity = 4)
:_top(0), _capacity(capacity)
{
	_elem = new T[_capacity];
}

template <class T>
void Stack<T>::push(const T &in)
{
	/*不考虑扩容......*/
	_elem[_top++] = in;
}

我们说过在类中如果只有成员函数的声明,在类外定义该成员函数时需要指明类域。上面代码当中的定义部分看起来非常奇怪,但实际上我正现在遵守刚才所说的原则,原因就在于类模板不是一个真正的类,类模板必须实例化之后才生成一个真正的类。所以在选择在类模板外部定义函数时,需要显式实例化类模板。

虽然函数模板可以在一个文件当中声明与定义分离,但是如果在多文件当中声明与定义分离编译,那么又会触发隐藏奖励:链接错误。类模板当中的成员函数也是如此:

// test.cpp
/*只有声明,定义在另一个源文件当中*/
template<class T>
T Add(const T &x, const T &y);

int main()
{
	cout << Add(1, 2) << endl;
	return 0;
}
// func.cpp
/*函数模板的定义*/
template <class T>
T Add(const T &x, const T &y)
{
	return x + y;
}

这个问题我们再熟悉不过了,原因就在于test.cpp文件当中没有Add()函数的定义,只有声明,所以编译器会认为Add()函数在其他文件当中存在,所以编译可以过;那么对于func.cpp文件来说,它也没有Add()函数的定义,因为它只有一个函数模板,并且没有人调用该模板实例化出一个函数,所以func.cpp被编译之后生成的符号表当中就没有Add()这个函数。那么test.cpp文件需要调用Add()函数但是本文件没有啊,就要发动链接器去func.cpp文件生成的符号表当中去找,但是因为func.cpp生成的符号表当中没有Add()函数,所以产生链接错误。

那么解决方法有两种,一种是在多文件分离编译中负责定义函数的文件当中显式实例化函数:

/*函数模板的定义*/
template <class T>
T Add(const T &x, const T &y)
{
	return x + y;
}

/*显式实例化*/
template int Add(const int &x, const int &y);
template double Add(const double &x, const double &y);

第二种便是模板不要在多文件当中声明和定义分离编译。第一种解决方案虽然可以正常调用函数,但是这不是一种明智的做法,因为这直接违背了泛型编程的初衷。对于类模板也一样,类模板当中的成员函数模板也不要多文件声明和定义分离编译。所以无论如何,在使用模板时,无论是函数模板还是类模板,都不要声明和定义在多文件当中分离编译

下面仅仅是演示一下类模板分离多文件分离编译也会产生链接错误:

// func.h
template <class T>
class Stack
{
public:
	/*类模板中只有声明*/
	Stack(int capacity = 4);
	void push(const T &in);
private:
	T *_elem;
	int _top;
	int _capacity;
};
// func.cpp

#include "func.h"

/*声明与定义分离*/
template <class T>
Stack<T>::Stack(int capacity = 4)
:_top(0), _capacity(capacity)
{
	_elem = new T[_capacity];
}

template <class T>
void Stack<T>::push(const T &in)
{
	/*不考虑扩容......*/
	_elem[_top++] = in;
}
// test.cpp

#include "func.h"
int main()
{
	Stack<int> st;
	st.push(1);
	return 0;
}

解决方案之一便是在定义的文件当中显式实例化:

/*声明与定义分离*/
template <class T>
Stack<T>::Stack(int capacity = 4)
:_top(0), _capacity(capacity)
{
	_elem = new T[_capacity];
}

template <class T>
void Stack<T>::push(const T &in)
{
	/*不考虑扩容......*/
	_elem[_top++] = in;
}

/*显式实例化*/
template class Stack<int>;

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

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

相关文章

重磅!OpenAI最新研究:用GPT-4解释神经元行为,网友:AI套娃?

来源 | 机器之心 这就是 GPT 的「抽象」&#xff0c;和人类的抽象不太一样。 虽然 ChatGPT 似乎让人类正在接近重新创造智慧&#xff0c;但迄今为止&#xff0c;我们从来就没有完全理解智能是什么&#xff0c;不论自然的还是人工的。 认识智慧的原理显然很有必要&#xff0c;如…

K8S 部署 seata

文章目录 创建 Deployment 文件创建 ConfigMap 文件创建 Service 文件运行访问高可用部署踩坑 官方文档 k8s中volumeMounts.subPath的巧妙用法 创建 Deployment 文件 deploymemt.yaml namespace&#xff1a;指定命名空间image&#xff1a;使用 1.5.2 版本的镜像ports&#xf…

加密算法和非对称加密的简单学习

加密算法和非对称加密的简单学习 前言对称加密算法DES特点&#xff1a;为什么不使用&#xff1a; 3DES&#xff08;Triple DES 或者 DESede&#xff09;特点&#xff1a;使用场景&#xff1a;为什么不用&#xff1a; AES&#xff08;Advanced Encryption Standard&#xff09;特…

SoLVES模型的详细使用教程

SoLVES&#xff08;Social Values for Ecosystem Services&#xff09;模型是由美国地质调查局落基山地理科学中心&#xff08;RMGSC&#xff09;和科罗拉多州立大学联合研究开发&#xff0c;主要用于评估生态系统服务的社会价值&#xff0c;能够量化美学、生物多样性、休闲生活…

鸿蒙Hi3861学习十一-Huawei LiteOS-M(内存池)

一、简介 LiteOS将内核与内存管理分开实现&#xff0c;操作系统内核仅规定了必要的内存管理函数原型&#xff0c;而不关心这些内存管理函数是如何实现的。 LiteOS内存管理模块管理系统的内存资源&#xff0c;包括&#xff1a;初始化、分配、释放。 不采用C标准库中的内存管理函…

精准锁定证件材料篡改位置,合合信息智能图像处理技术助力金融机构防范违规开户

得知帮助他人办理几张银行卡、电话卡&#xff0c;就能坐等“分红”&#xff0c;许多人怀着“吃馅饼”的心态掉入了陷阱。今年4月&#xff0c;海南一男子出借银行卡帮助不法分子进行电信网络诈骗&#xff0c;涉案资金流水近10万元&#xff0c;被警方抓捕。此前&#xff0c;西安某…

如何在VUE中使用andflow流程设计组件

andflow_js 是基于js、css、html开发的一个前端流程设计组件&#xff0c;目的是方便前端开发流程设计器。实现各种流程设计的样式风格&#xff0c;以及对设计结果的读取和显示&#xff0c;以便于与将设计结果提供给后端保存和执行。 由于VUE框架在许多实际项目中经常使用&…

安卓稳定性技术栈

涉及技术内容&#xff1a; framework java层 语言&#xff1a;Java 1.开机启动流程&#xff1a;Android 7.0平台开机启动_android7.0开机自启_jamousjang的博客-CSDN博客 Android系统启动流程解析 基于Android13的系统启动流程分析 2.Watchdog机制 3.Anr 机制 4.AMS四大…

Monte carlo 求解积分

Monte carlo 求解积分 文章目录 Monte carlo 求解积分[toc]1 单变量情形2 多变量情形 1 单变量情形 假设待求解积分形式为 θ ∫ 0 1 f ( x ) d x \theta\int_0^1 f(x) \mathrm{d} x θ∫01​f(x)dx 其中 θ \theta θ为积分值。引入随机变量 X ∼ U ( 0 , 1 ) X\sim U(0,1)…

服务攻防-应用协议-远控软件漏洞向日葵VNCTV-平台漏洞KibanaZabbix-附真实案例演示

目录 一、导图 二、远程控制-向日葵&Vnc&Teamviewer 1、向日葵 ▶漏洞利用工具下载地址&#xff1a; ▶实例展示&#xff1a; 2、Vnc ▶Vnc简介&#xff1a; ▶实例展示&#xff1a; 3、Teamviewer ▶Teamviewer简介&#xff1a; ▶实例展示&#xff1a; 三、设备…

小灰的基金,亏了67W。。。

2022年基金市场有多差&#xff1f;相信大家都有目共睹。小灰的基金在去年也赔得很惨&#xff0c;还每次写过几篇文章&#xff1a; 跌吧&#xff0c;继续跌吧&#xff0c;小灰的基金已亏损64万。。。 基金亏损84万&#xff0c;小灰反手把银行客户经理投诉了 今年是疫情结束的第一…

成为Smartbi合伙人,现金奖励可达15000元

2023年Smartbi推出合伙人计划即日起至2023年12月31日只要您成为思迈特软件合伙人推荐有效商机即有机会赢取上万元现金奖励商机奖励1000元&#xff0c;合同签约奖励可达15000元同时我们将为您提供全方位的支持和帮助实现共谋、共创、共赢&#xff01;*点击https://www.smartbi.c…

长文多图一步步讲清楚:DDD理论、建模与代码实现全流程

欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章&#xff0c;主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等&#xff0c;同时欢迎大家加我个人微信「java_front」一起交流学习 1 六个问题 1.1 为什么使用DDD DDD方法论核心是将问题不断分解&#xff0c…

院内导航移动导诊服务体系,院内导航怎么实现?

院内导航怎么实现&#xff1f;经过多年发展&#xff0c;医院规模愈加庞大&#xff0c;尤其是综合性医院&#xff0c;院区面积较大&#xff0c;门诊、医技、住院等大楼及楼区内部设计复杂&#xff0c;科室、诊室数量众多&#xff0c;对于新患者犹如进入了迷宫&#xff0c;客观环…

《花雕学AI》Poe:一个让你和 AI 成为朋友的平台,带你探索 ChatGPT4 和其他 八种AI 模型的奥秘

你是否曾经梦想过&#xff0c;能够在一个平台上&#xff0c;和多种不同的 AI 模型进行有趣、有用、有深度的对话&#xff0c;甚至还能轻松地把你的对话分享给其他人&#xff1f;如果你有这样的梦想&#xff0c;那么 Poe 一站式 AI 工具箱就是你的不二之选&#xff01; Poe 是国…

让AI来告诉你什么叫幽灵堵车

使用环境参考 CocosCreator v3.7.2 ChatGPT 正文 什么是幽灵堵车 堵车&#xff0c;大家都不陌生&#xff01; 堵车时我就思维发散&#xff0c;用 CocosCreator 模拟下堵车应该挺好玩&#xff0c;网上总说高速上最前面如果有个龟速的车&#xff0c;后面能堵车堵个两三公里。…

计算机毕业论文选题推荐|软件工程|系列四

文章目录 导文题目导文 计算机毕业论文选题推荐|软件工程 (***语言)==使用其他任何编程语言 例如:基于(***语言)门窗账务管理系统的设计与实现 得到:基于JAVA门窗账务管理系统的设计与实现 基于vue门窗账务管理系统的设计与实现 等等 题目 基于(***语言)的人脸识别系统…

python定时任务2_celery flower计划任务

启动worker&#xff1a; celery -A tasks worker --loglevelerror --poolsolo worker启动成功 启动beat celery -A tasks beat --loglevelinfo beat启动成功 启动flower celery -A tasks flower --loglevelinfo flower启动成功&#xff0c;然后进入http://localhost:5555 可…

手把手教你怎么搭建自己的ChatGPT和Midjourney绘图(含源码)

AI程序采用NUXT3LARAVEL9开发&#xff08;目前版本V1.1.7&#xff09; 授权方式&#xff1a;三个顶级域名两次更换 1.AI智能对话-对接官方和官方反代&#xff08;markdown输出&#xff09;PS:采用百度与自用库检测文字 2.AI绘图-根据关键词绘图-增加dreamStudio绘画-增加mid…

每日一个小技巧:1分钟告诉你图片怎么转语音

随着科技的不断进步&#xff0c;人们对于信息的获取方式也越来越多样化。而在这些方式中&#xff0c;图片和文字无疑是比较常见的两种。图片以其生动直观的特点吸引了许多人的眼球&#xff0c;而文字则以其更为详尽的信息呈现方式成为了人们了解事物的首选。然而&#xff0c;对…