C++模板(初识)

news2024/9/24 5:32:15

一、泛型编程

我们平时写交换函数的时候,会这样写:

//交换两个int类型变量
void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}
//交换两个double类型变量
void Swap(double& left, double& right)
{
	double temp = left;
	left = right;
	right = temp;
} 
//交换两个char类型变量
void Swap(char& left, char& right)
{
	char temp = left;
	left = right;
	right = temp;
}

这样写相比于C语言已经很方便了,因为C++支持函数重载和引用。

使用函数重载虽然可以实现,但是有几个不好的地方:

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

但这样写又会感觉怪怪的,它们的大体框架一摸一样,实现的功能也一摸一样,就是要交换的变量类型不同,有没有一种方式能解决这种问题?

这时候模板就应运而生了,编译器会根据不同的类型利用该模板来生成相应代码。有函数模板类模板两种。

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

二、函数模板

1、函数模板格式

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

函数模板格式:

template<typename T1, typename T2,......,typename Tn> 

返回值类型 函数名(参数列表){}

其中, T1,T2...是一种类型,具体个数自己设定。

对于上述的代码,我们可以写一个函数模板:

template<typename T>
void Swap( T& a, T& b)
{
    T temp = a;
    a = b;
    b = temp;
}

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

这样一个模板就可以解决上述出现的问题:

template<class T>
void Swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int i = 1, j = 2;
	cout << "Swap Before:" << i << "  " << j << endl;
	Swap(i, j); //调用时,T会是int类型
	cout << "Swap After:" << i << "  " << j << endl;

	double m = 1.1, n = 2.2;
	cout << "Swap Before:" << m << "  " << n << endl;
	Swap(m, n); //调用时,T会是double类型
	cout << "Swap After:" << m << "  " << n << endl;

	return 0;
}

2、函数模板原理

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。
所以其实模板就是将本来应该我们做的重复的事情交给了编译器。简单来说就是原本我需要写3个函数,现在我只写了1个模板,调用时,其实本质上还是调用了3个函数,只是那是编译器的工作,它会帮助我们来进行具体调用,我们只用写1个模板就行,减少了我们的工作量,增加了编译器的工作量而已,当然,编译器不会喊"累"的。

在调试过程中,它们Swap(i, j);Swap(m, n);都会走模板,但实质上它们走的不是同一个函数,我们通过汇编代码可以看出:

进而说明了是编译器帮助我们生成各自的函数。像下面这样:

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用(调用的不是同一个函数),比如:当用double类型使用函数模板时,编译器通过对实参类型的推演, 将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。用函数模板生成对应的函数这一过程被称为模板的实例化。

T到底是什么类型是根据实参传递给形参是推导出来的。

上述举例中函数模板只有一个参数T:

template<class T>
void Swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int i = 1, j = 2;
	double m = 1.1, n = 2.2;

	Swap(i, j);//ok
	Swap(m, n);//ok

	Swap(i, m);//err,参数模板只有一个类型,传参时就不能传2个不同类型的变量
    //因为编译器不知道T到底是int类型还是double类型

	return 0;
}

函数模板也可以有多个参数:

template<class T1, class T2>
void Func(T1& x,T2& y)
{
	//...
}
int main()
{
	int i = 1;
	double j = 1.1;
	Func(i, j);  //这里就能判断出T1是int类型,T2是double类型
	return 0;
}

3、单参函数模板可能遇到的情况

单参函数模板若传两个不同类型的变量就会报错,因为编译器分不清函数模板中的参数类型到底是哪一个。

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

int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	Add(a1, a2);  //这没问题,因为a1和a2的类型一样
	Add(d1, d2);  //这也没问题,d1和d2的类型一样

    Add(a1,d1);  //err,这里就会报错,因为编译器不知道T到底是int类型还是double类型

    return 0;
}

 那有什么办法可以解决这种问题呢?

(1)强制类型转换

将两个不同类型的变量,其中一个强制转换成另一个,这样编译器就可以推导出T的类型了,这个过程就是推导实例化。

	//推导实例化
	cout << Add(a1, (int)d1) << endl;
	cout << Add((double)a1, d1) << endl;

 (2)显示实例化

	//显示实例化
	cout << Add<int>(a1, d1) << endl;
	cout << Add<double>(a1, d1) << endl;

这段代码的意思就是,直接显示说明T的类型,第一行T的类型是int,d1是double类型会走隐式类型转换(转换成int类型),第二行T的类型是double,a1是int类型也会走隐式类型转换(转换成doubel类型)。

(3)改为两个参数的函数模板(具体几个参数看题目要求)

template<class T1,class T2>
T1 Add(const T1& a, const T2& b)
{
	return a + b;
}

这样就不会有歧义了。

有时候必须用显示实例化,比如:

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

int main()
{
    func(3);
    return 0;
}

这种情况,推导不出来T是什么类型。必须用显示实例化,说明T的类型。

double* p1 = func<double>(10);//这里必须显式实例化,T是double类型

4、函数模板和具体函数同时存在

如果函数模板和具体函数同时存在,会先调用哪一个?

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

int Add(const int& a, const int& b)
{
	return (a + b) * 10;
}

int main()
{
	int a1 = 10, a2 = 20;
	cout << Add(a1, a2) << endl;

	return 0;
}

运行结果:

编译器会优先调用具体的函数。俗话说的好,能省即省,我放着现成的不用为什么要走模板二次加工呢? 

三、类模板

1、类模板格式

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

这里的class也可以用typename,其它的和函数模板差不多。

我们先来写一个类模板:

//类模板
template<typename T>
class Stack
{
public:
	Stack(int n = 4)
		:_array(new T[n])
		, _capacity(n)
		, _size(0)
	{
	}
	~Stack()
	{
		delete[] _array;
		_array = nullptr;
		_size = _capacity = 0;
	}
	void Push(const T& x)
	{
		if (_size == _capacity)
		{
			//手动扩容
			T* tmp = new T[_capacity * 2];
			memcpy(tmp, _array, sizeof(T) * _size);
			delete[] _array;

			_array = tmp;
			_capacity *= 2;
		}

		_array[_size++] = x;
	}

private:
	T* _array;  //栈开辟空间的起始指针
	int _capacity;  //栈的容量
	int _size;  //栈中元素个数
};

 这个模板的主要功能是,模拟一个栈,写了一个Push成员函数用来向栈顶添加元素。栈中的成员类型是T。

我们要实现的是动态栈,即容量不够会自动扩容,自动扩容的机制需要我们自己写。

什么时候要判断容量是否满了?

这里其实就是Push时,像栈顶添加元素前要判断栈中容量是否能装下这个元素。

在C语言中有realloc函数支持扩容,但C++中可没有,我们可以手动写一个:

if (_size == _capacity)
{
	//手动扩容
	T* tmp = new T[_capacity * 2];  //扩为原来的2倍
	memcpy(tmp, _array, sizeof(T) * _size);  //拷贝原来的内容到新空间
	delete[] _array; //释放原有空间

	_array = tmp; //_array再次成为指向空间首位置的指针
	_capacity *= 2; //容量扩2倍
}

_array[_size++] = x; //在栈顶添加新元素同时_size需要加1

进入Push,首先要判断是否需要扩容,扩容时开辟原来两倍的空间(没有定性,一般都是2倍),再用memcpy函数将原有的空间的内容拷贝到新开辟的空间,释放原有的空间。这就是大致过程。

2、类模板实例化

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

int main()
{
	//类模板都是显示实例化
	Stack<int> st1;  //栈中的成员都是int类型
	st1.Push(1);
	st1.Push(2);
	st1.Push(3);

	Stack<double> st2; //栈中的成员都是double类型
	st2.Push(1.1);
	st2.Push(2.2);
	st2.Push(3.3);
	return 0;
}

为什么必须显示实例化呢?

因为如果不用显示实例化,模板中的T根本推导不出来是什么类型,那这个模板还有什么用处呢?

所以必须要显示实例化。

3、类模板中函数声明和定义分离

在同一文件下,类模板中,我们也可以让其中的函数的声明和定义分离:

//类模板
template<typename T>
class Stack
{
public:
	Stack(int n = 4)
		:_array(new T[n])
		, _capacity(n)
		, _size(0)
	{
	}
	~Stack()
	{
		delete[] _array;
		_array = nullptr;
		_size = _capacity = 0;
	}
	//声明
	void Push(const T& x);

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

//定义
template<typename T>
void Stack<T>::Push(const T& x)
{
	if (_size == _capacity)
	{
		//手动扩容
		T* tmp = new T[_capacity * 2];
		memcpy(tmp, _array, sizeof(T) * _size);
		delete[] _array;

		_array = tmp;
		_capacity *= 2;
	}

	_array[_size++] = x;
}

平时在类中,函数的声明和定义可不是这样写的?

void Stack::Push(const T& x)

但在模板中这样写是不行的,template<typename T>只管下面的一个类或函数,到了Push时就管不到了,所以Push中的T编译器是不认识的,所以我们应该这样写:void Stack<T>::Push(const T& x)。

模板中的函数的定义和分离尽量不要写到两个文件中,也可以理解为不能写到两个文件中。

四、总结

本篇到这里就结束了,主要写了模板的一些基本知识,希望对大家有所收获,祝大家天天开心!

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

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

相关文章

随着人们网络安全意识提高,软件架构设计与评估也成为重中之重

目录 案例 【题目】 【问题 1】(13 分) 【问题 2】(12分) 【答案】 【问题 1】答案 【问题 2】答案 相关推荐 案例 阅读以下关于软件架构设计与评估的叙述&#xff0c;回答问题 1 和问题 2。 【题目】 某电子商务公司为正更好地管理用户&#xff0c;提升企业销售业绩&…

Linux中Ubuntu系统安装Windows得字体

背景 安装了geoserver 然后geoserver中需要用到微软雅黑字体 所以需要安装一下Linux系统安装Windows中的字体 创建字体目录 cd /usr/share/fonts/ mkdir winfont在Windows找到对应字体 C:\Windows\Fonts 复制该字体到桌面 Linux系统中上传字体 roottest-server03:/usr/sha…

一键解决物流追踪难题:批量查询工具助力电商运营

探索固乔科技&#xff0c;解锁高效物流查询新纪元&#xff01;固乔快递批量查询助手&#xff0c;一款专为电商、物流从业者及自媒体人打造的神器&#xff0c;让繁琐的物流追踪变得轻松快捷。 想象一下&#xff0c;万级单号批量导入&#xff0c;仅需5分钟&#xff0c;所有物流动…

如何利用mHand Pro动捕数据手套连接虚拟与现实?

数据手套作为虚拟现实中的一种交互动捕设备&#xff0c;能够模拟真人手部的动作和感知反馈&#xff0c;实现人机交互的效果。随着虚拟现实技术的不断发展&#xff0c;数据手套也在不断地改进和升级。 mHand Pro是一款由拥有多年经验的惯性动作捕捉技术团队广州虚拟动力研发的数…

第142天: 内网安全-权限维持黄金白银票据隐藏账户C2 远控RustDeskGotoHTTP

案例一&#xff1a; 内网域&单机版-权限维持-基于用户-隐藏用户 项目下载地址&#xff1a; GitHub - wgpsec/CreateHiddenAccount: A tool for creating hidden accounts using the registry || 一个使用注册表创建隐藏帐户的工具 用这个工具的话在域中会把它加入adminis…

会声会影哪个版本最好用?

会声会影哪个版本最好用? 会声会影2023这个版本是最受欢迎的&#xff0c;它为多数用户提供了稳定且强大的功能。以下是关于为什么这个版本最好用的 一、功能丰富 会声会影X系列版本拥有从视频剪辑、音频编辑到特效添加等全方位的功能。用户可以轻松完成视频的录制、剪辑、转…

SAP ABAP 程序迁移工具 SAPLINK ABAP GIT

SAP ABAP 程序迁移工具 SAPLINK ABAP GIT 1. saplink 这个工具功能挺强大的. 但是使用起来有点麻烦, 需要针对不同的开发对象导入不同的插件.才能处理特定的对象. 而且版本还在不断变化. saplink 项目地址&#xff1a;https://github.com/sapmentors/SAPlink saplink plugin…

鸿蒙轻内核M核源码分析系列六 任务及任务调度(1)任务栈

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 持续更新中…… 继续分析鸿蒙轻内核源码&#xff0c;我们本文开始要分析下任务及任务调度模块。首先&#xff0c;我们介绍下任务栈的基础概念。任务栈是高…

SpringBoot整合Minio及阿里云OSS(配置文件无缝切换)

SpringBoot整合Minio及阿里云OSS 文章目录 SpringBoot整合Minio及阿里云OSS1.Minio安装测试1.Docker安装启动容器 2.创建bucket3.上传文件修改权限 2.SpringBoot整合Minio及阿里云OSS1.公共部分抽取2.Minio配置整合1.添加pom依赖2.添加配置文件3.操作接口实现 3.阿里云OSS配置整…

Class4——Esp32|Thonny两种方式同过电脑控制LED灯,路由器与电脑自带热点连接ESP32

上一节我们通过路由器和设备创建了连接&#xff0c;不懂可按上节配置 Class3——Esp32|Thonny——网络连接主机-wifi连接&#xff08;源代码带教程&#xff09;-CSDN博客文章浏览阅读57次。Esp32|Thonny网络连接主机-wifi连接&#xff08;源代码带教程&#xff09;https://blo…

免费开源的低代码表单FormCreate安装教程,支持可视化设计,适配移动端

低代码表单FormCreate 是一个可以通过 JSON 生成具有动态渲染、数据收集、验证和提交功能的表单生成组件。它支持 6 个 UI 框架&#xff0c;适配移动端&#xff0c;并且支持生成任何 Vue 组件。内置 20 种常用表单组件和自定义组件&#xff0c;再复杂的表单都可以轻松搞定 源码…

网页时装购物:Spring Boot框架的创新应用

第2章相关技术 2.1 B/S架构 B/S结构的特点也非常多&#xff0c;例如在很多浏览器中都可以做出信号请求。并且可以适当的减轻用户的工作量&#xff0c;通过对客户端安装或者是配置少量的运行软件就能够逐步减少用户的工作量&#xff0c;这些功能的操作主要是由服务器来进行控制的…

时尚购物革命:Spring Boot技术在网页时装系统中的应用

第1章 绪论 1.1背景及意义 随着社会的快速发展&#xff0c;计算机的影响是全面且深入的。人们生活水平的不断提高&#xff0c;日常生活中人们对时装购物系统方面的要求也在不断提高&#xff0c;喜欢购物的人数更是不断增加&#xff0c;使得时装购物系统的开发成为必需而且紧迫的…

Rspack 1.0 发布了!

文章来源&#xff5c;Rspack Team 项目地址&#xff5c;https://github.com/web-infra-dev/rspack Rspack 是基于 Rust 编写的下一代 JavaScript 打包工具&#xff0c; 兼容 webpack 的 API 和生态&#xff0c;并提供 10 倍于 webpack 的构建性能。 在 18 个月前&#xff0c;我…

深度学习 --- VGG16能让某个指定的feature map激活值最大化图片的可视化(JupyterNotebook实战)

VGG16能让某个指定的feature map激活值最大化图片的可视化 在前面的文章中&#xff0c;我用jupyter notebook分别实现了&#xff0c;预训练好的VGG16模型各层filter权重的可视化和给VGG16输入了一张图像&#xff0c;可视化VGG16各层的feature map。深度学习 --- VGG16卷积核的可…

说一下场外的伦敦银交易的技巧

在很多讨论伦敦银交易技巧的文章中&#xff0c;一上来就介绍各种交易指标、K线信号等等&#xff0c;这种开门见山的方式很直接也很方便&#xff0c;但也容易忽略了一些场外的技巧&#xff0c;下面我们就来讨论一下场外的关于伦敦银交易的技巧。 何为场外的技巧呢&#xff1f;场…

Java进阶13讲__第十讲__精简

字节流 字节输入流&#xff1a;FileInputStream&#xff08;原始流/低级流&#xff09; 字节缓冲流&#xff1a;BufferedInputStream&#xff08;包装流/处理流&#xff09; 参数是"低级流" 字节输入流/缓冲流常用格式 byte[] arr new byte[1024];//字节流 int l…

灯塔:MYSQL笔记(2)函数

函数 是指一段可以直接被另一段程序调用的程序或代码。 字符串函数 SELECT 函数(参数); 数值函数 SELECT 函数(参数); -- 生成一个六位验证码 select lpad(round(rand()*1000000,0) ,6,0)as 验证码; 日期函数 流程函数 总结&#xff1a; 约束&#xff1a; 1. 概述&#xff…

vim 安装与配置教程(详细教程)

vim就是一个功能非常强大的文本编辑器&#xff0c;可以自己DIY的那种 &#xff0c;不但可以写代码 &#xff0c;还可编译 &#xff0c;可以让你手不离键盘的完成鼠标的所有操作。 如果想要了解vim的的发展历史和详细解说&#xff0c;可以自行上网搜索&#xff0c;我主要是记录一…

第T10周:数据增强

>- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营](https://mp.weixin.qq.com/s/0dvHCaOoFnW8SCp3JpzKxg) 中的学习记录博客** >- **&#x1f356; 原作者&#xff1a;[K同学啊](https://mtyjkh.blog.csdn.net/)** 在本教程中&#xff0c;你将学会如何进行数…