【C++初阶】初识模板

news2024/11/8 13:52:55

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


目录

  • 一、 泛型编程
  • 二、函数模板
    • 2.1 函数模板概念
    • 2.2 函数模板格式
    • 2.3 例子演示
    • 2.4 函数模板的原理
    • 2.5 函数模板的实例化
      • 2.5.1 概念
      • 2.5.1 隐式实例化
      • 2.5.2显示实例化
  • 三、类模板
    • 3.1 类模板的定义
    • 3.2 为什么会有类模板(实际作用?)
    • 3.3 例子演示
    • 3.4 类模板的实例化
    • 3.5 类模板的声明和定义分离

一、 泛型编程

假设在一个项目中,要交换intchardouble等类型的数据,就要写出至少3个类型交换函数:

#include <iostream>
using namespace std;

void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

void Swap(double& x, double& y)
{
	double tmp = x;
	x = y;
	y = tmp;
}

void Swap(char& x, char& y)
{
	char tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 1, b = 2;
	Swap(a, b);
	cout << "a = " << a << ' ' << "b = " << b << endl;

	double c = 1.1, d = 2.2;
	Swap(c, d);
	cout << "c = " << c << ' ' << "d = " << d << endl;


	char e = 'a', f = 'b';
	Swap(e, f);
	cout << "e = " << e << ' ' << "f = " << f << endl;

	return 0;
}

【程序结果】

在这里插入图片描述

以上代码就使用 函数重载,虽然可以实现交换逻辑,但是有一下几个不好的地方:

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

那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?

答案当然是可以的!C++泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。

二、函数模板

2.1 函数模板概念

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

2.2 函数模板格式

 typename -- 类型名(照抄)
 Tn -- 变量名(名字可以随便取)

template<typename T1, typename T2, ......, typename Tn>
// 或者还能用class
template<classT1, classT2, ......, class Tn>

返回值类型 函数名(参数列表) 
{
	// 代码逻辑
}

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

2.3 例子演示

例如用函数模板把以上三个交换函数改成:

#include <iostream>
using namespace std;

template<typename T>
void Swap(T& x, T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}


int main()
{
	int a = 1, b = 2;
	Swap(a, b);
	cout << "a = " << a << ' ' << "b = " << b << endl;

	double c = 1.1, d = 2.2;
	Swap(c, d);
	cout << "c = " << c << ' ' << "d = " << d << endl;


	char e = 'a', f = 'b';
	Swap(e, f);
	cout << "e = " << e << ' ' << "f = " << f << endl;

	return 0;
}

【程序结果】

在这里插入图片描述

2.4 函数模板的原理

函数模板是一个蓝图,它本身并不是函数是编译器用使用方式产生特定具体类型函数的模具。所以其实 模板就是将本来应该我们做的重复的事情交给了编译器

我们可以通过反汇编来查看其底层原理:

在这里插入图片描述

通过观察反汇编我们发现:在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用int类型使用函数模板时,编译器通过对实参类型的推演,将T确定为int类型,然后产生一份专门处理int类型的代码,对于其它类型也是如此

2.5 函数模板的实例化

2.5.1 概念

函数模板根据调用,编辑器会自己推导模板参数的类型,实例化出对应的函数,这称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化

2.5.1 隐式实例化

让编译器来推演模板参数的实际类型。

【例子】

#include <iostream>
using namespace std;

template<typename T>

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

int main()
{
	int a = 1, b = 2;
	double d1 = 1.1, d2 = 2.2;

	cout << Add(a, b) << endl;
	cout << Add(d1, d2) << endl;

	return 0;
}

【程序结果】

在这里插入图片描述

在以上代码中以Add(a, b)为例,ab在传参时,编辑器就推段出Tint类型。所以代码在编辑器的眼里是 以下这样的:

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

那现在假设是a + d1结果会是如何呢?

在这里插入图片描述

答案已经很明显了,当然不行!原因是:在编译期间,当编译器看到该函数实例化时,通过实参a推演T的类型为int,通过实参d1推演T的类型为double,然而模板参数列表只有一个T,因此编译器无法确定Tint还是double,故导致参数T不明确。

有两种方法可以解决以上问题

  • 第一种:用户自己强制转化
// 输出的答案是int类型
cout << Add(a, (int)d1) << endl;

// 输出的答案是double类型
cout << Add((double)a, d1) << endl;

【程序结果】

在这里插入图片描述

  • 第二种方法就要涉及显示实例化

2.5.2显示实例化

在函数名后的<>中指定模板参数的实际类型

// 输出的答案是int类型
cout << Add<int>(a, d1) << endl;

// 输出的答案是double类型
cout << Add<double>(a, d1) << endl;

【程序结果】

在这里插入图片描述

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

但显示实例化在实现中并不常用,但以下场景是最经典的

假设要写一个函数,要求是返回一块动态开辟的空间

template<typename T>

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

int main()
{
	New(10); // 这是错误的

	return 0;
}

【程序结果】

**加粗样式**

New(10)是错误的。原因是:实参传给形参会推演类型,然而形参并没有T类型接收,就导致了编译器无法推导T的类型。

正确的方法是用显示实例化

int main()
{
	// 显示实例化
	int* p = New<int>(10); 

	delete p;

	return 0;
}

三、类模板

3.1 类模板的定义

// 定义格式

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

class 类模板名
{
	// 类内成员定义
};

3.2 为什么会有类模板(实际作用?)

假设要求每次实例化出的对象所存储的是不同类型的数据,那么有的人就会写n次不同类型的类,这无疑是增加了代码量(如下所示)。因此就有了类模板,编译器同样是根据实参推演出T的类型。

#include <iostream>
#include <stdlib.h>
using namespace std;

typedef int DataType;
class StackInt
{
public:
	StackInt(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
			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;
}

3.3 例子演示

#include <iostream>
#include <stdlib.h>
using namespace std;

template<class T>

class Stack
{
public:
	Stack(int capacity = 3)
	{
		a = (T*)malloc(sizeof(T) * capacity);
		if (NULL == a)
		{
			return;
		}

		capacity = capacity;
		size = 0;
	}

	void Push(T data)
	{
		a[size] = data;
		size++;
	}
private:
	T* a;
	int capacity;
	int size;
};

3.4 类模板的实例化

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

int main()
{
	// 类模板的实例化
	Stack<int> s1;    // int
	Stack<double> s2; // double
	Stack<char> s3;   // char
}

3.5 类模板的声明和定义分离

#include <iostream>
#include <stdlib.h>
using namespace std;

template<class T>

class Stack
{
public:
	Stack(int capacity = 3);

	void Push(T data);

private:
	T* a;
	int capacity;
	int size;
};

template<class T>
Stack<T>::Stack(int capacity)
{
	a = (T*)malloc(sizeof(T) * capacity);
	if (NULL == a)
	{
		return;
	}

	capacity = capacity;
	size = 0;
}

template<class T>

void Stack<T>::Push(T data)
{
	a[size] = data;
	size++;
}

需要注意的是:

  • 普通类,类名和类型是一样
  • 类模板,类名和类型不一样

因此以上代码中,类名:Stack; 类型:Stack<T>

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

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

相关文章

vue-router的实现原理hash/history、导航守卫、导航解析流程

文章目录 一、SPA与前端路由二、vue-router实现原理&#xff08;模式&#xff09;hash模式history模式 三、vue-router中的route和router四、vue-router有哪几种导航守卫全局守卫路由独享的守卫路由组件内的守卫 vue-router完整的导航解析流程 一、SPA与前端路由 前端路由本质是…

3年经验面试20K+测试岗,看到这样的面试题我懵了....

我要跳槽&#xff01;我是着急忙慌的准备简历——3年软件测试经验&#xff0c;可独立测试大型产品项目&#xff0c;熟悉项目测试流程...薪资要求&#xff1f;3年测试经验起码能要个20K吧 我加班肝了一页半简历&#xff0c;投出去一周&#xff0c;面试电话倒是不少&#xff0c;…

【Web服务应用】Tomcat部署与优化

Tomcat部署 一、Tomcat简介二、tomcat组件2.1核心组件2.2Tomcat功能组件2.3Tomcat 请求过程 三、部署Tomcat服务3.1Tomcat虚拟主机配置 四、Tomcat多实例部署五、tomcat优化5.1tomcat服务优化5.2JVM的优化5.3内核的优化 一、Tomcat简介 一款 java 开发的开源的 Web 应用服务程序…

8.面向对象编程(中级部分)|Java学习笔记

文章目录 IDEA介绍包注意事项和使用细节包的三大作用包的本质分析&#xff08;原理&#xff09; 访问修饰符面向对象编程三大特征封装封装的理解和好处封装的实现步骤&#xff08;三步&#xff09; 继承继承的深入讨论/细节问题继承的本质分析super关键字super 给编程带来的便利…

是德E3648A 双路输出电源技术参数

是德基础直流电源具备所需的各种基本特性&#xff0c;适用于预算紧张的用户。E3640系列30-100W GPIB单路和双路输出电源外形小巧&#xff0c;适合作为台式和系统电源使用。它的输出纹波和噪声较低&#xff0c;并内置测量和基本可编程功能。Keysight E3648A 90W多路输出电源是一…

和鲸社区数据分析每周挑战【第九十二期:学生成绩影响因素分析】

和鲸社区数据分析每周挑战【第九十二期&#xff1a;学生成绩影响因素分析】 文章目录 和鲸社区数据分析每周挑战【第九十二期&#xff1a;学生成绩影响因素分析】一、前言二、数据读取和初步探索三、数据预处理1、处理缺失值2、类别变量转换 四、学业表现可视化1、绘制数学成绩…

探究Vue源码:mustache模板引擎(1) 什么是模板引擎

之前在讲虚拟dom和diff算法时说过后续会讲模板引擎 啊 那这边 说到做到哈 对这个问题 有个比较官方的回答 模板引擎是将数据变为视图的最优雅的解决方案 比如 将左侧数据变为右侧视图 大家应该最先想到的就是 v-for 其实 v-for 就是一种模板引擎语法 从图中看出 模板引擎处理…

Linux文件理解和系统调用

本文已收录至《Linux知识与编程》专栏&#xff01; 作者&#xff1a;ARMCSKGT 演示环境&#xff1a;CentOS 7 文件理解和系统调用 前言正文文件概念文件描述符文件描述符概念文件管理关于 files_struct文件描述符的分配一切皆文件思想 C语言文件操作文件的打开与关闭文件读写 文…

IMX6ULL裸机篇之SPI原理图

一. IMX6ULL的 SPI 实验 I.MX6ULL 有4 个 SPI 接口&#xff0c;可以通过这 4 个 SPI 接口来连接一些 SPI 外设。 I.MX6U-ALPHA 使用 SPI3 接口 连接了一个六轴传感器 ICM-20608 &#xff0c;本章我们就来学习如何使用 I.MX6U 的 SPI 接口来驱动 ICM-20608…

【vue3】09-vue组件化额外知识补充(上)-生命周期-ref引用

组件化-额外知识补充&#xff08;上&#xff09; 生命周期认识生命周期生命周期函数的演练 refs引用 生命周期 认识生命周期 什么是生命周期呢? 生物学上&#xff0c;生物生命周期指得是一个生物体在生命开始到结束周而复始所历经的一系列变化过程;每个组件都可能会经历从创…

适应新时代的FTP已经出现?这种产品有何过人之处?

大家都知道&#xff0c;FTP是用于在网络上进行文件传输的一套标准协议&#xff0c;它作为互联网最经典的协议之一&#xff0c;至今已经存在了50年。而随着时代发展&#xff0c;越来越多的用户与企业开始觉得FTP不够满足大家的需求&#xff0c;出现的问题与漏洞越来越多&#xf…

好用工具第4期:全能播放器PotPlayer

好用工具第4期:全能播放器PotPlayer Global Potplayer 是一款 Windows 平台的全能播放器。支持几乎所有的视频格式&#xff0c;音频格式&#xff0c;以及在线播放全世界的电视直播。 其官网是&#xff1a; https://potplayer.daum.net/?langzh_CN 特点 支持强劲引擎加速支持3…

【微服务】SpringCloudAlibaba

一 微服务架构 1.1 微服务 微服务其实是一种架构风格&#xff0c;我们在开发一个应用的时候这个应用应该是由一组小型服务组成&#xff0c;每个小型服务都运行在自己的进程内&#xff1b;小服务之间通过HTTP的方式进行互联互通。 1.2 微服务架构的常见问题 一旦采用微服务系…

【论文阅读】(2013)Exact algorithms for the bin packing problem with fragile objects

文章目录 一、摘要二、介绍三、之前在这个问题上的工作四、易碎物品背包问题的求解4.1 ILP模型4.2 基于KP01的方法4.3 动态规划 五、二元分支方案5.1 分支方案1&#xff08;基于决策变量的分支&#xff09;5.2 分支方案2&#xff08;基于yj和xji的分支&#xff09;5.3 将L2嵌入…

精进嵌入式系统设计

当涉及到嵌入式系统设计时&#xff0c;以下是一些建议和关键点&#xff0c;可以帮助您进行有效的设计&#xff1a; 确定需求&#xff1a;明确系统设计的功能需求和性能指标。了解系统的预期用途、功能要求、资源限制和实时性需求等。 硬件选择&#xff1a;根据需求选择合适的硬…

Visio matlab 图像取消边框

visio 图像取消边框 1.菜单栏→文件→选项→自定义功能区→勾选“开发工具”→确定 2.菜单栏→开发工具→显示ShapeSheet→页→Print Properties→将“PageLeftMargin”、“PageRightMargin”、“PageTopMargin”和“PageBottomMargin”均修改为0 &#xff08;双击单元格来编辑…

报表生成器FastReport .Net用户指南:“Text“对象、文本编辑

FastReport .Net是一款全功能的Windows Forms、ASP.NET和MVC报表分析解决方案&#xff0c;使用FastReport .NET可以创建独立于应用程序的.NET报表&#xff0c;同时FastReport .Net支持中文、英语等14种语言&#xff0c;可以让你的产品保证真正的国际性。 FastReport.NET官方版…

微软MFC程序运行的正确顺序

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天来看一下微软MFC程序运行的正确顺序。这真的只是一个小众话题。但是对理解MFC很重要。 很多人写了一堆MFC程序&#xff0c;却不知道MFC程序是怎么运行顺序的。我们就来看一看这个问题。 我在之前的帖子中…

Java IO模型图解(BIO NIO AIO)

一、冯诺伊曼模型 如图&#xff1a; 图片来源&#xff1a;百度百科 输入设备向计算机输入数据&#xff0c;输出设备接收计算机输出的数据。 所有的计算机程序&#xff0c;也都可以抽象为从输入设备读取输入信息&#xff0c;通过运算器和控制器来执行存储在存储器里的程序&am…

热修复/热更新

热修复/热更新 一.Android热修复二.热修复框架三.类加载器0.BootClassLoader1.PathClassLoader2.DexClassLoader 四.实现思路五.代码1.FixManager2.App3.更加标准的代码 五.制作补丁包1.写段有bug的工具类&#xff0c;并写个点击按钮调用2.运行项目到模拟器上3.修复ToastUtils工…