淘先锋技术网

首页 1 2 3 4 5 6 7

函数模板与函数重载

一,什么是函数重载

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 西安图论教育杨和平老师