在OpenFOAM 11版本之前,OpenFOAM的应用是以各种各样的求解器程序体现的,如icoFoam、interFoam等。每一种求解器都对应着某种特定的应用场景,当应用场景发生改变时,就需要开发新的求解器。
下面以最简单的程序代码来举例说明为何要用求解器模块。
比如今天要算个加法计算,编写了一段程序代码,姑且将其命名为add.c。
#include <iostream>
using namespace std;
int main(int argc, char **argv)
{
int a = 2, b=4;
int c = a + b;
cout << c << endl;
return 0;
}
编译完成后得到了一个可执行文件add.exe,它的功能很明确,用来计算2+4的和。当然,这个程序通过对程序主入口进行简单的修改,也可以用来计算3+5的和。但是如果明天想要算个乘法,就得对上面的代码进行修改。
比如再重新创建一个新的源代码,命名mul.c。
#include <iostream>
using namespace std;
int main(int argc, char **argv)
{
int a = 2, b=4;
int c = a * b;
cout << c << endl;
return 0;
}
编译完成后得到可执行程序mul.exe,该程序可以计算乘法。这种方式最大的好处是,每个程序都是独立可用的,有明确的功能目标。但是这可苦了程序开发的人,万一后天要算除法呢?大后天又要算个加减乘除混合运算呢?但需求是无穷无尽的,不可能为了一点点细小的改动就去把主函数抄一遍,尤其是当主函数非常复杂的时候。
那么此时最好的做法是将程序功能模块和主函数分开。
最简单的模块设计是将功能包装成函数和类,这样程序功能就可以从主函数中独立出来。下次遇到新的功能需求时,就可以不修改主函数,而仅仅只需要创建或修改功能模块即可。用户在创建新的功能模块时,需要了解主函数与功能模块之间的调用接口。
如上面的程序可以改写成下面的代码:
#include <iostream>
using namespace std;
int add(int a, int b)
{
return a+b;
}
int mul(int a, int b)
{
return a*b;
}
int main(int argc, char **argv)
{
int a = 2, b=4;
int c = add(a,b);
int d = mul(a,b);
cout << c << endl;
cout << d << endl;
return 0;
}
如果下次有新的功能需求,要计算除法,此时只需要添加新的功能函数即可。
double div(int a, int b)
{
return (double)a/b;
}
上面是函数式的模块,当然也可以将功能模块包装成类的形式,这样程序会更紧凑(将函数参数以成员变量的形式独立出来,函数不用带有相同的参数),但本质上并无不同。
#include <iostream>
using namespace std;
class compute
{
private:
int a;
int b;
public:
compute(int x, int y)
{
this->a = x;
this->b = y;
}
int add()
{
return a + b;
}
int mul()
{
return a * b;
}
double div()
{
return (double)a / b;
}
};
int main()
{
compute c(2, 3);
cout << "Addition: " << c.add() << endl;
cout << "Multiplication: " << c.mul() << endl;
cout << "Division: " << c.div() << endl;
return 0;
}
如果采用类进行程序功能组织的话,有新需求的时候也可以考虑使用类继承。
OpenFOAM 11之前的版本均为散装模式,11.0之后进入模块模式,其中主函数放置在foamRun
中,各功能模块分散于其他文件中,编译后以动态链接库的形式被主函数调用。用户开发时无需关注foamRun中的代码,只需要将注意力集中于自己的功能模块即可。
com版到目前为止所有版本依然为散装模式。
(完)