C++

デストラクタ関数はコンストラクタ関数と対になる存在です。
デストラクタはオブジェクトが破棄された時、自動的に呼び出されます。

ローカルオブジェクトが破棄される時はオブジェクトを保持する関数がreturnした時です。
グローバルオブジェクトが破棄される時は、そのプログラムの実行が終了した時です。

デストラクタを宣言、定義する方法は、コンストラクタの書式に加え、関数の先頭に~を付けることです。
また、戻り値と引数を持たせることはできません。


このクラスから生成されたオブジェクトは、破棄される際にnameとHPをそれぞれヌル文字と0へ戻します。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
上の例はあまり実用的ではありません。ですが、コンストラクタで実行時メモリ確保を行い(malloc)デストラクタで実行時メモリ解放(free)を行うのは大変実用的です。
デストラクタの主な使い方はこれです。しかし、実行時メモリ確保は頻繁に使用するテクニックではない為、試したい方はもう一度mallocとfreeの使い方を復習して下さい。
【F限目】The Final Lesson※一番下です。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
デストラクタには副作用があります。厳密には副作用ではなく、正常な挙動なのですが、人間から見るとどうしても直感に反するというか、つまるところ都合の悪い挙動を起こすので副作用と呼ばれています。

ところでオブジェクトは変数のような存在とお話しました。
そのことを確認する為に、オブジェクトを引数として受け取り、オブジェクトを返却する関数を書いてみましょう。


PowerUP関数は、EnemyCharacterオブジェクトを受け取り、そのHPを+100して返却します。
EnemyCharacterクラスの下へ書いている点にご注目下さい。EnemyCharacterの上に書くと、コンパイラがEnemyCharacterの存在を把握せずにPowerUP関数を解読しようとする為、コンパイルエラーとなります。

では、PowerUP関数をmain関数から使用してみましょう。

156


Enemy1をPowerUP関数へ渡し、戻ってきたオブジェクトをそのままEnemy1へ代入しています。
これにより、魔王ラストボスのHPは100上昇しました。

さて、一見問題なく動いているように見えるこのコード、実はバグの温床があるのです。
それは……デストラクタ関数です。

想3:PowerUPへオブジェクトを渡した時、デストラクタは呼び出されるでしょうか?


デストラクタが2回呼び出されるとはどういうことでしょうか。
実は、関数へ変数やオブジェクトを渡す時、値渡しが行われます。
値渡しとは、渡す変数やオブジェクトと全く同じコピー変数あるいはコピーオブジェクトを作成し、関数内ではそれを操作する手法のことです。

つまり、PowerUP関数の終了時に、Enemy1のコピーオブジェクトのデストラクタが発動するのです。実際にはEnemy1はmain関数内でまだ使用しているにもかかわらず。

最も、コピーオブジェクトのデストラクタが発動しようが、main関数側のオブジェクトには関係ありません。
しかし、実はまだデストラクタは発動するのです。
なんと、C++のコンパイラは、変数やオブジェクトを戻す際にも、一時オブジェクトを作成するのです。これにより、戻り値としてオブジェクトを戻す際にも一時オブジェクトが作成され、すぐ破棄されるので、デストラクタが呼び出されます。
戻り値を戻す際になぜコピーが発生するかというと、戻り値の返却も値渡しだからです。まあこれもmain関数側のオブジェクトには関係ない一時オブジェクトなのでよいのですが。

まとめると、関数の終了時と、関数の返却時にコピーオブジェクトが生成され、関数の終了時と関数の返却時にコピーオブジェクトが破棄されるので、PowerUP関数へオブジェクトを渡した場合、デストラクタは意図せず(?)2回呼び出されるのです。

実際に確認してみましょう。
iostreamをEnemyCharacter.h側にもインクルードし、デストラクタの処理として文字列を画面へ出力します。

157
このエントリーをはてなブックマークに追加

コンストラクタの話をする前に、クラスを作成する練習をしましょう。
今回作成するクラスは敵キャラを生成するクラスです。
前回作成したCalculationクラスは削除して構いません。(右クリック→削除(V)→削除(D))
ソリューション エクスプローラーを右クリックし、追加→新しい項目 を選んで下さい。
146


今回はEnemyCharacter.hを作成します。なお、.hを付けなくても拡張子は自動的に.hになります。
152


敵キャラなので、名前とステータスを作成しましょうか。


さてこの時点でお気づきの方もいるかもしれませんが、
この敵キャラクラスから実際の敵キャラを生成するとどうなるでしょうか?

153


そうです。全く同じキャラが生成されるのです。しかも名前もHPもありません。
では、Cスタイルで初期化しておきましょう。


……ちょっと待って下さい。これでは魔王ラストボスが2体生成されるだけです。
154


なんというか、全然柔軟じゃないですよね。
宣言したオブジェクト毎に違う値で初期化できないものでしょうか……?

その機能を叶えてくれるのがコンストラクタ関数です。
コンストラクタ関数は、オブジェクトを生成する際に1度だけ自動実行される関数です。

コンストラクタ関数を作成するには、クラス名と同じ関数を宣言、定義します。
この際戻り値を持たせることはできません。そもそも必要ありません。


strcpyは文字列定数を文字配列へ代入してくれる関数です。cstringをインクルードして使います。また、仮引数に=1を付けていますが、これはデフォルト仮引数と言い、引数が指定されなかった時にhの値が1となることを意味します。


オブジェクトを生成する時に、コンストラクタ関数へ渡す引数も宣言します。
EnemyCharacter Enemy1("魔王ラストボス", 5000);
EnemyCharacter Enemy2("その辺の魔物", 20);

下記のサンプルコードを実行し、各オブジェクトが違うデータを保持していることを確認して下さい。

155


クラスには2種類の使い方があります。
単独のオブジェクトを生成し、関数を動かすのが主である管理クラスと
複数のオブジェクトを生成し、データを保持するのが主であるデータクラスです。
このエントリーをはてなブックマークに追加

これからあなたは、今までの手続き型プログラミング、関数プログラミングに加え、オブジェクト指向プログラミングというプログラミングスタイルを習得することになります。

ボクはオブジェクト指向のことを、機能分割型プログラミングと表現します。
ボクが勝手にそう呼んでいるだけです。

オブジェクト指向では、専門的な能力を持ったクラスをいくつか作ります。各クラスは得意分野で互いをカバーしあい、1つの処理を表現します。クラスとは、単に機能のことです。

例えば、計算が得意な計算クラス、データの転送が得意な転送クラスと、クラスを分け、処理内容に応じて適したクラスを使用します。また、クラスには変数と関数を記述します。
(構造体は変数のみを記述するのでした、クラスは構造体のパワーアップ版です)
149

こうすることで、計算ミスが発生した時は計算クラスを中心にデバッグすればよいことになります。
大規模なソフトウェアでは、どこでバグが発生したのかが分かりにくくなりがちですが、オブジェクト指向スタイルではその限りではありません。

C++ではオブジェクト指向プログラミングをサポートしています。
C言語では考えられない膨大なコードをたやすく扱えるのがC++の魅力です。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
それでは、計算を行うのが得意な計算クラスを作成してみましょう。
ソリューション エクスプローラーを右クリックし、追加→新しい項目 を選んで下さい。
146


ヘッダーファイル(.h)を選択し、名前を付けましょう。なんでも良いのですが、今回はCalculationと名付けておきました。
右下の追加を押してヘッダーファイルを追加して下さい。
147


さあいよいよクラスを作りますよ。クラスを作成する時はヘッダーファイルへ記述していきます。
とりあえず計算結果を格納する変数も宣言してみました。

これでCalculationクラスが作成されました。と言ってもまだなんの機能も持っていませんが。

クラスを作成する時はclassキーワードを使います。classキーワードの後にクラス名を付け、{};で囲みます。{};で囲まれた範囲がそのクラスの有効範囲です。
public:の意味は、公開です。つまり、public:以下に書かれた変数と関数はどこからでも参照できるということです。
148


非公開な変数や関数を作ることもできるのですが、ややこしいので最初は全て公開で作ります。その方がコードもシンプルで分かりやすいです。

それでは、クラスへ関数を追加しましょう。
クラスに記述する時も通常の関数を記述する時と全く同じです。


add関数は、整数を2つ受け取り、足してAnswerへ格納します。

これでCalculationクラスが完成しました!

今後、プログラム上で足し算が必要な時は、Calculationクラスへ任せればよいことになります。
それでは早速main関数からCalculationを呼び出して使用してみましょう。ちょうど、魔法使いが幻獣を呼び出して使役する感覚です。


hファイルはインクルードして使用するのでした。
こうすることで、Source.cpp上でCalculationを使用できるようになります。

さていよいよCalculationを呼び出しますよ。


Calculationは抽象的な存在なのです。なのでそのままでは使えません。
変数の宣言と同様の構文で実態を生成する必要があります。

Calculation Date1; はCalculation型のオブジェクトです。
オブジェクトとは、関数を内包する変数です。

ちょうど、int Number1; はint型の変数 というのに似ていますね。

それでは、Date1に計算を行ってもらいましょう。

150


オブジェクトが自身内部の変数や関数へアクセスする時は.演算子を使用してその名を指定します。
.addは引数を2つ受け取り、結果をAnswerへ格納するので、Date1.Answerで計算結果を取り出すことができるのです。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
さてここまで読んであなたはこう思ったのではないですか?
オブジェクト指向って……必要? と。
この程度の処理であればmain関数へ記述しても不都合はないでしょう。

しかし、オブジェクト指向の目的は膨大なコードを扱うことにあります。
例えばadd関数の処理が数千行に及ぶ場合を想像して下さい。変数もAnswerだけでなく、Avarage(平均)や、Absolute(絶対値)なども保持している時、果たしてmain関数だけで操作できるでしょうか?

これまでのプログラミングスタイルでは、main関数に実際の処理を記述してきました。
しかしC++ではmain関数はクラスを管理し、指示を出す関数です。
実際の処理は下請けであるクラス達が行います。
151
このエントリーをはてなブックマークに追加

C++とは、Cのスーパーセットです。
Cで出来ることの99%はC++でもできます。
CのソースコードをC++へコピペしても、修正作業はほとんど発生しません。
稀に発生することもあります。

本講義ではC言語の範囲の解説は致しかねます。
C言語を理解していない方は、まずC言語を学習して下さい。
古代魔術C(C言語講座)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
それでは最初の講義なので、標準入出力を学習しましょう。
次のソースコードはC++流のhello worldです。

140


std::cout はストリームを表します。ストリームとは、物理デバイスを隠蔽し、一意の論理デバイスを作成する機能のことです。

141


つまり、ディスプレイへデータを出力する時と、プリンタへデータを出力する時に記述する構文は同じということです。これは想像以上に便利で、std::coutは様々な実用的なアプリケーションで使用されています。また、ストリームの標準出力先はディスプレイです。

ストリームへデータを渡す時は、シフト演算子 << を使用します。

std::endlは単に改行を意味するマニピュレータです。
ストリームではありません。
C++のストリームは、std::cout std::cin std::cerr std::clogのみです。
他のstd::XXXXの構文は、マニピュレータと呼び、ストリームを補佐する目的で作成された関数です。
std::endlは単に改行を出力します。

ストリームとマニピュレータを使用する時はiostreamをインクルードして下さい。

問1:次の実行結果となるプログラムを書きなさい。
142



もちろん今まで通り変数の値を表示することもできます。
printfを使った時と比べて、データの流れが分かりやすくなっていますね。

143

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
では続いて、標準入力(キーボード)からデータの入力を受け付けるプログラムを書いてみましょう。

std::cinはストリームへの入力を受け付け、ストリームから変数へデータを届けます。

144


ストリームからデータを渡す時は、シフト演算子 >> を使用します。
このプログラムを実行すると、Numberの値は入力された整数値となります。

問2:上記のプログラムを改造して変数Numberの値を確認できるようにしなさい。

このエントリーをはてなブックマークに追加

Temp[10] = {}; ←全て0で初期化
Temp[10] = { 1 }; ←全て1で初期化

ちなみにこれは代入の際には使えないので注意(初期化時のみ有効)
このエントリーをはてなブックマークに追加

↑このページのトップヘ