C++ 模板详解——template<class T>

一. 前言

在我们学习C++时,常会用到函数重载。而函数重载,通常会需要我们编写较为重复的代码,这就显得臃肿,且效率低下。重载的函数仅仅只是类型不同,代码的复用率比较低,只要有新类型出现时,就需要增加对应的函数。此外,代码的可维护性比较低,一个出错可能会导致所有的重载均出错。

二. 什么是C++模板

泛型编程思想的引用

如果我们现在需要写一个交换两个值的函数,我们学习了C++的函数重载和引用,那么写法大致如下:

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++中,也能够存在这样一个模具,通过给这个模具填充不同颜色的材料(类型),从而得到形状相同但颜色不同的月饼(生成具体类型的代码),那将会大大减少代码的冗余。巧的是前人早已将树栽好,我们只需在此乘凉。
 

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础

三. C++模板的分类

三. 函数模板

函数模板概念

程序员定义一种形式的函数,这种函数可以用不同的数据类型操作,但具有相同的功能逻辑。通过使用函数模板,可以大大减少代码的重复,并提高代码的复用性和灵活性。

函数模板定义格式

template <typename Type>
ReturnType FunctionName(Parameters) {
    // 函数体
}
//template <typename Type> 是模板声明,指出这是一个模板,并定义了一个类型参数 Type。typename 可以替换为 class,两者在这里作用相同。
//ReturnType 是函数返回的数据类型,它可以依赖于模板类型。
//FunctionName 是函数的名称。
//Parameters 是函数接受的参数,这些参数的类型可以是模板类型 Type。
示例

代码实例: 

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

}

int main()
{
	int LeftI = 2;
	int RightI = 3;
	cout << LeftI << RightI << endl;
	Swap(LeftI,RightI);
	cout << LeftI << RightI << endl;

	double LeftD = 2.5;
	double RightD = 3.5;
	cout << LeftD << RightD << endl;
	Swap(LeftD, RightD);
	cout << LeftD << RightD << endl;

	char LeftC = 'A';
	char RightC = 'B';
	cout << LeftC << RightC << endl;
	Swap(LeftC, RightC);
	cout << LeftC << RightC << endl;
	return 0;
}

 我们通过这个函数模版,分别传入不同数据类型的参数,通过结果的观察可以发现这个函数模版可以根据不同的类型去做一个自动推导,继而去起到一个交换的功能。 

函数模板的原理

请问上述真的是调用一个函数吗?

当然不是,这里我们三次Swap不是调用同一个函数,这里可以通过反汇编观察到:Swap根据不同的类型通过模板定制出专属的类型的函数,然后再调用

可以发现,在进行汇编代码查看的时候,被调用的函数模版生成了三个不同的函数,它们有着不同的函数地址

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

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

对于一些常用的函数如swap,库里面已经定义好了模板,我们只需要直接用即可

 

函数模板的实例化

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

隐式实例化

隐式实例化发生在模板代码被实际使用时。编译器根据模板使用时提供的具体类型参数自动生成模板的一个实例。这意味着编译器在遇到模板函数或类模板的特定类型的调用或声明时,自动生成必要的代码。

例如,给定一个模板函数:

template <typename T>
void Swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

当你调用这个函数: 

int x = 1, y = 2;
Swap(x, y);  // 编译器将隐式地为int类型实例化Swap模板

 编译器自动为 int 类型参数创建 Swap 函数的实例,这个过程称为隐式实例化。

显式实例化

显式实例化是你明确告诉编译器为特定类型生成模板的实例,而不是让编译器根据需要来决定。通过显式实例化,编译器会在实例化的地方生成模板的代码,而不管模板是否会被用到,这常用于减少编译时间和控制模板的实例化位置。

//在函数名后的<>中指定模板参数的实际类型
template void Swap<int>(int&, int&);  // 显式地实例化int类型的Swap模板

这段代码不调用 Swap 函数,但它告诉编译器生成处理 int 类型的 Swap 函数的代码。

显式实例化确保模板只被实例化一次,这对于减少编译时间和解决链接问题是有帮助的,尤其是在大型项目中或者模板定义与实例化在不同的编译单元时。

总结来说,隐式实例化基于代码中的使用自动进行,而显式实例化基于程序员的指示进行。

 例如,还是Swap模板函数:

template <typename T>
void Swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

 调用Swap函数

int a1 = 10, a2 = 20;
double b1 = 10.0, b2 = 20.0;
//显示实例化
Swap<int>(a1,a2);
Swap<double>(b1,b2);

Swap<double>(a1,b2);//使用显示实例化时,如果传入的参数类型与模板参数类型不匹配,
//编译器会尝试进行隐式类型转换,显式指定T为double,int1将隐式转换为double.
//如果无法转换成功,则编译器将会报错。

外传 注意:以下使用情况可能一不小心就会出错

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

int main()
{
    int int1 = 10, int2 = 20;
	double double1 = 10.0, double2 = 20.0;
	Add(int1, double2); //err 编译器推不出来

//该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
//通过实参int1将T推演为int,通过实参double2将T推演为double类型,但模板参数列表中只有
//一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错
//注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅

}

在函数模板调用时,每个模板参数都必须是明确的,编译器将尝试从每个实参推导出对应的模板参数类型。在这个例子中,编译器将尝试通过 int1 推导出 Tint 类型,同时又通过 double2 推导出 Tdouble 类型。显然,这两种类型是冲突的,因为模板参数列表中只有一个 T,并且在C++模板中,编译器通常不会自动进行类型转换来匹配模板参数。

解决方法

方法1: 使用相同类型的参数

确保调用 Add 函数时使用相同类型的参数:

int main() {
    int int1 = 10, int2 = 20;
    Add(int1, int2);  // 正确,T被推导为int

    double double1 = 10.0, double2 = 20.0;
    Add(double1, double2);  // 正确,T被推导为double
}

方法2: 显式指定模板参数

显式指定模板参数 T 的类型,这将导致非模板参数类型的隐式转换:

int main() {
    int int1 = 10;
    double double2 = 20.0;
    Add<double>(int1, double2);  // 显式指定T为double,int1将隐式转换为double
}

方法3: 修改函数模板以接受两个不同的类型参数

修改 Add 函数模板以接受两个不同的类型参数,并明确指定返回类型:

template<class T1, class T2>
auto Add(const T1& left, const T2& right) -> decltype(left + right) {
    return left + right;
}

int main() {
    int int1 = 10;
    double double2 = 20.0;
    auto result = Add(int1, double2);  // result的类型将是T1和T2之和的类型,即double
}

模板参数的匹配原则

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

#include <iostream>
using namespace std;
//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{
	return x + y;
}
//通用类型加法的函数模板
template<typename T>
T Add(const T& x, const T& y)
{
	return x + y;
}
int main()
{
	int a = 10, b = 20;
	int c = Add(a, b); //调用非模板函数,编译器不需要实例化
	int d = Add<int>(a, b); //调用编译器实例化的Add函数
	return 0;
}

二. 对于非模板函数和同名的函数模板,如果其他条件都相同,在调用时会优先调用非模板函数,而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么选择模板

//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{
	return x + y;
}
//通用类型加法的函数模板
template<typename T1, typename T2>//这里传了两个模板,所以能传入两种不同类型的参数
T1 Add(const T1& x, const T2& y)
{
	return x + y;
}
int main()
{
	int a = Add(10, 20); //与非模板函数完全匹配,不需要函数模板实例化
	int b = Add(2.2, 2); //函数模板可以生成更加匹配的版本,编译器会根据实参生成更加匹配的Add函数
	return 0;
}

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

#include <iostream>
using namespace std;
template<typename T>
T Add(const T& x, const T& y)
{
	return x + y;
}
int main()
{
	int a = Add(2, 2.2); //模板函数不允许自动类型转换,不能通过编译
	return 0;
}

因为模板函数不允许自动类型转换,所以不会将2自动转换为2.0,或是将2.2自动转换为2。 

模板支持多个模板参数

template<class K, class V> //两个模板参数
void Func(const K& key, const V& value)
{
	cout << key << ":" << value << endl;
}
int main()
{
	Func(1, 1); //K和V均int
	Func(1, 1.1);//K是int,V是double
	Func<int, char>(1, 'A'); //多个模板参数也可指定显示实例化不同类型
}

总结:通过使用函数模板,可以写出更通用、更灵活的代码,同时减少重复和错误。编译时的类型检查提供了比C语言中的宏更高的类型安全性。函数模板在C++标准库中被广泛使用,例如在STL(标准模板库)的各种算法和容器中。这使得STL非常强大,因为它不仅能够处理几乎任何类型的数据,还保持了代码的紧凑和高效。

四. 类模板

类模板的概念

程序员定义一个蓝图,用于生成具体化的类,这些类可以处理多种数据类型。与函数模板类似,类模板目的在于提高代码的复用性、减少冗余,并提升类型安全性。通过类模板,可以创建出适用于任何数据类型的类结构,而无需对每种类型编写专门的代码。

类模板的定义格式 

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

例如:

template<class T>
class Score
{
public:
	void Print()
	{
		cout << "数学:" << _Math << endl;
		cout << "语文:" << _Chinese << endl;
		cout << "英语:" << _English << endl;
	}
private:
	T _Math;
	T _Chinese;
	T _English;
};

 注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。

template<class T>
class Score
{
public:
	void Print();
private:
	T _Math;
	T _Chinese;
	T _English;
};
//类模板中的成员函数在类外定义,需要加模板参数列表
template<class T>
void Score<T>::Print()
{
	cout << "数学:" << _Math << endl;
	cout << "语文:" << _Chinese << endl;
	cout << "英语:" << _English << endl;
}

外传:注意:类模板,是不支持,声明,定义,测试分开写的,会出现链接编译错误,如下:

解决办法:

类模板的实例化

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

template <class T>
class ExamScores {
private:
    vector<T> scores;  // 存储成绩的动态数组

public:
    // 向成绩列表中添加一个成绩
    void addScore(T score) {
        scores.push_back(score);
    }
    // 打印所有成绩
    void printScores() const {
        cout << "成绩列表: ";
        for (T score : scores) {
            cout << score << " ";
        }
        cout << endl;
    }
};

int main() {
    ExamScores<double> myScoresD; // 创建一个存储double类型成绩的实例

    // 添加成绩
    myScoresD.addScore(90.5);
    myScoresD.addScore(87.2);
    myScoresD.addScore(78.6);
    // 打印double类型成绩
    myScoresD.printScores();

    ExamScores<int> myScoresI; // 创建一个存储int类型成绩的实例
    myScoresI.addScore(90);
    myScoresI.addScore(80);
    myScoresI.addScore(78);
    // 打印int类型成绩
    myScoresI.printScores();

    ExamScores<std::string> myScoresC;  // 创建一个存储std::string类型成绩的实例
    myScoresC.addScore("优秀");
    myScoresC.addScore("良好");
    myScoresC.addScore("不及格");

    // 打印成绩
    myScoresC.printScores();
    return 0;
}

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

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

相关文章

2024统计建模:大数据与人工智能时代的统计研究

文章目录 题目解读你需要具备的知识点课题推荐视频分析 题目解读 主要做的是“大数据”与“人工智能”。 其中“大数据”所涉及的的第一个就是大量的数据&#xff0c;数据从哪里来&#xff1f;拿到数据后&#xff0c;我们需要做基本的数据分析&#xff0c;如何对大量的数据进…

图像处理技术与应用(一)

图像处理技术与应用入门 使用skimage进行图像读取和显示 skimage库&#xff08;Scikit-image&#xff09;提供了一个强大的工具集&#xff0c;用于执行各种图像处理任务。以下是如何使用skimage读取和显示图像的基本示例&#xff1a; from skimage import ioimg io.imread(…

Shopee日破8000单无货源大卖选品案例分享

选品是电商成功路上至关重要的一环&#xff0c;为了帮助虾皮商家更好地掌握选品技巧和打造爆款&#xff0c;在知虾当中涵盖了22项极具实用性的选品方法。本文以男士包类目&#xff0c;结合比较常用的热销跟卖法为例&#xff0c;介绍下如何通过核心指标及维度去落地选品。 分析…

AI人工智能培训老师叶梓:大数据治理的关键工具:开源数据血缘分析系统

在大数据时代&#xff0c;数据的产生和传播速度日益加快&#xff0c;数据之间的关系也变得日益复杂。为了更好地管理和理解数据之间的关系&#xff0c;数据血缘分析系统应运而生。本文将介绍几个开源的数据血缘分析系统&#xff0c;它们在数据治理、数据质量管理和数据隐私保护…

我宣布!软考真的是0基础小白的福音

大家为什么觉得有的证书是智商税呢&#xff1f;无非就是证书含金量达不到企业对于人才的选拔标准&#xff0c;或是满足不了自身的职业发展需要。 但是一方面大家又知道&#xff0c;含金量高且企业认可度高的证书&#xff0c;要么是价格太贵&#xff0c;要不就是考试难度大&…

个人音乐播放网站项目(SpringBoot+Linux部署上线)

在做完第一个博客系统项目以后&#xff0c;接着做下一个项目&#xff1a;音乐播放网站项目&#xff0c;此项目应用的技术栈和第一个项目是差不多的&#xff0c;即算是学完SSM等知识以后的两个入门级Java开发项目吧。 此项目包含的核心功能有&#xff1a; 一、登录、注册、退出…

知了汇智携手西科大举办“知了杯”网络安全趣味赛,共筑网络空间安全防线

为积极响应国家网络空间安全人才战略&#xff0c;加快攻防兼备网络创新人才培养步伐&#xff0c;实现以赛促学、以赛促教、以赛促用&#xff0c;推动网络空间安全人才培养和产学研用生态发展&#xff0c;成都知了汇智科技有限公司&#xff08;以下简称&#xff1a;知了汇智&…

随笔Ubuntu上的的一些使用

Ubuntu简易使用 常用指令 cdlsmkdirrf -rm 路径 换源 备份镜像 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak编辑文件设置 sudo gedit /etc/apt/sources.list清华源 # 阿里源 deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe mul…

数据库轻松切换:解读Spring中的AbstractRoutingDataSource

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 数据库轻松切换&#xff1a;解读Spring中的AbstractRoutingDataSource 前言AbstractRoutingDataSource介绍作用和优势&#xff1a;作用&#xff1a;优势&#xff1a; 使用 AbstractRoutingDataSource …

鬼手剪辑如何导入剪映草稿箱?含工程文件

鬼手剪辑导入剪映功能介绍 功能概述 鬼手剪辑导入剪映功能可以让您将鬼手剪辑翻译、克隆和一键解说等作品的工程文件导入到剪映草稿&#xff0c;以便通过剪映进行精细化调整。 推荐使用场景 视频二创 视频语音翻译 短剧解说等作品的微调 导出的工程文件包含以下元素 视频…

windows10小皮安装不同版本composer,实现自由切换使用

1、使用phpstudy小皮面板安装composer1.8.5和composer2.5.8两个版本&#xff1b; 2、打开刚才安装的composer安装目录&#xff1a;D:\phpstudy_pro\Extensions 3、打开composer1.8.5版本&#xff0c;修改composer.bat名称为composer1.8.5.bat&#xff1a; 4、打开composer2.5.8…

uniapp制作多选下拉框和富文本(短信页面)

实例 多选下拉框实现 http://t.csdnimg.cn/TNmcF 富文本实现 http://t.csdnimg.cn/Ei1iV

图解《图搜索算法》及代码实现

关注我&#xff0c;持续分享逻辑思维&管理思维&#xff1b; 可提供大厂面试辅导、及定制化求职/在职/管理/架构辅导&#xff1b; 有意找工作的同学&#xff0c;请参考博主的原创&#xff1a;《面试官心得--面试前应该如何准备》&#xff0c;《面试官心得--面试时如何进行自…

STM32F4使用FPU/DSP核心启用与测试

STEP1、下载DSP库 具体链接如下&#xff1a; https://www.st.com/en/embedded-software/stsw-stm32065.html?dl9w6sdOSAKySFxBhN764Stg%3D%3D%2CIS1vzyA84KLAefK%2B0DawUl0FScREpiT6AdC3qFjIMJnCIgXIwr82G2XUFo6w43Wp5L5CUyrX3vZAoaHRE3nsTmRsArV3hnQOEgX73SKt8ss1vGrLlfXT24j…

indexDB 大图缓存

背景 最近在项目中遇到了一个问题&#xff1a;由于大屏背景图加载速度过慢&#xff0c;导致页面黑屏时间过长&#xff0c;影响了用户的体验。从下图可以看出加载耗时将近一分钟 IndexDB 主要的想法就是利用indexDB去做缓存&#xff0c;优化加载速度&#xff1b;在这之前&am…

VNISEdit 制作安装包

1. 环境依赖 1.1. NSIS 下载 下载地址&#xff1a;https://nsis.sourceforge.io/Download 1.2. VNISEdit 下载 下载地址1&#xff1a;https://sourceforge.net/projects/hmne/ 下载 exe 安装。 下载地址2&#xff1a;https://hmne.sourceforge.net/ 可以下载 exe 安装。也…

基础算法---前缀和

文章目录 基本思想1.前缀和2.子矩阵的和3.长度最小的子数组4&#xff0c;除自身以外数组的乘积总结 基本思想 前缀和数组就是一个数组的前i项和 前缀和的用处&#xff1a;前缀和数组求出来之后我们就可以就可以求数组中的某个特定区间的和 就比如说求l到R的和&#xff0c;我…

linux休眠唤醒流程,及示例分析

休眠流程 应用层通过echo mem > /sys/power/state写入休眠状态&#xff0c;给一张大概流程图 这个操作对应在kernel/power/main.c的state这个attr的store操作 static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,const char *buf, size_t n) …

网站想实现HTTPS访问需要有哪些步骤?

网站要实现HTTPS访问&#xff0c;以确保数据传输安全和提升用户信任度&#xff0c;主要需按以下步骤操作&#xff1a; 1. 购买或申请SSL证书&#xff1a; - 根据网站类型和需求&#xff0c;选择合适的SSL证书&#xff1a;DV&#xff08;域名验证&#xff09;、OV&#xff08;组…

Maxwell安装使用和简单案例

一、解压 cd /opt/software/ ​ tar -zxvf maxwell-1.29.2.tar.gz -C /opt/module/ ​ cd /opt/module/ 二、MySQL 环境准备 1、修改 mysql 的配置文件 修改 mysql 的配置文件&#xff0c;开启 MySQL Binlog 设置 vi /etc/my.cnf 添加以下内容 server_id1 log-binmysql-…