随笔 - 28  文章 - 0  评论 - 0 

用c语言实现函数重载

一.    什么是函数重载?
        函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表(参数个数、类型、顺序)的函数,这组函数被称为重载函数。重载函数通常用来声明一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。


二、为什么要用函数重载
在我们之前学习的C中,我们对一个功能函数要实现不同类型的调用时,就必须得取不同的名称。如果调用的非常的多,就必须得起好多的名字,这样就大大增加了工作量,所以在C++中,我们就考虑到了函数重载。

三、 C++函数重载如何实现?
    在C++的底层,有重命名机制,比如下面这个函数。

    实现一个加法函数,可以对int型、double型、long型进行加法运算。在C++中,我们可以这样做:

#include<iostream>
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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

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<stdio.h>  
#include<stdarg.h>  
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<n;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<stdio.h>

void func_int(void * a)
{
printf("%d\n",*(int*)a); //输出int类型,注意 void * 转化为int
}

void func_double(void * b)
{
printf("%.2f\n",*(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 %f\n", s.a, s.b, s.c);
}

void gcc_overload_s2(struct s2 s)
{
    printf("Got a struct s2: %lld %lld\n", 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)

转自:https://blog.csdn.net/ypshowm/java/article/details/89096042

posted on 2020-05-08 00:05  TotallyNewUser  阅读(203)  评论(0编辑  收藏