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

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

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

我认为没有直接的方法;我在寻找变通办法,如果有的话。

240867 次浏览

有以下几种可能性:

  1. Printf样式函数(类型作为参数)
  2. Opengl风格函数(输入函数名)
  3. c++的c子集(如果你能使用c++编译器)

你不能只使用c++而不使用除这个以外的所有其他c++特性吗?

如果仍然没有严格的C,那么我会推荐可变的函数代替。

就你的意思来说——不,你不能。

你可以像这样声明va_arg函数

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

,但是你需要在第一个参数中传递一些关于变量数量及其类型的信息——就像printf()那样。

通常在名称前附加或加一个表示类型的疣子。在某些实例中,可以不使用宏,但这取决于您要做什么。C中没有多态性,只有强制。

简单的泛型操作可以用宏完成:

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

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

如前所述,c不支持重载。解决这个问题的常用方法是让函数接受标记的联盟。这是由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);
}

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

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

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

是的,有点。

下面举个例子:

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

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

这可能没有任何帮助,但如果你使用clang,你可以使用overloadable属性-即使在编译为C时也可以

< a href = " http://clang.llvm.org/docs/AttributeReference.html overloadable " > 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) { ... }

是的!

自从提出这个问题以来,由于在C11中添加了_Generic关键字,标准C(没有扩展)已经有效地获得了支持函数重载(而不是操作符)。(GCC从4.9版开始支持)

(重载并不是真正“内置”在问题中显示的方式,但它是非常容易实现这样的工作。)

_Generic是一个编译时操作符,与sizeof_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中的数量参数(而不是类型)。


注意,C计算类型的方式可能会让您出错。如果你试图传递一个字符字面值给它,它会选择foo_int,例如,如果你想让你的重载支持字符串字面值,它会选择而你需要做点事。不过总体来说还是很酷的。

下面是我发现的演示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

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

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

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     \
)                               \
)

好的,我已经添加了空白过载–然而,这实际上不包括在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;
}