将数组传递给函数指针(在c中)?

时间:2017-05-23 20:18:17

标签: c function-pointers numerical-methods numerical-integration runge-kutta

我要做的是使用Runge Kutta解决二阶ODE问题。我有RK方法的大纲,并且ODE本身已经建立,但我无法继续进行。我在一个数组中有ODE-s,我尝试将这个数组传递给RK,但是在f(dy [i])部分它给出了“预期的双*但是类型为double”错误。当我尝试将两个ODE-s分成两个函数并将它们传递给RK时,它只适用于第一个函数。传递dy元素而不是函数指针不是一种选择,因为RK本身应该能够解决包含任意数量变量的ODE。

如何正确添加数组并使用它? 这是我对应的代码:

double* harmOsc(double* p, double t, double* y, double* dy, int n)
{
    double k = p[0];
    double m = p[1];
    double x = y[0];
    double v = y[1];
    dy[0] = v;
    dy[1] = -k/m*x;
    return(dy);
}


double* HarmOsc1(double* dy, double t)
{
    return(dy);
}


void RK4(
    double t,       //independent variable
    double dt,      //stepsize

    double *y,      //variables
    double *dy,     //derivatives
    int n,          //number of equations
    double* (*f)(double*, double))
{

    int j;
    double* l = (double*)calloc(4*n,sizeof(double));
    for(j=0;j<n;j++)
    {
        l[0*n+j] = dt*f(dy[j],t);
        l[1*n+j] = dt*f(dy[j]+0.5*l[0*n+j],t+0.5*dt);
        l[2*n+j] = dt*f(dy[j]+0.5*l[1*n+j],t+0.5*dt);
        l[3*n+j] = dt*f(dy[j]+0.5*l[2*n+j],t+0.5*dt);
        y[j] = y[j] + (l[0*n+j] + 2*l[1*n+j] + 2*l[2*n+j] + l[3*n+j])/6;
    }
}

int main(int argc, char *argv[])
{
    double* p = (double*)calloc(2,sizeof(double));
    p[0] = 15; p[1] = 140;
    double* y = (double*)calloc(2,sizeof(double));
    y[0] = 12.4; y[1] = 1.1;
    double t=0;

    double* dy = (double*)calloc(2,sizeof(double));
    dy = harmOsc(p,t,y,dy,2);
    dy = HarmOsc1(dy,t);

    RK4(t,0.05,p,y,dy,2,&HarmOsc1);
    printf("%f, %f\n",dy[1],dy[0]);
}

HarmOsc1是我调用的函数,因此它具有所需的参数量。

当然还有警告:

  

RK3.c:55:13:错误:'f'的参数1的不兼容类型

  RK3.c:55:13:注意:预期'double *'但参数类型为'double'

  RK3.c:56:6:错误:'f'

的参数1的类型不兼容   RK3.c:56:6:注意:预期'double *'但参数类型为'double'

  RK3.c:57:6:错误:'f'

的参数1的类型不兼容   RK3.c:57:6:注意:预期'double *'但参数类型为'double'

  RK3.c:58:6:错误:'f'

的参数1的类型不兼容   RK3.c:58:6:注意:预期'double *'但参数类型为'double'

4 个答案:

答案 0 :(得分:1)

根据评论中提供的说明,我们已经确定您的函数RK4()应该使用Runge-Kutta方法以数字方式求解每种形式的几个ODE的一个值

y'(t) = f(y(t), t)

具有表格的初始值条件

y(t0) = y0

其中t是标量,每个y是标量值,f()对于所有ODE都相同(或者至少由相同的C函数表示)

我们进一步确定函数参数具有以下含义:

t    equivalent to t0 above
dt   the distance from t to the point at which the solutions are to be computed
y    points to an array in which to return the solutions
dy   points to the initial function values (y0 above) for all the ODEs
n    the number of ODEs to solve
f    the function `f()` above

您遇到了各种各样的问题,但我认为其中大多数问题最终都是以RK4()参数f的声明开头的。请特别注意,上述ODE表单需要f()作为参数接受与每个y的值相同维度的一个值,并将另一个与每个{{1}相同的维度/数量作为参数接受}的参数,并返回与每个y的值相同的维度的值。但我们已经确定y是每个一个标量参数的标量函数,因此y应该接受两个f s并返回一个double。这将使我们得到这个宣言:

double

您不需要void RK4( double t, //initial point double dt, //delta double *y, //result values double *dy, //initial values int n, //number of equations double (*f)(double, double)) { 在一次运行中计算多个ODE的结果,因为您的f会在ODE上进行迭代,并为每个ODE分别调用RK4()

接下来,让我们看一下你的变量f()。您正在动态分配足够的空间来存储每个输入ODE的所有四个RK常量(而不是释放它),但这没用。您只使用一组RK常数,因此在继续下一个等式时,您不需要记住以前的常量。因此,您只需要四个常量的空间,因为这是一个固定的数字,您不需要动态分配。你甚至没有从使用阵列中获得任何好处;我只想使用在循环体内声明的四个标量变量。

l

观察现在声明 int j; // double* l = (double*)calloc(4*n,sizeof(double)); for (j = 0; j < n; j++) { double k1 = dt * f(dy[j], t); double k2 = dt * f(dy[j] + 0.5 * k1, t + 0.5 * dt); double k3 = dt * f(dy[j] + 0.5 * k2, t + 0.5 * dt); double k4 = dt * f(dy[j] + k3, t + dt); // <-- note corrections 的参数和返回类型与输入ODE形式的RK方程的要求相匹配。

最后,计算结果:

f

注意最后一行与您的版本之间的本质区别 - RK计算delta y,但您的初始y值位于 y[j] = dy[j] + (k1 + 2 * k2 + 2 * k3 + k4) / 6; ,而不是dy

就是这样:

y

我在这一点上观察到,我在处理你的问题时遇到的最大问题是由于不了解你想要做的事情的细节。这源于几件事,其中很大一部分

  • 你的变量和参数名称很短且没有表现力,更糟糕的是,他们似乎意味着某些东西,其中有几个实际上代表了与他们的名字不同的东西

  • 代码文档很少,显然不正确。这似乎与前一点有点不同,因为根据有限(但不完全正确)的参数文档,函数参数命名更有意义。

  • 代码本身的帮助有限,因为提出问题的缺陷很多。

带回家:编写良好代码文档。使用完整的句子。描述每个函数对每个参数的期望,以及它对它们的承诺。描述返回值。记录处理函数执行的任何错误。当你这样做时,试着像想要使用你的功能的人一样思考,但不能参考它的实现来看它的作用 - 那个人需要知道什么?实际上,我建议在编写该函数的实现之前为每个函数编写文档。在您发现需要时更新文档,但文档可以帮助您保持正确,并且首先编写文档可以帮助您编写,就像您无法看到实现一样。

答案 1 :(得分:0)

函数f期待double *,即第一个参数指向double的指针。但是,在您调用f的每个位置,您传递的是double值,而不是指向double的指针,也不是数组(它会衰减到指向第一个值的指针):

    // here ------------v
    l[0*n+j] = dt*f(  dy[j],                 t);
    l[1*n+j] = dt*f(  dy[j]+0.5*l[0*n+j],    t+0.5*dt);
    l[2*n+j] = dt*f(  dy[j]+0.5*l[1*n+j],    t+0.5*dt);

如果此函数的第一个参数需要是一个数组,则传递一个数组,而不是一个数值。

答案 2 :(得分:0)

f期望第一个参数是double *,而你用dy [j]调用f,这是一个double。 只需将dy [j]更改为dy + j即可。

您可能需要一个临时变量

double tempv;

然后在循环内,

tempv = dy[j];
...
tempv = dy[j] + ...;

使用tempv的地址调用f,

f(&tempv,...)

答案 3 :(得分:0)

在C中,不建议返回数组。有可能,但管理哪些代码负责释放这些内存是一件麻烦事。而且,它反对某些C哲学,它产生高性能代码,不必要地重复生成基本上相同的数组,只是为了在此后不久销毁它。在脚本语言中使用原型算法时,您可以这样做。

因此,最好使衍生函数具有签名

void f( double * dy, double * y, double t);

其中维和常量是外部常量(或将参数数组添加到参数列表中)

然后在rk4循环中使用它作为

f(k1,  y, t      );
for(i=0; i<n; i++) yt[i] = y[i] + 0.5*h*k1[i];
f(k2, yt, t+0.5*h);
for(i=0; i<n; i++) yt[i] = y[i] + 0.5*h*k2[i];
f(k3, yt, t+0.5*h);
for(i=0; i<n; i++) yt[i] = y[i] +     h*k3[i];
f(k4, yt, t+h);
for(i=0; i<n; i++) y[i] = y[i] + h/6*(k1[i]+2*(k2[i]+k3[i])+k4[i]);

当然,您需要确保每次集成时只分配k1,k2,k3,k4,yt一次。这可以通过三种方式实现:

  • 集成过程包含完整循环,必要时返回一个点列表,
  • 有一些结构包含在第一个集成步骤之前构建的工作数组
  • 或者您使用较新的动态堆栈分配,该分配也应该没有禁止的分配惩罚。