C++模板初阶学习

news2024/11/25 10:27:45

目录

  • 1.函数模板
    • 1.1函数模板概念
    • 1.2函数模板格式
    • 1.3函数模板的原理
    • 1.4函数模板实例化
      • 隐式实例化
      • 显示实例化
    • 1.5模板参数适配原则
  • 2.类模板
    • 2.1类模板的定义格式
    • 2.2类模板实例化
  • 总结

1.函数模板

如何实现一个通用的交换函数呢?

#include<iostream>
using namespace std;
void Swap(int& p1, int& p2)
{
	int tmp = p1;
	p1 = p2;
	p2 = tmp;
}
void Swap(double& p1, double& p2)
{
	double tmp = p1;
	p1 = p2;
	p2 = tmp;
}
void Swap(char& p1, char& p2)
{
	char tmp = p1;
	p1 = p2;
	p2 = tmp;
}
int main()
{
	int a = 10, b = 20;
	cout << "a=" << a << " " << "b=" << b << endl;
	Swap(a, b);
	cout << "a=" << a << " " << "b=" << b << endl;
	double c = 10.1, d = 20.2;
	cout << "c=" << c << " " << "d=" << d << endl;
	Swap(c, d);
	cout << "c=" << c << " " << "d=" << d << endl;
	char x = 'a', y = 'b';
	cout << "x=" << x << " " << "y=" << y << endl;
	Swap(x, y);
	cout << "x=" << x << " " << "y=" << y << endl;
	return 0;
}

代码运行的结果为:
在这里插入图片描述
使用函数重载虽然可以实现,但是有一下几个不好的地方:

  1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错

我们可以使用以下代码实现:

template<class T>
void Swap(T& p1, T& p2)
{
	T tmp = p1;
	p1 = p2;
	p2 = tmp;
}
int main()
{
	int a = 10, b = 20;
	cout << "a=" << a << " " << "b=" << b << endl;
	Swap(a, b);
	cout << "a=" << a << " " << "b=" << b << endl;
	double c = 10.1, d = 20.2;
	cout << "c=" << c << " " << "d=" << d << endl;
	Swap(c, d);
	cout << "c=" << c << " " << "d=" << d << endl;
	char x = 'a', y = 'b';
	cout << "x=" << x << " " << "y=" << y << endl;
	Swap(x, y);
	cout << "x=" << x << " " << "y=" << y << endl;
	return 0;
}

代码运行的结果为:
在这里插入图片描述
上面的代码也可以实现不同类型的交换,其中涉及了模板的知识点,接下来我们一起学习一下。

1.1函数模板概念

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。

1.2函数模板格式

template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
template<typename T>//模板参数--类型
void Swap( T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}

其中,typename是用来定义模板的关键字,也可以使用class,不能使用struct代替class

1.3函数模板的原理

在上面的代码中,我们使用函数模板调用不同类型的数据进行交换,调用的是同一个函数吗?
汇编代码:

	int a = 10, b = 20;
001227FF  mov         dword ptr [a],0Ah  
00122806  mov         dword ptr [b],14h  
	//cout << "a=" << a << " " << "b=" << b << endl;
	Swap(a, b);
0012280D  lea         eax,[b]  
00122810  push        eax  
00122811  lea         ecx,[a]  
00122814  push        ecx  
00122815  call        std::operator<<<std::char_traits<char> > (0121460h)  
0012281A  add         esp,8 
	double c = 10.1, d = 20.2;
0012281D  movsd       xmm0,mmword ptr [__real@4024333333333333 (0129B48h)]  
00122825  movsd       mmword ptr [c],xmm0  
0012282A  movsd       xmm0,mmword ptr [__real@4034333333333333 (0129B58h)]  
00122832  movsd       mmword ptr [d],xmm0  
	//cout << "c=" << c << " " << "d=" << d << endl;
	Swap(c, d);
00122837  lea         eax,[d]  
0012283A  push        eax  
0012283B  lea         ecx,[c]  
0012283E  push        ecx  
0012283F  call        Swap<char> (012145Bh)  
00122844  add         esp,8 

在编译器编译阶段,编译器根据传入的参数,推导出对应的模板参数类型,从而实例化出具体的函数!
在这里插入图片描述
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器

1.4函数模板实例化

实现Add()函数不同类型的数据相加
eg1:

template <class T1,class T2>
T1 Add(T1& x1, T2& x2)
{
	return x1 + x2;
}
int main()
{
	int a = 10;
	double d = 20.1;
	cout << Add(a, d) << endl;
	return 0;
}

代码运行的结果为:
在这里插入图片描述
eg2:

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	int a = 10;
	double d = 20.1;
	cout << Add(a, d) << endl;
	return 0;
}

代码编译运行的结果:
在这里插入图片描述

当函数模板只有一个参数时,而传递不同类型的实参,编译器因调用不明确而无法推导实例化出具体函数而报错,这就涉及函数模板实例化问题。

函数模板实例化概念:

不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化

隐式实例化

隐式实例化:编译器根据实参推演模板参数的实际类型。

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	int a = 10;
	double d = 20.1;
	cout << Add(a, (int)d) << endl;//强制类型转换
	return 0;
}

通过强制类型转换,是传递的数据为同一类型,编译器便可以实例化出具体的函数,需要注意的是强制类型转换的临时变量具有常性,函数参数需要加const修饰,以上便是隐式实例化的过程。

显示实例化

显示实例化:在函数名后的<>中指定模板参数的实际类型
eg1:

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
void Test()
{
	int a = 10;
	double d = 20.1;
	cout << Add<int>(a, d) << endl;
}

代码编译运行的结果为:
在这里插入图片描述

如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译将会报错。

eg2:

template<class T>
T* Alloc(int n)
{
	return new T[n];
}

int main()
{
	int* p = Alloc(10);
	return 0;
}

在这里插入图片描述
以上的代码就不能通过隐式实例化调用模板函数,需要通过函数实例化才能调用。
正确的调用方式:

T* Alloc(int n)
{
	return new T[n];
}

int main()
{
	int* p = Alloc<int>(10);
	return 0;
}

1.5模板参数适配原则

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

// 专门处理int的加法函数
int Add(int left, int right)
{
	return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
	return left + right;
}
void Test()
{
	Add(1, 2); // 与非模板函数匹配,编译器不需要特化
	Add<int>(1, 2); // 调用编译器特化的Add版本
}

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

// 专门处理int的加法函数
int Add(int left, int right)
{
	return left + right;
}
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
	return left + right;
}
void Test()
{
	Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
	Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}

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

// 专门处理int的加法函数
int Add(int left, int right)
{
	return left + right;
}
void Test()
{		
	Add(1, 2.0); // 调用普通函数,编译器会自动转换类型
}
//同类型加法函数
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
void Test()
{		
	Add(1, 2.0); 
	//调用模板函数,编译器不会自动强转而报错,需要自己手动强转或显示实例化
}

2.类模板

2.1类模板的定义格式

eg1:

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

实现不同类型的栈:

typedef int DataType;
class StackInt
{
public:
	StackInt(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = capacity;
		_size = 0;
	}

	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}

	// 其他方法...

	~StackInt()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}

private:
	DataType* _array;
	int _capacity;
	int _size;
};

typedef double DataType;
class StackDouble
{
public:
	StackDouble(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = capacity;
		_size = 0;
	}

	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}

	// 其他方法...

	~StackDouble()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}

private:
	DataType* _array;
	int _capacity;
	int _size;
};

int main()
{
	StackInt s1; // int
	StackDouble s2; // double

	return 0;
}

平时我们实现要使用栈存储不同类型的数据的时候,需要不同的栈的才能实现,需要对已有其他类型的栈进行相应的修改,即类名以及函数名都要修改;当我们需要添加不同功能函数时,不同的栈都要实现一遍,我们可以使用类模板解决这个问题!

eg2:

template<class T>
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		_array = (T*)malloc(sizeof(T) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = capacity;
		_size = 0;
	}

	void Push(T data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}

	// 其他方法...

	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}

private:
	T* _array;
	int _capacity;
	int _size;
};
int main()
{
	Stack<int> s1; // int
	Stack<double> s2; // double
	return 0;
}

使用类模板实现不同类型的栈,需要显示实例化。

eg3:

template<class T>
class Stack
{
public:
//声明
	Stack(size_t capacity = 3);

	void Push(T data);

	// 其他方法...

	~Stack();

private:
	T* _array;
	int _capacity;
	int _size;
};
//定义
template<class T>
Stack<T>::Stack(size_t capacity)
{
	_array = (T*)malloc(sizeof(T) * capacity);
	if (NULL == _array)
	{
		perror("malloc申请空间失败!!!");
		return;
	}

	_capacity = capacity;
	_size = 0;
}
template<class T>
void Stack<T>::Push(T data)
{
	// CheckCapacity();
	_array[_size] = data;
	_size++;
}
template<class T>
Stack<T>::~Stack()
{
	if (_array)
	{
		free(_array);
		_array = NULL;
		_capacity = 0;
		_size = 0;
	}
}
int main()
{
	Stack<int> s1; // int
	Stack<double> s2; // double
	return 0;
}

使用类模板,要实现类成员函数声明和定义分离:①要指定函数的类模板域,如上代码需要在函数名前面加上Stack<int>::②类模板的作用域在最近的{}域里面,在类外面实现成员函数,需要结合类模板一起实现。

2.2类模板实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

//类模板与普通类的区别
//普通类:类名和类型一样,如用普通类实现栈,类名和类型都为Stack
//类模板:类名和类型不同,如用类模板实现栈,类名为Stack,类型为Stack<T>
//类模板实例化
Stack<int> s1; // int
Stack<double> s2; // double

总结

本章我们一起初步学习了C++模板的相关知识,希望对大家了解C++模板有些许帮助,感谢大家阅读,如有不对欢迎纠正!🎠🎠🎠

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

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

相关文章

软件外包开发的JAVA开发框架

Java的开发框架有很多&#xff0c;以下是一些常见的Java开发框架及其特点&#xff0c;每个框架都有其特定的使用场景和优势&#xff0c;开发者可以根据项目的需求选择合适的框架。今天和大家介绍常见的框架及特点&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&…

Day50 算法记录| 动态规划 17(子序列)

这里写目录标题 647. 回文子串516.最长回文子序列总结 647. 回文子串 1.动态规划和2.中心扩展 这个视频是基于上面的视频的代码 方法1:动态规划 布尔类型的dp[i][j]&#xff1a;表示区间范围[i,j] &#xff08;注意是左闭右闭&#xff09;的子串是否是回文子串&#xff0c;如…

面向切面编程(SpringAOP)、通过注解实现AOP代码、AOP的工作流程

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaweb 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 SpringAOP 一、AOP简介1.1连接点&#xff08;JoinPoint&am…

IntelliJ IDEA 2023.1.3 主菜单不见了

通过以下操作&#xff0c;去掉了勾&#xff0c;把主菜单玩没了 然后主菜单找半天都不知道怎么回来&#xff0c;下面记录找回来的过程 双击shift&#xff0c;在弹出的菜单里面搜索 "main menu"&#xff0c;在下图高亮位置选项改为 on

火警智能感知定位 智慧消防大数据可视化监测系统

伴随着城市建设的快速发展&#xff0c;城市消防安全风险的不断上升&#xff0c;城市高层、超高层建筑和大型建筑日益增多&#xff0c;建筑消防安全问题越来越突出。 建设背景 市场背景 近年来&#xff0c;我国多数省级以上城市、90%左右地级以上城市均提出了智慧消防建设计划…

无涯教程-jQuery - width( )方法函数

width()方法获取第一个匹配元素的当前计算像素宽度。 width( ) - 语法 selector.width( ) 此方法能够找到窗口和文档的宽度&#xff0c;如下所示: $(window).width(); //返回浏览器窗口的宽度 $(document).width(); //返回 HTML 文档的宽度 width( ) - 示例 <html&g…

如何用C#实现上位机与下位机之间的Wi-Fi通信?

有IP协议支持的话用UDP报文或者TCP直接发IP地址和端口不行么&#xff1f;你说的WiFi难道是2.4GHz频率模块那种东东&#xff1f; 你既然用了wifi&#xff0c;那么只要上位机和下位机的对应wifi网卡都具有ip地址以及其协议支持&#xff0c;那么和网络编程没啥子明显区别的吧………

pycharm制作柱状图

Bar - Bar_rotate_xaxis_label 解决标签名字过长的问题 from pyecharts import options as opts from pyecharts.charts import Barc (Bar().add_xaxis(["高等数学1&#xff0c;2","C语言程序设计","python程序设计","大数据导论",…

MBA拓展有感-见好就收,还是挑战到底?MBA拓展有感-见好就收,还是挑战到底?

今天看到新闻提到某位坚持了14年高考的同学滑档&#xff0c;让人心生感叹&#xff1a;无论在日常工作还是生活中&#xff0c;选择都是非常重要的。不由想起前段时间我参加研究生新生拓展时的一些感悟&#xff0c;和大家分享一下。 事情的起因是拓展活动中的一个分队竞技类的活…

利用DIE、de4dot和dnspy进行反编译

1.数据准备 1.软件 知云翻译7.0这个版本好破解&#xff0c;该软件是C#写的程序 DIE&#xff08;Detect it Easy&#xff09;该软件是进行查看软件的壳及编译程序&#xff0c;详细了解EXE软件 de4dot是对PE进行脱壳处理&#xff0c;源码下载链接&#xff1a;GitHub - de4dot…

JAVA开发工具-maven的安装与配置(最新最详细教程)

引言 Maven项目对象模型(POM)&#xff0c;可以通过一小段描述信息来管理项目的构建&#xff0c;报告和文档的项目管理工具 软件。 Maven 除了以程序构建能力为特色之外&#xff0c;还提供高级项目管理工具。由于 Maven 的缺省构建规则有较 高的可重用性&#xff0c;所以常常用两…

lc1074.元素和为目标值的子矩阵数量

创建二维前缀和数组 两个for循环&#xff0c;外循环表示子矩阵的左上角&#xff08;x1,y1&#xff09;&#xff0c;内循环表示子矩阵的右下角&#xff08;x2,y2&#xff09; 两个for循环遍历&#xff0c;计算子矩阵的元素总和 四个变量&#xff0c;暴力破解的时间复杂度为O(…

点云处理——terrasolid教程

加载terrasolid软件模块 3、通过microstation的utilities->mdl applications加载terrasolid四个模块,加载成功后将显示tscan和tphoto的主窗口&#xff0c;以及四个模块的主工具箱。 浏览点云 4、显示点云坐标信息(类&#xff0c; 航带号&#xff0c;GPS信息&#xff0c;东…

小夜灯的体势红外传感器 > 红外知识学习

红外是电磁辐射谱中的一部分&#xff0c;它位于可见光谱的红色边缘之外&#xff0c;具有较长的波长。可见光谱是人眼能够感知的电磁辐射范围&#xff0c;而红外光的波长较长&#xff0c;人眼无法感知。 生命光的范围是6~14um 红外光的波长范围一般约为0.7um~1000um&#xff08;…

多模块Springboot项目maven单独打包子模块

背景介绍 最近接手一个项目代号XXL&#xff0c;是一个多模块的Springboot项目&#xff0c;在解决了线上的bug之后&#xff0c;想单独给子模块打包上线部署&#xff0c;问题来了。如果整个工程一起mvn -X -DskipTests clean package&#xff0c;打包出来的XXL-web.jar是可以正常…

打通数据治理全链路,火山引擎DataLeap数据治理平台公有云版本正式发布

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 近日&#xff0c;火山引擎DataLeap正式对外发布数据治理平台公有云版。DataLeap是火山引擎大数据研发治理套件&#xff0c;随着其子套件数据治理平台与CDH引擎底座成…

等待已久,新品上市 | RevPi Connect 4系列:基于树莓派CM4计算模块的全新工业树莓派

新品来袭 势不可挡 备受期待的虹科工业树莓派第四代产品—RevPi Connect 4终于来啦&#xff01;作为全球领先的工业自动化产品&#xff0c;RevPi Connect 4融合了工业树莓派多年技术积累与创新突破&#xff0c;以及现代物联网技术的结晶。无论您是行业领先者、工程师还是智能科…

tinkerCAD案例:29. 摇头娃娃

Research Your Favorite Bobblehead 摇头娃娃 Project Overview: 项目概况&#xff1a; Design and create your favorite Minecraft 3D bobble head. All you need is a computer, 3D printer, spring and your creativity to your favorite Minecraft character in the for…

远程访问本地mysql

文章目录 一、设置本地mysql允许外部访问找到mysql配置文件my.ini &#xff0c;linux环境是my.cnf配置mysql配置文件 二、创建外部访问的mysql用户三、配置mysql用户的权限四、配置防火墙端口五、连接查看本地ip地址 参考 连接命令 mysql -h <host> -P <port> -u &…

Hive/Spark/Yarn: User Not Found 错误和 Kerberos / AD / OpenLDAP 之间的关系与解释

有时候,当你向Spark或Hive提交作业时,可能会遇到如下的错误: 提交作业使用的用户是example-user-1,但是Yarn返回的错误信息是:该用户不存在。类似的问题大多发生在启动了Kerberos的Hadoop集群上,或者集群集成了Windows AD或OpenLDAP后。本文,我们把这个问题梳理清楚并给…