一个有趣的问题,我查看了引用的答案中的论文(在 C/C++/Objective-C 中具有 Curried 函数的更多功能可重用性)。
因此,以下是您可能想去的建议路径。我不认为这真的是一个 Curried 函数,因为我不完全理解论文在说什么,而不是一个函数式程序员。然而,做一些工作,我发现这个概念的一些有趣的应用。另一方面,我不确定这是否是您想要的,这让我大吃一惊,您可以在 C 中做到这一点。
似乎有两个问题。
首先是能够处理带有任意参数列表的任意函数调用。我采用的方法是使用标准 C 库变量参数功能(带有 va_start()、va_arg() 和 va_end() 函数的 va_list),然后将函数指针与提供的参数一起存储到数据区域中,以便它们然后可以在以后执行。我借用并修改了printf()
函数如何使用格式行来了解提供了多少参数及其类型。
接下来是函数及其参数列表的存储。我只是使用了一个任意大小的结构来尝试这个概念。这将需要更多的思考。
这个特定版本使用一个被视为堆栈的数组。有一个函数可以用来将某个任意函数及其参数推送到堆栈数组中,还有一个函数可以将最顶层的函数及其参数从堆栈数组中弹出并执行它。
但是,您实际上可以在某种集合中包含任意结构对象,例如哈希映射,这可能非常酷。
我只是从论文中借用了信号处理程序示例,以表明该概念适用于那种应用程序。
所以这里是源代码,我希望它可以帮助你想出一个解决方案。
您需要向开关添加其他情况,以便能够处理其他参数类型。我只是做了一些概念证明。
这也不会执行调用函数的函数,尽管从表面上看这似乎是一个相当简单的扩展。就像我说的,我不完全明白这个咖喱的东西。
#include <stdarg.h>
#include <string.h>
// a struct which describes the function and its argument list.
typedef struct {
void (*f1)(...);
// we have to have a struct here because when we call the function,
// we will just pass the struct so that the argument list gets pushed
// on the stack.
struct {
unsigned char myArgListArray[48]; // area for the argument list. this is just an arbitray size.
} myArgList;
} AnArgListEntry;
// these are used for simulating a stack. when functions are processed
// we will just push them onto the stack and later on we will pop them
// off so as to run them.
static unsigned int myFunctionStackIndex = 0;
static AnArgListEntry myFunctionStack[1000];
// this function pushes a function and its arguments onto the stack.
void pushFunction (void (*f1)(...), char *pcDescrip, ...)
{
char *pStart = pcDescrip;
AnArgListEntry MyArgList;
unsigned char *pmyArgList;
va_list argp;
int i;
char c;
char *s;
void *p;
va_start(argp, pcDescrip);
pmyArgList = (unsigned char *)&MyArgList.myArgList;
MyArgList.f1 = f1;
for ( ; *pStart; pStart++) {
switch (*pStart) {
case 'i':
// integer argument
i = va_arg(argp, int);
memcpy (pmyArgList, &i, sizeof(int));
pmyArgList += sizeof(int);
break;
case 'c':
// character argument
c = va_arg(argp, char);
memcpy (pmyArgList, &c, sizeof(char));
pmyArgList += sizeof(char);
break;
case 's':
// string argument
s = va_arg(argp, char *);
memcpy (pmyArgList, &s, sizeof(char *));
pmyArgList += sizeof(char *);
break;
case 'p':
// void pointer (any arbitray pointer) argument
p = va_arg(argp, void *);
memcpy (pmyArgList, &p, sizeof(void *));
pmyArgList += sizeof(void *);
break;
default:
break;
}
}
va_end(argp);
myFunctionStack[myFunctionStackIndex] = MyArgList;
myFunctionStackIndex++;
}
// this function will pop the function and its argument list off the top
// of the stack and execute it.
void doFuncAndPop () {
if (myFunctionStackIndex > 0) {
myFunctionStackIndex--;
myFunctionStack[myFunctionStackIndex].f1 (myFunctionStack[myFunctionStackIndex].myArgList);
}
}
// the following are just a couple of arbitray test functions.
// these can be used to test that the functionality works.
void myFunc (int i, char * p)
{
printf (" i = %d, char = %s\n", i, p);
}
void otherFunc (int i, char * p, char *p2)
{
printf (" i = %d, char = %s, char =%s\n", i, p, p2);
}
void mySignal (int sig, void (*f)(void))
{
f();
}
int main(int argc, char * argv[])
{
int i = 3;
char *p = "string";
char *p2 = "string 2";
// push two different functions on to our stack to save them
// for execution later.
pushFunction ((void (*)(...))myFunc, "is", i, p);
pushFunction ((void (*)(...))otherFunc, "iss", i, p, p2);
// pop the function that is on the top of the stack and execute it.
doFuncAndPop();
// call a function that wants a function so that it will execute
// the current function with its argument lists that is on top of the stack.
mySignal (1, doFuncAndPop);
return 0;
}
您可以从中获得的另一个乐趣是pushFunction()
在一个被调用的函数中使用该函数,doFuncAndPop()
以使另一个函数可以与其参数一起放入堆栈。
例如,如果您将otherFunc()
上面源代码中的函数修改为如下所示:
void otherFunc (int i, char * p, char *p2)
{
printf (" i = %d, char = %s, char =%s\n", i, p, p2);
pushFunction ((void (*)(...))myFunc, "is", i+2, p);
}
如果您随后添加另一个调用,doFuncAndPop()
您将看到 firstotherFunc()
被执行,然后执行myFunc()
被插入otherFunc()
的调用,然后最后myFunc()
调用被推送的main ()
调用。
编辑 2:
如果我们添加以下函数,这将执行所有已放入堆栈的函数。这将允许我们通过将函数和参数压入堆栈然后执行一系列函数调用来创建一个小程序。这个函数还允许我们推送一个没有任何参数的函数,然后推送一些参数。当从堆栈中弹出函数时,如果参数块没有有效的函数指针,那么我们所做的就是将该参数列表放在堆栈顶部的参数块上,然后执行它。也可以对上述函数进行类似的更改doFuncAndPop()
。如果我们在执行的函数中使用 pushFunction() 操作,我们可以做一些有趣的事情。
实际上,这可能是Threaded Interpreter的基础。
// execute all of the functions that have been pushed onto the stack.
void executeFuncStack () {
if (myFunctionStackIndex > 0) {
myFunctionStackIndex--;
// if this item on the stack has a function pointer then execute it
if (myFunctionStack[myFunctionStackIndex].f1) {
myFunctionStack[myFunctionStackIndex].f1 (myFunctionStack[myFunctionStackIndex].myArgList);
} else if (myFunctionStackIndex > 0) {
// if there is not a function pointer then assume that this is an argument list
// for a function that has been pushed on the stack so lets execute the previous
// pushed function with this argument list.
int myPrevIndex = myFunctionStackIndex - 1;
myFunctionStack[myPrevIndex].myArgList = myFunctionStack[myFunctionStackIndex].myArgList;
}
executeFuncStack();
}
}
编辑 3:
然后我们使用以下附加开关进行更改pushFunc()
以处理双精度:
case 'd':
{
double d;
// double argument
d = va_arg(argp, double);
memcpy (pmyArgList, &d, sizeof(double));
pmyArgList += sizeof(double);
}
break;
因此,使用这个新功能,我们可以执行以下操作。首先创建类似于原始问题的两个函数。我们将在一个函数中使用 pushFunction() 来推送参数,然后堆栈上的下一个函数使用这些参数。
double f1 (double myDouble)
{
printf ("f1 myDouble = %f\n", myDouble);
return 0.0;
}
double g2 (double myDouble) {
printf ("g2 myDouble = %f\n", myDouble);
myDouble += 10.0;
pushFunction (0, "d", myDouble);
return myDouble;
}
新我们将我们的新功能与以下一系列语句一起使用:
double xDouble = 4.5;
pushFunction ((void (*)(...))f1, 0);
pushFunction ((void (*)(...))g2, "d", xDouble);
executeFuncStack();
这些语句将首先执行g2()
值为 4.5 的函数,然后该函数g2()
将其返回值推送到我们的堆栈中,以供f1()
首先推送到我们堆栈中的函数使用。