19

是否有一种“正确”的方法可以在 C 中实现高阶函数。

我主要对这里的可移植性和语法正确性等问题感到好奇,以及是否存在不止一种优点和缺点。

编辑:我想知道如何创建高阶函数的原因是我编写了一个系统来将 PyObject 列表(您在调用 python 脚本时得到)转换为包含相同数据但组织方式不同的 C 结构列表依赖于 python.h 库。所以我的计划是有一个函数,它遍历一个pythonic列表,并在列表中的每个项目上调用一个函数,并将结果放在一个列表中,然后返回。

所以这基本上是我的计划:

typedef gpointer (converter_func_type)(PyObject *)

gpointer converter_function(PyObject *obj)
{
    // do som stuff and return a struct cast into a gpointer (which is a void *)
}

GList *pylist_to_clist(PyObject *obj, converter_func_type f)
{
   GList *some_glist;
   for each item in obj
   {
       some_glist = g_list_append(some_glist, f(item));
   }
   return some_glist;
}

void some_function_that_executes_a_python_script(void)
{
   PyObject *result = python stuff that returns a list;
   GList *clist = pylist_to_clist(result, converter_function);
}

为了澄清这个问题:我想知道如何在更安全、更正确的 C 语言中做到这一点。我真的很想保持高阶函数风格,但如果不赞成,我非常感谢以其他方式做到这一点的方法。

4

8 回答 8

21

从技术上讲,高阶函数只是接受或返回函数的函数。所以像 qsort 这样的东西已经是高阶的了。

如果您的意思更像是函数式语言中的 lambda 函数(高阶函数真正有用的地方),那么这些函数要困难得多,并且不能在当前标准 C 中自然地完成。它们只是不属于语言。Apple 的 blocks 扩展是最好的选择。它只适用于 GCC(和 LLVM 的 C 编译器),但它们真的很有用。希望这样的事情会流行起来。以下是一些相关资源:

于 2010-03-29T03:52:14.160 回答
8

在 C 中实现高阶函数的一个大问题是,做任何不平凡的事情都需要闭包,闭包是函数指针,增加了包含它们可以访问的局部变量的数据结构。由于闭包背后的整个想法是捕获局部变量并将它们与函数指针一起传递,因此如果没有编译器支持就很难做到。即使有编译器支持,也很难没有垃圾收集,因为变量可以存在于它们的范围之外,因此很难弄清楚何时释放它们。

于 2010-03-29T05:28:17.970 回答
6

在直接 c 中,这实际上只能通过函数指针来完成,这既是一种痛苦,也不意味着这种类型的事情(这就是它们之所以痛苦的部分原因)。不过,块(或非苹果公司的闭包)对此非常有用。他们在 gcc-4.x 或其他东西中编译,并在 icc 中编译,但不管这就是你要找的东西。不幸的是,我似乎无法在网上找到任何好的教程,但可以说它的工作原理如下:

void iterate(char *str, int count, (^block)(str *)){
  for(int i = 0; i < count; i++){
    block(list[i]);
  }
}

main() {
  char str[20];
  iterate(str, 20, ^(char c){
    printf("%c ", c);
  });

  int accum = 0;
  iterate(someList, 20, ^(char c){
    accum += c;
    iterate(str, 20, ^(char c){
      printf("%c ", c);
    });
  });
}

显然这段代码是没有意义的,但它打印字符串 (str) 的每个字符,中间有一个空格,然后将所有字符一起添加到 accum 中,每次它都会再次打印出字符列表。

希望这可以帮助。顺便说一句,块在 Mac OS X Snow Leopard api-s 中非常明显,我相信在即将到来的 C++0x 标准中,所以它们并不是那么不寻常。

于 2010-03-29T03:37:34.847 回答
6

这是对以下问题的回答:how to compose functions in C,在此处重定向。

您可以创建数据结构来实现列表数据类型。该结构可以包含函数指针。

#include<stdlib.h>
#include<malloc.h>

typedef (*fun)();

typedef struct funList { fun car; struct funList *cdr;} *funList;

const funList nil = NULL;

int null(funList fs){ return nil==fs; }

fun car(funList fs)
{
   if(!null(fs)) return fs->car; 
   else 
   {
     fprintf(stderr,"error:can't car(nil) line:%d\n",__LINE__);
     exit(1);
   }
}

funList cdr(funList ls)
{ if(!null(ls)) return ls->cdr; 
  else 
  {
    fprintf(stderr,"error:can't cdr(nil) line:%d\n",__LINE__);
    exit(1);
  }
}

funList cons(fun f, funList fs)
{  funList ls;

   ls=(funList) malloc(sizeof(struct funList));
   if(NULL==ls)
   {
     fprintf(stderr,"error:can't alloc mem for cons(...) line:%d\n",__LINE__);
     exit(1);
   }

   ls->car=f;
   ls->cdr=fs;

   return ls;
}

我们可以编写一个函数 comp 来应用函数列表:

type_2 comp(funList fs, type_1 x)
{  
   return (null(fs)) ? x : car(fs)(comp(cdr(fs),x)); 
}

它是如何工作的一个例子。我们使用 (fgh) 作为 cons(f,cons(g,cons(h,nil))) 的简写,它应用于给定的参数 x:

comp((f g h),x)

=

f(comp((g h),x))

=

f(g(comp((h),x)))

=

f(g(h(comp(nil,x))))

=

f(g(h(x)))

如果您在 SML 或 Haskell 等类型化语言中使用了多态列表类型,则 comp 的类型应该是:

comp :: ([a -> a],a) -> a

因为在这种情况下,列表中的所有成员都具有相同的类型。从这个意义上说,C 可以更灵活。也许像

typedef void (*fun)();

或者

typedef (*fun)();

您应该看到 C 手册对此有何评论。并确保所有连续的函数都具有兼容的类型。

组合函数应该是纯函数,即没有副作用也没有自由变量。

于 2017-05-20T14:35:28.193 回答
5

如果您热衷于在纯 C 中执行此操作,则需要记住包含将上下文指针从函子的调用者(高阶函数)传递给传入函数的选项。这可以让您进行足够的模拟一个闭包,你可以让事情变得足够容易。该指针指向的内容...好吧,这取决于您,但它应该是void*仿函数 API 中的 a(或它的许多别名之一,例如gpointer在 GLib 世界或ClientDataTcl C API 中)。

[编辑]:使用/调整您的示例:

typedef gpointer (converter_func_type)(gpointer,PyObject *)

gpointer converter_function(gpointer context_ptr,PyObject *obj)
{
    int *number_of_calls_ptr = context_ptr;
    *number_of_calls_ptr++;
    // do som stuff and return a struct cast into a gpointer (which is a void *)
}

GList *pylist_to_clist(PyObject *obj, converter_func_type f, gpointer context_ptr)
{
   GList *some_glist;
   for each item in obj
   {
       some_glist = g_list_append(some_glist, f(context_ptr,item));
   }
   return some_glist;
}

void some_function_that_executes_a_python_script(void)
{
   int number_of_calls = 0;
   PyObject *result = python stuff that returns a list;
   GList *clist = pylist_to_clist(result, converter_function, &number_of_calls);
   // Now number_of_calls has how often converter_function was called...
}

这是一个关于如何做到这一点的简单示例,但它应该向您展示方法。

于 2010-03-29T14:37:10.280 回答
3

实际上,任何有趣的高阶函数应用程序都需要闭包,这在 C 中需要手动定义和填充 struct 函数参数的繁琐且容易出错的例程。

于 2010-03-29T03:50:27.123 回答
2

在直接的 C 中很难做到。在 C++ 中更有可能(参见函子教程或 Boost 的绑定函数库)。最后,C++0x 增加了对 lambda 函数的原生支持,它负责在闭包中捕获函数所依赖的所有变量。

于 2010-03-29T04:35:02.390 回答
1

如果您想创建高阶函数,请不要使用 C。C 可以解决您的问题。它们可能并不优雅,或者它们可能比您意识到的更优雅。

[编辑] 我建议实现这一目标的唯一方法是使用脚本语言。其他人已经把我叫出来了。所以,我用这个代替这个建议:[/Edit]

你想达到什么目的?如果您想模仿闭包,请使用支持它们的语言(您可以通过库绑定到 Ruby、lua、javascript 等)。如果你想使用回调,函数指针是可以的。函数指针结合了 C 语言中最危险的领域(指针和弱类型系统),所以要小心。函数指针声明读起来也不好玩。

您会发现一些 C 库使用函数指针,因为它们必须这样做。如果您正在编写一个库,也许您也需要使用它们。如果您只是在自己的代码中使用它们,那么您可能不会用 C 语言思考。您正在思考 lisp 或 scheme 或 ruby​​ 或……并尝试用 C 语言编写它。学习 C 语言。

于 2010-03-29T03:35:24.670 回答