函数在C中重载

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

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

我认为没有一个简单的方法, 我正在寻找解决方法,如果有的话。

有几种可能性:

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

是!

在问这个问题之后,由于在C11中增加了_Generic关键字,所以标准C(没有扩展)有效地获得了对函数重载(而不是操作符)的支持。 (从版本4.9开始支持GCC)

(重载不是以问题中所示的方式真正“内置”的,但是实现像这样工作的东西并不容易。)

_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中重载参数的数量 (不是类型)。


[1]请注意,C评估类型的方式可能会让你感到不安。 foo_int如果您尝试将字符文字传递给字符,则会选择foo_int ,如果您希望重载支持字符串文字, 那么您需要 foo_int一下。 不过总体来说还是挺酷的。

如前所述,重载意义上的C不被C支持。解决这个问题的一个常见习惯是让函数接受一个带标签的联合 。 这是通过一个struct参数来实现的, struct本身由某种类型的指示符组成,比如enum ,以及不同类型的值的联合。 例:

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

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

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

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

这可能根本没有帮助,但是如果你使用了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) { ... } 

在这个意义上你的意思是 – 不,你不能。

你可以像声明一个va_arg函数

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

,但是你需要在第一个参数中传递一些有关变量数量和类型的信息,比如printf()

我见过的最好的:

 #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

正常情况下,一个表示类型的疣被附加或附加在名字上。 你可以通过宏来获得一些实例,但是这取决于你想要做什么。 C中没有多态,只有强制。

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

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

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

你不能只使用C ++,不能使用除此之外的所有其他C ++特性?

如果仍然不是严格的C,那么我会推荐variadic函数 。

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

 #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) ,我们就麻烦了。

所以我决定详细说明答案,包括允许一个无效的重载( 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标准覆盖,它不允许空的可变参数,也就是说,我们依靠编译器扩展

首先,一个空的宏调用( 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; } 

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