如何将函数转换为 C 中的结构?

How to cast function into a struct in C?

这是我在 Whosebug 上的第一个 post,希望格式没问题。

我想将函数作为参数传递给另一个函数。为此,我声明了一个结构来描述函数。 但是随后,我在编译时遇到了一个无效的分析器错误。

在functions.h中,我有这个位:

struct double_fun{
    double (*function)(double x);
};
// Test functions.
extern struct double_fun square;
// Other test functions

// Exponential function.
extern double exponential_temp(double x,double temperature);
extern struct double_fun exponential(double temperature);

然后在 functions.c:

// Test functions for somme.
double square_fun(double x){
    return x*x;
}
struct double_fun square = square_fun();
// Other test functions

// Exponential function.
double exponential_temp(double x, double temperature){
    return exp(x/temperature); // From math.h
}

struct double_fun exponential(double temperature){
    double aux_fun(double x){return exponential_temp(x, temperature);};
    struct double_fun aux = aux_fun;
    return aux;
}


double somme(double* liste, int length, struct double_fun fun){
    double poids = 0;
    for(int i=0;i<length;++i){
        poids = poids + (fun.function)(liste[i]);
    }
    return poids;
}

我的最终目标是使用 somme(list, length, function) 将函数应用于列表然后 return 元素的总和(特别是指数函数)。 但在另一个我称之为 somme 的文件中,我多次调用它以获得不同的温度值。 这就是为什么我有指数(温度),它应该 return 一个取决于温度值的函数。

这是我得到的错误:

make learning
gcc -Iinclude/ -o source/functions.o -c source/functions.c -Wall -Werror -lm
source/functions.c:83:28: error: invalid initializer
   83 | struct double_fun square = square_fun;
      |                            ^~~~~~~~~~
[[Same error for other test functions]]
source/functions.c: In function ‘exponential’:
source/functions.c:104:29: error: invalid initializer
  104 |     struct double_fun aux = aux_fun;
      |                             ^~~~~~~
make: *** [Makefile:21 : source/functions.o] Erreur 1

我尝试不使用结构和 return 指向函数的指针,它适用于测试函数,但似乎没有有效的指数(温度)语法 return 取决于温度的函数。 在 Whosebug 上,我找到了几种关于如何将函数作为参数传递的解释,但是没有示例允许我将温度作为参数而不是参数。

如果您能帮助我解决这个错误或找到另一种方法将函数作为参数传递给 somme,我将不胜感激!

您没有将函数转换为结构。该函数是struct的成员,所以你用一个普通的初始化列表来填充它:

struct double_fun square = {square_fun};

此外,您不要在函数名称后加上 ()。调用函数,它不计算函数指针本身。

为此使用结构没有什么意义。如果结构包含要一起传递的多个函数,则可以使用它。例如,一个设备在内核中表示为一个结构体,其中包含用于打开、读取、写入、关闭等设备的函数指针。

您实际上尝试实现闭包。

在 C 中惯用的做法是传递 一个函数指针和一个指向上下文的空指针

然而,前段时间我想出了一个不同的方法。令人惊讶的是,C 语言中有一个内置类型家族,它既承载数据又承载代码本身。这些是 指向函数指针的指针

诀窍是使用这个单一对象通过取消引用函数指针来传递这两个代码。接下来传递与上下文完全相同的双函数指针作为第一个参数。它看起来有点复杂,实际上它导致了非常灵活和可读的闭包机制。

见代码:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

// typedefing functions makes usually makes code more readable
typedef double double_fun_t(void*, double);

struct exponential {
   // closure must be placed as the first member to allow safe casting
   // between a pointer to `closure` and `struct exponential`
  double_fun_t *closure;
  double temperature;
};

double exponential(void *ctx_, double x) {
  struct exponential *ctx = ctx_;
  return exp(x / ctx->temperature);
}

// the "constructor" of the closure for exponential
double_fun_t **make_exponential(double temperature) {
  struct exponential *e = malloc(sizeof *e);
  e->closure = exponential;
  e->temperature = temperature;
  return &e->closure;
}

// now simple closure with no context, a pure x -> x*x mapping
double square(void *_unused, double x){
    (void)_unused;
    return x*x;
}

// use compound literal to transform a function to a closure
double_fun_t **square_closure = & (double_fun_t*) { square };

// the worker that process closures, note that `double_fun_t` is not used
// because `double(**)(void*,double)` is builtin type
double somme(double* liste, int length, double (**fun)(void*,double)){
    double poids = 0;
    for(int i=0;i<length;++i)
        // calling a closure, note that `fun` is used for both obtaing
        // the function pointer and for passing the context
        poids = poids + (*fun)(fun, liste[i]);
    return poids;
}

int main(void) {
    double list[3] = { 1, 2, 3 };
    
    printf("%g\n", somme(list, 3, square_closure));

    // a dynamic closure
    double_fun_t **exponential = make_exponential(42);
    printf("%g\n", somme(list, 3, exponential));
    free(exponential);

    return 0;
}

这种方法的优点是闭包导出了一个用于调用 double->double 函数的纯接口。闭包的 all 客户端使用的装箱结构无需介绍。唯一的要求是非常自然的“调用约定”,不需要共享任何代码。