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

news/2024/11/13 5:31:30/

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

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

目录

  • 🎄一、概述
    • ✨1.1 函数模板是什么?为什么需要函数模板?
  • 🎄二、函数模板的定义
  • 🎄三、函数模板的使用
  • 🎄四、函数模板的局限性与特化
  • 🎄五、函数模板的重载
  • 🎄六、总结



在这里插入图片描述

🎄一、概述

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

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

在泛型编程中,使用泛型(通常通过模板在 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;
}

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


在这里插入图片描述

🎄二、函数模板的定义

函数模板定义的格式:

template<typename T1, typename T2,......,typename Tn>
返回值类型 函数模板名(参数列表)
{//……
}
注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)

函数模板的定义看起来与普通函数相似,但使用了模板参数。模板参数通常放在尖括号 <> 内。以下是上个小节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++函数模板,函数模板定义、函数模板的使用、函数模板的特化、函数模板的重载。

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


http://www.ppmy.cn/news/1505459.html

相关文章

基于Orangepi全志H616学习Python3

目录 一、功能需求 二、Python的安装和环境搭建 三、Python的基础学习 3.1 Python的特点&#xff1a; 3.2 编写并运行第一个Python程序&#xff1a; 3.3 标识符&#xff1a; 3.4 关键字&#xff1a; 3.5 注释&#xff1a; 3.6 行与缩进&#xff1a; 3.7 多行语句&…

医院不良事件监测预警上报系统,PHP不良事件管理系统源码

不良事件上报系统&#xff0c;支持医院进行10大类医疗安全&#xff08;不良&#xff09;事件的上报管理&#xff1b;帮助医院管理部门更好把控不良事件的发生趋势&#xff0c;分析医院内部潜在的问题和风险&#xff0c;采取适当的管理措施&#xff0c;有效加强质量控制&#xf…

【Material-UI】Autocomplete 组件中的虚拟化功能(Virtualization)详解

文章目录 一、虚拟化概述二、实现虚拟化的步骤1. 引入依赖2. 自定义 Popper 组件和 Listbox 组件3. 代码详解4. 性能优化 三、实际应用场景1. 大规模数据列表2. 高性能需求的应用 四、总结 在现代 Web 开发中&#xff0c;处理大量数据时&#xff0c;性能优化显得尤为重要。Mate…

揭秘LoRA:利用深度学习原理在Stable Diffusion中打造完美图像生成的秘密武器

文章目录 引言LoRA的原理LoRA在角色生成中的应用LoRA在风格生成中的应用LoRA在概念生成中的应用LoRA在服装生成中的应用LoRA在物体生成中的应用结论 引言 在生成式人工智能领域&#xff0c;图像生成模型如Stable Diffusion凭借其出色的生成效果和广泛的应用场景&#xff0c;逐…

Android 多语言切换

文章目录 在系统设置修改语言创建资源目录创建资源文件示例验证 代码手动切换语言在Application中设置新的语言环境在MainActivity / BaseActivity中设置新的语言环境验证 问题1. makeText()方法context传入是Application的context&#xff0c;无法获取正确的资源字符串原因解决…

[Git] fatal: the remote end hung up unexpectedly

1 问题介绍 在使用Git进行代码管理和版本控制的过程中&#xff0c;许多开发者可能会遇到这样一个错误提示&#xff1a;fatal: the remote end hung up unexpectedly。这是一种常见的错误&#xff0c;通常出现在将本地代码推送&#xff08;push&#xff09;到远程仓库&#xff…

C语言——预处理和指针

C语言——预处理和指针 预处理宏宏定义宏的作用域带参的宏 文件包含条件编译 指针指针的概念指针的定义指针变量初始化指针一维整型数组 预处理 编程的流程分为&#xff1a;编辑、编译、运行、调试四个阶段&#xff1b; 预处理属于编译阶段&#xff0c;编译过程又可以分为&…

【Python机器学习】支持向量机——寻找最大间隔

如何求解数据集的最佳分隔直线&#xff1f;如下图中&#xff1a; 分隔超平面的形式可以写成&#xff0c;要计算点A到分隔超平面的距离&#xff0c;就必须给出点到分隔面的法线或垂线的长度&#xff0c;该值为。这里的常数b类似于Logistic回归中的截距。这里的向量w和常数b一起描…