これからOpenFOAMのC++を学ぶために基本的な内容をメモとして残しておきます。
OpenFOAMはC++プログラミング言語により実装されたCFDに必要な機能がまとめられたオブジェクト指向の考え方で設計されているツールです。
C++の基本的な内容を理解する。
クラスの定義、ファイルの分割について。
C++の全てを学ぼうとすると膨大過ぎてとても扱いきれないので、必要最低限知っておくとよい内容を簡単にまとめていきます。
クラスの定義
クラスと言えばプログラム実行のための設計図のようなものです。
●クラスの定義
●クラスのインスタンス化(実態を作る)
●クラス内のメンバ関数、メンバ変数にアクセス
こちらを意識していれば良いでしょう。
main007.cpp
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 |
#include <iostream> using namespace std; class MyClass{ //クラスの宣言 private: int a; public: int b; int set_a(); int show_a(); }; //クラスの最後に;が必要 int MyClass::set_a() //メンバ関数の定義 { cout << "a----: "; cin >> a; return 0; } int MyClass::show_a() //メンバ関数の定義 { cout << "set_a() a = "<< a << endl; return 0; } int main() { MyClass mc; //クラスのインスタンス化 mc.b = 100; cout << "b = " << mc.b << endl; mc.set_a(); //クラスのset_a()関数の呼び出し mc.show_a(); //クラスのshow_a()関数の呼び出し return 0; } |
【結果】
1 2 |
b = 100 a----: |
aの入力を求められるので「20」と打ちます。
1 2 3 |
b = 100 a----: 20 set_a() a = 20 |
こちらのコードでは以下でクラスの定義を行っています。
1 2 3 4 5 6 7 8 |
class MyClass{ //クラスの宣言 private: int a; public: int b; int set_a(); int show_a(); }; //クラスの最後に;が必要 |
変数定義の範囲指定には3つあります。
- praivate:クラスのメンバ関数のみがアクセス可能
- public:クラスのすべてのユーザがアクセス可能
- protected: 基底クラスから派生したどのクラスのメンバーおよびフレンドからアクセス可能
main()関数で
1 |
MyClass mc; //クラスのインスタンス化 |
とすることでクラスのインスタンス化を行い「mc」という名前でクラスの実態を作っています。
クラス内のメンバ変数やメンバ関数を呼び出すには、
- mc.b:メンバ変数へのアクセス
- mc.set_a():メンバ関数へのアクセス
とします。
BMIの計算
もう少し意味のあるコードを書いてみましょう。
身長と体重からBMIを計算するプログラムです。
main008.cpp
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 |
#include <iostream> using namespace std; class BMI { private: double bw; double bl; double bmi; public: int setdata(); int showbmi(); }; //クラスの後ろに;はいる。 int BMI::showbmi() { cout << "BMI" << bmi << endl; return 0; } int BMI::setdata() { cout << "身長(cm)----"; cin >> bl; cout << "体重(kg)----"; cin >> bw; bmi = bw/((bl/100.0)* (bl/100.0)); return 0; } int main() { BMI b; //クラスのインスタンス化 b.setdata(); b.showbmi(); return 0; } |
身長と体重をターミナル上に入力すると以下のようにBMIを計算してくれます。
【結果】
1 2 3 |
身長(cm)----180 体重(kg)----75 BMI23.1481 |
ポインタからアクセス
クラスオブジェクトをポインタを使ってメンバ関数にアクセスすることができます。
1 |
ptr -> setx(); //ポインタを介してメンバ関数を呼び出す |
↑このようなアロー演算子によりメンバ関数にアクセスできます。
main009.cpp
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 |
#include <iostream> using namespace std; class MyClass{ int x; public: int setx(); int showx(); }; int MyClass::setx() { cout << "xの値を入力----" ; cin >> x; return 0; } int MyClass::showx() { cout << "x = " << x << endl; return 0; } int main() { MyClass* ptr; //MyClassのオブジェクトの指すポインタ MyClass mc; //MyClassオブジェクト ptr = &mc; //mcオブジェクトのアドレスをポインタに代入 cout << "ptr : " << ptr << endl; // cout << "*ptr" << *ptr << endl; // cout << "mc" << mc << endl; cout << "&mc : " << &mc << endl; ptr -> setx(); //ポインタを介してメンバ関数を呼び出す cout << "ptr -> showx() //ポインタを介してメンバ関数を呼び出す----"; ptr -> showx(); //ポインタを介してメンバ関数を呼び出す cout << "mc.showx()---- // オブジェクトmcからメンバ関数を呼び出す----"; mc.showx(); return 0; } |
【結果】
1 2 3 4 5 |
ptr : 0x7ffdd533fdcc &mc : 0x7ffdd533fdcc xの値を入力----100 ptr -> showx() //ポインタを介してメンバ関数を呼び出す----x = 100 mc.showx()---- // オブジェクトmcからメンバ関数を呼び出す----x = 100 |
コンストラクタ:初期値
クラスのインスタンス化した時点で実行してくれる関数を定義します。
それがコンストラクタと呼ばれるものです。
コンストラクタの実装 コンストラクタ名はクラス名と同じにしなければなりません。
main010.cpp
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 |
#include <iostream> using namespace std; class Const{ public: int a; int b; Const(); //コンストラクタの宣言 }; Const:: Const() //コンストラクタの実装 コンストラクタ名はクラス名と同じにしなければなりません。 :a(100), b(150) { cout << "コンストラクタが呼ばれました!" << endl; cout << "データメンバaに値を設定しました。" << endl; } int main() { Const c; //クラスのインスタンス化 cout << "main関数からaの値を読みだします。" << endl; cout << "c.a= " << c.a << endl; cout << "main関数からbの値を読みだします。" << endl; cout << "c.b= " << c.b << endl; return 0; } |
【結果】
1 2 3 4 5 6 |
コンストラクタが呼ばれました! データメンバaに値を設定しました。 main関数からaの値を読みだします。 c.a= 100 main関数からbの値を読みだします。 c.b= 150 |
引数のあるコンストラクタ
コンストラクタによる初期化関数にデフォルト引数を指定することができます。
main011.cpp
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 |
#include <iostream> #include <string> //stringを使うので必要 using namespace std; class Person{ string name; int age; string sex; public: Person(const string &, int, const string); int show(); }; Person::Person(const string &nm, int n, const string s) :name(nm), age(n), sex(s)// データメンバを引数で初期化 { } int Person::show() { cout << "氏名:" << name << endl; cout << "年齢:" << age << "歳" << endl; cout << "性別:" << sex << endl; return 0; } int main() { Person yamada("山田太郎", 26, "m"); Person tanaka("田中花子", 24, "f"); cout << "=================" << endl; yamada.show(); cout << "=================" << endl; tanaka.show(); cout << "=================" << endl; return 0; } |
【結果】
1 2 3 4 5 6 7 8 9 |
================= 氏名:山田太郎 年齢:26歳 性別:m ================= 氏名:田中花子 年齢:24歳 性別:f ================= |
デストラクタ
デストラクタは、スタック上に作成されたオブジェクトが自動的に破棄されるときに呼び出される関数で「クラス破棄時の後処理」です。
デストラクタは以下のようにクラス内で定義します。
1 |
~Person(); // デストラクタ |
main012.cpp
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 |
#include <iostream> #include <string> //stringを使うので必要 using namespace std; class Person{ string name; int age; string sex; public: Person(const string &, int, const string); ~Person(); // デストラクタ int show(); }; Person::Person(const string &nm, int n, const string s) :name(nm), age(n), sex(s)// データメンバを引数で初期化 { } Person::~Person() { cout << "デストラクタの呼び出し" << endl; } int Person::show() { cout << "氏名:" << name << endl; cout << "年齢:" << age << "歳" << endl; cout << "性別:" << sex << endl; return 0; } int main() { Person yamada("山田太郎", 26, "m"); Person tanaka("田中花子", 24, "f"); cout << "=================" << endl; yamada.show(); cout << "=================" << endl; tanaka.show(); cout << "=================" << endl; return 0; } |
【結果】
1 2 3 4 5 6 7 8 9 10 11 |
================= 氏名:山田太郎 年齢:26歳 性別:m ================= 氏名:田中花子 年齢:24歳 性別:f ================= デストラクタの呼び出し デストラクタの呼び出し |
クラスからメンバ関数を
1 2 |
Person yamada("山田太郎", 26, "m"); Person tanaka("田中花子", 24, "f"); |
と2回定義しているため、main()関数を抜けるときにはデストラクタが2回呼び出されています。
ヘッダーファイルとメインファイルを分ける
C++はソースコードから計算実行できる実行形式ファイルに変換する必要があります。これをコンパイルと言います。
コードが長くなるとひとつのファイルに全てを記述するのではなく、機能ごとにファイル分けしておく方が汎用性も高く、管理がしやすいため今回はソースファイルを複数に分割したプログラムを書きました。
■ヘッダーファイルには基本的に関数宣言やクラス定義だけを書いておきます。
sample.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#ifndef _SAMPLE_H_ #define _SAMPLE_H_ // class CSample { public: void set(int num); int get(); private: int m_num; }; #endif //_SAMPLE_H_ |
2重インクルード防止のためにcalc.hに、
1 2 3 4 5 6 |
#ifndef _SAMPLE_H_ #define _SAMPLE_H_ (省略) #endif //_SAMPLE_H_ |
という記述を行いましたが、これは例えばmain.cppでcalc.hをインクルードして、別のファイルでもcalc.hをインクルードして・・・・また別のふぁいるでもcalc.hをインクルードしてとなると、何回もcalc.hの中身をインクルードしていることになり重複してしまいます。
なので、
●#ifndef _SAMPLE_H_が定義されているかを確認
●#define~#endifまでの記述を1回だけしかインクルードしないようにコンパラに指令を与える役割があります。
モジュールファイルとしてsample.cppとし、ここにはクラス内の関数を書きます。冒頭にヘッダーファイルをインクルードして元のクラス定義を参照します。
sample.cpp
1 2 3 4 5 6 7 8 9 10 11 |
#include "sample.h" void CSample::set(int num) { m_num = num; } int CSample::get() { return m_num; } |
後は、main.cppでメインファイルを作成するだけです。
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <iostream> #include "sample.h" using namespace std; int main() { CSample obj; // CSampleクラスのインスタンス化 int num; cout << "整数を入力してください:"; cin >> num; obj.set(num); // CSampleのメンバ変数をセット cout << obj.get() << endl; // メンバ変数の値を出力 return 0; } |
Sample obj;はクラスのインスタンス化を行っている部分です。
obj.set(num)とobj.get()でクラス内のメンバ関数にアクセスしています。
分割コンパイル
今回は以下の3つのファイルがあります。
これらすべてをコンパイルして、さらにそれぞれのリンクで結合する必要があります。
C++のコンパイルはこちらを参考にしました。
まずはmainファイルからオブジェクトファイルを作成します。
1 |
g++ -c main.cpp -o main.o |
そうするとmain.oというオブジェクトファイルが生成されます。
次にsample.cppのオブジェクトファイルを生成します。
1 |
g++ -c sample.cpp -o sample.o |
最後に、2つのオブジェクトファイルをコンパイルしてリンクさせます。
これをリンカーと言います。
1 |
g++ sample.o main.o |
そして、./a.outを実行すれば完成(‘ω’)ノ
1 |
./a.out |
こんな感じですね。
分割ファイルにした場合にコンパイルが面倒ですがMakefileを使って一括でコンパイルすることができます。
クラスの継承
CSampleクラスの機能を保持したまま拡張して使いたい場合は、クラスの継承を行います。(親クラス︓CSample,派⽣クラス,⼦クラス︓NewCSample)
■ヘッダーファイルには基本的に関数宣言やクラス定義だけを書いておきます。
sample.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#ifndef _SAMPLE_H_ #define _SAMPLE_H_ // class CSample { public: void set(int num); int get(); protected: int m_num; }; class NewCSample //子クラス :public CSample //親クラス { public: void set2(int num); }; #endif //_SAMPLE_H_ |
sample.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include "sample.h" void CSample::set(int num) { m_num = num; } int CSample::get() { return m_num; } void NewCSample::set2(int num) { m_num = 2 * num; } |
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <iostream> #include "sample.h" using namespace std; int main() { CSample obj; // CSampleクラスのインスタンス化 int num; cout << "整数を入力してください:"; cin >> num; obj.set(num); // CSampleのメンバ変数をセット cout << obj.get() << endl; // メンバ変数の値を出力 NewCSample obj2; obj2.set2(num); // CSampleのメンバ変数をセット cout << obj2.get() << endl; // メンバ変数の値を出力 return 0; } |
先ほど同様
1 2 3 |
g++ -c main.cpp -o main.o g++ sample.o main.o g++ sample.o main.o |
参考記事
OpenFOAMの簡単なコードを書いてカスタマイズ練習ができる内容を挙げておきます。
C++の基礎を学びたいときにとても参考になる動画を挙げておきます。
OpenFOAMに特化した内容を学びたい場合は以下の記事が参考になります。
参考書
持っている参考書をC++の書籍を載せておきます。
↑こちらは初めにC++を学ぶにはちょうど良い内容かと思います。
実はこちらに書籍の内容がまとめられています。
↑基礎が身に付いたら体系的に学ぶために持っていても良いと思う書籍です。
「現行の規格に準拠したC++プログラムの書き方を徹底的に解説していく」と謳っているように内容も中級者以上を対象にした本格的なC++の書籍です。
実はこちらに本の内容がそのままアップされています。