首页 文章

如何在C中实现函数重载?

提问于
浏览
214

有没有办法在C中实现函数重载?我正在寻找简单的函数来重载像

foo (int a)  
foo (char b)  
foo (float c , int d)

我认为没有直接的方式;我正在寻找解决方法,如果存在的话 .

14 回答

  • 1

    通常会在名称后附加或附加一个表示类型的疣 . 你可以通过宏来逃避某些情况,但这取决于你想要做什么 . 没有C中的多态性,只有强制 .

    可以使用宏完成简单的通用操作:

    #define max(x,y) ((x)>(y)?(x):(y))
    

    如果您的编译器支持typeof,则可以在宏中放置更复杂的操作 . 然后你可以使用符号foo(x)来支持不同类型的相同操作,但是你可以尝试) .

  • 115

    Leushenko's answer真的很酷 - 单独: foo 示例不能用GCC编译,它在 foo(7) 失败,绊倒了 FIRST 宏和实际函数调用( (_1, __VA_ARGS__) ,保留了剩余逗号 . 此外,如果我们想要的话,我们遇到麻烦提供额外的重载,例如 foo(double) .

    所以我决定进一步阐述答案,包括允许空载过载( foo(void) - 这引起了一些麻烦......) .

    现在的想法是:在不同的宏中定义多个通用,并根据参数的数量选择正确的通用!

    根据this answer,参数数量非常简单:

    #define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__)
    
    #define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__)
    #define CONCAT(X, Y) CONCAT_(X, Y)
    #define CONCAT_(X, Y) X ## Y
    

    这很好,我们解决了 SELECT_1SELECT_2 (或更多的参数,如果你想要/需要它们),所以我们只需要适当的定义:

    #define SELECT_0() foo_void
    #define SELECT_1(_1) _Generic ((_1),    \
            int: foo_int,                   \
            char: foo_char,                 \
            double: foo_double              \
    )
    #define SELECT_2(_1, _2) _Generic((_1), \
            double: _Generic((_2),          \
                    int: foo_double_int     \
            )                               \
    )
    

    好吧,我已经添加了void重载 - 但是,这个实际上并没有被C标准覆盖,它不允许空的可变参数,i . 即然后我们依赖编译器扩展!

    最初,空的宏调用( foo() )仍然会生成一个令牌,但是一个空令牌 . 因此,即使在空宏调用上,计数宏实际上返回1而不是0 . 我们可以"easily"消除这个问题,如果我们有条件地放置 __VA_ARGS__ 之后的逗号,取决于列表是否为空:

    #define NARG(...) ARG4_(__VA_ARGS__ COMMA(__VA_ARGS__) 4, 3, 2, 1, 0)
    

    这看起来很简单,但 COMMA 宏是一个沉重的宏;幸运的是,这个话题已经在blog of Jens Gustedt中提到了(谢谢,Jens) . 基本的技巧是,如果没有后跟括号,则不扩展函数宏,为了进一步解释,看看Jens ' blog... We just have to modify the macros a little to our needs (I' m将使用更短的名称和更少的参数以简洁起见) .

    #define ARGN(...) ARGN_(__VA_ARGS__)
    #define ARGN_(_0, _1, _2, _3, N, ...) N
    #define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 1, 0)
    
    #define SET_COMMA(...) ,
    
    #define COMMA(...) SELECT_COMMA             \
    (                                           \
            HAS_COMMA(__VA_ARGS__),             \
            HAS_COMMA(__VA_ARGS__ ()),          \
            HAS_COMMA(SET_COMMA __VA_ARGS__),   \
            HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \
    )
    
    #define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3)
    #define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3
    
    #define COMMA_0000 ,
    #define COMMA_0001
    #define COMMA_0010 ,
    // ... (all others with comma)
    #define COMMA_1111 ,
    

    现在我们很好......

    一个块中的完整代码:

    /*
     * demo.c
     *
     *  Created on: 2017-09-14
     *      Author: sboehler
     */
    
    #include <stdio.h>
    
    void foo_void(void)
    {
        puts("void");
    }
    void foo_int(int c)
    {
        printf("int: %d\n", c);
    }
    void foo_char(char c)
    {
        printf("char: %c\n", c);
    }
    void foo_double(double c)
    {
        printf("double: %.2f\n", c);
    }
    void foo_double_int(double c, int d)
    {
        printf("double: %.2f, int: %d\n", c, d);
    }
    
    #define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__)
    
    #define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__)
    #define CONCAT(X, Y) CONCAT_(X, Y)
    #define CONCAT_(X, Y) X ## Y
    
    #define SELECT_0() foo_void
    #define SELECT_1(_1) _Generic ((_1), \
            int: foo_int,                \
            char: foo_char,              \
            double: foo_double           \
    )
    #define SELECT_2(_1, _2) _Generic((_1), \
            double: _Generic((_2),          \
                    int: foo_double_int     \
            )                               \
    )
    
    #define ARGN(...) ARGN_(__VA_ARGS__)
    #define ARGN_(_0, _1, _2, N, ...) N
    
    #define NARG(...) ARGN(__VA_ARGS__ COMMA(__VA_ARGS__) 3, 2, 1, 0)
    #define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 0)
    
    #define SET_COMMA(...) ,
    
    #define COMMA(...) SELECT_COMMA             \
    (                                           \
            HAS_COMMA(__VA_ARGS__),             \
            HAS_COMMA(__VA_ARGS__ ()),          \
            HAS_COMMA(SET_COMMA __VA_ARGS__),   \
            HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \
    )
    
    #define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3)
    #define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3
    
    #define COMMA_0000 ,
    #define COMMA_0001
    #define COMMA_0010 ,
    #define COMMA_0011 ,
    #define COMMA_0100 ,
    #define COMMA_0101 ,
    #define COMMA_0110 ,
    #define COMMA_0111 ,
    #define COMMA_1000 ,
    #define COMMA_1001 ,
    #define COMMA_1010 ,
    #define COMMA_1011 ,
    #define COMMA_1100 ,
    #define COMMA_1101 ,
    #define COMMA_1110 ,
    #define COMMA_1111 ,
    
    int main(int argc, char** argv)
    {
        foo();
        foo(7);
        foo(10.12);
        foo(12.10, 7);
        foo((char)'s');
    
        return 0;
    }
    
  • 11

    难道你不能只使用C而不使用除此之外的所有其他C功能吗?

    如果仍然没有严格的C,那么我会推荐variadic functions .

  • 9

    如果编译器支持,请尝试将这些函数声明为 extern "C++"http://msdn.microsoft.com/en-us/library/s6y4zxec(VS.80).aspx

  • 196

    可能性很小:

    • printf样式函数(作为参数输入)

    • opengl样式函数(输入函数名)

    • c c的子集(如果可以使用c编译器)

  • 3

    Yes!

    自从提出这个问题以来,由于在C11中添加了 _Generic 关键字,标准C(无扩展)已经有效地获得了对函数重载(而不是运算符)的支持 . (自4.9版以来在GCC中受支持)

    (重载不是真正“内置”的问题所示的方式,但实现类似的东西很容易 . )

    _Generic 是与 sizeof_Alignof 属于同一系列的编译时运算符 . 它在标准的6.5.1.1节中描述 . 它接受两个主要参数:表达式(在运行时不会被计算),以及看起来有点像 switch 块的类型/表达式关联列表 . _Generic 获取表达式的整体类型,然后在其上"switches"以在列表中为其类型选择最终结果表达式:

    _Generic(1, float: 2.0,
                char *: "2",
                int: 2,
                default: get_two_object());
    

    上面的表达式求值为 2 - 控制表达式的类型是 int ,因此它选择与 int 关联的表达式作为值 . 这一切都没有在运行时 . ( default 子句是可选的:如果将其关闭且类型不匹配,则会导致编译错误 . )

    这对于函数重载很有用,它可以由C预处理器插入,并根据传递给控制宏的参数类型选择结果表达式 . 所以(来自C标准的例子):

    #define cbrt(X) _Generic((X),                \
                             long double: cbrtl, \
                             default: cbrt,      \
                             float: cbrtf        \
                             )(X)
    

    这个宏实现了一个重载的 cbrt 操作,通过调度宏的参数类型,选择适当的实现函数,然后将原始宏参数传递给该函数 .

    因此,为了实现您的原始示例,我们可以这样做:

    foo_int (int a)  
    foo_char (char b)  
    foo_float_int (float c , int d)
    
    #define foo(_1, ...) _Generic((_1),                                  \
                                  int: foo_int,                          \
                                  char: foo_char,                        \
                                  float: _Generic((FIRST(__VA_ARGS__,)), \
                                         int: foo_float_int))(_1, __VA_ARGS__)
    #define FIRST(A, ...) A
    

    在这种情况下,我们可以在第三种情况下使用 default: 关联,但这并未说明如何将原则扩展为多个参数 . 最终结果是您可以在代码中使用 foo(...) 而不必担心(多[1])关于其参数的类型 .


    对于更复杂的情况,例如函数重载大量的参数或不同的数字,你可以使用实用程序宏来自动生成静态调度结构:

    void print_ii(int a, int b) { printf("int, int\n"); }
    void print_di(double a, int b) { printf("double, int\n"); }
    void print_iii(int a, int b, int c) { printf("int, int, int\n"); }
    void print_default(void) { printf("unknown arguments\n"); }
    
    #define print(...) OVERLOAD(print, (__VA_ARGS__), \
        (print_ii, (int, int)), \
        (print_di, (double, int)), \
        (print_iii, (int, int, int)) \
    )
    
    #define OVERLOAD_ARG_TYPES (int, double)
    #define OVERLOAD_FUNCTIONS (print)
    #include "activate-overloads.h"
    
    int main(void) {
        print(44, 47);   // prints "int, int"
        print(4.4, 47);  // prints "double, int"
        print(1, 2, 3);  // prints "int, int, int"
        print("");       // prints "unknown arguments"
    }
    

    implementation here)因此,通过一些努力,您可以减少样板量,使其看起来非常像具有本机支持重载的语言 .

    顺便说一下,it was already possible要重载C99中的参数数量(不是类型) .


    [1]请注意,C评估类型的方式可能会让你失望 . 如果你试图传递一个字符文字,这将选择 foo_int ,例如,如果你希望你的重载支持字符串文字,and you need to mess about a bit . 尽管如此,整体还是很酷 .

  • 19

    正如已经说过的那样,C语言不支持您的意思重载 . 解决问题的一个常用习惯是使函数接受tagged union . 这是由 struct 参数实现的,其中 struct 本身由某种类型指示符组成,例如 enumunion 的不同类型的值 . 例:

    #include <stdio.h>
    
    typedef enum {
        T_INT,
        T_FLOAT,
        T_CHAR,
    } my_type;
    
    typedef struct {
        my_type type;
        union {
            int a; 
            float b; 
            char c;
        } my_union;
    } my_struct;
    
    void set_overload (my_struct *whatever) 
    {
        switch (whatever->type) 
        {
            case T_INT:
                whatever->my_union.a = 1;
                break;
            case T_FLOAT:
                whatever->my_union.b = 2.0;
                break;
            case T_CHAR:
                whatever->my_union.c = '3';
        }
    }
    
    void printf_overload (my_struct *whatever) {
        switch (whatever->type) 
        {
            case T_INT:
                printf("%d\n", whatever->my_union.a);
                break;
            case T_FLOAT:
                printf("%f\n", whatever->my_union.b);
                break;
            case T_CHAR:
                printf("%c\n", whatever->my_union.c);
                break;
        }
    
    }
    
    int main (int argc, char* argv[])
    {
        my_struct s;
    
        s.type=T_INT;
        set_overload(&s);
        printf_overload(&s);
    
        s.type=T_FLOAT;
        set_overload(&s);
        printf_overload(&s);
    
        s.type=T_CHAR;
        set_overload(&s);
        printf_overload(&s); 
    }
    
  • 6

    如果您的编译器是gcc并且您不介意每次添加新的重载时都进行手动更新,那么您可以执行一些宏魔术并根据调用者获得所需的结果,但编写起来并不是很好...但它可能

    看看__builtin_types_compatible_p,然后使用它来定义一个类似的宏

    #define foo(a) \
    ((__builtin_types_compatible_p(int, a)?foo(a):(__builtin_types_compatible_p(float, a)?foo(a):)
    

    但是讨厌,只是不要

    EDIT: C1X将获得类型通用表达式的支持,它们如下所示:

    #define cbrt(X) _Generic((X), long double: cbrtl, \
                                  default: cbrt, \
                                  float: cbrtf)(X)
    
  • 72

    这是我发现在C中演示函数重载的最清晰,最简洁的例子:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int addi(int a, int b) {
        return a + b;
    }
    
    char *adds(char *a, char *b) {
        char *res = malloc(strlen(a) + strlen(b) + 1);
        strcpy(res, a);
        strcat(res, b);
        return res;
    }
    
    #define add(a, b) _Generic(a, int: addi, char*: adds)(a, b)
    
    int main(void) {
        int a = 1, b = 2;
        printf("%d\n", add(a, b)); // 3
    
        char *c = "hello ", *d = "world";
        printf("%s\n", add(c, d)); // hello world
    
        return 0;
    }
    

    https://gist.github.com/barosl/e0af4a92b2b8cabd05a7

  • 11

    是的,有点 .

    在这里你举例:

    void printA(int a){
    printf("Hello world from printA : %d\n",a);
    }
    
    void printB(const char *buff){
    printf("Hello world from printB : %s\n",buff);
    }
    
    #define Max_ITEMS() 6, 5, 4, 3, 2, 1, 0 
    #define __VA_ARG_N(_1, _2, _3, _4, _5, _6, N, ...) N
    #define _Num_ARGS_(...) __VA_ARG_N(__VA_ARGS__) 
    #define NUM_ARGS(...) (_Num_ARGS_(_0, ## __VA_ARGS__, Max_ITEMS()) - 1) 
    #define CHECK_ARGS_MAX_LIMIT(t) if(NUM_ARGS(args)>t)
    #define CHECK_ARGS_MIN_LIMIT(t) if(NUM_ARGS(args) 
    #define print(x , args ...) \
    CHECK_ARGS_MIN_LIMIT(1) printf("error");fflush(stdout); \
    CHECK_ARGS_MAX_LIMIT(4) printf("error");fflush(stdout); \
    ({ \
    if (__builtin_types_compatible_p (typeof (x), int)) \
    printA(x, ##args); \
    else \
    printB (x,##args); \
    })
    
    int main(int argc, char** argv) {
        int a=0;
        print(a);
        print("hello");
        return (EXIT_SUCCESS);
    }
    

    它将从printA和printB输出0和hello ..

  • 17

    以下方法类似于a2800276,但添加了一些C99宏魔法:

    // we need `size_t`
    #include <stddef.h>
    
    // argument types to accept
    enum sum_arg_types { SUM_LONG, SUM_ULONG, SUM_DOUBLE };
    
    // a structure to hold an argument
    struct sum_arg
    {
        enum sum_arg_types type;
        union
        {
            long as_long;
            unsigned long as_ulong;
            double as_double;
        } value;
    };
    
    // determine an array's size
    #define count(ARRAY) ((sizeof (ARRAY))/(sizeof *(ARRAY)))
    
    // this is how our function will be called
    #define sum(...) _sum(count(sum_args(__VA_ARGS__)), sum_args(__VA_ARGS__))
    
    // create an array of `struct sum_arg`
    #define sum_args(...) ((struct sum_arg []){ __VA_ARGS__ })
    
    // create initializers for the arguments
    #define sum_long(VALUE) { SUM_LONG, { .as_long = (VALUE) } }
    #define sum_ulong(VALUE) { SUM_ULONG, { .as_ulong = (VALUE) } }
    #define sum_double(VALUE) { SUM_DOUBLE, { .as_double = (VALUE) } }
    
    // our polymorphic function
    long double _sum(size_t count, struct sum_arg * args)
    {
        long double value = 0;
    
        for(size_t i = 0; i < count; ++i)
        {
            switch(args[i].type)
            {
                case SUM_LONG:
                value += args[i].value.as_long;
                break;
    
                case SUM_ULONG:
                value += args[i].value.as_ulong;
                break;
    
                case SUM_DOUBLE:
                value += args[i].value.as_double;
                break;
            }
        }
    
        return value;
    }
    
    // let's see if it works
    
    #include <stdio.h>
    
    int main()
    {
        unsigned long foo = -1;
        long double value = sum(sum_long(42), sum_ulong(foo), sum_double(1e10));
        printf("%Le\n", value);
        return 0;
    }
    
  • -3

    这可能根本没用,但是如果你使用clang,你可以使用overloadable属性 - 这甚至在编译为C时也有效

    http://clang.llvm.org/docs/AttributeReference.html#overloadable

    extern void DecodeImageNow(CGImageRef image, CGContextRef usingContext) __attribute__((overloadable));
    extern void DecodeImageNow(CGImageRef image) __attribute__((overloadable));
    

    履行

    void __attribute__((overloadable)) DecodeImageNow(CGImageRef image, CGContextRef usingContext { ... }
    void __attribute__((overloadable)) DecodeImageNow(CGImageRef image) { ... }
    
  • 13

    从某种意义上说,你的意思是 - 不,你不能 .

    您可以声明 va_arg 函数

    void my_func(char* format, ...);

    ,但你需要在第一个参数中传递一些关于变量数量及其类型的信息 - 比如 printf() .

  • -3

    我希望下面的代码可以帮助您理解函数重载

    #include <stdio.h>
    #include<stdarg.h>
    
    int fun(int a, ...);
    int main(int argc, char *argv[]){
       fun(1,10);
       fun(2,"cquestionbank");
       return 0;
    }
    int fun(int a, ...){
      va_list vl;
      va_start(vl,a);
    
      if(a==1)
          printf("%d",va_arg(vl,int));
       else
          printf("\n%s",va_arg(vl,char *));
    }
    

相关问题