关于模板的大致认识【C++】

文章目录

  • 函数模板
    • 函数模板的原理
    • 函数模板的实例化
    • 模板参数的匹配原则
  • 类模板
    • 类模板的定义格式
    • 类模板的实例化
  • 非类型模板参数
  • typename 与class
  • 模板的特化
    • 函数模板特化
    • 类模板特化
      • 全特化
      • 偏特化
  • 模板的分离编译

函数模板

函数模板的原理

template <typename T> //模板参数 ——类型 
void Swap(T& x1, T& x2)
{
	T tmp = x1;
	x1 = x2;
	x2 = tmp;
}
int main()
{
	int a = 0, b = 1;
	double c = 1.1, d = 2.2;

	swap(a, b);
	swap(c, d);
	int* p1 = &a;
	int* p2 = &b;
	swap(p1, p2);
	return 0;
}

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

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

函数模板的实例化

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

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

template<class T>
T Add(const T& left , const T& right)
{
	return left + right;
}
int main()
{

	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	//函数模板根据调用,自动推导模板参数的类型 ,实例化对应的参数 
		cout << Add(a1, a2) << endl;
		cout << Add(d1, d2) << endl;
	//实参传递的类型, 推演T的类型  
		cout << Add( a1, (int)d1  ) << endl;
		cout << Add( (double)a1, d1) << endl;
	//显示实例化 ,用指定的类型实例化 ,相当于隐式类型转换 
		cout << Add<int> (a1, d1) << endl;
		cout << Add<double>(a1, d1) << endl;

	return 0;
}

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

template<class T>
T Add(const T& left , const T& right)
{
	return left + right;
}
int main()
{

	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	//函数模板根据调用,自动推导模板参数的类型 ,实例化对应的参数 
		cout << Add(a1, a2) << endl;
		cout << Add(d1, d2) << endl;
	//实参传递的类型, 推演T的类型  
		cout << Add( a1, (int)d1  ) << endl;
		cout << Add( (double)a1, d1) << endl;
	//显示实例化 ,用指定的类型实例化 ,相当于隐式类型转换 
		cout << Add<int> (a1, d1) << endl;
		cout << Add<double>(a1, d1) << endl;

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

 int main()
 {
	 //有些函数无法自动推导函数模板的类型,实例化对应的参数,只能显式实例化
	 double *p1 = Alloc <double>(10);
	 return 0;
 }

模板参数的匹配原则

类模板

类模板 ,无法推演实例化,所以类模板都是显式实例化

class Stack
{
public :
	Stack(int capacity = 3)
	{
		_array= new  T[capacity];
			_size = 0;
		_capacity = 0; 
	}
	void Push(const T &  data)
	{
		_array[_size++] = data;
	}
	~Stack()
	{
		free(_array);
		_size = _capacity = 0;
	}
private :
	T * _array;
	int _size;
	int _capacity;
};
int main()
{
	Stack <int> s1(); // int 
	Stack <double> s2();//double 
	Stack <char> s3();//char
	//Stack <int ,doule> s2();


	return 0; 
}

类模板的定义格式

函数类模板的声明和定义分离

template<class T>


class Stack
{
public:
	//声明 
	Stack(int capacity );
	void Push(const T& data)
	{
		_array[_size++] = data;
	}
	~Stack()
	{
		free(_array);
		_size = _capacity = 0;
	}
private:
	T* _array;
	int _size;
	int _capacity;
};
//定义 
template<class T>
 Stack<T>::Stack(int capacity )
{
	_array = new  T[capacity];
	_size = 0;
	_capacity = 0;
}
int main()
{
	Stack <int> s1(); // int 
	Stack <double> s2();//double 
	Stack <char> s3();//char
	//Stack <int ,doule> s2();


	return 0;
}

对于普通类,类名和类型是一样的,但是对于类模板 ,类名和类型是不一样的 上面的代码中Stack是类名 ,但是Stack < T >是类型

类模板的实例化

非类型模板参数

模板参数可分为类型形参非类型形参

类型形参: 出现在模板参数列表中,跟在class或typename关键字之后的参数类型名称。

非类型形参: 用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

如果此时有一个需求,实现一个静态数组的类,就需要用到非类型模板参数

template<class T, size_t N> //N:非类型模板参数
// N是常量 ,且N必须是整形
class StaticArray
{
public:
	size_t arraysize()
	{
		return N;
	}
private:
	T _array[N]; //利用非类型模板参数指定静态数组的大小
};
int main()
{
	StaticArray<int, 10> a1; //定义一个大小为10的静态数组
	cout << a1.arraysize() << endl; //10
	StaticArray<int, 100> a2; //定义一个大小为100的静态数组
	cout << a2.arraysize() << endl; //100
	return 0;
}

注意:

1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
2. 非类型的模板参数必须在编译期就能确认结果。

typename 与class

一般来说,typename 和class 没有什么区别,但是在有一种情景下是有区别的

template<class Container>
void Print(  const Container& v  )
{
	//Container::const_iterator it = v.begin();是不行的
	// 因为编译不确定Container::const_iterator是类型还是对象
	// typename的作用就是明确告诉编译器这里是类型,等模板实例化再去找
	typename Container::const_iterator it = v.begin();
	
	while (it != v.end() )
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	Print(v);
	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);
	Print(lt1);
	return 0;
}

以上情景需要使用typename

模板的特化

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理

模板的特化 即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。
模板特化中分为函数模板特化 和 类模板特化。

函数模板特化

1、首先必须要有一个基础的函数模板。
2、关键字template后面接一对空的尖括号<>。
3、函数名后跟一对尖括号,尖括号中指定需要特化的类型。
4、函数形参表必须要和模板函数的基础参数类型完全相同,否则不同的编译器可能会报一些奇怪的错误。

template<class T>
bool Less(T left ,T right)
{
	return left < right;
}
//函数模板的特化
template<>
bool Less<int * >(int * left, int* right)
{
	return *left < *right;
}
int main()
{
	int a = 1, b = 2;
	cout << Less(1, 2);
	cout << endl;
	cout << Less(&a, &b);
	return 0;
}

类模板特化

不仅函数模板可以进行特化,类模板也可以针对特殊类型进行特殊化实现,并且类模板的特化又可分为全特化和偏特化(半特化)。

全特化

全特化即是将模板参数列表中所有的参数都确定化。

例如,对于以下类模板:

template<class T1, class T2>
class Data
{
public:

Data() {cout<<"Data<T1, T2>" <<endl;}
	private:
	T1 _d1;
	T2 _d2;
};

template<>
class Data<int, char>
{
public:

Data() {cout<<"Data<int, char>" <<endl;}
	private:
	int _d1;
	char _d2;
};

void TestVector()
{
 Data<int, int> d1;
 Data<int, char> d2;
}

偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。

偏特化有以下两种表现方式:
1、部分特化 ,将模板参数类表中的一部分参数特化

template<class T1, class T2>
class Data
{
public:
	Data()
	 {
	 cout<<"Data<T1, T2>" <<endl;
	 }
private:
	T1 _d1;
	T2 _d2;
};

// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
	Data() 
	{
	cout<<"Data<T1, int>" <<endl;
	}
private:
	T1 _d1;
	int _d2;
};
int main()
{
	Data<double, int> d1;//偏特化
	Data<int, double> d2;//调用基础的模板
	return 0;
}

2、参数更进一步的限制

template<class T1, class T2>
class Data
{
public:
	Data()
	 {
	 cout<<"Data<T1, T2>" <<endl;
	 }
private:
	T1 _d1;
	T2 _d2;
};
//偏特化:对类型的进一步限制
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
	Data() { cout << "Data<T1*, T2*>" << endl; }
private:
};

int main()
{
	Data<int, int > d1;
	Data<int, double > d2;
	Data<int*, double > d3;
	Data<int*, double* > d4;
	Data<void*, void* > d5;
	return 0;
}

模板的分离编译

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。

在分离编译模式下,我们一般创建三个文件

一个头文件用于进行函数声明
一个源文件用于对头文件中声明的函数进行定义
最后一个源文件用于调用头文件当中的函数

举个例子:如果对一个加法函数模板进行分离编译

在这里插入图片描述

如果这三个文件生成可执行文件时,会在链接阶段产生报错
这是为什么呢?

C / C++程序要运行起来一般要经历以下四个步骤:
预处理、编译、汇编、链接

如果需要详细的了解这四个步骤,请点击这里

在这里插入图片描述

这三个文件经过预处理后就只剩下两个文件了

Visual Studio平台:

预处理后就进入编译阶段,
虽然在 main.i 当中有调用Add函数的代码,但是在 main.i 里面也有Add函数模板的声明,在编译阶段并不会发现任何语法错误,在编译阶段将 Add.i 和 main.i 翻译成了汇编语言,即将 Add.i 和 main.i 变成了Add.s 和 main.s

进入汇编阶段,利用 Add.s 和 main.s 这两个文件分别生成了两个目标文件,
即将 Add.s 和 main.s变成了Add.o和 main.o

最后将Add.o和 main.o进行链接操作生成a.out,
但在链接时发现,在main函数当中调用的两个Add函数实际上并没有被真正定义
原因是函数模板T还没有实例化,可以将模板定义的位置显式实例化。(这种方法不实用,不推荐使用)。

总结:
编译阶段看有没有声明,声明是一种承诺
在编译阶段,检查声明,查看函数名、参数、返回值是否对上,如果对上,则编译阶段通过
进入链接阶段,编译器会拿着修饰后的函数去其他文件符号表查找,如果查到,则链接阶段通过

模板分离编译失败的原因:
在函数模板定义的地方(Add.cpp)没有进行实例化,而在需要实例化函数的地方(main.cpp)没有模板函数的定义,无法进行实例化。

解决方法

  1. 将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实也是可以的。推荐使用这种。
  2. 模板定义的位置显式实例化。(这种方法不实用,不推荐使用)。

如果你觉得这篇文章对你有帮助,不妨动动手指给点赞收藏加转发,给鄃鳕一个大大的关注你们的每一次支持都将转化为我前进的动力!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mongodb集群

端口192.168.115.3 192.168.115.4 1192.168.115.5 下载MongoDB软件包版本为4.2.14并安装 rpm -ih --force --nodeps *.rpm 2创建文件夹mkdir -p /opt/local/mongo-cluster/conf 3.在目录里创建配置文件cd /opt/local/mongo-cluster/conf …

3D数据转换工具HOOPS Exchange概览

HOOPS Exchange SDK是一组C软件库&#xff0c;使开发团队能够快速为其应用程序添加可靠的2D和3D CAD导入和导出功能。这允许访问广泛的数据&#xff0c;包括边界表示&#xff08;BREP&#xff09;、产品制造信息&#xff08;PMI&#xff09;、模型树、视图、持久ID、样式、构造…

低代码开发ERP:精打细算,聚焦核心投入

企业数字化转型已经成为现代商业环境中的一项关键任务。如今&#xff0c;企业面临着日益激烈的竞争和不断变化的市场需求。在这样的背景下&#xff0c;数字化转型不仅是企业生存的必然选择&#xff0c;也是取得竞争优势和实现可持续发展的关键因素。 在数字化转型的过程中&…

【Hello Network】数据链路层协议

本篇博客简介&#xff1a;介绍数据链路层的各协议 数据链路层 以太网协议认识以太网协议以太网帧格式局域网通信原理再理解 MTU认识MTUMTU对IP协议的影响MTU对UDP协议的影响MTU对于TCP协议的影响如何查看ip地址 mac地址 以及mtu ARP协议ARP协议的作用ARP协议在哪里ARP的工作过程…

解决charles无法抓取localhost数据包

我们有时候在本地调试的时候&#xff0c;使用charles抓取向本地服务发送的请求的&#xff0c;发现无法抓取。 charles官方也作了相应说明&#xff1a; 大概意思就是 某些系统使用的是硬编码不能使用localhost进行传输&#xff0c;所以当我们连接到 localhost的时候&#xff0c…

leetcode 188. 买卖股票的最佳时机 IV

2023.8.21 这道题是 买卖股票的最佳时机III 的升级版&#xff0c;即买卖次数限制为k次&#xff0c;做法和上一篇如法炮制&#xff0c;直接看代码&#xff1a; class Solution { public:int maxProfit(int k, vector<int>& prices) {vector<vector<int>>…

常用性能测试工具及其功能

在软件开发周期的不同阶段&#xff0c;性能测试工具被广泛用于评估系统的性能和发现潜在的性能瓶颈。本文介绍了几种常用的性能测试工具&#xff0c;包括负载测试工具、压力测试工具和基准测试工具&#xff0c;并详细描述了它们的功能和用法。 性能测试在软件开发的各个阶段都至…

Docker的数据管理及端口映射与容器互联(使用centos镜像)

目录 Docker数据管理 1&#xff0e;数据卷 2&#xff0e;数据卷容器 Docker端口映射 Docker容器互联 Docker数据管理 管理 Docker 容器中数据主要有两种方式&#xff1a;数据卷&#xff08;Data Volumes&#xff09;和数据卷容器&#xff08;DataVolumes Containers&…

Java接口详解

接口 接口的概念 在现实生活中&#xff0c;接口的例子比比皆是&#xff0c;比如&#xff1a;笔记本上的USB口&#xff0c;电源插座等。 电脑的USB口上&#xff0c;可以插&#xff1a;U盘&#xff0c;鼠标&#xff0c;键盘等所有符合USB协议的设备 电源插座插孔上&#xff0c;…

linux 上安装es

首先 到官网 https://www.elastic.co/cn/downloads/elasticsearch 下载对应的安装包&#xff0c;我这里下载的是 https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.9.1-linux-x86_64.tar.gz 然后讲该压缩包上传到 linux 的/usr/local 目录下执行 tar -z…

redis 存储结构原理 2

咱们接着上一部分来进行分享&#xff0c;我们可以在如下地址下载 redis 的源码&#xff1a; https://redis.io/download 此处我下载的是 redis-6.2.5 版本的&#xff0c;xdm 可以直接下载上图中的 **redis-6.2.6 **版本&#xff0c; redis 中 hash 表的数据结构 redis hash …

(6)(6.2) 任务命令

文章目录 前言 6.2.1 概述 6.2.2 导航命令 6.2.3 条件命令 6.2.4 DO命令 前言 本文介绍了 Copter、Plane 和 Rover 切换到自动模式时支持的任务指令。 &#xff01;Warning 这是一项正在进行中的工作&#xff0c;尚未经过全面审核。有关 Copter 的更佳列表&#xff0c;请…

git拉取失败/git fatal终极解决方法

前言 被折磨不下20次总结出来的终极方案 步骤 0 首先关闭代理试试&#xff0c;不行就下一步 1 重置代理或者取消代理的方式 git config --global --unset http.proxy git config --global --unset https.proxy添加全局代理 git config --global http.proxy git config …

【从零学习python 】56. 异常处理在程序设计中的重要性与应用

文章目录 异常的概念读取文件异常try...except语句try...else语句try...finally语句 进阶案例 异常的概念 在程序运行过程中&#xff0c;由于编码不规范或其他客观原因&#xff0c;可能会导致程序无法继续运行&#xff0c;此时就会出现异常。如果不对异常进行处理&#xff0c;…

RabbitMQ---work消息模型

1、work消息模型 工作队列或者竞争消费者模式 在第一篇教程中&#xff0c;我们编写了一个程序&#xff0c;从一个命名队列中发送并接受消息。在这里&#xff0c;我们将创建一个工作队列&#xff0c;在多个工作者之间分配耗时任务。 工作队列&#xff0c;又称任务队列。主要思…

优化指南:带宽限制的可行策略

大家好&#xff01;作为一名专业的爬虫程序员&#xff0c;我们经常面临的一个挑战就是带宽限制。尤其是在需要快速采集大量数据时&#xff0c;带宽限制成为了我们提升爬虫速度的一大阻碍。今天&#xff0c;我将和大家分享一些解决带宽限制的可行策略&#xff0c;希望能帮助大家…

【算法系列篇】二分查找——这还是你所知道的二分查找算法吗?

文章目录 前言什么是二分查找算法1.二分查找1.1 题目要求1.2 做题思路1.3 Java代码实现 2.在排序数组中查找元素的第一个和最后一个位置2.1 题目要求2.2 做题思路2.3 Java代码实现 3.搜索插入位置3.1 题目要求3.2 做题思路3.3 Java代码实现 4.x的平方根4.1 题目要求4.2 做题思路…

element上传图片,调取接口传值,参数FormData为空

需求 输入完reason&#xff0c;选完文件后&#xff0c;点击提交按钮后 调取接口。 遇到的问题 上传文件orderFile 字段一直为空 打印了发现&#xff0c;上传文件也是有值得。但是传到接口中就为空 原因 json里边不能放file&#xff0c;但是formData里可以放 file 也可以放…

论文阅读——Imperceptible Adversarial Attack via Invertible Neural Networks

Imperceptible Adversarial Attack via Invertible Neural Networks 作者&#xff1a;Zihan Chen, Ziyue Wang, Junjie Huang*, Wentao Zhao, Xiao Liu, Dejian Guan 解决的问题&#xff1a;虽然视觉不可感知性是对抗性示例的理想特性&#xff0c;但传统的对抗性攻击仍然会产…

汽配企业MES管理系统如何追溯产品质量问题

随着汽车行业的快速发展&#xff0c;汽配行业也面临着越来越严格的质量要求。为了满足客户需求并提高产品质量&#xff0c;汽配企业需要实现生产过程的可追溯性。MES管理系统解决方案作为生产过程的核心管理系统&#xff0c;可以通过记录生产数据和流程&#xff0c;实现产品质量…