【问题标题】:How to achieve function overloading in C?如何在 C 中实现函数重载?
【发布时间】:2010-10-03 12:19:24
【问题描述】:

有没有办法在 C 中实现函数重载?我正在寻找要重载的简单函数,例如

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

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

【问题讨论】:

  • 您为什么要这样做? C没有多态能力。所以 foo(random type) 是不可能的。只需制作真正的函数 foo_i、foo_ch、foo_d 等。
  • 你可以使用 void 指针和类型 id 走坏路。
  • 我觉得我应该提请注意这个问题的答案has changed since it was originally asked,使用新的 C 标准。

标签: c overloading


【解决方案1】:

是的!

自从提出这个问题以来,由于在 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"
}

(implementation here) 因此,通过一些努力,您可以减少样板代码的数量,使其看起来非常像具有本机支持重载的语言。

顺便说一句,it was already possible 在 C99 中重载 个参数(不是类型)。


[1] 请注意,C 评估类型的方式可能会让您感到困惑。如果您尝试将字符文字传递给它,这将选择foo_int,例如,如果您希望重载支持字符串文字,则选择and you need to mess about a bit。不过总体来说还是很酷的。

【讨论】:

  • 根据您的示例,看起来唯一重载的是宏之类的函数。让我看看我是否理解正确:如果你想重载函数,你只是使用预处理器根据传入的数据类型来转移函数调用,对吧?
  • 唉,每当 C11 开始流行时,我认为 MISRA 不会接受此功能,原因与他们禁止变量参数列表的原因相同。在我的世界里,我尽量坚持 MISRA。
  • @Nick 这就是重载。它只是在其他语言中隐式处理(例如,您无法在任何语言中真正获得“指向重载函数的指针”,因为重载意味着多个主体)。请注意,这不能由预处理器单独完成,它需要某种类型的分派;预处理器只是改变了它的外观。
  • 作为一个相当熟悉 C99 并想学习如何做到这一点的人,这似乎过于复杂,即使对于 C 来说也是如此。
  • @TylerCrompton 它在编译时进行评估。
【解决方案2】:

可能性很小:

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

【讨论】:

  • 你能解释一下或者提供opengl风格函数的链接吗?
  • @Lazer: Here's one 简单的类似printf的函数实现。
  • 没有。 printf 不是函数重载。它使用可变参数!而且C不支持函数重载。
  • @hqt 答案从来没有提到过重载这个词。
  • @kyrias 如果答案不是关于超载,那就是错误的问题
【解决方案3】:

如前所述,C 不支持您所说的重载。解决该问题的常用方法是使函数接受tagged 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); 
}

【讨论】:

  • 你为什么不把所有的whatevers 变成单独的函数(set_intset_float 等)。然后“使用类型标记”变为“将类型名称添加到函数名称”。这个答案中的版本涉及更多的输入,更多的运行时成本,更多在编译时不会被捕获的错误的机会......我看不到以这种方式做事的任何优势! 16 票?!
  • Ben,这个答案得到了赞成,因为它回答了这个问题, 而不是仅仅说“不要那样做”。您是正确的,在 C 中使用单独的函数更习惯用语,但是如果想要在 C 中使用多态,这是一种很好的方法。此外,此答案显示了如何在编译器或 VM 中实现运行时多态性:使用类型标记值,然后基于该类型进行调度。因此,这是对原始问题的极好回答。
【解决方案4】:

这是我发现的在 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

【讨论】:

  • 我认为这是 stackoverflow.com/a/25026358/1240268 在精神上的欺骗(但解释较少)。
  • 我绝对更喜欢 1 个完整且可运行的代码的连续块,而不是 #1240268 的切片和切块。各有各的。
  • 我更喜欢解释他们在做什么以及为什么工作的答案。这也不行。 “我见过的最好的:”不是展览。
  • 此示例存在内存泄漏,请检查提供的 gist 上的 cmets:“您通过在打印之前不存储指向连接字符串的指针来创建内存泄漏,因为现在您无法在之后释放它你malloc'd 它。”
【解决方案5】:

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

【讨论】:

    【解决方案6】:

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

    【讨论】:

      【解决方案7】:

      是的,有点。

      举个例子:

      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;打印(一);打印(“你好”);返回(EXIT_SUCCESS); } 将输出 0 和 hello .. 从 printA 和 printB ...
      • __builtin_types_compatible_p,那不是 GCC 编译器特有的吗?
      【解决方案8】:

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

      【讨论】:

      • 哇。我尝试使用其他 cmets 中描述的 _Generic,现在我认为它太复杂以至于不实用 - 放弃重载函数的整个想法会更容易。这个解决方案要容易得多,它是 clang 的一个很好的论据。谢谢。
      【解决方案9】:

      你的意思是——不,你不能。

      您可以像这样声明va_arg 函数

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

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

      【讨论】:

        【解决方案10】:

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

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

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

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

        【讨论】:

        • 你能解释一下基于宏的方法吗?
        【解决方案11】:

        Leushenko's answer 真的很酷 - 仅此而已:foo 示例无法使用 GCC 编译,它在 foo(7) 处失败,绊倒了 FIRST 宏和实际的函数调用 ((_1, __VA_ARGS__),剩下一个多余的逗号。另外,如果我们想提供额外的重载,我们就有麻烦了,比如foo(double)

        所以我决定进一步详细说明答案,包括允许 void 重载(foo(void) - 这造成了一些麻烦......)。

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

        参数数量很简单,基于this answer

        #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 标准所涵盖,它不允许空的可变参数,即。 e.然后我们依赖编译器扩展

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

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

        看起来很简单,但 COMMA 宏却相当沉重;幸运的是,blog of 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;
        }
        

        【讨论】:

          【解决方案12】:

          难道你不能只使用 C++ 而不使用除此之外的所有其他 C++ 功能吗?

          如果仍然没有严格的 C,那么我会推荐 variadic functions

          【讨论】:

          • 如果 C++ 编译器不适用于他正在编码的操作系统,则不会。
          • 不仅如此,他可能还想要一个没有名称修饰的 C ABI。
          【解决方案13】:

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

          【讨论】:

          • 这可能会改变名称修改以赋予它们唯一的名称(可能不会),但它不会突然给出 C 重载解析规则。
          【解决方案14】:

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

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

          【讨论】:

          • 答案应该解释它在做什么以及它为什么起作用。如果没有,它如何帮助任何人理解任何事情?
          • 这里没有重载。
          • va_end 从未被调用
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-02-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-01-24
          相关资源
          最近更新 更多