函数模板与函数重载
一,什么是函数重载
1,在理解什么是函数重载之前,先理解一下函数符号生成规则,在C语言中,函数符号的生成只与函数名相关,这个是什么意思呢?如下代码:
#include<stdio.h>
int sum(int a,int b){return a+b;}
double sum (double a,double b){return a+b;}
float sum (float a,float b){return a+b;}
这些代码在main.c文件下运行,会出现这样的报错:
main.c:error c2371: "sum"重定义。
(1)对于使用__cdecl调用约定的函数,在函数名称前加一下划线,不考虑参数和返回值。
例如上述三个函数: 都是_sum
(2)对于使用__fastcall调用约定的函数,在函数名称前后各加一@符号,后跟参数的长度,不考虑返回值。
例如 int __fastcall Test(int n)的修饰名称为@Test@4;
(3)对于使用 __stdcall 标准调用约定的函数,在函数名称前加一下划线,名称后加@符号,后跟参数的长度,不考虑返回值。
例如 int __stdcall Test(int n,int m)的修饰名称为 _Test@8
2,在C++中,函数符号的生成与函数名,函数的返回值,函数的形参列表,即函数原型有关,如在main.cpp中:
#include<iostream.h>
using namespace syd;
int sum(int a,int b){return a+b;}
double sum (double a,double b){return a+b;}
float sum (float a,float b){return a+b;}
生成的函数符号如格式:?函数名称@@YA返回值参数列表@Z/Z(@Z表示有形参,Z表示没有形参)
int 型: ?sum@@YAHHH@Z
double型: ?sum@@YANNN@Z
float型: ?sum@@YAMMM@Z
调用约定: YA _cdecl YG _stdcall YI _fastcall
返回值类型:bool _N int H double N float M
所以对于C++编译器来说,上述三个函数生成的函数符号完全不相同,即为三个不同的函数,
如:bool compare (int a, int b);来说,函数符号为:?compare@@_NHH@Z
从上面可以知道:函数重载的条件是:1,函数名同名;2,函数的参数不同;3,同作用域
(1)函数的参数顺序不同可以作为重载的条件;
(2)函数参数的个数不同可以作为重载的条件;
(3)函数的参数不同也可以作为重载的条件;
调用约定
_cdecl c语言标准调用约定
_stdcall windows标准调用约定
_fastcall 快速调用约定
_thiscall 类成员方法的调用约定
对于形参的开辟和清理:
cdecl 由调用方开辟,由调用方清理;
stdcall 由调用方开辟,被调用方清理;
fastcall 前两个参数不开辟内存,之后的参数由调用方开辟,被调用方清理
二,模板函数的重载
1,什么是函数模板?
为什么要讲函数模板呢,因为对于C++来说,函数虽然允许重载,但是如果要实现任意两个数之和这种功能,如果用函数重载的话,需要写很多种重载函数,十分不方便,这个时候需要一个能够统一的智能函数,不管是什么类型,都能实现两数之和的这个函数功能,这个时候就需要函数模板。
函数模板允许程序员编写一个单独的函数定义,以处理许多不同的数据类型,而不必为每个使用的数据类型编写单独的函数。函数模板不是实际的函数,而是编译器用于生成一个或多个函数的 “模具”。在编写函数模板时,不必为形参、返回值或局部变量指定实际类型,而是使用类型形参来指定通用数据类型。当编译器遇到对函数的调用时,它将检查其实参的数据类型,并生成将与这些数据类型配合使用的函数代码。
template<模板参数表>
返回类型 函数名(形式参数表)
{
……;
}//函数体
template为函数模板的关键字,如求两数之和函数模板为:
template<typename group>
group Sum(group a, group b)
{
return a+b;
}
int main()
{
int a = 10;
int b = 20;
double c = 2.1;
double d = 2.8;
cout << Sum(a, b) << endl;
cout << Sum(c, d) << endl;
return 0;
}
什么是函数模板,什么是模板函数?template group Sum(group a, group b){return a+b;}是函数模板,其 实例化之后的函数Sum 是模板函数,这个实例化过程不是宏替换,而是重命名,即typedef int Group
模板函数的推演分为隐式推演和显式推演:
隐式推演就是编译器推演,显式推演是程序猿指定好这个函数采用何种参数:如
Sum<int>(a,b); Sum<double>(a,b);
程序员为这个函数指定参数类型。
2,什么是函数模板的重载?
#include<iostream>
using namespace std;
template <class T>
void fun(T a, T b)
{
}
template <class T>
void fun(T a)
{
}
int main ()
{
fun(11,22);
fun(23);
return 0;
}
这就是模板函数的重载,fun如果是两个参数,那么就实例化fun(T a, T b),如果是一个参数,那么就实例化fun(T a)。
template<class T> void fun (T a) {}
template<class U> void fun (U a) {}
那么这两个算模板函数的重载吗?
#include<iostream>
using namespace std;
template <class U>
void fun( U b)
{
}
template <class T>
void fun(T a)
{
}
int main()
{
fun(12);
return 0;
}
系统会出现这样的报错:
1>------ 已启动生成: 项目: C++上课代码集合, 配置: Debug Win32 ------
1>函数重载.cpp
1>C:\Users\ccayj\source\repos\C++上课代码集合\函数重载.cpp(10,6): error C2995: “void fun(U)”: 函数 模板已经定义
1>C:\Users\ccayj\source\repos\C++上课代码集合\函数重载.cpp(5): message : 参见“fun”的声明
1>C:\Users\ccayj\source\repos\C++上课代码集合\函数重载.cpp(16,2): error C2065: “fun”: 未声明的标识符
1>已完成生成项目“C++上课代码集合.vcxproj”的操作 - 失败。
========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ==========
显而易见,上述代码不算函数模板重载,那么是怎么个原因呢?
在函数模板推演的过程中,typedef int T void fun (T a){} 和 typedef int U void fun (U a),都被推演为整形,对于编译器来说,这两个重定义了,所以不能函数重载,我们不能拿函数模板名称不同还辨别函数重载。
下面两个模板函数算重载吗?
#include<iostream>
using namespace std;
template <class T>
void fun( T b)
{
}
template <class T>
void fun(T a, int x)
{
}
int main()
{
fun(12);
fun(12, 23);
return 0;
}
1>------ 已启动生成: 项目: C++上课代码集合, 配置: Debug Win32 ------
1>函数重载.cpp
1>C++上课代码集合.vcxproj -> C:\Users\ccayj\source\repos\C++上课代码集合\Debug\C++上课代码集合.exe
========== 生成: 成功 1 个,失败 0 个,最新 0 个,跳过 0 个 ==========
这个就是模板函数的重载,编辑器在编译的时候,因为形参数量不同,在推演之后生成的函数符号也不相同,构成重载。
3,模板函数和宏替换有什么区别?
#include<iostream>
using namespace std;
template <class T>
void fun( T b)
{
T x, y; //TX =>
//TY =>
}
int main()
{
int a = 10;
int* p = &a;
fun(a); //TA =>
fun(p); //TP =>
return 0;
}
那么TA,TP,fun§ 调用的TX,TY应该被推演成什么类型?
我们应该很容易的就能推演出:TA => int ; TP => int*
那么TX,TY应该被推演为什么?
TX => int* ; TY => int ;
这个结果对还是不对,我们在刚才已经说了,这个实例化过程不是宏替换,那么这个结果是对还是不对?
其实是不对的,正确的应该是:
TX => int* ; TY => int* ;
那fun(a)对于的TX,TY应该是什么类型,都是int型;
typedef int T
void fun (T a)
{
T x,y; //x和y的类型都是int
}
typedef int* T
void fun (T a)
{
T x,y;//x和y的类型都是int*
}
宏替换和模板函数采用的重命名不同:
#define PINT int*
typedef int* SINT;
int main()
{
PINT x, y;
SINT a, b;
}
x和y类型一样吗?不一样,x是int*,y是int类型,这是宏替换;
a和b类型都是int*;
可以使用头文件typeinfo中的typeid(x).name 检验类型;
#include<iostream>
using namespace std;
template<class T>
void fun(T* a)
{
T x, y;
//这个T是什么类型,x是什么类型,y是什么类型?
}
int main()
{
int a = 10;
int* p = &a;
fun(p);
return 0;
}
为什么T是整型而不是指针?
根本原因是因为:我们在定义指针是,我们的*是和变量名结合,所以在我们推演的时候,推演成为了typedef int T;
所以我们的T,x,y都是int型;
#include<iostream>
#include<typeinfo>
using namespace std;
template<class T> //一般的模板函数
void fun(T a)
{
}
template<class T> //部分特化的模板函数
void fun(T *a)
{
}
void fun(char *p) //完全特化的模板函数
{
}
int main()
{
int a = 10;
int* p = &a;
fun(a);
fun(p);
return 0;
}
//这个概念是什么概念呢?
//为什么可以编译通过呢?
//这三个函数有什么差别呢?
为什么可以编译通过呢?
从函数重载来说,这三个函数推演之后,生成的函数符号不同,所以可以重载,void fun(T a), 只要是个类型我都可以接受,class 、int、double、char都行,我都可以推演;但是 void fun(T *a),我只能接受指针类型的参数;void fun(char *a),只能接受char类型的指针。
from 西安图论教育杨和平老师