初识C++ · 模板初阶

news2025/1/13 15:54:52

目录

1 泛型编程

2 函数模板

3 类模板


1 泛型编程

模板是泛型编程的基础,泛型我们碰到过多次了,比如malloc函数返回的就是泛型指针,需要我们强转。

既然是泛型编程,也就是说我们可以通过一个样例来解决类似的问题,比如:

void swap(int& a,int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

就交换问题来说,我们可以交换整型,可以交换浮点型,但是我们可不可以在一个文件里面实现同时交换两种类型呢?实际上对于C语言来说是不可以的,因为C语言没有模板的概念。

可能会说用typedef ,但是用了也只能解决交换其他类型的原因,不能实现同时交换两种不同的类型,也可能说用auto,但是auto不能作为函数参数使用。

这里就需要用到模板了,模板使用到了两个关键字,一个是template ,一个是typename,template是模板的英文名,typename是类型名的英文,还是很好理解的。

使用如下:

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

2 函数模板

相关的关键字只有两个,这里来谈一下使用的问题,具体使用如下:

template <typename T>
//模板
void Swap(T& a,T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int a = 1, b = 2;
	Swap(a, b);
	double c = 1.1, d = 2.2;
	Swap(c, d);
	return 0;
}

可能看起来比较抽象?

模板实现的原理是模板实例化,背后靠的都是编译器,编译器会自动推演需要的类型,所以这里会有一个问题:两次交换调用的是同一个函数吗?

当然不是的,这里可以借助汇编看一下:

汇编层面可以看到调用的函数的地址不一样,所以调用的函数不是一个函数。

模板其实是编译器在负重前面,我们使用模板的时候,编译器自动推演出我们需要的函数,

那么这里就涉及效率问题了,某种意义上来说模板的调用效率确实没有直接调用来的快,但是省事儿,我们不用再去多写那么多行代码了,毕竟函数重载也要多写代码的

基本的使用我们了解了,以后我们使用交换函数也不用自己去实现了,因为库里面有,可以直接进行使用:

现在有一些问题:

使用模板的时候,我们想要交换类型不同的怎么办?

如果我们尝试直接交换a c的话,就会报错,即没有函数模板是可以使用的

为了方便演示使用加法函数:

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

解决方法1->强制转换:

int main()
{
	int a = 1;
	double b = 2.5;
	Add(a,(int)b);
	return 0;
}

但是这种处理方法丢失了原本我们期待的结果,它造成了精度的丢失。

解决方法2->显式实例化:

int main()
{
	int a = 1;
	double b = 2.5;
	double ret = Add<double>(a,b);
	cout << ret;
	return 0;
}
int main()
{
	int a = 1;
	double b = 2.5;
	cout << Add<int,double>(a, b) << endl;
	return 0;
}

 显式实例化可以让编译器不去自动推演参数类型。

我们知道最后的结果是double类型的,所以显式调用double类型的加法函数,调用的写法是函数后面加个<>,里面写要显式调用的类型,但是呢,都差点意思。

解决方法3->多个模板混用 + auto:

template <typename T1,typename T2>
auto Add(const T1& x,const T2& y)
{
	return x + y;
}
int main()
{
	int a = 1;
	double b = 2.5;
	cout << Add(a, b) << endl;
	return 0;
}

既然是多参数,那么我们使用不同的模板就行,但是返回值的话,就用auto即可,这里可以说auto很妙,不会存在丢失精度的问题,也不用显式实例化,也不用强转什么的。

但是有些情况,是必须要显式实例化的:

T* Func(int a)
{
	T* p = (T*)operator new(sizeof(T));
	new(p)T(a);
	return p;
}

返回的是一个指针,但是函数返回什么类型呢?如果没有显式实例化的话是没有办法返回的。

这里就必须要显式实例化了:

int main()
{
	int a = 1;
	Func<int>(a);
	return 0;
}

再补充一个小点,typename可以用class进行代替,二者完全等价的。

template <typename T>
template <class T>

两种写法均可。

这里在引入一个点,叫匹配原则,有合适的优先选择合适的:

//整型的加法函数
int Add(int x,int y)
{
	cout << "int Add(int x,int y)" << endl;
	return x + y;
}
//通用加法函数
template <typename T1,typename T2>
auto Add(const T1& a,const T2& b)
{
	cout << "auto Add(const T1& a,const T2& b)" << endl;
	return a + b;
}
//一般加法
template<typename T>
T Add(const T& left, const T& right)
{
	cout << "T Add(const T& left, const T& right)" << endl;

	return left + right;
}

这里提供了三个函数可以实现加法,一个是类型完全匹配的,一个是通用的加法函数,一个是需要编译器需要自己推演的:

int main()
{
	cout << Add(1, 3) << endl;
	cout << Add(1, 3.3) << endl;
	cout << Add(1.1, 3.1) << endl;
	return 0;
}

那么这里的调用,就会:

第一个,因为完全匹配非模板的函数,所以优先调用;

第二个,因为更匹配通用的加法函数,需要编译器自己推演的力度没有那么大,所以调用两个模板的参数。

第三个,完全需要编译器自己推理,那就只能它了。

所以函数模板的调用也要看谁更匹配,优先使用更匹配的,编译器也想休息~


3 类模板

类模板其实后面用的最多的,现在先做个了解,比如stack,我们要实现两种栈,一个是用来存储int数据,一个是用来存储数据double的,就需要用到模板,不然解决不了一个文件存在两个类型栈的情况:

template<typename T>
class Stack
{
public:
	Stack(size_t capacity = 4)
	{
		_array = (T*)malloc(sizeof(T) * capacity);
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}

	void Push(const T& data);
private:
	T* _array;
	size_t _capacity;
	size_t _size;
};

有了模板之后,我们实现相同的就很容易了,这里如果里面的函数定义和声明分离的话,如果不是同一个.h文件的话,造成的效果是很难想象的,会造成编译的时间大幅度的增加。

分离之后的写法:

template<typename T>
class Stack
{
public:
	Stack(size_t capacity = 4)
	{
		_array = (T*)malloc(sizeof(T) * capacity);
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}

	void Push(const T& data);
private:
	T* _array;
	size_t _capacity;
	size_t _size;
};
template<class T>
void Stack<T>::Push(const T& data)
{
	;
}

创建不同的Stack就显式实例化就可以了:

int main()
{
    stack<int> p1;
    stack<double> p2;
    return 0;
}

模板初阶还是好理解的,后面介绍高阶的。


感谢阅读!

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

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

相关文章

通过AOP实现项目中业务服务降级功能

最近项目中需要增强系统的可靠性&#xff0c;比如某远程服务宕机或者网络抖动引起服务不可用&#xff0c;需要从本地或者其它地方获取业务数据&#xff0c;保证业务的连续稳定性等等。这里简单记录下业务实现&#xff0c;主要我们项目中调用远程接口失败时&#xff0c;需要从本…

Springboot 单体thymeleaf极简门户网站

企业门户网站&#xff0c;基于Springboot和layui 1、原介绍 使用技术&#xff1a;后端框架&#xff1a;SpringBoot&#xff0c;Mybatisplus ### 数据库&#xff1a;MySQL,redis ## 前端框架&#xff1a;Layui ## 权限框架&#xff1a;shiro ## 网页模板引擎&#xff1a;thyme…

Python基础详解三

一&#xff0c;函数的多返回值 def methodReturn():return 1,2x,ymethodReturn() print(x,y) 1 2 二&#xff0c;函数的多种参数使用形式 缺省参数&#xff1a; def method7(name,age,address"淄博"):print("name:"name",age"str(age)&quo…

案例分享:BACnet转Modbus提升暖通系统互操作性

现代智能建筑中系统的集成与互操作性是决定其智能化程度的关键因素。随着技术的发展&#xff0c;不同标准下的设备共存成为常态&#xff0c;而BACnet与Modbus作为楼宇自动化领域广泛采用的通讯协议&#xff0c;它们之间的无缝对接显得尤为重要。本文将通过一个实际案例&#xf…

react状态管理之state

第三章 - 状态管理 随着你的应用不断变大&#xff0c;更有意识的去关注应用状态如何组织&#xff0c;以及数据如何在组件之间流动会对你很有帮助。冗余或重复的状态往往是缺陷的根源。在本节中&#xff0c;你将学习如何组织好状态&#xff0c;如何保持状态更新逻辑的可维护性&…

Densenet+SE

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊# 前言 前言 这周开始学习关于经典模型的改进如加注意力机制&#xff0c;这周学习Densenet加通道注意力即SE注意力机制。 ##SE注意力机制简介 SE&#xff08;…

Flutter笔记:手动配置VSCode中Dart代码自动格式化

Flutter笔记 手动配置VSCode中Dart代码自动格式化 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csd…

景源畅信电商:抖音小店需要请专业人员装修店铺吗?

在数字营销的海洋中&#xff0c;抖音小店如一艘航船&#xff0c;装修得当才能吸引顾客登船。那么&#xff0c;小店是否需要请专业人员来装修呢?答案是肯定的。 一、视觉冲击力是关键 专业设计师擅长运用色彩、布局与图像创造出强烈的视觉冲击力&#xff0c;这对于抓住用户的注…

亲测快捷高效的编写测试用例方法

前言 测试用例是任何测试周期的第一步&#xff0c;对任何项目都非常重要。如果在此步骤中出现任何问题&#xff0c;则在整个软件测试过程中都会扩大影响。如果测试人员在创建测试用例模板时使用正确的过程和准则&#xff0c;则可以避免这种情况。 在本篇文章中将分享一些简单而…

stack的使用

1.栈的定义 我们可以看到模板参数里面有一个容器适配器 &#xff0c;什么是适配器&#xff1f;比如充电器就叫做电源适配器&#xff0c;用在做转换&#xff0c;对电压进行相关的转换适配我们的设备。栈&#xff0c;队列不是自己直接管理数据&#xff0c;是让其他容器管理数据&a…

MVC与MVVM架构模式

1、MVC MVC&#xff1a;Model-View-Controller&#xff0c;即模型-视图-控制器 MVC模式是一种非常经典的软件架构模式。从设计模式的角度来看&#xff0c;MVC模式是一种复合模式&#xff0c;它将多个设计模式结合在一种解决方案中&#xff0c;从而可以解决许多设计问题。 MV…

【Linux】线程的内核级理解详谈页表以及虚拟地址到物理地址之间的转化

一、线程的概念 对于进程来说&#xff0c;进程创建时间和空间成本较高&#xff0c;因为进程是承担分配系统资源的基本实体&#xff0c;所以线程的出现就成为了必然。Linux线程与进程非常相似&#xff0c;Linux设计者在设计之初觉得如果再为线程设计数据结构和调度算法就会使整个…

JUC下的ScheduledThreadPoolExecutor详解

ScheduledThreadPoolExecutor是Java并发编程框架中一个强大且灵活的线程池实现&#xff0c;专为定时与周期性任务而设计。作为ThreadPoolExecutor的子类&#xff0c;它不仅继承了线程池管理的高效与灵活性&#xff0c;还内置了基于优先级队列的延迟任务调度机制&#xff0c;支持…

【stm-4】PWM驱动LED呼吸灯 PWM驱动舵机PWM驱动直流电机

1.PWM驱动LED呼吸灯 void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); //结构体初始化输出比较单元 void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef*…

智能家居4 -- 添加接收消息的初步处理

这一模块的思路和前面的语言控制模块很相似&#xff0c;差别只是调用TCP 去控制 废话少说&#xff0c;放码过来 增添/修改代码 receive_interface.c #include <pthread.h> #include <mqueue.h> #include <string.h> #include <errno.h> #include <…

算法学习(6)-最短路径

目录 Floyd-Warshall算法 Dijkstra算法 Bellman-Ford算法 Bellman-Ford的队列优化 最短路径算法对比分析 Floyd-Warshall算法 现在回到问题&#xff1a;如何求任意两点之间的最短路径呢&#xff1f; 通过之前的学习&#xff0c; 我们知道通过深度或广度优先搜索可以求出两…

学习记录:AUTOSAR R20-11的阅读记录(五)【CP(5.11-5.19)】完

接上回&#xff1a;学习记录&#xff1a;AUTOSAR R20-11的阅读记录&#xff08;四&#xff09;【CP&#xff08;5.6-5.10&#xff09;】 五、CP 11、General&#xff08;4个&#xff09; 5.11 File Name 说明 1 AUTOSAR_EXP_ LayeredSoftwareArchitecture.pdf 描述了AUTO…

多模态大模型MLLM VIT CLIP BLIP

一、Vit模型介绍 Vit&#xff08;Vision Transformer&#xff09;即将Transformer应用于视觉领域。 Transformer输入输出都是一个序列&#xff0c;若需要应用于视觉领域&#xff0c;则需要考虑如何将一个2d图片转化为一个1d的序列&#xff0c;最直观的想法将图片中的像素点输…

merge函数占用内存过大

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

使用python将`.mat`文件转换成`.xlsx`格式的Excel文件!!

要将.mat文件转换成.xlsx格式的Excel文件 第一步&#xff1a;导入必要的库第二步&#xff1a;定义函数来转换.mat文件第三步&#xff1a;调用函数注意事项 要将.mat文件转换成.xlsx格式的Excel文件&#xff0c;并保持文件名一致&#xff0c;你可以使用scipy.io.loadmat来读取.m…