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

news2024/12/23 17:47:20

文章目录

  • 函数模板
    • 函数模板的原理
    • 函数模板的实例化
    • 模板参数的匹配原则
  • 类模板
    • 类模板的定义格式
    • 类模板的实例化
  • 非类型模板参数
  • 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.s 和 main.s 这两个文件分别生成了两个目标文件,

前面的预处理、编译和汇编都没有问题,现在就需要将生成的两个目标文件进行链接操作了,但在链接时发现,在main函数当中调用的两个Add函数实际上并没有被真正定义,主要原因是函数模板并没有生成对应的函数,因为在全过程中都没有实例化过函数模板的模板参数T,所以函数模板根本就不知道该实例化T为何类型的函数。

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

Linux平台下:

预处理后就需要进行编译,就生成了 Add.s 和 main.s 文件。

汇编:生成了 Add.o 和 main.o 两个目标文件。

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

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

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

相关文章

el-input输入框 输入数字中文 来回切换之后 监听失效问题如何解决

实现一个vue自定义指令——输入框&#xff08;input,el-input&#xff09;输入内容类型限制&#xff0c;解决中文输入法双向绑定失效问题&#xff0c;多种类型支持&#xff0c;数字类型&#xff0c;浮点类型、英文类型、整数类型、四则运算等 直接上代码 首先新建input.js ex…

Git如何操作本地分支仓库?

基本使用TortoiseGit 操作本地仓库(分支) 分支的概念 几乎所有的版本控制系统都以某种形式支持分支。 使用分支意味着你可以把你的工作从开发主线上分离开来&#xff0c;避免影响开发主线。多线程开发,可以同时开启多个任务的开发&#xff0c;多个任务之间互不影响。 为何要…

10个好用的网络画图工具推荐,专业办公绘图必备!

在当今数字化时代&#xff0c;网络画图工具成为了各行各业的重要辅助工具。无论是制作流程图、思维导图、原型设计&#xff0c;还是插图绘制、数据可视化&#xff0c;网络画图工具为用户提供了便捷、高效的创作平台。本文将向大家推荐10个好用的网络画图工具&#xff0c;帮助你…

深度解析淘宝API商品评论接口的实现原理与使用方法

淘宝API商品评论接口&#xff0c;主要用于获取某个商品的评价信息。通过该接口&#xff0c;我们可以获取到商品的所有评价内容、评价时间、评价等级等相关信息&#xff0c;帮助我们更好地了解用户对商品的反馈&#xff0c;进而进行数据分析和业务优化。 一、接口鉴权 在使用淘…

波奇学C++:stl的list模拟实现

list是双向带头链表。所以迭代器end()相当于哨兵卫的头。 list不支持和[]重载&#xff0c;原因在于list空间不是连续的&#xff0c;和[]的代价比较大。 访问第n个节点&#xff0c;只能用for循环&#xff0c;来实现 list<int> l; l.push_back(0); l.push_back(1); l.pu…

代码随想录算法训练营之JAVA|第三十五天|343. 整数拆分

今天是第 天刷leetcode&#xff0c;立个flag&#xff0c;打卡60天&#xff0c;如果做不到&#xff0c;完成一件评论区点赞最高的挑战。 算法挑战链接 343. 整数拆分https://leetcode.cn/problems/integer-break/ 第一想法 题目理解&#xff1a;将一个整数拆分为k个整数&…

python matlab 画坐标图

画一个坐标系&#xff0c;同时显示两条直线&#xff0c;效果图如下&#xff1a; 功能点&#xff1a; 同时显示两个纵坐标数据 显示图片名称 图片最大化保存 到本地 在图片某个位置显示字符信息 不同的线名称提示 代码如下&#xff1a; import matplotlib.pyplot as pltde…

学习左耳听风栏目90天——第七天 7/90(学习左耳朵耗子的工匠精神,对技术的热爱)【每个程序员都该知道的事】

每个程序员都该知道的事 每个程序员都应该要读的书每个搞计算机专业的学生应有的知识LinkedIn 高效的代码复查技巧编程语言和代码质量的研究报告 每个程序员都应该要读的书 每个搞计算机专业的学生应有的知识 LinkedIn 高效的代码复查技巧 编程语言和代码质量的研究报告

C++头文件和std命名空间

C 是在C语言的基础上开发的&#xff0c;早期的 C 还不完善&#xff0c;不支持命名空间&#xff0c;没有自己的编译器&#xff0c;而是将 C 代码翻译成C代码&#xff0c;再通过C编译器完成编译。 这个时候的 C 仍然在使用C语言的库&#xff0c;stdio.h、stdlib.h、string.h 等头…

CentOS7网络配置

本文是我从另外三个文章中整合而来&#xff0c;用于自存&#xff0c;如有侵权请联系我删除。 CentOS 7教程&#xff08;二&#xff09;-网络设置 - 知乎 (zhihu.com) VMware安装、Linux下CentOS7的配置及网络环境的配置&#xff08;最新版特别全&#xff09;_centos7 配置_Co…

田间气象站的优势与应用

在农业生产中&#xff0c;田间气象站是重要的气象监测工具&#xff0c;它能够对农田间的气象信息进行实时监测和记录&#xff0c;为农民伯伯提供农业生产科学依据。 田间气象站是由多个传感器共同组成&#xff0c;能够收集各项气象参数&#xff0c;包括我们常见的风速、风向、…

STM32--MPU6050与I2C外设

文章目录 前言MPU6050参数电路MPU6050框图 IIC外设框图 IIC的基本结构软件IIC实现MPU6050硬件IIC实现MPU6050 前言 在51单片机专栏中&#xff0c;用过I2C通信来进行实现AT24C02的数据存储&#xff1b; 里面介绍的是利用程序的编程来实现I2C的时序&#xff0c;进而实现AT24C02与…

关于android studio 几个简单的问题说明

自信是成功的第一步。——爱迪生 1. android studio 如何运行不同项目是否要更换不同的sdk 和 gradle 2.编译Gradle总是错误为什么 3.如何清理android studio 的缓存 4. 关于android Studio中的build 下面的rebuild project

『SEQ日志』在 .NET Core 中轻松集成 NLog 和 SEQ ,打造轻量级的分布式日志系统

&#x1f4e3;读完这篇文章里你能收获到 如何在Docker中部署 SEQ&#xff1a;介绍了如何创建和运行 SEQ 容器&#xff0c;给出了详细的执行操作如何使用 NLog 接入 .NET Core 应用程序的日志&#xff1a;详细介绍了 NLog 和 NLog.Seq 来配置和记录日志的步骤日志记录示例&…

判断三角形

int main() {int a 0;int b 0;int c 0;scanf("%d%d%d", &a, &b, &c);if ((ab>c)&&(ac>b)&&(bc>a)){if (a b && b c){printf("等边三角形\n");}else if ((a b && b ! c) || (a c && c…

企业的降本增益,从源头抓起——精细化管理每一条销售线索

存量经济时代&#xff0c;降本增效成为企业增长的重点抓手&#xff0c;也是企业自身数字化建设重要目标。 如何能让销售更珍惜线索&#xff1f;如何能让线索产生更快的转化&#xff1f;如何能让对的线索给到对的销售&#xff1f;如何能依托线索转化结果找到更有效的获客渠道&a…

【踩坑日记】STM32 USART 串口与 FreeRTOS 冲突

文章目录 问题描述问题出现的环境问题解决过程第一步第二步第三步第四步第五步第六步第七步第八步 后续验证一些思考类似的问题后记 问题描述 笔者使用 FreeRTOS 创建了两个任务&#xff0c;使两颗 LED 以不同频率闪烁&#xff0c;但是在加入串口 USART 部分代码后&#xff0c…

Shader 用到的数学函数总结及用法

在写shader相关的程序的时候往往需要用到 OpenGL内置的数学库&#xff0c;下面列举几个常用的函数用法和效果&#xff0c;下面的所有代码都是运行在shadertoy在线工具上的&#xff0c;推荐一个好用的数学工具desmosDesmos 目录&#xff1a; abs(x)step(edge,x)dot(uv1,uv2) …

MySQL双主架构、主从架构

为什么要对数据库做优化&#xff1f; MySQL官方说法&#xff1a; 单表2000万数据就达到瓶颈了。所以为了保证查询效率&#xff0c;要让每张表的大小得到控制。 MySQL主主架构 主数据库都负责增删改查。 比如有1000W的数据&#xff0c;有两个主数据库&#xff0c;就将数据分流给…

古巴勒索软件-CVE-2023-27532工具利用

黑莓捕获古巴勒索软件&#xff0c;其中一项工具有关CVE漏洞工具利用&#xff0c;利用有关CVE-2023-27532编号的漏洞利用工具进行投放。 MD5 9a3349f062130201a3dc16e78d5ab05d SHA-1 96e0a6a2aa12ed572fea9a1a858c903356bce1ab SHA-256 cf87a44c575d391df668123b05c207eef0…