有没有办法在C中实现函数重载?我正在寻找简单的函数被重载像
foo (int a)
foo (char b)
foo (float c , int d)
我认为没有直接的方法;我在寻找变通办法,如果有的话。
有没有办法在C中实现函数重载?我正在寻找简单的函数被重载像
foo (int a)
foo (char b)
foo (float c , int d)
我认为没有直接的方法;我在寻找变通办法,如果有的话。
当前回答
下面是我发现的演示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
其他回答
Yes!
自从提出这个问题以来,由于在C11中添加了_Generic关键字,标准C(没有扩展)已经有效地获得了对函数重载(不是操作符)的支持。(GCC从4.9版开始支持)
(重载并不是真正“内置”在问题中显示的方式,但它是非常容易实现这样的工作。)
_Generic是一个编译时操作符,与sizeof和_Alignof属于同一家族。在标准章节6.5.1.1中有描述。它接受两个主要参数:一个表达式(在运行时不会计算)和一个类型/表达式关联列表,它看起来有点像一个开关块。_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
在本例中,我们可以为第三种情况使用默认的:association,但这并没有演示如何将原则扩展到多个参数。最终的结果是,你可以在你的代码中使用foo(…),而不用担心它的参数类型。
对于更复杂的情况,例如函数重载更大数量的参数,或者变化的数量,你可以使用实用程序宏自动生成静态分派结构:
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,如果你试图传递它一个字符字面量,例如,如果你想让你的重载支持字符串字面量,你需要一点混乱。不过总体来说还是很酷的。
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_1或SELECT_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标准覆盖的,它不允许空的可变参数,也就是说,我们依赖于编译器扩展!
首先,一个空宏调用(foo())仍然产生一个令牌,但是是一个空令牌。计数宏实际上返回1而不是0,即使是空宏调用。我们可以“轻松”消除这个问题,如果我们有条件地在__VA_ARGS__后面加上逗号,这取决于列表是否为空:
#define NARG(...) ARG4_(__VA_ARGS__ COMMA(__VA_ARGS__) 4, 3, 2, 1, 0)
这看起来很简单,但是逗号宏是一个相当沉重的宏;幸运的是,这个话题已经在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;
}
你不能只使用c++而不使用除这个以外的所有其他c++特性吗?
如果仍然没有严格的C,那么我会推荐变进函数代替。
通常在名称前附加或加一个表示类型的疣子。在某些实例中,可以不使用宏,但这取决于您要做什么。C中没有多态性,只有强制。
简单的泛型操作可以用宏完成:
#define max(x,y) ((x)>(y)?(x):(y))
如果你的编译器支持typeof,更复杂的操作可以放在宏中。然后可以使用符号foo(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。