一. 什么是函数重载?
函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表(参数个数、类型、顺序)的函数,这组函数被称为重载函数。重载函数通常用来声明一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。
二、为什么要用函数重载
在我们之前学习的C中,我们对一个功能函数要实现不同类型的调用时,就必须得取不同的名称。如果调用的非常的多,就必须得起好多的名字,这样就大大增加了工作量,所以在C++中,我们就考虑到了函数重载。
三、 C++函数重载如何实现?
在C++的底层,有重命名机制,比如下面这个函数。
实现一个加法函数,可以对int型、double型、long型进行加法运算。在C++中,我们可以这样做:
#include
using namespace std;
int Add(int left, int right)
{
return left + right;
}
double Add(double left, double right)
{
return left + right;
}
long Add(long left, long right)
{
return left + right;
}
int main()
{
Add(10, 10);
Add(10.0, 10.0);
Add(10L, 10L);
return 0;
}
通过上面代码的实现,可以根据具体的Add()的参数去调用对应的函数。
底层的重命名机制将Add函数根据参数的个数,参数的类型,返回值的类型都做了重新命名。那么借助函数重载,一个函数就有多种命名机制。
在C++调用约定(_cdecl 调用约定)中Add函数在底层被解析为:
"int __cdecl Add(int,int)" (?Add@@YAHHH@Z)
"double __cdecl Add(double,double)" (?Add@@YANNN@Z)
"long __cdecl Add(long,long)" (?Add@@YAJJJ@Z)
在C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”声明?
(1)C++中可以通过在函数声明前加 extern "C" 将一个函数按照 C 语言的风格来进行编译。
(2)C++语言支持函数重载。而C不支持函数重载。
(3)函数在C中和C++中编译过的函数名字是不一样的。加上extern”C”是说明是说明C已经编译过的。
C++想要调用已经编译过的C函数,由于编译过的名字不同,是不能直接调用的,所以C++加extern“C”生命来解决这个问题。
例如:假设某个函数的原型为: void foo(int x, int y);该函数被C 编译器编译后在库中的名字为_foo, 而C++ 编译器则会产生像
"int __cdecl Add(int,int)" (?Add@@YAHHH@Z)
"double __cdecl Add(double,double)" (?Add@@YANNN@Z)
"long __cdecl Add(long,long)" (?Add@@YAJJJ@Z)
_foo_int_int 之类的名字,加上extren”C”后,就相当于告诉编译器,函数foo是个C编译后的函数,在库里应该找的是_foo,而不是_foo_int_int.
。
接下来讲讲怎么通过c语言去实现函数重载
C语言实现函数重载
(1)利用可变参数
但是,在很多情况下,利用可变参数可以实现 C 语言的函数重载的,POSIX 接口中定义的 open 函数就是一个非常好的例子,
#include
#include
#include
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
以下是一个简单的例子,”重载”了两个函数,第一个函数是两个参数,第二个函数带了三个函数,其中第三个函数是可选的,
ANSI C 标准中,有可变参数的概念,可以通过一组宏实现
函数 描述
col 3 is right-aligned
va_list arg_ptr 定义一个可变参数列表指针
va_start(arg_ptr, argN) 让arg_ptr指向参数argN
va_arg(arg_ptr, type) 返回类型为type的参数指针,并指向下一个参数
va_copy(dest, src) 拷贝参数列表指针,src->dest,
va_end(arg_ptr) 清空参数列表,并置参数指针arg_ptr无效。每个va_start()必须与一个va_end()对应
#include
#include
int getMax(int n, ...)
{
va_list va;
va_start(va,n); // init va, pointing to the first argument
int tmp,smax=-1;
int i;
for(i=0;i
tmp=va_arg(va,int); // get the next argument, the type is int
if(tmp>smax) smax=tmp;
}
va_end(va);
return smax;
}
int main()
{
printf("%d/n",getMax(4,9,5,2,19));
printf("%d/n",getMax(6,1,3,4,5,2,0));
}
参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈
因此,假设定义一个可变参数的函数 void f(int x, …), 通过f( x, y, z) 调用,那么,z先入栈,然后y, 然后x。 因此我们只要得到任何一个变量的地址,就可以找到其它变量的地址。
va_start(va, n) 就是让va指向n的地址。这样,后面就可以得到所有参数的值。前提是,我们必须知道每个参数的类型。在本例子中,都是int类型。函数指针实现的参数重载(这个是重点,要掌握)
#include
void func_int(void * a)
{
printf("%dn",*(int*)a); //输出int类型,注意 void * 转化为int
}
void func_double(void * b)
{
printf("%.2fn",*(double*)b);
}
typedef void (*ptr)(void *); //typedef申明一个函数指针
void c_func(ptr p,void *param)
{
p(param); //调用对应函数
}
int main()
{
int a = 23;
double b = 23.23;
c_func(func_int,&a);
c_func(func_double,&b);
return 0;
}
(3)实现参数类型的重载
这主要是利用了 GCC 的内置函数,__builtin_types_compatible_p()和__builtin_choose_expr(),
例如:
struct s1
{
int a;
int b;
double c;
};
struct s2
{
long long a;
long long b;
};
void gcc_overload_s1(struct s1 s)
{
printf("Got a struct s1: %d %d %fn", s.a, s.b, s.c);
}
void gcc_overload_s2(struct s2 s)
{
printf("Got a struct s2: %lld %lldn", s.a, s.b);
}
// warning: dereferencing type-punned pointer will break strict-aliasing rules
#define gcc_overload(A)
__builtin_choose_expr(__builtin_types_compatible_p(typeof(A), struct s1),
gcc_overload_s1(*(struct s1 *)&A),
__builtin_choose_expr(__builtin_types_compatible_p(typeof(A), struct s2),
gcc_overload_s2(*(struct s2 *)&A),(void)0))
或者一个更高级的写法:
void gcc_type_overload_aux(int typeval, ...)
{
switch(typeval)
{
case 1:
{
va_list v;
va_start(v, typeval);
struct s1 s = va_arg(v, struct s1);
va_end(v);
gcc_overload_s1(s);
break;
}
case 2:
{
va_list v;
va_start(v, typeval);
struct s2 s = va_arg(v, struct s2);
va_end(v);
gcc_overload_s2(s);
break;
}
default:
{
printf("Invalid type to 'gcc_type_overload()'n");
exit(1);
}
}
}
#define gcc_type_overload(A)
gcc_type_overload_aux(
__builtin_types_compatible_p(typeof(A), struct s1) * 1
+ __builtin_types_compatible_p(typeof(A), struct s2) * 2
, A)