C++ 初识函数模板
1. 前言
什么是函数模板?
理解什么是函数模板,须先搞清楚为什么需要函数模板。
如果现在有一个需求,要求编写一个求 2 个数字中最小数字的函数,这 2 个数字可以是 int类型,可以是 float 类型,可以是所有可以进行比较的数据类型……
常规编写方案:针对不同的数据类型编写不同的函数。
#include <iostream>
using namespace std;
//针对 int 类型
int getMin(int num1,int num2) {
return num1>num2?num2:num1;
}
//针对 float 类型
float getMin(float num1,float num2) {
return num1>num2?num2:num1;
}
//针对 double 类型
double getMin(double num1,double num2) {
return num1>num2?num2:num1;
}
int main() {
//整型数据比较
int min=getMin(10,4);
cout<<min<<endl;
//float 类型数据比较
float minf=getMin(3.8f,2.9f);
cout<<minf<<endl;
//double 类型数据比较
double mind=getMin(1.8,2.1);
cout<<mind<<endl;
return 0;
}
重载函数(当然上述几个函数名也可以不相同)可以解决这个问题。显然,上述 3 个函数的算法逻辑是一模一样的,仅是函数的参数类型不一样。既然函数的形式参数可以接受值不同的同类型数据,能否把函数形参的数据类型也参数化,用来接受不同的数据类型。
函数模板实质就是参数化数据类型,称这种编程模式为数据类型泛化编程。
Tips: 泛化的意思是一般化、抽象化,先不明确指定,需要时再指定。
如:我对班长说,我需要
一名学生帮我搬课桌。这名学生到底是谁,我没有明确,由班长具体化。换在函数模板中,表示函数模板需要一种数据类型的数据,具体是什么数据类型,由使用者决定。
2. 初识函数模板
2.1 语法
在重构上述代码时,先了解一下函数模板的语法结构:
template <模板形式参数列表> 返回类型 函数名(函数形式参数列表)
{
函数体
}
语法结构说明:
template关键字说明了此函数是一个函数模板。template <>的尖括号里是模板参数列表,也可称此处的参数为数据类型参数,用来对函数算法所针对的数据类型的泛化,表示可以接受不同的数据类型。Tips:
模板参数列表中的参数可以是一个或多个泛化数据类型参数,也可以是一个或多个具体数据类型参数。泛化类型参数前面要加上
typename关键字。后面便是函数的一般性说明,只是在函数中可以使用
模板数据类型参数。Tips: 函数模板中有
2类参数,模板参数和函数参数。
使用函数模板重构上面求最小值的代码:
template<typename T> T getMin(T num1,T num2){
return num1>num2?num2:num1;
}
说明:
typename T声明了一个数据类型参数,用于泛化任一种数据类型,或者说T可以表示任意一种数据类型。Tips:
typename 是 C++11 标准,也可以使用class关键字,但建议不用,避免和类定义混淆。T数据类型可以作为函数的参数类型、返回值类型、以及作为算法实施过程中临时变量的数据类型。Tips:
T是一个变量标识符,在遵循变量命名规则的前提下,可以起任意名称。
2.2 实例化
函数模板如现实生活中制作陶瓷的模具一样,只有往模具中注入原材料,才能生成可实用的陶瓷。函数模板不是函数,仅是一个模板,不能直接调用,需要实例化后才能调用。
实例化:指编译器根据开发者对函数模板注入的具体(实参)数据类型构造出一个真正的函数实体(实例),这个过程由编译器自动完成,且实例化的函数对于开发者不可见。
int res= getMin<int>(1,6);
cout<<res<<endl;
//输出结果:1
如上,编译器通过函数模板<>内的int数据类型,实例化的函数可以对 int类型的数据进行算法操作。同理,下面的代码会让编译器实例化针对不同数据类型的数据进行算法操作的函数。
//实例化原型为 float getMin(float num1,float num2){函数体} 的函数
float resf=getMin<float>(3.2f,8.2f);
cout<<resf<<endl;
//实例化原型为 double getMin(double num1,double num2){函数体} 的函数
double resd=getMin<double>(1.2,0.2);
cout<<resd<<endl;
//实例化原型为 char getMin(char num1,char num2){函数体} 的函数
char resc=getMin<char>('A','B');
cout<<resc<<endl;
//输出结果分别为 3.2f 0.2 A
使用函数模板的优点不言而喻,声明一次,便可以实现针对不同数据类型的数据的操作。当然,中间会有匹配、实例化的代价。
Tips:高级业务层面的一劳永逸往往会以牺牲底层的性能为代价,但是,这是值得的。
除了通过显示声明数据类型提示编译器实例化,也可以使用函数指针实例化。
typedef int(*PF)(int,int); // 1
PF pf=getMin; // 2
int res= pf(6,8); //3
cout<<res; //4
说明:
1处先定义一个函数指针类型。2处这行代码,千万不要理解是取函数模板的地址,编译器在底层做了相应处理。编译器会根据函数指针类型说明先实例化一个函数。
再取实例化函数的内存地址,并赋值给
pf。3处以函数指针方式调用函数。
实例化时要注意的几个问题:
- 实例化时,可能会有一个直观问题:真的能指定任意一种数据类型实例化函数模板吗?
答案是:任何高级层面的逻辑行为都不能脱离基础知识的认知范畴,不同的数据类型有着语法系统赋予它的运算操作能力,当指定一个不支持函数模板内部算法操作的数据类型时,必然会出错。
如声明一个求 2 个数字相除的余数的函数模板。
template<typename T> T getYuShu(T num1,T num2) {
return num1 % num2;
}
如果指定 double 数据类型实例化 getYuShu 函数模板时,就会抛出错误,因为 double数据类型不能使用 %运算符。
double res=getYuShu<double>(6.2,2.4); //出错
Tips: 编译器在实例化函数模板时,会遵循
语法标准检查给定的数据类型是否支持函数模板中的运算操作。
编译器实例化的时机。
常规而言,编译器会在程序中第一次需要函数模板的某个实例时对其进行编译。但是,同一份代码中,可能会出现对同一个实例多次调用的需要,如下面的代码:
template <typename T > test(T num) {
return num;
}
int f() {
int res= test<int>(12);
return res;
}
double f1() {
int res= test<int>(24);
return double(res);
}
f和f1函数都需要使用 test<int>实例,于编译器而,无法知道 f和f1函数谁先会被调用(也就无法确定第一次编译的时间点),但为了保证编译期间完成实例化工作,早期C++编译器采用对同一实例每一次出现的地方都编译的策略,然后从多个编译结果中选一个作为最终结果,显然,编译时间会大大延长。
C++充许显式实例化声明,用来显示指定某一个函数模板的实例化的时间点,从而解决同一个实例被多次编译的问题。其语法如下:
template 返回值类型 模板名<模板参数列表>(函数形参列表);
针对上述函数模板可以编写如下代码,告之编译器编译时间点。
template <typename T > test(T num) {
return num;
}
//显示指定实例化
template int test<int>(int);
Tips: 显示声明只对一个源文件有效。
2.3 实参推导
所谓实参推导,在使用函数模板时省略<>,不明确指定数据类型参数,而是由编译器根据函数的实参类型自动推导出类型参数的真正类型。如下代码:
int res=getMin(4,7);
实参是int 类型, 编译器由此推导出 T 是 int类型,从而使用 int类型实例化函数模板,类似于下面的显示声明代码:
int res=getMin<int>(4,7);
实参推导可以像调用普通函数一样使用函数模板。但是实参推导是有前提条件的:函数参数使用了类型参数的才能通过函数实参类型推导。如下的函数模板。
template <typename T1,typename T2> T2 myMax(T1 num1,T1 num2) {
//函数体
}
因为 T2是作为函数模板的返回类型,是无法通过实参类型推导出来的。如下图所示:

使用如上函数模板,需要显示指定具体的数据类型。
double res= myMax<int,double>(6,8); //正确
是否可以让函数模板的类型参数一部分显示指定,一部分由实参推导?
答案是可以,但是,要求在声明函数模板时,把需要显示指定的类型参数放在前面,可由实参推导的参数类型放在后面。把上面的函数模板的 T1、T2参数说明交换位置。
template <typename T2,typename T1> T2 myMax(T1 num1,T1 num2) {
//函数体
}
实例化时,只需要显示指定 T2的类型,T1类型由编译器根据实参推导。如下代码可正确调用。
double res= myMax<double>(6,8); //正确
编译器把 T2指定为 double类型,然后根据实参6和8推导出 T1是 int类型。
了解什么是实参推导后,使用时,需要知道实参推导是不支持自动类型转换的。如下代码是错误的。
int res=getMin(4,7.5); //错误
编译器认定实参 4是int类型,实参7.5是 double类型,那么是到底是使用 int 类型还是使用 double类型实例化 getMin 函数模板,会让编译器不知所措、左右为难。
Tips: 即使支持自动类型转换,于编译器而言也无法知道开发者是想使用
int类型还是double类型。如此自动类型转换没有存在的意义。
对于上述问题可以采用如下几种方案解决:
通过
强制类型操作把实参转换成统一数据类型。int res=getMin(4,int(7.5));
或者
int res=getMin(double(4),7.5);
显示指定实例化时的数据类型。
int res=getMin<int>(4,7.5);
//或者
int res=getMin<double>(4,7.5);
如果有必要传递
2个不同类型的参数,可需要修改函数模板,使其能接受2种类型参数。template<typename T1,typename T2> T1 getMin(T1 num1,T2 num2){
return num1>num2?num2:num1;
}
3. 重载函数模板
C++中普通函数和函数模板可以一起重载,面对多个重载函数,编译器需要提供相应的匹配策略。如下代码:
//普通函数
int getMax(int num1,int num2){
return num1>num2?num1:num2;
}
//函数模板
template<typename T> T getMax(T num1,T num2) {
return num1>num2?num1:num2;
}
如下调用时,编译器是选择普通函数还是函数模板?
int res= getMax(6,8);
函数实参是 int类型,相比较函数模板,普通函数不需要实例化可直接使用,编译器会优先选择普通函数。但是如下的调用,编译器会选择函数模板。
getMax(2.4,6.8); //调用 getMax<double>(实参推导)
getMax('a','b'); //调用 getMax<char>(实参推导)
getMax<>(7,3) //调用 getMax<int> (实参推导)
getMax<double>(4,9) //显示指定
编译器选择函数模板的原则:
- 如果函数模板能实例出一个完全与函数实参类型相匹配的函数,那么就会选择函数模板,如
getMax(2.4,6.8);调用。编译器会根据函数模板实例化一个double getMax(double a,double b)函数与需求完全相匹配的函数。 - 如果即想使用实参推导,且想使用函数模板而非普通函数,可以使用空
<>尖括号语法。如上的getMax<>(7,7);调用。一旦指定<>标识符,显示指定使用函数模板,无论其中是否有实参类型说明。
如下的函数调用,实参有 2 个,但 2者之间可以发生自动类型转换。
char和int之间可以相互转换。
getMax('a',98);
编译器会选择谁?可以做一个实验,把普通函数注释,保留函数模板。
#include <iostream>
#include <cstring>
using namespace std;
//函数模板
template<typename T> T getMax(T num1,T num2) {
return num1>num2?num1:num2;
}
int main(int argc, char** argv) {
int t= getMax('a',98)
return 0;
}
执行后:

再恢复普通函数后执行,代码可以正常执行。显然,编译器选择的是普通函数。原因很简单,在使用实参推导时,函数模板是不支持自动类型转换,而普通函数表示没有压力。
总结一下,选择时,编译器会先考虑有没有类型完全相匹配的普通函数,没有,试着看能不能实例化一个完全匹配的函数。
4. 总结
本文只讲到了函数模板的语法、实例化和重载 3 个方面的内容,除此之外,函数模板还有其它高级应用,受限于篇幅,本文仅抛砖引玉,有兴趣者可以查阅相关文档。
C++ 初识函数模板的更多相关文章
- day7学python 初识简单模板
初识简单模板 模块与包 1.模块:用来从逻辑上组织python代码(变量,函数,类,逻辑:实现功能),本质是.py结尾的文件 但导入的模块名,无.py 2.包:从逻辑上组织模块,本质就是目录(含有_i ...
- C++进阶-1-模板基础(函数模板、类模板)
C++进阶 模板 1.1 函数模板 1 #include<iostream> 2 using namespace std; 3 4 // 模板 5 6 // 模板的简单实例 7 // 要求 ...
- c++函数模板作为类的成员函数,编译报错LNK2019的解决方法
为了使某个类的成员函数能对不同的参数进行相同的处理,需要用到函数模板,即template<typename T> void Function(). 编译时报错LNK2019 解决方法: 1 ...
- C++STL - 函数模板
模板主要是为了泛型编程,做到与类型无关 模板有函数模板和类模板,本文主要整理的是函数模板 1.函数模板定义 template<typename 类型形参1,typename 类型形参2,...& ...
- 使用getopt_long来解析参数的小函数模板
getopt_long原型 #define no_argument 0 #define required_argument 1 #define optional_argument 2 struct o ...
- C++函数重载和函数模板
1.函数重载 这是小菜鸟写的一个例子. 函数重载应该注意以下几点: 1.1重载函数有类似的功能: 1.2只能以参数的类型(形参个数和类型)来重载函数, int max(int a,int b);flo ...
- 零值初始化&字符串常数作为函数模板参数
1.在定义一个局部变量时,并希望该局部变量的初始化一个值,可以显示调用其默认构造函数,使其值为0(bool类型默认值为false). template <typename T> void ...
- 让gcc支持成员函数模板的trick
让gcc支持成员函数模板的trick 罗朝辉 (http://www.cnblogs.com/kesalin/) 本文遵循“署名-非商业用途-保持一致”创作公用协议 gcc 4.7.3 不支持成员 ...
- 不可或缺 Windows Native (16) - C++: 函数重载, 缺省参数, 内联函数, 函数模板
[源码下载] 不可或缺 Windows Native (16) - C++: 函数重载, 缺省参数, 内联函数, 函数模板 作者:webabcd 介绍不可或缺 Windows Native 之 C++ ...
随机推荐
- BUUCTF-[BJDCTF2020]认真你就输了
[BJDCTF2020]认真你就输了 下载通过16进制查看发现是压缩包,直接就binwalk分离查看. 分离直接得到几个文件,不过好像压缩包里的和外面的文件是一样的,所以直接翻一下目录 直接就找到了 ...
- python 基础知识-day6(内置函数)
1.sorted():用于字典的排序 dict1={"name":"cch","age":"3","sex&q ...
- RabbitMD大揭秘
RabbitMD大揭秘 欢迎关注H寻梦人公众号 通过SpringBoot整合RabbitMQ的案例来说明,RabbitMQ相关的各个属性以及使用方式:并通过相关源码深刻理解. Queue(消息队列) ...
- jenkins配置自动执行sql脚本
shell脚本: bigsql="select big_version,small_version from d0mstore.db_current_version order by big ...
- 开通博客-学习java之路
已被西南交通大学录取,毕设也已经进入末期.开始狂神说的Java学习之路,纪念一下!!!
- Django【执行查询】(二)
官方Django3.2 文档:https://docs.djangoproject.com/en/3.2/topics/db/queries/ 本文大部分内容参考官方3.2版本文档撰写,仅供学习使用 ...
- MarkDown语法——更好地写博客
MarkDown语法--更好地写博客 我们在学习过程中要尽量养成编写博客的 好习惯:一方面方便自己在学习之后进行一次汇总,其次自己书写的文章可以在以后的时间里反复查看以便于巩固,在找工作时博客也是被招 ...
- 深度学习基础-基于Numpy的多层前馈神经网络(FFN)的构建和反向传播训练
本文是深度学习入门: 基于Python的实现.神经网络与深度学习(NNDL)以及花书的读书笔记.本文将以多分类任务为例,介绍多层的前馈神经网络(Feed Forward Networks,FFN)加上 ...
- APISpace 空号检测API接口 免费好用
空号检测也称空号在线过滤,在线筛号,号码在线清洗.空号检测平台借助第五代大数据空号检测系统,为用户提供高精准的空号检测.号码过滤.号码筛选.号码清洗等众多号码检测功能,让用户快速准确的检测出活跃号.空 ...
- Wpf 多指应用开发解析
1 首先分析多指事件与单指事件,以及执行顺序 2 事件阻断 订阅多指事件后,在TouchDown时 采用e.handle = true,阻断多指事件,或在ManipulationStarting. ...