【C++ | 泛型编程】C++函数模板详解(定义、使用、特化、重载)

#编程小白如何成为大神?大学新生的最佳入门攻略#

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍C++泛型编程的函数模板 🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭
⏰发布时间⏰:

本文未经允许,不得转发!!!



在这里插入图片描述

🎄一、概述

介绍函数模板之前,先了解一下泛型编程。

泛型编程是一种编程范式,它强调在编写代码时 尽可能地使算法和数据结构独立于所处理的数据类型

在泛型编程中,使用泛型(通常通过模板在 C++ 等语言中实现)来创建可以处理多种不同类型数据的通用函数、类和数据结构,而无需为每种具体的数据类型单独编写重复的代码。其主要优点包括:

  • 提高代码的复用性:一次编写的泛型代码可以用于多种不同的数据类型,减少了重复开发。
  • 增强代码的可读性和可维护性:因为通用的逻辑被集中在一个地方,而不是分散在针对不同类型的多个实现中。
  • 提高程序的灵活性:可以轻松地适应新的数据类型,而无需对现有代码进行大量修改。

✨1.1 函数模板是什么?为什么需要函数模板?

模板是 C++中泛型编程的基础。函数模板可以说是一个创建函数的蓝图或公式,在代码编译期间按照调用方式转换成特定类型的函数。

🌰先看个例子,我们写一个函数比较两个值,如果有多个类型,我们可能需要重载,像下面代码一样:

int compare(const string &v1, const string &v2)
{
	if (v1 < v2) return -1;
	if (v2 < v1) return 1;
	return 0;
}

int compare(const double &v1, const double &v2)
{
	if (v1 < v2) return -1;
	if (v2 < v1) return 1;
	return 0;
}

这两个函数几乎是相同的,唯一的差异是参数的类型,函数体则完全一样。对于这种只有类型差异的函数,我们就可以使用函数模板来编写代码,让编译器根据调用时使用的参数类型去帮我们转换成对应的函数。这样可以避免写相同的代码。


在这里插入图片描述

🎄二、函数模板的定义

函数模板的定义看起来与普通函数相似,但使用了模板参数。模板参数通常放在尖括号 <> 内。以下是上个小节compare函数的函数模板:

template<typename T>
int compare(const T &v1, const T &v2)
{
	if (v1 < v2) return -1;
	if (v2 < v1) return 1;
	return 0;
}

我们看看函数模板是怎样定义的:

  • 1、使用关键字 template 表示定义一个模板;
  • 2、template 后面是使用尖括号<>模板参数列表,这是用逗号分隔的零个或多个模板参数;
  • 3、模板参数列表里可以使用关键字typenameclass来传递一个类型,一般使用typename。之所以会有class来传递类型,是因为在标准 C++98 添加关键字 typename 之前,C++使用关键字 class 来创建模板:
    template<typename T, class T2>
    
  • 4、在函数编写时,需要使用类型的地方,使用模板参数里面的对应类型T来替换。
  • 5、虽然类型名可以自己定义,当常见的做法,是使用T来作为类型名。
  • 6、模板参数有两种,模板类型参数(参数前使用typenameclass)、非类型模板参数(使用特定类型名指定)
    template<typename T, class T2, int N>
    

注意:模板也可以有声明,但需要将 模板参数列表 带上,并且模板的声明和定义必须在同一文件中。更常见的情形是,将模板放在头文件中,并在需要使用模板的文件中包含头文件

template<typename T> int compare(const T &v1, const T &v2);  // 模板声明
...
template<typename T>	// 模板定义
int compare(const T &v1, const T &v2)
{
	if (v1 < v2) return -1;
	if (v2 < v1) return 1;
	return 0;
}

在这里插入图片描述

🎄三、函数模板的使用

使用函数模板时,编译器会根据传递的实际参数类型来为我们 实例化 (instantiate) 一个特定版本的函数。C++中有允许使用下面两种方式来使用函数模板:①自动推导参数类型;②指定模板参数类型。

  • 自动推导参数类型
    自动推导参数类型,是指在使用函数模板时,直接传入实参,让编译器根据传递的实际参数类型来自动生成相应的函数实例。例如下面代码:
    int x=5, y=6;
    compare(x,y);	// 编译过程中,生成 int compare(const int &v1, const int &v2)
    
    double f=1.23, d=4.56;
    compare(f,d);	// 编译过程中,生成 int compare(const double &v1, const double &v2)
    
  • 指定模板参数类型
    一般情况下,在调用函数模板时,编译器通常能够根据传递的参数自动推导模板参数的类型。但我们也可以显式指定模板参数类型,例如:
    int x=5, y=6;
    compare<int>(x,y);	// 编译过程中,生成 int compare(const int &v1, const int &v2)
    
    double f=1.23, d=4.56;
    compare<double>(f,d);	// 编译过程中,生成 int compare(const double &v1, const double &v2)
    

再次强调一下,函数模板并不是函数,它会在编译期间根据调用方式实例化一个函数出来,只是这个函数我们是看不到的,那怎么证明它的存在呢?你可以使用一个函数指针去指向一个函数模板的实例,然后打印出其函数地址,如下代码,最终打印出来的PFun、PFun1将会是同一个地址:

int (*PFun)(const int&, const int&) = compare<int>;
int (*PFun1)(const int&, const int&) = compare<int>;
cout << "PFun=" << (unsigned long)*PFun << ", PFun1=" << (unsigned long)*PFun1 << endl;

🌰完整例子:

// g++ 25_fun_template.cpp
#include <iostream>

using namespace std;

template<typename T> int compare(const T &v1, const T &v2);  // 模板声明

int main()
{
	int x=5, y=6;
	int ret = compare(x,y);	// 编译过程中,生成 int compare(const int &v1, const int &v2)
	cout << "x=" << x << ", y=" << y << ", compare ret is " << ret << endl;

	double f=1.23, d=4.56;
	ret=compare(f,d);	// 编译过程中,生成 int compare(const double &v1, const double &v2)
	cout << "f=" << f << ", d=" << d << ", compare ret is " << ret << endl;
	
	compare<int>(x,y);	// 编译过程中,生成 int compare(const int &v1, const int &v2)
	compare<double>(f,d);// 编译过程中,生成 int com1pare(const double &v1, const double &v2)

	int (*PFun)(const int&, const int&) = compare<int>;	// 函数指针 PFun 指向 compare<int> 函数
	int (*PFun1)(const int&, const int&) = compare<int>;// 函数指针 PFun1 指向 compare<int> 函数
	cout << "PFun=" << (unsigned long)*PFun << ", PFun1=" << (unsigned long)*PFun1 << endl;
	
	return 0;
}

template<typename T>	// 模板定义
int compare(const T &v1, const T &v2)
{
	if (v1 < v2) return -1;
	if (v2 < v1) return 1;
	return 0;
}

运行结果如下:
在这里插入图片描述


在这里插入图片描述

🎄四、函数模板的局限性与特化

虽然函数模板有很多优点,但也存在一些局限。模板实例化时,传入的类型必须支持函数模板函数体内的所有操作

例如,上面的 compare 函数模板,它的函数体内使用了 < 操作来判断两个对象的大小,所以就要求实例化时,传入的对象的类型必须支持 < 操作,否则就会报错。假设我们传入一个结构体对象来实例化这个函数模板就会编译不通过。这个就是函数模板的局限性。

那怎样解决这个问题呢?有2个方法,一是给传入的结构体重载<运算符;二是使用函数模板的特化,定义一个支持这种类型的函数模板。

什么时候需要使用函数特化?如果某个函数模板在处理某种类型时,其原有的函数体需要改变(存在不支持该类型的操作),就可以考虑使用函数模板的特化(specialization)

函数模板特化的步骤:
1、函数原型前加template <>
2、函数名后加<Type>,Type为要特化的类型。这一步可以省略。

template<> int compare<stType>(const stType &v1, const stType &v2) 	// 模板特化
{
	if (v1.id < v2.id) return -1;
	if (v2.id < v1.id) return 1;
	return 0;
}

上面代码是 compare 函数模板关于stType类型的特化。意思是告诉编译器不要使用 compare 函数模板来为 stType 类型生成函数定义,而是使用专用的特化模板来生成。

编译器在选择原型时,非模板版本优先于模板特化版本函数模板版本, 而模板特化版本优先于使用模板生成的版本。

完整例子:

// g++ 25_fun_template1.cpp
#include <iostream>

using namespace std;

struct stType
{
	int id;
	char name[20];
};

template<typename T> int compare(const T &v1, const T &v2);  // 模板声明
template<> int compare<stType>(const stType &v1, const stType &v2);
int compare(const stType &v1, const stType &v2);

int main()
{
	int x=5, y=6;
	int ret = compare(x,y);	// 编译过程中,生成 int compare(const int &v1, const int &v2)
	cout << "x=" << x << ", y=" << y << ", compare ret is " << ret << endl;
	
	stType st1, st2;
	ret=compare(st1,st2);
	return 0;
}

template<typename T>	// 模板定义
int compare(const T &v1, const T &v2)
{
	if (v1 < v2) return -1;
	if (v2 < v1) return 1;
	return 0;
}

template<> int compare<stType>(const stType &v1, const stType &v2) 	// 模板特化
{
	cout << "compare<stType>" << endl;
	if (v1.id < v2.id) return -1;
	if (v2.id < v1.id) return 1;
	return 0;
}

int compare(const stType &v1, const stType &v2)
{
	cout << "compare" << endl;
	if (v1.id < v2.id) return -1;
	if (v2.id < v1.id) return 1;
	return 0;
}

在这里插入图片描述

🎄五、函数模板的重载

使用函数模板时会发现,并非所有的类型都使用相同的算法。为满足这种需求,可以像重载常规函数定义那样重载模板定义。重载函数模板也是要求 参数数目、参数类型、参数的排列顺序 不同。

下面这些都会构成函数模板的重载:

template<typename T>	// 模板定义
int compare(const T &v1, const T &v2)
{
	if (v1 < v2) return -1;
	if (v2 < v1) return 1;
	return 0;
}

template<typename T>
int compare(const T &v1, const T &v2, int n)// 参数个数不一样,构成重载
{
	if (v1 < v2) return -1;
	if (v2 < v1) return 1;
	return 0;
}

template<typename T, typename T1>
int compare(const T &v1, const T1 &v2)// 参数类型不一样,构成重载
{
	if (v1 < v2) return -1;
	if (v2 < v1) return 1;
	return 0;
}

template<typename T, typename T1>
int compare(const T1 &v1, const T &v2)// 参数顺序不一样,构成重载
{
	if (v1 < v2) return -1;
	if (v2 < v1) return 1;
	return 0;
}

在这里插入图片描述

🎄六、总结

👉本文介绍了C++函数模板,函数模板定义、函数模板的使用、函数模板的特化、函数模板的重载。

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wkd_007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值