当前位置: 首页 > news >正文

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

😁博客主页😁:🚀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++函数模板,函数模板定义、函数模板的使用、函数模板的特化、函数模板的重载。

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 工具学习_CONAN_Consuming Packages
  • 如何在 Debian 上安装运行极狐GitLab Runner?【一】
  • Hadoop入门:构建你的第一个大数据处理平台
  • Spring Boot 使用多线程完成 统计当日用户所属区域
  • 选电脑——电脑配置
  • ViP-LLaVA: Making Large Multimodal Models Understand Arbitrary Visual Prompts
  • 江协科技51单片机学习- p31 LCD1602液晶屏驱动
  • Java二十三种设计模式-组合模式(11/23)
  • 揭秘LoRA:利用深度学习原理在Stable Diffusion中打造完美图像生成的秘密武器
  • c++ | vector
  • 【多线程-从零开始-肆】线程安全、加锁和死锁
  • 线程 【Linux】
  • vue3+axios请求导出excel文件
  • 【优秀python大屏】基于python flask的广州历史天气数据应用与可视化大屏
  • Jboss 漏洞合集
  • php的引用
  • [LeetCode] Wiggle Sort
  • 【347天】每日项目总结系列085(2018.01.18)
  • 【node学习】协程
  • 【vuex入门系列02】mutation接收单个参数和多个参数
  • Angular6错误 Service: No provider for Renderer2
  • Essential Studio for ASP.NET Web Forms 2017 v2,新增自定义树形网格工具栏
  • fetch 从初识到应用
  • leetcode46 Permutation 排列组合
  • open-falcon 开发笔记(一):从零开始搭建虚拟服务器和监测环境
  • spring-boot List转Page
  • ViewService——一种保证客户端与服务端同步的方法
  • 大整数乘法-表格法
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 老板让我十分钟上手nx-admin
  • 问:在指定的JSON数据中(最外层是数组)根据指定条件拿到匹配到的结果
  • 想写好前端,先练好内功
  • 一道闭包题引发的思考
  • 从如何停掉 Promise 链说起
  • ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTr
  • (2)MFC+openGL单文档框架glFrame
  • (9)STL算法之逆转旋转
  • (cljs/run-at (JSVM. :browser) 搭建刚好可用的开发环境!)
  • (done) NLP “bag-of-words“ 方法 (带有二元分类和多元分类两个例子)词袋模型、BoW
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (过滤器)Filter和(监听器)listener
  • (接上一篇)前端弄一个变量实现点击次数在前端页面实时更新
  • (六)c52学习之旅-独立按键
  • (七)理解angular中的module和injector,即依赖注入
  • (十二)springboot实战——SSE服务推送事件案例实现
  • (转)EXC_BREAKPOINT僵尸错误
  • (转载)Linux网络编程入门
  • (转载)深入super,看Python如何解决钻石继承难题
  • ***详解账号泄露:全球约1亿用户已泄露
  • **PHP二维数组遍历时同时赋值
  • ./include/caffe/util/cudnn.hpp: In function ‘const char* cudnnGetErrorString(cudnnStatus_t)’: ./incl
  • .libPaths()设置包加载目录
  • .Net 8.0 新的变化
  • .NET C#版本和.NET版本以及VS版本的对应关系
  • .NET 材料检测系统崩溃分析