首页产品库评测行情新闻|手机数码笔记本台式机DIY硬件数字家庭数码相机办公外设|软件下载游戏开发|社区

更多

数码相机
MP4
LCD
机箱
音箱

软件资讯设计 工具 系统 开发 安全 办公 陶吧 IT教育 Vista频道 | 下载中心酷我音乐盒 腾讯QQ
天极网 > 开发频道>用C++实现可重用的数学例程

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

2005-06-02 08:33作者:i_like_cpp出处:blog责任编辑:方舟

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

  引言

  “代码重用”是软件工程追求的神圣目标之一。采用面向对象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++不支持包含了模板的类型定义。

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

template struct P2MFHelper {
 typedef double (T::*P2MF)(double);
};

  上面的代码中演示了一种奇怪但是又很有趣的templete和typedef用法。现在,我可以重新定义库函数如下:

template
double NewtonRaphson(T & func,
P2MFHelper::P2MF func_of_x,
double x_init, ) {
 ...
 double y = (func.*func_of_x)( x_init );
 ...
}

  注意func.*func_of_x两边的括号对代码的正确编译和运行是必需的,因为接下来的函数调用比操作符.*具有更高的优先级。同时Helper类和库函数也可以合并到一个单一的模板类中,并且能够达到同样的目的。然而,把Newton-Raphson例程设计成一个类并不是一种好的方式。不用说,这将会导致语法变得稍微有一些复杂。

  现在,该库例程可以适用于具有不同参数数目的用户定义函数,只要客户程序员定义一个FuncObj风格的类来封装每一种该类型的函数。但是,这种方法还具有两个小的问题。首先,你无法再向Newton-Raphson历程传递指向普通函数的指针。其次,同时传递一个对象和一个指向成员函数的指针将使程序变得不健壮和不经济。下面我来讨论如何克服这两个缺点。

关注此文的读者还看过:

返回开发频道首页

共2页。 12下一页

软件频道最新更新

热点推荐

IT嘉年华

编辑推荐

软件下载

热门
推荐

网友关注

软件
资料
游戏

装机推荐

文章排行

本周
本月
最新更新
天极服务|关于我们|About us|网站律师|RSS订阅|友情合作|加入我们|天极动态|网站地图|意见反馈|MSN/QQ上看天极
Copyright (C) 1999-2012 Yesky.com, All Rights Reserved 版权所有 天极网络