【问题标题】:How to write C function accepting (one) argument of any type如何编写接受任何类型的(一个)参数的 C 函数
【发布时间】:2012-09-08 18:59:31
【问题描述】:

我正在用 C 实现简单的列表库,但在编写 find 函数时遇到问题。

我希望我的函数接受 any 类型的参数来查找,两者: find(my_list, 3)find(my_list, my_int_var_to_find)我已经知道列表元素的类型是什么

目前我已经找到了几种方法来处理这个问题:

  • 不同类型后缀的不同函数:int findi(void* list, int i)int findd(void* list, double d) - 但我不喜欢这种方法,这对我来说似乎是多余的,而且 API 令人困惑。

  • 使用联合:

    typedef union {
       int i;
       double d;
       char c;
       ...
    } any_type;
    

    但是通过这种方式,我强制用户了解any_type union,并在调用find 之前创建它。我想避免这种情况。

  • 使用可变参数函数:int find(void* list, ...)。我喜欢这种方法。但是,我担心参数数量没有限制。用户可以随意写int x = find(list, 1, 2.0, 'c'),虽然我不知道它应该是什么意思。

我也看到了这个问题的答案:C : send different structures for one function argument 但这无关紧要,因为我想接受非指针参数。

处理这个函数的正确方法是什么?

【问题讨论】:

  • 我会做方法 #1 并使用使用新 C11 _Generic 的宏来区分类型。

标签: c types variadic any


【解决方案1】:

您可以尝试实现类似于bsearch 等通用函数的函数,它可以对任何数据类型的数组执行二进制搜索:

void *bsearch(const void *key, const void *base, size_t nmemb, size_t size,
              int (*compar)(const void *, const void *))

与其在函数中对不同数据类型的不同实现进行硬编码,不如将指针传递给将执行类型相关操作的函数,并且只有它知道底层实现。在您的情况下,这可能是某种遍历/迭代函数。

另一件事bsearch需要知道(除了显而易见的——搜索键和数组长度)是数组中每个元素的大小,这样它就可以计算出数组中每个元素的地址并传递给它到比较函数。


如果您有一个要操作的类型的有限列表,那么拥有一系列 findX() 函数并没有错。上述方法需要将每种数据类型的函数传递给bsearch 函数,但主要区别之一是不需要重复常用功能,通用函数可用于any 数据类型。

我真的不会说有任何正确方法可以做到这一点,这取决于你,并且真的取决于你要解决的问题。

【讨论】:

  • 这看起来确实是最简单的方法(乍一看)。现在我有有限的类型列表。但是如果我想添加另一种类型,我需要编写另一个包装函数。我认为这不是解决此问题的最佳方法。
  • @BartekChaber:是的,与 C++ 和 Java 等语言中的模板不同,在 C 中没有真正优雅的方式来做你想要的事情。制作通用函数并传递函数指针是我在项目中通常使用的方式。
  • @AusCBloke:取决于你的要求,如果 C11 没问题,那就是 _Generic。
【解决方案2】:

我不确定回答我自己的问题是否礼貌,但我想听听你的意见。

我尝试使用 va_list 来解决这个问题。为什么这样?因为这样我只能写一个函数。请注意,我知道参数应该是什么类型。这样我就可以做到:

    int find(void* list, ...) {
      any_type object = {0};
      int i = -1;
      va_list args;
      va_start(args, list);
      switch(type_of_elem(list)) {
        case INT: object.i = va_arg(args, int); break;
        ...
      }
      /* now &object is pointer to memory ready for comparision
       * f.eg. using memcmp */
      return i;
    }

此解决方案的优点是我可以包装呈现的 switch-case 并将其与其他功能一起使用。

在对我对参数数量没有限制的担忧进行了更多研究后,我意识到printf 也没有这个限制。你可以写printf("%d", 1, 2, 3)。 但我用额外的宏调整了我的解决方案:

    #define find_(list, object) find((list), (object))

在编译时产生错误消息,说find_ macro expects 2 arguments not 3

你怎么看?您认为这比之前建议的解决方案更好吗?

【讨论】:

  • 我肯定会采用 va_list 方法。我不确定宏是否增加了很多。最可能的问题是当列表是双打时有人会做find(list, 12)。在这种情况下,宏仍然没有发出警告。为什么有人会尝试做find(list, 1, 2.0, 'c')
  • @asc99c 为什么?因为函数的签名允许这样做:) 你在搜索时不安全是绝对正确的。我希望这是这种方法的唯一缺点。
猜你喜欢
  • 1970-01-01
  • 2016-07-07
  • 2019-12-05
  • 2011-02-24
  • 1970-01-01
  • 2016-03-28
  • 1970-01-01
  • 1970-01-01
  • 2019-09-14
相关资源
最近更新 更多