【C++】模板初阶

2023-09-18 17:57:52

今天开始将图片的水印全部去掉,以方便大家的观看和知识截屏分享,希望对大家都有所帮助

模板初阶目录:

一、什么是泛型编程(编写与类型无关的代码)

二、函数模板

2.1概念与格式

2.2底层原理

2.3实例化(细节较多)

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

2.3.2显示实例化

 2.4参数的匹配规则

2.4.1尽管看起来非模板函数是模板函数的子集,但是他们俩个是不会冲突的

2.4.2模板函数与非模板函数的优先级

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

三、类模板

3.1概念与格式

3.2实例化(必须显示,因为没有推演时机)


一、什么是泛型编程(编写与类型无关的代码)

我们通过实现一个通用的交换函数来引入泛型编程:

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

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

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

 在C语言阶段,要实现一个通用的交换函数我们只能通过定义对应不同参数类型的多个函数来实现,而且各函数的函数名不能相同,比如 Swapi、Swapd、Swapc;到了C++阶段,我们可以通过函数重载来定义多个参数类型不同但函数名相同的函数来实现,但是函数重载有以下几个缺陷:

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

那么我们能否给编译器提供一个模型,让编译器能够根据不同的参数类型自动利用该模型来生成对应函数呢?就像浇筑一样,我们可以根据同一个浇筑模具来浇筑出不同类型的模具

所以泛型编程的意义就来了:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础


二、函数模板

2.1概念与格式

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

模板函数的定义格式如下:

template <class 类型参数1, class 类型参数2, ...>
返回值类型  模板名(形参表)
{
    函数体
}

其中,class也可以用typename来替换!!

上面的 template 为模板关键字,class/typename 关键字用于指定模板参数中的类型,类型参数用于代表该类型 (我们一般使用 T 作为类型参数)

我们以Swap函数为例:

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

2.2底层原理

函数模板是一个蓝图,它本身并不是函数,所以当我们实际调用时编译器会根据传入的实参类型来推演生成对应类型的函数以供调用,此过程在编译阶段完成;比如当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型和整形也是如此

 这是通过反汇编调用的,发现正是调用了不同的函数,而不是一个函数模板可以兼容吞吐所有东西

2.3实例化(细节较多)

用不同类型的参数使用函数模板时,称为函数模板的实例化

模板参数实例化分为:隐式实例化和显式实例化

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

如上图,我们调用 Add 函数模板时并不需要显式指定 T 为 int 或 double 类型,编译器会根据实参类型自动去推演模板参数的类型,然后实例化出对应函数

注意:我们在使用函数模板时需要避免下面这种情况(一个传int 一个传double)

上述语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,编译器会无法确定此处到底该将T确定为int 或者 double类型而报错

注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅

对于上面这种情况一共有三种解决方式:用户对实参进行强转、增加模板参数或者显示实例化

a.用户对实参进行强转

但是这里还是报错,这是因为权限放大的问题,类型转化会产生常量,常量具有常性,所以必须在函数参数上面加上const

b.增加模板参数(用俩个T)

 但是其实这里又会产生一个新的问题 – 函数返回值的类型到底应该用T1还是T2

2.3.2显示实例化

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

 显式示例化的原理和用户强转类似,只不过这里是编译器自动将 d1 强转为 int 然后传递给形参,或者将 a1 强转为 double 传递给形参;同时,这里函数的形参也必须用 const 修饰

 2.4参数的匹配规则

2.4.1尽管看起来非模板函数是模板函数的子集,但是他们俩个是不会冲突的

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

// 专门处理int的加法函数
int Add(int left, int right)
{
	cout << "normal add" << endl;
	return left + right;
}

// 通用加法函数
template<class T>
T Add(T left, T right)
{
	cout << "template add" << endl;
	return left + right;
}

可以看到,当一个非模板函数可以和一个同名的函数模板同时存在时,如果我们不显式实例化,编译器会去调用非模板函数,而不会去实例化模板;如果我们显示实例化,编译器会调用通过函数模板实例化得到的函数。

另外,显示实例化后程序能够正常运行,也侧面的说明了通过函数模板实例化出的函数与非模板函数 (普通函数) 的函数名修饰规则不同,否则会发生编译错误

2.4.2模板函数与非模板函数的优先级

这个问题在上面的小标题就已经解决完毕,能用非模板绝对不调用模板

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


三、类模板

问题引入:我们都已经有了typedef了,为什么还需要类模板呢??

3.1概念与格式

对于栈来说,简单解决上面的问题引入就是因为,当我在主函数中同时需要int类型的栈和char的栈的时候,编译器就傻眼了

客观点讲:类模板比typedef可以更好的解决可维护性,typedef不是泛型编程

和非模板函数一样,我们以前实现的类也有一个缺点 – 同一个类只能示例一种类型的对象,我们以 Stack 为例:

typedef int STDateType;
class Stack
{
public:
	Stack(int capacity = 4)
		:_top(0)
		, _capacity(capacity)
	{
		_a = (STDateType*)malloc(sizeof(STDateType) * capacity);
		if (_a == nullptr)
		{
			perror("malloc fail\n");
			exit(-1);
		}
	}

	~Stack()
	{
		free(_a);
		_a = NULL;
		_top = _capacity = 0;
	}

	void Push(STDateType x)
	{
		_a[_top++] = x;
	}

private:
	STDateType* _a;
	int _top;
	int _capacity;
};

我们可以通过修改 STDateType 来让 _a 存储不同类型的数据,但是在同一个项目中 STDateType 只能被指定为一种类型;那么和模板函数一样,我们也需要一个类模板,使得我们能够根据同一个模板来实例化出不同的类

类模板的定义格式如下:

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

有了类模板,我们就可以将 Stack 修改为下面这样:

// 注意:Stack不是具体的类,是编译器根据被实例化的类型生成具体类的模具
template <class T>
class Stack
{
public:
	Stack(int capacity = 4)
		:_top(0)
		, _capacity(capacity)
	{
		_a = (T*)malloc(sizeof(T) * capacity);
		if (_a == nullptr)
		{
			perror("malloc fail\n");
			exit(-1);
		}
	}

	~Stack()
	{
		free(_a);
		_a = NULL;
		_top = _capacity = 0;
	}

	void Push(T x)
	{
		_a[_top++] = x;
	}

private:
	T* _a;
	int _top;
	int _capacity;
};

小tip:(解决写的非常搓的push)

如果栈中的元素是很复杂的元素类型,传值就会浪费大量的拷贝空间,所以传引用

而且x的值是没必要被修改的,所以参数前面嗷嗷加上const!

3.2实例化(必须显示,因为没有推演时机)

类模板实例化与函数模板实例化不同:

类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,即类模板必须显示实例化

类模板名字不是真正的类,而实例化的结果才是真正的类;在之前我们说过,C++中类名就是类型,但是类模板和普通的类不同 – 类模板不是具体的类,是编译器根据被实例化的类型生成具体类的模具;即只有我们对类模板进行显示实例化之后编译器才会生成具体的类,而这个类才是我们能够正常使用的类;所以实例化的结果才是真正的类

Stack -- 类名;
Stack<int> -- 类型;
Stack<double> -- 类型;

从另一个好理解的角度来讲:是因为类模板并没有推演过程,函数模板的传参其实就是在进行推演过程,所以函数模板可以隐式调用


ps:模板还有一些其他的内容,比如非类型模板参数、类模板的特化、模板的分离编译等,我们在学完 STL 初阶后再进行学习


 希望这篇模板初阶对你有帮助!

更多推荐

(入门向)面向萌新的算法比赛入门指南

什么是算法算法是指解决问题或完成特定任务的一系列明确指令或步骤集合。它是一个定义良好、逐步执行的操作序列,用于将输入转换为输出。算法可用于计算、数据处理、自动化控制、问题解决等各个领域。算法通常由一系列简单的操作组成,这些操作可以是基本的数学运算、逻辑判断、条件分支、循环控制等。通过组合和重复执行这些操作,算法能够解决

大模型从入门到应用——LangChain:代理(Agents)-[工具包(Toolkit)]

分类目录:《大模型从入门到应用》总目录LangChain系列文章:基础知识快速入门安装与环境配置链(Chains)、代理(Agent:)和记忆(Memory)快速开发聊天模型模型(Models)基础知识大型语言模型(LLMs)基础知识LLM的异步API、自定义LLM包装器、虚假LLM和人类输入LLM(HumanInpu

数据结构——红黑树

1.什么是红黑树?红黑树是一种特定类型的二叉树,用于组织数据。它是一种平衡二叉查找树(AVL树)的变体,每个结点都带有颜色属性(红色或黑色)。在红黑树中,从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。具体来说,红黑树满足以下性质:每个结点要么是红色,要么是黑色。根结点是黑色。每个叶结点(NIL或空结点)是黑色

PHP8的类与对象的基本操作之类的实例化-PHP8知识详解

定义完类和方法后,并不是真正创建一个对象。类和对象可以描述为如下关系。类用来描述具有相同数据结构和特征的“一组对象”,“类”是“对象”的抽象,而“对象”是“类”的具体实例,即一个类中的对象具有相同的“型”,但其中每个对象却具有各不相同的“值”。例如,人就是一个抽象概念,即人类,但是程序员小张就是人类中具体的一个实例,即

TSINGSEE视频AI智能分析技术:水泥厂安全生产智能监管解决方案

一、方案背景随着人工智能技术的快速发展以及视频监控系统在全国范围内的迅速推进,基于AI视频智能分析技术的智能视频监控与智慧监管系统,也已经成为当前行业的发展趋势。在工业制造与工业生产领域,工厂对设备的巡检管理、维护维修、资产管理、安全运行管理等方面也提出了更高的监管要求。二、方案介绍TSINGSEE青犀视频围绕AI算法

网络安全(黑客)自学笔记

前言作为一个合格的网络安全工程师,应该做到攻守兼备,毕竟知己知彼,才能百战百胜。计算机各领域的知识水平决定你渗透水平的上限。【1】比如:你编程水平高,那你在代码审计的时候就会比别人强,写出的漏洞利用工具就会比别人的好用;【2】比如:你数据库知识水平高,那你在进行SQL注入攻击的时候,你就可以写出更多更好的SQL注入语句

【算法】算法设计与分析 课程笔记 第一章 概述

第一章算法概述算法的性质算法的四个性质:输入、输出、确定性和有穷性。算法的时间复杂度1.常见的时间复杂度常数阶O(1)对数阶O(logn)线性阶O(n)线性对数阶O(nlogn)平方阶O(n^2)立方阶O(n^3)k次方阶O(n^k)指数阶O(2^n)注:上面的logn均代表以2为底的对数。2.时间复杂度排序常见的算法

【web开发】10、数据统计(echarts)--柱状图、折线图、饼图

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、echarts是什么?二、使用步骤1.引入CDN2.设置高度&宽度3.后端4.前端前言提示:这里可以添加本文要记录的大概内容:例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础

CAD丢失mfc140u.dll怎么办,mfc140u.dll丢失的解决方法分享

许多用户在运行AutoCAD时可能会遇到一个问题:丢失mfc140u.dll文件,导致软件无法正常运行。本文将详细介绍mfc140u.dll文件的作用,以及如何解决丢失mfc140u.dll文件的问题。一、mfc140u.dll文件的作用MFC(MicrosoftFoundationClass)是一个由微软公司开发的C

量子计算基础知识—Part1

1.什么是量子计算机?量子计算机是基于量子力学原理构建的机器,采用了一种新的方法来处理信息,从而使其具有超强的功能。量子计算机使用Qubits处理信息。2.什么是量子系统?一个量子系统指的是由量子力学规则描述和控制的物理系统。在量子力学中,物理系统的状态不再是经典物理中的确定性值,而是由一个称为波函数的数学对象描述的概

接入国家能源平台MQTT应用案例

一、项目介绍随着国家对节能环保措施的力度不断加大,基于物联网技术搭建的国家能源平台在国家相关部门的建设下逐渐成熟。致力于利用实际能耗数据建立能效仿真模型,通过实时寻优运算,获得当前的最优化运行策略,并将控制指令下发控制系统,快速帮助能源全面实现自身能源管理的信息化、流程化、可视化和可操作性。二、项目所面临的问题1.常规

热文推荐