当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《如何在C中实现函数重载?》经验,为你挑选了10个好方法。

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

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

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



1> Leushenko..:

是!

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

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

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

_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"
}

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

顺便说一句,已经可以在C99中重载参数(不是类型)的数量.


[1]请注意,C评估类型的方式可能会让你失望.foo_int例如,如果您尝试将字符文字传递给它,则会选择,如果您希望重载支持字符串文字,则需要稍微混淆一下.虽然整体还很酷.


@Nick,所有超载都是.它只是在其他语言中隐式处理(例如,您无法在任何语言中获得"指向重载函数的指针",因为重载意味着多个主体).请注意,这不能仅由预处理器完成,它需要某种类型的调度; 预处理器只是改变它的外观.
@TylerCrompton它在编译时进行了评估.

2> Jacek Ławryn..:

可能性很小:

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

    opengl样式函数(在函数名中输入)

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


@hqt答案永远不会提到重载这个词.
不,printf不是函数重载.它使用vararg !!! 并且C不支持函数重载.
是否有“ printf样式函数”的良好链接?

3> a2800276..:

如前所述,C语言不支持你所指的意义上的重载.解决问题的一个常用习惯是使函数接受标记的联合.这是通过struct参数实现的,其中struct自身由某种类型指示符组成,例如enuma union和不同类型的值.例:

#include 

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); 
}


你为什么不把所有的东西都变成单独的函数(`set_int`,`set_float`等).然后"使用类型标记"变为"将类型名称添加到函数名称".这个答案中的版本涉及更多的输入,更多的运行时成本,更多的错误机会,在编译时不会被捕获......我没有看到*任何优势*以这种方式做事!16赞成?!
Ben,这个答案是赞成的,因为它*回答了问题,*而不只是说"不要那样做".你是正确的,在C中使用单独的函数是更惯用的,但如果你想在C中使用多态,这是一个很好的方法.此外,这个答案显示了如何在编译器或VM中实现运行时多态性:使用类型标记值,然后根据该值进行调度.因此,它是对原始问题的出色答案.

4> Spudd86..:

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

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

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

但是讨厌,只是不要

编辑: C1X将获得类型泛型表达式的支持,它们看起来像这样:

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



5> Jay Taylor..:

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

#include 
#include 
#include 

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



6> Nautical..:

是的,有点.

在这里你举例:

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 ..


int main(int argc,char**argv){int a = 0; 打印的(a); 打印( "你好"); return(EXIT_SUCCESS); 将从printA和printB输出0和hello ..

7> Christoph..:

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

// we need `size_t`
#include 

// 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 

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;
}



8> Steazy..:

这可能没有任何帮助,但如果您使用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) { ... }



9> Quassnoi..:

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

你可以声明一个va_arg像这样的函数

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

,但你需要在第一个参数中传递关于变量数量及其类型的某种信息 - 就像printf()这样.



10> Pete Kirkham..:

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

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

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

如果您的编译器支持typeof,则可以在宏中放置更复杂的操作.然后,您可以使用符号foo(x)来支持不同类型的相同操作,但不能改变不同重载之间的行为.如果你想要实际的功能而不是宏,你可以将类型粘贴到名称并使用第二个粘贴来访问它(我还没有尝试过).

推荐阅读
罗文彬2502852027
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有