zoukankan      html  css  js  c++  java
  • 如何实现 C 的函数重载

    大家都知道 C++ 等面向对象的语言支持函数重载,C++ 实现函数重载很大程度上依赖与编译器对函数名的 Mangling(损坏,破坏),即 C++ 的源代码被编译后同名的重载函数名字会被破坏,一般是在原函数名前后加上特定的字符串,以区分不同重载函数,然后在调用的时候根据参数的不同选择合适的函数,如下代码说明了编译器是如何处理普通函数重载的:

    #include <iostream>
    using namespace std;
    
    int func(void)
    {
        cout << "func without parameters" << endl;
    }
    
    int func(int ia)
    {
        cout << "func with one int parameter: " << endl;
        cout << ia << endl;
    }
    
    int func(int ia, float fb)
    {
        cout << "func with one int parameter and one float parameter" << endl;
        cout << ia << endl;
        cout << fb << endl;
    }
    
    int main()
    {
    
        func();
        func(5);
        func(5, 5.0);
    }
    g++ -S  ./t.cc

    编译后生成汇编代码可能如下:

        .file    "t.cc"
        .local    _ZStL8__ioinit
        .comm    _ZStL8__ioinit,1,1
        .section    .rodata
    .LC0:
        .string    "func without parameters"
        .text
        .globl    _Z4funcv
        .type    _Z4funcv, @function
    _Z4funcv:
    .LFB966:
        .cfi_startproc
        pushl    %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        subl    $24, %esp
        movl    $.LC0, 4(%esp)
        movl    $_ZSt4cout, (%esp)
        call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
        movl    $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
        movl    %eax, (%esp)
        call    _ZNSolsEPFRSoS_E
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
    .LFE966:
        .size    _Z4funcv, .-_Z4funcv
        .section    .rodata
    .LC1:
        .string    "func with one int parameter "
        .text
        .globl    _Z4funci
        .type    _Z4funci, @function
    _Z4funci:
    .LFB967:
        .cfi_startproc
        pushl    %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        subl    $24, %esp
        movl    $.LC1, 4(%esp)
        movl    $_ZSt4cout, (%esp)
        call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
        movl    8(%ebp), %edx
        movl    %edx, 4(%esp)
        movl    %eax, (%esp)
        call    _ZNSolsEi
        movl    $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
        movl    %eax, (%esp)
        call    _ZNSolsEPFRSoS_E
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
    .LFE967:
        .size    _Z4funci, .-_Z4funci
        .section    .rodata
        .align 4
    .LC2:
        .string    "func with one int parameter and one float parameter"
        .text
        .globl    _Z4funcif
        .type    _Z4funcif, @function
    _Z4funcif:
    .LFB968:
        .cfi_startproc
        pushl    %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        subl    $24, %esp
        movl    $.LC2, 4(%esp)
        movl    $_ZSt4cout, (%esp)
        call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
        movl    $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
        movl    %eax, (%esp)
        call    _ZNSolsEPFRSoS_E
        movl    8(%ebp), %eax
        movl    %eax, 4(%esp)
        movl    $_ZSt4cout, (%esp)
        call    _ZNSolsEi
        movl    $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
        movl    %eax, (%esp)
        call    _ZNSolsEPFRSoS_E
        movl    12(%ebp), %eax
        movl    %eax, 4(%esp)
        movl    $_ZSt4cout, (%esp)
        call    _ZNSolsEf
        movl    $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
        movl    %eax, (%esp)
        call    _ZNSolsEPFRSoS_E
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
    .LFE968:
        .size    _Z4funcif, .-_Z4funcif
        .globl    main
        .type    main, @function
    main:
    .LFB969:
        .cfi_startproc
        pushl    %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        andl    $-16, %esp
        subl    $16, %esp
        call    _Z4funcv
        movl    $5, (%esp)
        call    _Z4funci
        movl    $0x40a00000, %eax
        movl    %eax, 4(%esp)
        movl    $5, (%esp)
        call    _Z4funcif
        movl    $0, %eax
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
    

    可以看到,func 的三个版本重载函数在编译后名字都被破坏了,编译器将他们重命名为了 _Z4funcv, _Z4funci, _Z4funcif, (g++ 编译器可能根据函数参数类型为函数名加上了与参数类型相关的特定后缀,如func(void) 变成了 _Z4funcv, func(int) 变成了_Z4funci, func(int, float)变成了 _Z4funcif),然后在调用各个版本的func()时,编译器根据参数类型的不同选择合适的重载函数,如调用 func() 其实是调用了 _Z4funcv, 调用 func(5, 5.0)实际上是调用了 _Z4funcif等。

    但是,在很多情况下,利用可变参数可以实现 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);

    第二个 open 函数的第三个参数用来表明创建文件的权限,所以这就是 C 实现函数重载活生生的例子 :-)

    那么如何实现 C 的函数重载呢,比较通用的做法是利用 C 的可变参数va_args:

    #include <stdarg.h>
    
    void va_start(va_list ap, last);
    type va_arg(va_list ap, type);
    void va_end(va_list ap);
    void va_copy(va_list dest, va_list src);

    以下是一个简单的例子,"重载"了两个函数,第一个函数是两个参数,第二个函数带了三个函数,其中第三个函数是可选的,

    #include <stdio.h>
    #include <stdarg.h>
    
    void va_overload2(int p1, int p2)
    {
        printf("va_overload2 %d %d\n", p1, p2);
    }
    
    void va_overload3(int p1, int p2, int p3)
    {
        printf("va_overload3 %d %d %d\n", p1, p2, p3);
    }
    
    static void va_overload(int p1, int p2, ...)
    {
        if (p2 == 3)
        {
            va_list v;
            va_start(v, p2);
            
            int p3 = va_arg(v, int);
            va_end(v);
            va_overload3(p1, p2, p3);
            
            return;
        }
    
        va_overload2(p1, p2);
    }

    那么调用的时候可以如下调用:

    #include <stdio.h>
    int main()
    {
        va_overload(1, 2);
        va_overload(1, 2, 3);
        return 0;
    }

    除了根据参数个数实现重载以外,还可以实现参数类型的重载(typeof),这主要是利用了 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)

    另外两种用 C 实现函数重载的方法可以是利用宏和预处理,以及函数指针,只不过具体的重载方式也要根据特定的应用场景来决定。

    不过,C 实现函数重载需要开发人员自己编写很多额外的代码,门槛稍微高了,这也使得 C 语言不太适合用函数重载方式来编写规范的应用程序接口。

    所以,以后别人如果问你,C 可不可以实现函数重载,你就不能说“C 是不支持函数重载的”,而应该回答:“看情况看心情看应用场景咯 :-)“。

     参考链接:http://locklessinc.com/articles/overloading/

  • 相关阅读:
    selector
    Shape 属性解释
    Button 自定义图片,代码绘制样式,添加音效的方法
    android中得到颜色,图片资源的方式
    EditText 中文文档
    EditText 限制输入,自定义样式,监听输入的字符,自动换行
    TextView字体,行距,html格式,超链接,对大长度的设定
    TextView 中文文档
    Eclipse 汉化的和修改字体的方法
    让Android SDK Manager正常更新的办法
  • 原文地址:https://www.cnblogs.com/haippy/p/2835358.html
Copyright © 2011-2022 走看看