当前位置导航:炫浪网>>网络学院>>编程开发>>C++教程>>C++进阶与实例

用C++实现可重用的数学例程

通常情况下,需要调用由用户提供的函数的算法是难以实现重用的。而实现重用的关键就在于寻找一种封装用户定义代码的有效途径。

  引言

“代码重用”是软件工程追求的神圣目标之一。采用面向对象(object-oriented, OO)的程序设计方法的一个主要方面也就是为了代码重用,这可以从任何介绍OO程序设计的书籍看得出来。然而实际应用中,使用C++一类的OO语言来实现代码重用比我们想象的要难得多。事实上,正如一位作者所说,由于C++程序员普遍倾向于创建自己的容器类,“C++对科学计算软件的可重用性造成了很大的阻碍”。

  在本文中,我展示了怎样用C++语言创建可重用的数学例程。相对于OO来说,我使用的方法更依赖于通用编程(Generic Programming)。为了讨论的方便,我使用了一个广泛应用的估计算法——Newton-Raphson算法来作为例子。Newton-Raphson算法必须调用一个用户定义的函数。本文首先给出了用户定义函数的典型(并不让人满意)封装方法,然后提出了一种建立在模板和操作符重载基础上的更加令人满意的封装方式。

  Newton-Raphson算法

  在科学计算和财经工程领域,许多数值算法都是通用的(至少在理论上是),可广泛地用于解决一类问题。一个大家熟悉的例子就是Newton-Raphson例程,它可用来寻找方程f(x)=0的数值解。标准的数学表达式f(x)表示f是变量x的函数,其通常的表达形式为f(x,a,b,...)=0,f被定义为多于一个变量的函数。在这种情况下,Newton-Raphson算法试图把x以外的变量固定并作为参数,而寻找关于变量x的数值解。

  由于Newton-Raphson算法需要知道被求解函数的确切表达,其传统实现方法是直接将代码嵌入到客户应用程序中。这就使得算法的实现代码经过针对不同被求解函数的少量修改后在客户程序中反复出现。

  同许多其它数学例程一样,Newton-Raphson算法的具体实现是应该与特定用户无关的。并且,重复编码在任何情况下都应该尽量避免。我们很自然地会想到把该类例程作为库函数来实现,以使客户程序可以直接调用它们。但是,这种实现方式必然会涉及到如何将用户自定义函数(Newton-Raphson例程需要调用该函数)封装成可以作为参数传递的形式。下面部分描述了一种通常的,也是存在很多问题的用户定义函数封装方法。

  通常的实现途径——函数指针

  现在的任务就是把Newton-Raphson算法作为一个库例程来实现,客户程序可以直接调用该例程来对任何形如f(x,a,b,..)=0的方程求取关于x的数值解。问题的关键就是算法的实现必须使用(能够调用)f(x,a,b,...)形式的通用函数,而该函数的具体定义由库的用户在以后提供,并且只能在运行时才提交给库。对于C和C++程序员,一种自然的可能方式就是把函数指针作为参数传递给库例程:
 typedef double (*P2F)(double);

double NewtonRaphson(P2F func_of_x, double x_init,) {
 ...
 //通过函数指针调用函数
 double y = func_of_x( x_init );
 ...
}
该库例程工作得很好,但这仅仅是对于恰好只有一个参数的函数来说的。在C++中,程序员可以对库函数进行重载,为具有不同参数数目的用户定义函数分别定义一个例程。但是这样会使得库代码出现大量的重复,并且更为糟糕的是,你不知道到底需要定义多少个这样的库例程。

    另一种想法就是利用可选参数,如下面语句所示:

    typedef double (*P2F)(double, ...);
    这似乎看来可以结束这个问题的讨论了。但是幸运也不幸运的是,C++不允许如上面代码所期望的那样使用可选参数。由于指向函数的指针必须准确地知道函数参数的类型和个数,该typedef定义的函数指针就只能与有一个double类型参数并跟上C风格的varargs的函数匹配,而不能用于包含了更多指定类型参数的函数。

  当然还有其它的传递多参数函数的途径,比如说可使用函数外壳。但是这种方法对于作者来说,除了求助于全局变量以外,并不清楚该怎样去做。

  为使其简化,就需要使用一组包含了一定参数的构造,这些构造定义了复杂的用户函数,并为库例程通过传递单个参数来调用这个函数提供了途径。这就将是一个对象——一个纯粹并简单的对象。因此,我为通用函数f(x,a,b,...)定义了一个类,并将其命名为FuncObj。(为了简化叙述,从现在开始,参数的个数被固定为3个。)
 class FuncObj {
 private:
  double _a;
  int _b;
 public:
  FuncObj(double a_in, int b_in);

  // 用x, a, b的形式定义用户定义函数
  double theFunc(double x_init);
};

    你可能试图通过向先前定义的库例程传递一个指向FuncObj对象的theFunc成员函数的指针来调用该例程。但是这种方法不能工作,至少因为两点原因。首先,在成员函数的表示中包含有类的名称,指向它的指针不能用于需要一个指向普通函数的指针的地方。其次,指向成员函数的指针必须通过一个该类的对象实例来存取。我将在下一个部分解决这两个问题。(需要注意的是,把theFunc定义成static类型无法真正解决问题,因为这样的话,theFunc就不能存取FuncObj的非静态成员变量,而正是这些成员变量保存了运算所需的其它“常值”变量。)

  使用指向成员函数的指针

  正如上一部分所讨论到的,必须对库接口进行修改,以便通过指向成员函数的指针来访问。并且库接口应该定义成函数模板,使得它不局限于某一个特定的类。

 template
double NewtonRaphson(T & func, double (T::*func_of_x)(double),double x_init, ) {
 ...
 // 通过对象(引用)和指向成员函数的指针
 // 调用成员函数
 double y = (func.*func_of_x)( x_init );
 ...
}
    这段代码能够正常工作,但是其语法显得有些难于理解。

  为指向成员函数的指针创建类型定义是使代码简化并更可读的一种有效途径,就象先前为指向简单函数的指针创建类型定义那样。换句话说,创建带参数化类型的类型定义会使程序显得更加易懂,如下所示:
 template
typedef double (T::*P2MF)(double);

    如果上述代码符合C++语法的话,P2MF的类型就是指向类T的成员函数的指针,该函数需要一个double型的参数,并返回一个double类型的值。然而遗憾的是,C++不支持包含了模板的类型定义。

  按照计算机科学的惯例,最终的解决办法就是引入另一级重定向。在该例中,可以通过定义一个封装模板来使得上述类型定义变得合法:

共3页 首页 上一页 1 2 3 下一页 尾页 跳转到
相关内容
赞助商链接