C++:模板详解

模板详解

  • 1.函数模板
    • 1.概念
    • 2.语法
    • 3.原理
    • 4.实例化
      • 1.隐式实例化
      • 2.显示实例化
    • 5.匹配原则
  • 2.类模板
    • 1.格式
    • 2.实例化
  • 3.非类型模板参数
    • 注意点
  • 4.特化
    • 1.概念
    • 2.函数模板特化
      • 1.前提
      • 2.语法说明
      • 3.示例
    • 3.类模板特化
      • 1.全特化
      • 2.偏特化/半特化
      • 3.选择顺序
    • 4.按需实例化
  • 5.模板的分离编译
    • 1.分离编译
    • 2.模板的分离编译
      • 问题
      • 分析问题
      • 解决方案
    • 3.模板的两次编译
  • 6.总结
    • 优点
    • 缺点

1.函数模板

1.概念

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

2.语法

template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){ ...}
template<typename T>
void Swap( T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
}

3.原理

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

4.实例化

1.隐式实例化

通过传入参数的类型让编译器自己推理。

2.显示实例化

自己手动写出传入的类型。在函数名后的<>中指定模板参数的实际类型

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

int main()
{
	int a = 10;
 	double b = 20.0;
 	// 显式实例化
	Add<int>(a, b);
 	return 0;
}

5.匹配原则

有现成的(现有的函数重载)使用现成的,没有现成的就使用模板实例化。

注意:模板函数不允许隐式类型转换,但普通函数可以。

int Add(int left, int right)
{
	return left + right;
}

template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
 	return left + right;
}

void Test()
{
 	Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
 	Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数,而不会进行隐式类型转换
}

2.类模板

1.格式

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

2.实例化

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

vector<int> s1;

3.非类型模板参数

在之前模板参数都是给的类型,比如Int,double之类,但是我们也可以在模板参数之中给一个常量。

namespace test
{
	// 定义一个模板类型的静态数组
 	template<class T, size_t N = 10>
 	class array
 	{
	public:
		T& operator[](size_t index){return _array[index];}
		const T& operator[](size_t index)const{return _array[index];}
 		size_t size()const{return _size;}
 		bool empty()const{return 0 == _size;}
	private:
		T _array[N];
		size_t _size;
 	}}

比如在实现静态顺序表时,其的空间大小是确定的,使用N来确定,如果需要同时创建一个数组大小为10和大小为1000的顺序表呢?我们只能将N改为1000来满足需求,但是这样会浪费一定的空间。如果可以将常量也可以作为模板参数使用,那么我们分别需要一个数组大小为10和数组大小为1000的顺序表时,可以通过传入参数来确定大小,从而不会浪费多余的空间。

注意点

1.非类型模板参数默认只能给整型家族的,直到C++20以后才支持double,string等

2.非类型模板参数必须在编译的时候就能确定大小因为在编译期就需要开辟空间。

4.特化

由于模板,我们可以实现出与类型无关的代码,来实现相同的功能,但是对于某些类型,使用模板的逻辑会产生我们预期之外的结果,这种类型需要特殊处理,因此产生了特化。

1.概念

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

2.函数模板特化

1.前提

必须有函数模板—>模板特化不能单独存在

2.语法说明

template<>
返回值类型 函数名<需要特化的类型> (形参1,....)
{
}

注意:函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

3.示例

template<class T>
bool Less(T x, T y)
{
	cout << "Less(T x, T y)" << endl;
	return x < y;
}

template<>
bool Less<int*>(int* x, int* y)
{
	cout << "Less<int*>(int* x, int* y)" << endl;
	return *x < *y;
}
template<class T>
bool Less(T* x, T* y)
{
	cout << "Less(T* x, T* y)" << endl;
	return *x < *y;
}
template<class T>
bool Less(T* x, T* y)
{
	cout << "Less(T* x, T* y)" << endl;
	return *x < *y;
}
int main()
{
	int* a = new int(4);
	int* b = new int(5);
	cout << Less(a, b) << endl;
	double* c = new double(1.1);
	double* d = new double(2.2);
	cout << Less(c, d) << endl;
	return 0;
}

在这里插入图片描述

总结:尽量不要使用函数模板的特化,因为语法等各种比较复杂,如果有需要,直接函数重载即可。

注意:其调用规则为有重载调用重载,没有重载再看特化中是否有符合的,如果特化中没有调用模板实例化的函数。

3.类模板特化

1.全特化

全特化就是把该类模板的所有参数特化(确定化)。

//函数模板
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;
};

2.偏特化/半特化

偏特化有两种形式:

1.部分参数特化
	部分特化就是把该类模板的一部分参数确定化。
2.参数限制(即类型特化)
	参数限制特化就是对该类模板的参数符合一定格式要求的确定化。(比如正常类型走模板实例化,指针类型走特化等)
//函数模板
template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

//参数半特化
template<class T1>
class Data<T1, char>
{
public:
	Data() { cout << "Data<T1, char>" << endl; }
private:
	T1 _d1;
	char _d2;
};

//类型限制
template<class T1,class T2>
class Data<T1*, T2*>
{
public:
	Data() { cout << "Data<T1*, T2*>" << endl; }
private:
	T1 _d1;
	char _d2;
};

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

在这里插入图片描述

3.选择顺序

有现成的(即特化好的)使用现成的,没有现成的使用模板实例化出来的。

4.按需实例化

按需实例化就是编译器在实例化时用到哪个函数才会实例化哪个函数。

情景:

//array.h
namespace test
{
	// 定义一个模板类型的静态数组
	template<class T, size_t N = 10>
	class array
	{
	public:
		T& operator[](size_t index) { 
			size(1); //此处是有一个错误的!!!
			return _array[index]; 
		}
		size_t size()const { return _size; }
		bool empty()const { return 0 == _size; }
	private:
		T _array[N];
		size_t _size = 0;
	};
}
//test.cpp
int main()
{
	test::array<int, 100> a;
	cout << a.empty() << endl;
    cout << a.size() << endl;
    a[10];
	return 0;
}

上面的代码如果运行起来是没有报错的,因为模板是一个半成品,编译器在预处理之后,编译之前会对模板的大体框架进行粗略检查,比如有无分号括号等,但是不会检查内部细节,比如上面size()中参数个数不一致,在创建a这个对象时实例化了默认构造函数,由于我们只调用empty和size(),因此只实例化了这两个函数,没有实例化错误的operator[],就不会报错,除非我们调用它。

5.模板的分离编译

1.分离编译

分离编译就是每一个源文件都是独立编译生成其各自的目标文件,然后所有的目标文件在链接过程中才会整合最后形成可执行文件。

2.模板的分离编译

如果一个类使用了模板,然后其的函数声明和定义分开,声明在.h文件,定义在.cpp文件,那么运行后编译器会报错:无法解析的外部符号。

问题

//array.h
#pragma once
#include<iostream>
using namespace std;
namespace Array
{
	// 只支持整形做非类型模板参数
	// 非类型模板参数  类型 常量
	// 类型模板参数   class 类型
	template<class T, size_t N = 10>
	class array
	{
	public:
		size_t size() const;
	private:
		T _array[N];
		size_t _size = 0;
	};
	void func();
}


//array.cpp
#include"array.h"
namespace Array
{
	template<class T, size_t N>
	size_t array<T, N>::size() const
	{
		T x = 0;
		x += N;
		return _size;
	}
	void func()
	{
		cout << "I am func" << endl;
	}
}

//test.cpp
#include"array.h"
int main()
{
	Array::array<int, 100> a;
	cout << a.size() << endl;

	Array::func();
}

运行后报错:

在这里插入图片描述

分析问题

出现这种链接错误是因为找不到这个函数的地址。

可是我们已经定义了该函数,为什么会没有它的地址呢?

由于每一个文件都是分离编译生成对应的目标文件,然后再进行链接。

对于func函数,array.h在test.cpp文件中展开,由于有函数的声明,并且其也有定义,在生成符号表时会正确的加入符号表从而被查找到。

对于size函数,array.h在test.cpp文件中展开,其有函数声明,在编译时不会报语法错误,但是在链接时,由于调用了size(),因此需要去符号表找其对应的地址去调用,但是我们的size()定义时只是一个模板,并没有实例化出来,因此在符号表中无法找到对应的函数去调用,因此报链接错误。

那么我们明明已经在test.cpp中实例化了对象,为什么size()没有实例化呢?

因为每一个文件是分离编译的!!!只有test.cpp文件知道array这个类需要用int和100来实例化,但是array.cpp并不知道,因此也不会实例化test.cpp在链接时直接去找自然找不到。

解决方案

1.显示实例化

即在.cpp文件中加入这样的代码:

template
array<int, 100>;

这样可以显示告诉编译器需要实例化出的案例,这样这样在分离编译时,array.cpp才会实例化出对应的函数。

但是不推荐这样的写法。因为如果这样,那么每一次实例化出类型不同的类,都需要再次进行显示实例化。

2.将声明和定义写在同一个.h中(强烈推荐!!!)

因为array.h在test.cpp中展开时,由于声明和定义在一个文件,展开后也在一个文件,那么自然知道需要实例化成什么类型。

3.模板的两次编译

模板的两次编译是指:
第一次在预处理之后,编译之前,会进行按需实例化,第二次是在编译的时候对实例化的部分进行语法排查。

6.总结

优点

1.模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
2.增强了代码的灵活性

缺点

1.模板会导致代码膨胀问题,也会导致编译时间变长
2.出现模板编译错误时,错误信息非常凌乱,不易定位错误

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

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

相关文章

开发一个农场小游戏需要多少钱

开发一个农场小游戏的费用因多个因素而异&#xff0c;包括但不限于游戏的规模、复杂性、功能需求、设计复杂度、开发团队的规模和经验&#xff0c;以及项目的时间周期等。因此&#xff0c;无法给出确切的费用数字。 具体来说&#xff0c;游戏的复杂程度和包含的功能特性数量会直…

巧用断点设置查找bug【debug】

默认设置的断点&#xff0c;当代码运行到断点处MCU就会被挂起&#xff0c;从而停在断点处。 但在某些情况下&#xff0c;如调试FCCU时&#xff0c;如果设置断点&#xff0c;MCU停下后将会导致 FCCU 配置WDG超时。或在调试类似电机控制类的应用时&#xff0c;不适当的断点会导 致…

镜舟科技荣获金科创新社 2024 年度金融数据智能解决方案奖

近日&#xff0c; 镜舟科技凭借领先的金融实时数仓构建智能经营解决方案&#xff0c;在“金科创新社第六届金融数据智能优秀解决方案评选”活动中&#xff0c;成功入选“数据治理与数据平台创新优秀解决方案”榜单。 金科创新社主办的“鑫智奖”评选活动&#xff0c;旨在展示…

详解IIC通信协议以及FPGA实现

一、IIC简介 IIC也称为I2C&#xff08;Inter-Integrated Circuit&#xff09;由飞利浦公司&#xff08;现在的恩智浦半导体&#xff09;开发&#xff0c;是一种用于短距离数字通信的串行&#xff0c;同步&#xff0c;半双工通信接口协议&#xff1b;传输在标准模式下可以达到10…

python:元组,字符串,切片

一、元组# 列表可以修改内容&#xff0c;元组可以不被修改 # 在程序内封装数据&#xff0c;不希望数据被篡改&#xff0c;所以使用元组 # 语法&#xff1a; 不限制类型 # 定于元组的字面量&#xff1a; &#xff08;元素&#xff0c;元素&#xff0c;元素.....&#xff09; # 定…

apipost、postman等工具上传图片测试flask、fastapi的文件api接口

参考&#xff1a;https://blog.csdn.net/qq_15821487/article/details/119354129 https://www.cnblogs.com/wyxjava/p/16076176.html 选择from-data&#xff0c;下拉选择file上传文件发送即可

线上真实案例之执行一段逻辑后报错Communications link failure

1.问题发现 在开发某个项目的一个定时任务计算经销商返利的功能时&#xff0c;有一个返利监测的调度&#xff0c;如果某一天返利计算调度失败了&#xff0c;需要重新计算&#xff0c;这个监测的调度就会重新计算某天的数据。 在UAT测试通过&#xff0c;发布生产后&#xff0c…

NVIDIA安装程序失败-Nsight Visual Studio Edition失败解决办法

博主是要升级cuda版本&#xff0c;那么在安装新版本之前需要卸载以前的版本。 博主一溜卸载下去&#xff0c;最后有这么个东西卸载不掉&#xff0c;Nsight Visual Studio Edition 不管是电脑系统卸载还是360卸载&#xff0c;都卸载不掉。 此时安装新的cuda也遇到了这个问题 由…

PLC存储器分类及西门子SIMATIC S7-1200存储器参数

存储器用来储存程序和数据&#xff0c;分为系统存储器和用户存储器。 系统存储器存放由PLC生产厂商编写好的系统程序&#xff0c;并固化在只读存储器&#xff08;ROM&#xff09;内&#xff0c;用户不能修改。用户存储器存放用户根据控制要求编写的应用程序。目前大多数PLC采用…

面试经典150题——从中序与后序遍历序列构造二叉树

1. 题目描述 2. 题目分析与解析 其实这个题目和昨天那个很相似&#xff0c;思考思路如下&#xff1a; 解决从中序&#xff08;inorder&#xff09;与后序&#xff08;postorder&#xff09;遍历序列构造二叉树的问题时&#xff0c;考虑到这两个遍历序列为我们提供了树结构中…

解决方案 SHUTDOWN_STATE xmlrpclib.py line: 794 ERROR: supervisor shutting down

Supervisor操作命令 重新加载 Supervisor 配置&#xff1a; sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl restart all这将重新读取 Supervisor 的配置文件&#xff0c;更新进程组&#xff0c;然后重启所有进程。 查看 Supervisor 日志&#xff1…

尺取法知识点讲解

一、固定长度的情况&#xff1a; 最小和(sum) 输入N个数的数列&#xff0c;所有相邻的M个数的和共有N-M1个&#xff0c;求其中的最小值。 输入格式 第1行&#xff0c;2个整数N&#xff0c;M&#xff0c;范围在[3…100000]&#xff0c;N>M。 第2行&#xff0c;有N个正…

R语言入门:“Hellinger“转化和“normalize“转化(弦转化)的公式表示与R代码实现

1、写在前面 vegan包中的decostand()函数为群落生态学研究提供了一些流行的(和有效的)标准化方法。有关decostand()函数标准化的一些标准化方法可以看我的另一篇笔记&#xff1a;R语言入门&#xff1a;vegan包使用decostand()函数标准化方法 由于在网络上没有找到关于这两个转…

Redis-键值设计

Redis-键值设计 1.设置key的规范 遵循基本格式&#xff1a;【业务名称】&#xff1a;【数据名】&#xff1a;【id】 可读性强&#xff0c;在客户端的情况下使用:如果前缀相同会分目录层级长度不超过44字节 string数据结构的三种类型&#xff0c;在44字节之内是embstring 内存…

鸿蒙应用开发之Web组件3

前面学习了从网上加载网页的显示,本文将要学习加载本地的网页。比如很多显示的内容,可以制作网页的文件格式,然后直接使用它来显示,就可以减少界面的制作。另外,当手机没有网络的时候,如果想从网络上获取内容就会失败,这时候可以使用本地的网页内容来代替。这样不会导致…

Python的Logging模块高级用法-日志处理

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 探索Python中的日志处理&#xff1a;Logging模块的高级用法 在Python应用程序中&#xff0…

lementui el-menu侧边栏占满高度且不超出视口

做了几次老是忘记&#xff0c;这次整理好逻辑做个笔记方便重复利用&#xff1b; 问题&#xff1a;elementui的侧边栏是占不满高度的&#xff1b;但是使用100vh又会超出视口高度不美观&#xff1b; 解决办法&#xff1a; 1.获取到侧边栏底部到视口顶部的距离 2.获取到视口的高…

【动态规划】dp 路径问题(不同路径、路径最小和、地下城游戏...)

文章目录 1. 前言 - 理解动态规划算法1.5 关于dp路径问题2. 例题2.1_不同路径Warning. 关于状态表示 3. 算法题3.1_不同路径II3.2_珠宝的最高价值3.3_下降路径最小和3.4_最小路径和3.5_地下城游戏关于状态表示的两种选法&#xff1a; 1. 前言 - 理解动态规划算法 关于 动态规划…

Pytorch 之torch.nn初探 池化--Pooling Layers

任务描述 本关任务&#xff1a;本关提供了一个Variable 类型的变量x&#xff0c;要求按照条件创建一个Conv2d变量conv&#xff0c;一个MaxPool2d变量pool&#xff0c;对x应用卷积和最大池化操作并赋值给变量outpout_pool&#xff0c;并输出outpout_pool 的大小。 相关知识 P…

Blerden4.1基础操作方法

软件安装 下载软件地址 中文文档 偏好设置 编辑——》偏好设置——》界面——》设置分辨率缩放 1.20 方便观看字体 设置快捷键 是为了方便几个3d软件都变成同一种操作方式 这样就不会自己搞混了 编辑——》偏好设置——》键位映射——》3D视图——》3D视图&#xff08;全局…
最新文章