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


240

有什么方法可以在C中实现函数重载吗?我正在看简单的函数要重载像

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

我认为没有直接的方法。我正在寻找解决方法(如果存在)。


6
你为什么想做这个?C没有多态能力。因此foo(随机类型)是不可能的。只要制作真正的函数foo_i,foo_ch,foo_d等即可
jmucchiello

4
您可以使用void指针并输入id来达到邪恶的目的。
ALK

11
我觉得我应该提请注意以下事实:自从最初提出该问题以来,这个问题的答案就已经使用新的C标准进行了更改
Leushenko 2014年

Answers:


127

可能性很小:

  1. printf样式函数(类型作为参数)
  2. opengl样式函数(在函数名称中键入)
  3. C ++的C子集(如果可以使用C ++编译器)

1
您可以解释或提供opengl样式函数的链接吗?
FL4SOF

1
@Lazer:这是一个简单的类似于printf的函数实现。
阿列克谢·弗伦兹

12
否。printf不会函数重载。它使用vararg !!! C不支持函数重载。
hqt

52
@hqt答案从来没有提到单词重载。
kyrias

1
@kyrias如果答案不是关于超载,那就是错误的问题
Michael Mrozek '18

233

是!

自问这个问题以来,由于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(...)在代码中使用,而不必担心(uch [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,如果你试图通过它字符文字,例如,你需要做手脚了一下,如果你希望你的过载,以支持字符串常量。总体来说还是很酷的。


根据您的示例,看起来好像唯一被重载的函数就是宏。让我看看我是否理解正确:如果您想重载函数,您将只是使用预处理器根据传入的数据类型转移函数调用,对吗?
尼克

C,每当C11开始流行时,我都认为MISRA不会出于禁止可变参数列表的相同原因而接受此功能。在我的世界中,我尽力坚持MISRA。
尼克

9
@Nick就是所有超载。它只是在其他语言中隐式处理的(例如,您无法真正获得任何语言的“指向重载函数的指针”,因为重载意味着多个主体)。注意,这不能单独通过预处理器来完成,它需要某种类型的类型分派。预处理器只是改变外观。
Leushenko

1
正如有人谁是相当熟悉C99和想学习如何做到这一点,这似乎过于复杂,即使对C.
泰勒康普顿

5
@TylerCrompton在编译时进行评估。
JAB

75

如前所述,从某种意义上讲,C并不支持重载。解决该问题的常用习惯是使函数接受带标记的union。这由struct参数实现,其中参数struct本身由某种类型指示符组成,例如enum,和union,以及不同类型的值。例:

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

22
为什么你不只是让所有的whatevers转换不同的功能(set_intset_float,等)。然后,“使用类型标记”变为“将类型名称添加到函数名称”。此答案中的版本涉及更多的键入,更多的运行时成本,更多的错误可能性,这些错误在编译时不会被捕获……我完全看不到以这种方式做事的任何优势!16个赞?

20
Ben,此答案之所以被推荐,是因为它回答了问题,而不仅仅是说“不要那样做”。没错,在C语言中使用单独的函数更为习惯,但是,如果要在C语言中使用多态性,这是一种很好的方法。此外,此答案还显示了如何在编译器或VM中实现运行时多态性:使用类型标记值,然后基于该值进行分派。因此,这是对原始问题的绝佳答案。
尼尔斯·冯·巴特

20

这是我发现的最清楚,最简洁的示例,它演示了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


1
我认为这是对stackoverflow.com/a/25026358/1240268的虚伪(但解释较少)。
安迪·海登

1
我绝对更喜欢1个连续的完整且可运行的代码的连续块,而不是切片和切块,即#1240268。给每个人自己。
杰伊·泰勒

1
我更喜欢解释他们在做什么以及为什么工作的答案。两者都不做。“我所见过的最好的:”不是博览会。
underscore_d

19

如果您的编译器是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)

13

是的,有点。

这里以示例为例:

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


2
int main(int argc,char ** argv){int a = 0; 打印(a); print(“ hello”); 返回(EXIT_SUCCESS); }将输出0并从printA和printB中打招呼..
船长Barbossa

1
__builtin_types_compatible_p,这不是GCC编译器专用的吗?
Sogartar

11

以下方法类似于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;
}

11

这可能根本没有帮助,但是如果您使用的是clang,则可以使用重载属性-即使编译为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) { ... }

10

从某种意义上说,您的意思是-不,您不能。

你可以声明一个va_arg

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

,但是您需要在第一个参数中传递有关变量数量及其类型的某种信息- printf()确实如此。


6

通常,用于指示类型的疣会附加到名称之前或之前。在某些情况下,您可以使用宏,但是它取决于您要执行的操作。C中没有多态性,只有强制性。

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

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

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


您能否进一步解释基于宏的方法。
FL4SOF,2009年

4

Leushenko的答案确实很酷-仅此一个foo例子:该示例无法使用GCC进行编译,后者会在foo(7),,遍历FIRST宏和实际函数调用((_1, __VA_ARGS__),并保留一个多余的逗号)处失败,此外,如果要提供其他重载,我们会遇到麻烦例如foo(double)

因此,我决定进一步详细说明答案,包括允许void重载(foo(void)–造成了很多麻烦……)。

现在的想法是:在不同的宏中定义多个泛型,然后根据参数数量选择正确的泛型!

基于此答案,参数数量非常容易:

#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标准实际上并未涵盖该重载,C标准不允许空的可变参数,即我们依赖编译器扩展

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

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

看起来很容易,但是COMMA宏相当繁琐。幸运的是,该主题已被Jens Gustedt博客涵盖(感谢Jens)。基本技巧是,如果不在函数宏后面加上括号,则不会对其进行扩展,有关更多说明,请查看Jens的博客...我们只需要对宏进行一些修改即可满足我们的需要(我将使用较短的名称以及简短的争论)。

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

1

您不能只使用C ++而不能使用除此功能以外的所有其他C ++功能吗?

如果仍然不是严格的C语言,那么我建议使用可变参数函数


3
如果C ++编译器不适用于他要编码的OS,则不可以。
布赖恩

2
不仅如此,他可能还想要一个没有名称修改的C ABI。
Spudd86


-4

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

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

2
答案应该解释它在做什么以及为什么起作用。如果没有,它将如何帮助任何人理解任何东西?
underscore_d

这里没有超载。
melpomene

从未调用过va_end
user2262111
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.