在读 OpenFOAM 代码过程中,有一类应用初看之下觉得很费解,比如 OpenFOAM-2.3.x 的 twoPhaseEulerFoam
,createFields.H 有这么一段:1
2
3
4
5phaseModel& phase1 = fluid.phase1();
phaseModel& phase2 = fluid.phase2();
volScalarField& alpha1 = phase1;
volScalarField& alpha2 = phase2;
乍看之下,感觉有点奇怪:怎么能将 phaseModel 类的引用直接赋值给 volScalarField 类的引用呢?后来查看了一下 phaseModel 类的定义,发现原来 phaseModel 类是 volScalarField 类的派生,由此上面代码就好理解了,无非是将派生类引用赋值给基类引用而已。
但是,下面代码,虽然深究下去发现原理类似,但是乍看上去却更加费解:1
2
3
4
5
6
7
8
9
10Foam::tmp<Foam::volVectorField> Foam::twoPhaseSystem::U() const
{
return phase1_*phase1_.U() + phase2_*phase2_.U();
}
Foam::tmp<Foam::surfaceScalarField> Foam::twoPhaseSystem::calcPhi() const
{
return
fvc::interpolate(phase1_)*phase1_.phi()
+ fvc::interpolate(phase2_)*phase2_.phi();
}
这里将 phase1_
直接与 phase1_.U()
(或者 phase1_.phi()
)相乘,怎么理解?从原理上讲,应该是 phase1_
的体积分率与其速度的乘积。
仔细看一下 phaseModel 类的构造函数,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22Foam::phaseModel::phaseModel
(
const twoPhaseSystem& fluid,
const dictionary& phaseProperties,
const word& phaseName
)
:
volScalarField
(
IOobject
(
IOobject::groupName("alpha", phaseName), // "alpha.particle"
fluid.mesh().time().timeName(),
fluid.mesh(),
IOobject::READ_IF_PRESENT,
IOobject::AUTO_WRITE
),
fluid.mesh(),
dimensionedScalar("alpha", dimless, 0)
),
......
......
原来是这样,读取 “alpha.phaseName” 数据文件构建了一个 IOobject 对象,并用该对象对一个临时的 volScalarField 对象进行了初始化,然后用成员初始化列表,将该临时 volScalarField 对象对基类 volScalarField 进行初始化。
这样一来,对于上面的情形, phase1_ * phase1_.U()
,实际上是进行了一个隐式转换,先将 phase1_
转换成基类 volScalarField 类型,由于上述 phaseModel 类的初始化设定,转换以后,phase1_
其实就相当于用 alpha 初始化过的那个 volScalarField 类的对象了。所以, phase1_ * phase1_.U()
其实相当于 alpha.phaseName * phase1_.U()
,是两个 volScalarField 类对象之间的乘法运算。
在 twoPhaseEulerFoam
的代码里,类似的用法还有很多,这里不能一一举例,当看到某处费解的时候,不妨想想是否是上面提到的情形。
为了便于理解这个原理,我这里写了一个简单的 c++ 测试小程序1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69#include<iostream>
using namespace std;
class A
{
public:
int a;
A(int a);
void print();
};
A::A(int a)
{
this->a = a;
}
void A::print()
{
cout<<"base class:"<<"a="<<a<<endl;
}
class B : public A
{
public:
int b;
B(int a, int b);
void print();
};
B::B(int a, int b):A(a)
{
this->b = b;
}
void B::print()
{
cout<<"Derived class:"<<"a="<<a<<",b="<<b<<endl;
}
void print_test( A& obja)
{
obja.print();
}
int operator*(A& a, A& b)
{
return a.a * b.a;
}
int main(int argc, char *argv[])
{
int a=2,b=4;
A obj1(a);
obj1.print();
B obj2(a+b,b);
obj2.print();
A& obj3 = obj2;
obj3.print();
cout<<"obj3.a="<<obj3.a<<endl;
// cout<<"obj3.b="<<obj3.b<<endl; Error! class A has no member named 'b'.
print_test(obj1);
print_test(obj2);
cout<<"obj1 * obj2 :"<< obj1 * obj2 << endl;
return 0;
}
输出如下:
base class:a=2
Derived class:a=6,b=4
base class:a=6
obj3.a=6
base class:a=2
base class:a=6
obj1 * obj2 :12
可以将上述程序中派生类对象赋值给基类的规律简单小结如下:
- 当将基类的引用指向派生类的对象时,用该引用只能调用派生类从基类继承而来的成员。像上面程序中,obj3.a 输出的是 对象 obj2 的成员 a,但是 obj3 无法调用成员b。
- 当一个函数需要的参数是基类的引用时,可以直接将派生类的对象传递给该函数,像上面的
print_test
函数和重载的*
运算符那样。此事相当于作了一个隐式的将派生类对象赋值给基类引用。