オブジェクト指向について語ったところで、他にも幾つか知っておく事が有りそうなので、ここで、まとめて語ってしまおう。というか、今後も基本的な事項で、記述しなければならない事項が出来たらここに追加していく。逆に、今後の説明の要所で参照させるので、とりあえず、流して読んでから次に行って欲しい。
- クラスとインスタンス
クラスというのがオブジェクト指向の共通用語になっているが、クラス=オブジェクトかと言うと、ちょっと違う。そもそも、何故クラスやインスタンスと言うややこしい呼び方をするのかは不明であるが、クラス=「入出力関数付き構造体」,インスタンス=「入出力関数付き構造体の生成された実体」と言うのは呼びづらいという意見もあるな…。
クラスと言うのは、C言語の構造体と同じ様に、単なる宣言であって、実行開始時点でメモリ上に存在しない場合が多い。(静的に宣言されている場合は、存在する,静的に宣言出来ない場合もあるので注意!) 構造体と決定的に違うのは、メンバ関数と言う入出力関数を実装する事である。実は、このメンバ関数の実体は、クラスがインスタンス化されようが、されまいが、実行開始時には実行ファイル上に存在する。間違って欲しくないのは、このメンバ関数は、1つのクラス宣言に実体は1つだけ存在していて、1つのクラスから3個のインスタンスを生成したとしても、存在は1個だけと言う事である。(要は、そのアクセス先を適宜切り替えて、生成されたインスタンスにアクセスしている訳である)
ここまで書くと、インスタンスについても大体察しが付くと思うが、インスタンスはクラスを実体化した時の実体と考えれば良い。
- 動的インスタンスと静的インスタンス
基本的な事なのだが、書いておかないと忘れてしまいそうなので、書いておくのだ。
class ClassA{
…
public:
ClassA() ;
~ClassA() ;
void test(int) ;
} ;
が有った時、
ClassA *ca ;
の様に、クラスへのポインタで宣言すると、この時点で、インスタンス自体は存在しないのだ。この状態では、インスタンスへのアドレスを入れる箱が1個用意されたと思えば良い。これを、クラスの実体にする(インスタンス化)するには、
ca=new ClassA() ;
とするのだが、まさにこの時、コンストラクタ(ClassA::ClassA())が呼ばれている事に注意しなければならない。もし、コンストラクタが引数を持っているのであれば、ここで指定しなければならないのである。この時、C++のランタイムは、このクラスを使用出来るメモリの確保を行い、インスタンスへのポインタを、caに返す訳だ。さて、この場合、ここで生成したインスタンスは、ユーザが解放しない限り、いつまでもメモリ上に居続ける(Javaの様なガーベージコレクタは居ない)ので、使い終わった時点で、必ず、
delete ca ;
を行うのである。実はここで、デストラクタ(ClassA::~ClassA())が呼ばれているのだ。この様に、クラスをポインタ宣言して、new〜deleteで制御する方法を、動的インスタンスと呼んでおこう。(実際、こう呼ばれているかは不明)
対して、
ClassA ca ;
の様に宣言された場合は、静的インスタンスと呼ぶ。この場合、コンパイルされた時点から、このクラスが動作する領域は確保されていて、実行を開始された時点から、固定領域として実在する訳である。また、プログラムを終了させれば、一緒に消滅してしまうのである。この場合、コンストラクタは、プログラムの実行開始時に呼ばれ、デストラクタは、プログラムの終了時に呼ばれる事になる。即ち、newもdeleteも要らないのである。こちらの方が、安定するし、プログラムもスッキリする様に思えるのだが、自分の知る限りでは、コンストラクタに引数が指定できないのと、プログラムの開始から終わりまで、メモリを占有し続けるので、大きい変数領域を使用する様なクラスは、プログラムの動作自体の妨げになるかも知れない。
また、注意する点は、メンバ関数の呼び出し方の違いである。動的インスタンスの場合、メンバ関数の呼び出し方は、
ca->test(100) ;
の様に'->'を挟んで呼び出すが、静的インスタンスの場合,
ca.test(100) ;
とする。これは、C言語でも、構造体を動的に使う場合と、静的に使う場合の違いと同様の記述方法なので、構造体を沢山使っていた人には馴染みのある表現だと思う。
- 継承
継承についての書式や機能は大筋で理解していると考えて記述する。
要するに、『既に在るクラスを利用して、何か別のクラスを造る』場合に使うのだ。
決まっている訳ではないが、クラスの継承を考える時、目安にしたい事項がある。
『is a関係』と、『has a』関係である。例えば、『コウモリ:bat』と言うクラスを作成する際、元々ある『ほ乳類:mammal』と言うクラスを継承する場合は、コウモリはほ乳類である(a bat is a mammal)の図式が成り立つので、
class Bat : public Mamal
の様に、継承で行うのが習わしらしい。
また、『コウモリ』というクラスが、『翼:wing』と言うクラスを継承(この場合、厳密には継承と言わないのかも知れないが)する場合、(a bat has a wing)の図式が成り立つので、
class Bat : public Mamal
{
private:
Wing wings ;
の様に、プロパティ(クラスの変数として持つ)として宣言するのが習わしらしい。
実は、今まで自分の場合は、この辺の習わしを無視して適当に継承してきたのだが、これからは、ちと気を付けてみようと思う。
- 多重継承(なぜ、多重継承は危険か?)
実は、これを書いている途中,Javaについて、講義しなくてはならなくなってしまい、そんなこんあで、大昔にちらっと触ったJavaについて、紐解かなければならなくなった訳だ。で、Javaでは多重継承が出来ないという事の説明に、C++では許容される多重継承を用いようと思う訳だ。上の例を「継承」して説明すれば、『ほ乳類:mamal』を継承して、『コウモリ:bat』というクラスを造ったが、「飛ぶ」という機能が欲しくて、『鳥類:bird』というクラスを多重継承してしまったとしよう。
class Bat : public Mamal,public Bird
{
private:
Wing wings ;
この場合、出来上がったコウモリは、卵を産んだり、クチバシが有ったりするのだ。一挙に変な動物になってしまうだろう?即ち、多重継承すると、色々余分な機能まで継承してしまう場合が有るので、バグの原因になりやすいのである。
- リソース
Windowsアプリのプログラムに触れれば、必ずこの「リソース」というものに直面する事になる。最初は、何をやっているのか全然分からないのが、これだと思う。リソースというと、システムリソース,メモリリソース等の用語が有って、これも、理解の妨げになっているのだ。ここで言うリソースは、システムリソースにあたる部分だと思うのだが、正直言うと、自分自身も、まだ完全に理解している訳ではないのだ。
簡単に言うと、Windowsが予め用意している機能を、簡単に利用出来る様に、インタフェースを公開している部分なのだ。(例えば、アプリのアイコン,ビットマップ,メニューバーの部分,及びダイヤログボックス等が、これに当たる)従って、Windowsのアプリを作成する際には、「このアイコンを使う」,「この様なメニュー構成にする」,「この様なダイヤログボックスを定義する」と言った要求を、一種のフォーマットとして、リソースプログラム(.rcのソースファイル)に定義して使う。
さて、このリソース定義をどの様に、実行ファイルにリンクさせたら良いのだろうか?というと、まず、実行ファイル(C++で記述した部分)をコンパイル,リンクした上で、最後にリソースコンパイラ(BC++では、BRC32.EXE)にてリソース定義をコンパイルして、実行ファイルにリンクするのである。実際の使い方については、追々説明出来ると思うが、この辺の理屈について、理解しておいて欲しい。
- MAKEFILEの書き方(分割コンパイル)
Windowsアプリは、規模が大きくなれば成る程、分割コンパイルの必要性が出てくる。また、生成されたEXEファイルに、リソースコンパイラを使って、リソースをリンクさせる必要が出てくるので、MAKEFILEによって、そのアプリ プロジェクト全体の生成方法を管理した方が効果的である。例えば、バッチファイル等を作成して行う方法も有るが、MAKEFILEを使用すると、もう一つ利点が有って、あるファイルの構成ファイルが更新されたものだけを処理対象とする。という動作をする。これだと、修正したソースファイルが関連する部分のみが再コンパイルされ、更新されるので、コンパイルの時間を短縮出来、効率的なコンパイルを実施できるのだ。小規模なアプリでは、この差は大したものでは無いが、規模が大きくなればなるほど、MAKEFILEのありがたみは確実に見えてくるのである。また、リンクの際、全てのオブジェクトを列挙しなければならないので、リンクするオブジェクトが大量になった場合,バッチファイルでは書ききれない場合にも、MAKEFILEを使えば、数個ずつのオブジェクトファイルを一つの名前で定義し、最終的にリンカに渡すのは、その名前の列挙で済むという利点も有る。
尚、コンパイルの実施は、MAKEFILEの有るフォルダにて、MAKEを実行するだけである。
例として、以下に示す様なファイルをコンパイル/リンクする為のMAKEFILEとBATCHファイルの例を示す。
- TEST.cpp … アプリメインプログラム
- TOBJ1.cpp … アプリのクラス定義ファイル
- TOBJ1.h … アプリのクラス定義ファイル(インクルードファイル)
- TEST.rc … アプリのリソース定義ファイル
- RESOURCE.h … アプリのリソース,メッセージID定義ファイル
- TEST.ico … アプリのアイコン
MAKEFILE
TEST.EXE: TEST.obj TOBJ1.obj TEST.rc RESOURCE.h TEST.ico
BCC32 -W -eTEST TEST.obj TOBJ1.obj shlwapi.lib
BRC32 TEST.rc
TEST.obj: test.cpp tobj1.h resource.h
BCC32 -c -W test.cpp
TOBJ1.obj: tobj1.cpp tobj1.h
BCC32 -c -W tobj1.cpp
|
書式の説明
生成目的のファイル名: 構成ファイル名を列挙
実行するコンパイルコマンドを1段下げて記述
(各コマンドのオプションについては、コンパイラのヘルプを参照)
注)リソースコンパイラBRC32は、実行ファイルが生成された後に実行して、リンクさせる。
例では、実行ファイル名(TEST.EXE)と同名(TEST.rc)の為、
リンクさせる実行ファイル名を指定していないが、違う場合は、オプションにて指定する。
※shlwapi.libは、Windowsのアプリ生成時にリンクさせるライブラリである。
|
TESTMAKE.BAT
BCC32 -c -W test.cpp
BCC32 -c -W tobj1.cpp
BCC32 -W -eTEST TEST.obj TOBJ1.obj shlwapi.lib
BRC32 TEST.rc
|
見た目、BATCHファイルの方が簡単だが、TEST.icoが変わっただけでも、全てのコンパイルをやり直す事になる。ここに書いた例では、アプリの規模が小規模であり、コンパイルの時間が気にならなければ、好みでバッチファイルを使用しても構わない。MAKEFILEの制御用記述を使用していないので、複雑ではあるが、この記述を使用すれば、もっと簡単に記述する事も出来る。
- MessageBoxの使い方
割と、頻繁に使われるのが、このMessageBoxである。簡単に呼び出せるので、ヘルプのバージョン表示や、ちょっとしたデバッグ時などにも、活用出来る便利な機能である。引数は,
int MessageBox(
HWND hWnd, … 親Windowのハンドル
LPCTSTR lpText, … メッセージボックス内に表示するメッセージ文字列
LPCTSTR lpCaption, … メッセージボックスのタイトルバーに表示する文字列
UINT uType … メッセージボックスのスタイル
);
スタイルには、
- MB_OK(メッセージボックスにOKのボタンを表示)
- MB_OKCANCEL(メッセージボックスにOKとCANCELのボタンを表示)
- MB_YESNO(メッセージボックスにYESとNOのボタンを表示)
等が選択できる。また、どのボタンが押されたかについては、MessageBoxの戻り値にて判断が出来るのである。
- TextOutの使い方
この関数は、計算結果の表示等に使用出来るので、色々と使い道が有る。引数は、
BOOL TextOut(
HDC hdc, …ディスプレイドライバのデバイスコンテキスト
int nXStart, …文字表示開始のX座標(Windowフレーム内の左上が(0,0)となる)
int nYStart, …文字表示開始のY座標(Windowフレーム内の左上が(0,0)となる)
LPCTSTR lpString,…表示する文字列
int cbString …文字列のレングス
);
である、第二引数以降は、説明の必要は無いと思うが、第一引数のhdcがどうも分からない。BeginPaint()関数と組で使う場合は問題無いが、その他のイベントが発生した場合の事を考えて、PAINTSTRUCT ps ;の宣言は、グローバルにしておいた方が良いかも知れない。そうすれば、一旦BeginPaint()関数をコールしておけば、いつでもTextOut()が使える。尚、数値を表示したい場合には、wprintf()関数を利用して、数値を文字列に変換しておけば良い。
|