文件布置
在 OpenFOAM 中,所有代碼都以注釋段開頭,使用有限體積的 CFD 類型文件都包括以下頭文件
#include "fvCFD.H"
在此頭文件種,僅包含類或函數的定義,函數的內容會在運行時以動態形式調用。
當 fvCFD.H
文件被引用后,在對應的編譯設置文件 Make/options
中還需要添加如下命令
EXE_INC = \
-I${LIB_SRC}/finiteVolume/lnInclude
EXE_LIBS = \
-lfiniteVolume
這兩句話指定了在編譯時尋找頭文件的位置和需要鏈接的函數庫。
在 fvCFD.H
中,為了避免被多次引用,定義了如下宏變量
# ifndef fvCFD_H
# define fvCFD_H
當頭文件已經引用過后,ifndef
判斷返回 0,此時文件內直到 #endif
命令之間內容全部不會被再次引用。
在 fvCFD.H
中之后包含了一系列頭文件,並以如下內容結束
#ifndef namespaceFoam
#define namespaceFoam
using namespace Foam;
#endif
此代碼定義了命令空間 Foam,在包含的庫中所有的函數聲明都屬於命令空間 Foam,因此在調用庫函數時必須使用。
在 c++ 代碼中,必須包含 main 函數的實例,並且程序執行時是從此函數開始。在 c++ 的 main 函數中,使用以下參數並返回一個整數
int main(int argc, char *argv[])
c++ 編程基礎
操作符
對於輸入和輸出可以使用標准庫 iostream
cout << "Please type an integer!" << endl;
cin >> myInteger;
其中操作符 <<
和 >>
為輸出和輸入操作符,endl
為換行操作符。在 OpenFOAM 中推薦使用新的輸出流 info
,其優點是可以在並行計算情況中使用。
變量可以相加減,相乘除,並且當自定義類型指定了轉化方法時還可以轉化為其他類型的變量。在 OpenFOAM 中一些變量還可以使用算術運算符,但並非所有都可以。
c++ 中一些運算符包括 +
、-
、*
、/
等,以及其他標准運算符,例如 %
、++
、--
、+=
、-=
、/=
、%=
等,用戶自定義類型應自己定義這些運算符計算過程。
標准數學函數定義在標准庫 cmath
中,因此並不是 c++ 的一部分,例如三角函數,指數函數和對數函數等。
分支
判斷語句形式為: if (variable1 > variable2) {...CODE...} else {...CODE...}
。
比較操作符包括:<
、>
、<=
、>=
、==
、!=
。
循環語句形式為:for (int; condition; change) {...CODE...}
。
函數
在 c++ 中,函數可能有或者沒有返回值,對於沒有參數或返回值的函數,對應位置用 void 指定。在 c++ 中允許多個函數為同一函數名,只要其參數的個數或類型不同就不會在編譯時報錯。
變量域由花括號指定 {}
,一個在花括號內定義的變量,僅在此區域內可見。可能有多個變量為同一個名字,但是在每個域內僅代表唯一的一個變量。為了使用全局變量,可以用 ::
操作符指明變量所在的域。
函數的調用前必須進行聲明,函數的聲明一般放在頭文件內,如 #include "file.h"
或 #include <standardfile>
。在編程時,將函數的聲明和定義放在不同的文件中是一種良好的習慣,在 OpenFOAM 中也是采用這種方式。
如果函數中某個參數需要改變變量值,那么參數的類型必須是引用,例如
vodi change(double& x1)
此時對應的變量 x1
將為參數的引用,而非函數內的局部變量。在 c++ 內對函數進行調用時,需要對輸入參數進行復制,引用也可以避免對內存占用較多的變量進行復制。為了避免對輸入參數進行錯誤的修改,也可以定義輸入參數的類型為常量型引用,即
void changeWord(const string& s)
定義函數時可以給定參數的默認值,在函數調用時可以減少此參數的值。
類型
在 c++ 中變量可以包含不同的類型,在定義時可以用 int myInteger
,或用 cont int myConstantInteger = 10
來定義常數。在 c++ 中也可以自定義類型,並且在 OpenFOAM 中包含了多種自定義類型。
指針是指向內存空間的變量,指針變量可以從變量的定義看出
int *pint;
double *pdouble;
char *pchar;
可以用 typedef
來定義新的變量類型
typedef vector<int> integerVector;
integerVector iV;
這種方法可以簡化大型程序,例如 OpenFOAM 代碼的復雜度,使得代碼易讀性更好。
命名空間
當不同編程人員使用 c++ 寫程序時可能會有命名重復的風險。
通過將聲明的區域增加命名空間可以有效控制聲明的變量是否可見,例如 OpenFOAM 中常用的
using namespace Foam;
可以使所有定義在命名空間 Foam 內的聲明可見。
定義命名空間的形式為
namespace name {
// declarations
}
此時,新的定義就加入到了命名空間內,並且在此作用域內可以使用命名空間定義的其他代碼。
面向對象
面向對象的思想是將關注點放在對象而非函數上。對象是類的實例,對於屬於同一類的對象包含有相同的屬性。面向對象的優勢在於增加了代碼的復用性,每個類可以為不同的目標進行設計和編寫。在 c++中,類與變量類型是同一個概念,所以類也可以看做是一種新的變量類型。
對象聲明
下面代碼定義了 name
類及其公有或私有的方法和數據
class name{
public:
// public member functions and data members
private:
// hidden member functions and data members
}
類的公有屬性(方法和數據)在類外部是可見的,而私有屬性則不可見。
對於沒有指定 public 或 private 的屬性,其默認是 private。
類中方法和數據的聲明與普通函數與變量相同。
類的使用
定義類的對象方法為
name nameObject;
一個類可以有多個對象,並且每個對象包含的屬性都是相互獨立的。可以聲明對象的指針或引用,但是其調用類包含的方法時需要用 ->
符號,例如
p1 = &nameObject; // reference
p2 = new name; // pointer
p1->write();
p2->write();
類中方法的定義可以在類的定義里,也可以在其外部。
inline void name::write()
{
// Contents of the member function.
}
例如上述代碼定義了 name
類中的 write()
方法。在函數定義時,name::
指出了方法 write
屬於 name
類,而 inline
關鍵字則指出方法會在調用處進行展開,而非像普通函數那樣在內存空間中跳轉。對於直接定義在類中的方法,會自動使用 inline
關鍵字進行展開。
在類的方法中可以自由方法類的所有數據和方法,而不受任何限制。
和普通函數一樣,類的聲明和定義也應該分開,放在不同的文件內。在 OpenFOAM 中,大部分類都是使用此種模式。對於 inline
類型函數,需要將函數的定義放在對應的頭文件內。
構造函數
構造函數是類的對象在調用時使用的特定的初始化函數。當沒有使用特定的構造函數時,即使用 null
構造函數,對象的所有屬性都是未定義的。
在初始化時,根據給定的參數不同調用對應的構造函數。下面給出了 Vector
類的構造函數的幾個示例
// Constructors
// Construct null
inline Vector();
// Construct given VectorSpace
inline Vector(const VectorSpace<Vector<Cmpt>, Cmpt, 3>&);
// Construct given three components
inline Vector(const Cmpt& vx, const Cmpt& vy, const Cmpt& vz);
// Construct from Istream
inline Vector(Istream&;)
析構函數
當對內存空間申請后,在類的析構函數中必須對內存進行釋放。為保證所有內容都得到釋放,最好顯式的對析構函數進行定義。
在析構函數中不需要任何參數,並且函數名與類名相同,但是函數名前增加了 ~
符號。
定義的對象在離開作用域時應該釋放其空間,特別是使用 new
關鍵字申請對象應手動使用 delete
進行刪除。
常數成員函數
常數關鍵字 constant 可以用於修飾成員函數。對於常數類型成員函數,其含義是函數內對象不會進行修改。常數的成員函數定義方法為在參數后添加 const
修飾符,例如
template <class Cmpt>
inline const Cmpt& Vector<Cmpt>::x() const
{
return this->v_[X];
}
友元
友元 friend
代表一個函數或類可以訪問某個類的私有屬性。一個類可以聲明哪些類型為自己的友元,但是無法聲明自己為哪些類型的友元。
操作符
操作符定義了如何對特定的類型進行操作。標准操作符包括以下幾種
操作符應當被定義為成員函數或友元函數,函數名為 operatorX
,其中 X
用對應的操作符號代替。
在 OpenFOAM 中為所有的類型都定義了對應的操作符,包括 iostream 符 <<
和 >>
。
靜態成員
靜態成員在類之中具有單獨的實例,即所有對象中都一樣。靜態成員使用關鍵字 static
,可以應用在數據成員或成員函數中。由於靜態成員不屬於任何一個特定的對象,因此調用時必須用類名進行調用,如
className::staticFunction(parameters);
繼承
一個類可以繼承另一個已有類的屬性,並擴展包含其他屬性。繼承的定義方法為
class newClass : public oldClass { ...members... }
在 OpenFOAM 中,類的繼承形式為
template <class Cmpt>
class Vector
:
public VectorSpace<Vector<Cmpt>, Cmpt, 3>
這里,Vector
是 VectorSpace
的一個子類。
子類的名字可以與父類相同,此時父類中所有同名成員會被隱藏,即使父類與子類成員函數的變量個數不同。
隱藏的父類成員可以通過 oldClass::member
方式訪問。在類中的定義的不同訪問屬性中,private 成員無法在子類中訪問,而 public 和 protected 屬性成員可以。子類可以有多個父類,從而合並各個類的特性。
虛函數
虛成員函數主要用於動態綁定,即根據運行時調用的方式來確定具體函數。
虛函數使用 virtual
關鍵字,通過使用指針指向父類的對象來實現動態綁定 p = new subClass (...parameters...)
,指針可以指向任何子類,從而使用 p->memberFunction
來調用具體函數。
具有至少一個虛函數的類為虛類,虛類無法實例化對象,主要作用是規定子類的定義方式。
模板
大部分類的定義都是針對特性數據類型,但是有些操作是與數據類型無關的,此時與其將相同的算法針對不同的類型定義許多遍,更常用的方法是定義模板使之適用於任意類型。
模板類在定義時在類前添加如下代碼
template<class T>
其中 T
是一般參數,代表任意類型,關鍵字 class
定義了 T
為類型變量。一般參數隨后在類的定義中使用指定的類型進行定義,模板類在構造對象時的形式為
templateClass<type> templateClassObject;
OpenFOAM 廣泛使用模板,為了使代碼易讀性更好,通常將模板類名字用 typedef
重新定義,例如
typedef List<vector> vectorList;
上面代碼將 vectorList
定義為 vector
數據類型的模板 List
類的別名。