C++

C++でコメントを書く際は、//を2つ並べます。
Cでコメントを書く際は、/* でコメントを始め、*/までをコメントとしていましたが、そちらも使用可能です。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
これまでストリームやマニピュレータを使用する時は、std::cout あるいは std::endlなどと書いてきました。ところでこのstd::はなんなのでしょうか。

std::は名前空間です。名前空間とはある変数や関数を一つのグループとして扱う機能のことです。
標準入出力ストリームや標準入出力マニピュレータはstd名前空間内に存在する為、std::を付けていたのです。しかし、毎回std::を付けるのは辛いので、std::は省略できます。

ソースファイルのいずれかの場所にusing namespace std;と書いて下さい。直訳で名前空間stdを使用しますですね。

この行がコンパイラに読まれると、同一ソースファイルのそれ以降の行はstd::を付けなくてもストリームやマニピュレータを検出できるようになります。内部的には、識別されない関数を見つけると、std::で補完するといった形です。

問5:using namespace std;を使用して、画面にhelloと出力するプログラムを書きなさい。

名前空間はプログラマが定義することもできますが、作成する機会は少ないのでここでは割愛します。名前空間はクラスよりさらに大きい概念と考えればよいでしょう。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
これまでクラスにはpublic:を指定してきました。しかしここにはprivate:を指定することもできます。

private:を指定すると、その行以下のメンバには、そのクラスのメンバしかアクセスできなくなります。このようなメンバ(クラスに所属する変数や関数)を非公開メンバと言います。
つまりこのままではSecretNumberの値は他のクラスやmain関数から知ることはできません。

ところでアクセス指定子(public:など)は、書いた行以下に範囲が及ぶことを覚えているでしょうか?
つまり、private:の下から更にpublic:と書けば、public:以下の行は公開メンバになるということです。


このように、同じクラスでも公開領域と非公開領域を分けることができるのです。
非公開領域には、ほかのクラスやmain関数から変更されて欲しくない変数を記述するのが主流です。
例えば、ユーザーIDを保持する変数がmain関数から呼び出せるとどうでしょうか?
うっかりプログラマがユーザーIDを変更する関数を書いてしまう(あるいはバグによりそうなってしまう)可能性もあります。このようなヒューマンエラーを回避する為に領域を分ける機能が用意されているのです。

では、private:メンバを参考にしたい時はどうすればよいのでしょうか。
多くのプログラマは、get系の関数をpublic:にて実装してmain関数からはその関数を呼び出します。

private:メンバは、同じクラスからしかアクセスできないのでした。
その為このような関数を作成し、main関数側でSecretNumberの情報が必要な時は、GetSecretNumberを発動します。仮に取得したSecretNumberの値を書き換える処理がmain関数で行われるとしても、GetSecretNumberから返却された値は、本物のSecretNumberのコピーでしかない為、SecretNumberの値が書き換えられることはありません。GetSecretNumberで取得した値は、そのまま捨てても構いません。

では、main関数からGetSecretNumberを使用してSecretNumberを参考してみましょう。

1


最も、今回はSecretNumberを初期化していない為、値は不定なのですが。(環境により不定の値は変わります)
このエントリーをはてなブックマークに追加

前回のポインタ渡しはCの機能でした。そしてポインタ渡しは全く以って正しい手法で、C++でも使用可能です。
しかし、C++には更に安全な参照があります。
参照は、ポインタの構文をより直感的に、かつ安全にしたものと言えます。

参照渡しとは、オブジェクトのアドレスを関数へ渡す手法です。
……ここまではポインタと同じなのですが、構文が違います。
ポインタ渡しでは、オブジェクトのアドレスを受け取る関数の仮引数には型名*を指定しました。
参照渡しでは、オブジェクトのアドレスを受け取る関数の仮引数には型名&を指定します。

EnemyCharacter型のオブジェクトのアドレスを受け取るPowerUP2関数を書きましょう。


参照では、.演算子を使用します。
この構文が非常に重要で、参照では->や*を使用する必要が一切ありません。

参照渡しを行うには、オブジェクトのアドレスを受け取る関数の仮引数に&を指定すればよいだけです。それだけで、値渡しと全く同じように操作を行うことができます。

main関数からPowerUP2関数を呼び出してみます。


main関数側からも、Enemy1オブジェクトを渡す際に、なんらかの演算子を付けていません。
ポインタと違い、&演算子を付ける必要がないのです。
なぜなら、関数側に&が付いている時点でアドレスを関数へ渡すことが明白だからです。
よって、オブジェクト名を指定するだけで、そのオブジェクトのアドレスがコンパイラによって関数へ渡されます。

参照渡しの優れた点は、構文が値渡しとほとんど同じである点です。
違うのは、仮引数を指定するところだけで、C++では仮引数へ&を付けるか否かで値渡しか参照渡しかが決定されます。

main関数からのオブジェクトの渡し方が値渡しと同じな為、関数名を変える必要があります。main関数の処理から見れば、値渡しも参照渡しも同じ構文だからです。

オブジェクトを関数へ渡す時は、是非参照渡しを使用して下さい。煩わしいデストラクタのことを考える必要がなくなるばかりか、値渡しとほとんど同様の構文を使用することで、*を付けるか否かや、->か.かを考える必要がなくなります。
このエントリーをはてなブックマークに追加

関数へオブジェクトを値渡しすると、一時オブジェクトが発生するので、デストラクタが複数回呼び出される問題を前回取り上げました。
この問題を解決する方法は、実はあなたはご存知です。

想4:この問題を解決する方法とは?


そう、ポインタはどこから見ても同じメモリ上のアドレスを表すが故に、オブジェクトのコピーが行われると不都合になるのでした。
であればそのポインタを利用してやればよいのです。
実値ではなく、実値を指すポインタ変数を渡す関数を作成してみましょう。

C++では、一番下の関数PowerUPは、中段のPowerUPと引数が異なるので、違う関数として扱われます。名前が同じで引数が違う関数を作成することを、関数をオーバーロードするとも言います(戻り値が違っても引数が同じであれば同じ関数として扱われ、コンパイルエラーとなります)

新しく作成したPowerUP関数では、オブジェクトを直接受け取らず、オブジェクトへのポインタ変数を受け取ります。ポインタ変数とは、単にアドレスが入った変数のことでした。
つまりこの関数はオブジェクトのアドレスを受け取るということです。
なので、PowerUPを使用する時は、PowerUP(&Enemy1);と書くことができます。

今まで、クラスのメンバーへアクセスする際は、.を使ってきましたが、それはオブジェクトから直接アクセスする場合においてです
オブジェクトのアドレスが格納されたポインタ変数からメンバーへアクセスする時は、->を使います。
これはただの文法規則なので意味は洞察しないで下さい。
オブジェクトから直接アクセス .
オブジェクトのアドレスが格納された、オブジェクト型のポインタ変数からのアクセス ->


また、->(アロー演算子)で実値を表現しているので、オブジェクト型においては*で実値を表現する必要はありません。

アローでの実値の表現(オブジェクト)ep->HP
アスタリスクでの実値の表現(整数)*HP


更に、ポインタによってオブジェクトを遠隔で直接操作しているのでreturnで値を返す必要はありません。



これでエラー落ちすることはなくなりました。
なぜならデストラクタが呼び出されないからです。
再びiostreamをEnemyCharacter.hにインクルードし、デストラクタが呼び出されているか確認してみましょう。

159

デストラクタは呼ばれませんでした! 関数も正しく動作しています。
このエントリーをはてなブックマークに追加

ところで以前、コンストラクタで実行時メモリ確保を行い、デストラクタで破棄する手法をご紹介しましたよね。実はその手法には欠点があるのです。


コンストラクタでメモリを実行時に確保し、デストラクタで破棄しています。
このソースコードは一見正しく見えますが、実行すると例外が発生し、プログラムが落ちます。

2枚目のソースファイルを見て下さい。生成したEnemy1オブジェクトを関数へ渡していますね。
そうです。値渡しです。つまりこのオブジェクトEnemy1は、PowerUP関数へオブジェクトのコピーが渡され、returnする時に破棄され、ここでデストラクタが呼び出されます

その後、戻り値を戻す際にも一瞬オブジェクトのコピーが生成され、そこでもMPが指すメモリ上のアドレスが解放されます。同じアドレスを2回deleteするので、例外が発生するのです。
戻り値を戻す際になぜコピーが発生するかというと、戻り値の返却も値渡しだからです。
158

int* MP;はアドレス変数です。newで領域を確保し、その領域の場所をMPへ格納しているだけなので、MPをコピーしても新しく領域が確保される訳ではありません。値渡しの際に作成されるコピーも、戻り値を戻す際に一瞬作成されるコピーも、main関数で定義されたEnemy1オブジェクトも、全て同じ領域を指します。
なので、main関数が終了すると、更にデストラクタでメモリ上の領域を解放するのです。

この問題を解決する最も簡単な手法を次回、ご紹介致します。
このエントリーをはてなブックマークに追加

Cでは実行時のメモリの確保はmallocとfreeを使っていました。
しかしC++ではmalloc と freeの代わりに、newdeleteを使用できます。

newdeleteはmallocより洗練され、より直感的かつ簡単にメモリを確保できるようになりました。
C++の構文なので、特別なにかをインクルードする必要はなく、型をキャストする必要もなく、割り当てるメモリのバイト数を明示的に指定する必要もありません。


このプログラムは、コンパイル時にint型の領域EXEMemoryが確保されるのではなく、実行時の処理がnewへ移った時にint型のEXEMemory領域が確保されます。

*EXEMemoryは EXEMemoryがアドレス変数なので、*で実際のデータへアクセスします。
最後にdeleteでEXEMemoryを解放しています。
deleteへは、解放すべき領域のアドレスを渡します。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
newで実行時にメモリを確保する際、変数を初期化することもできます。

確保する領域の型の後ろに()を付け、対応するデータを渡して下さい。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
また、newで動的にオブジェクトを生成することもできます。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
newで配列を確保することもできます。しかし、初期化することはできません。

初期化はできないので、手作業で必要な値を入れる必要があります(forでループさせるなど)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
本講義で作成したプログラムは、可読性を高める為に確保した値を確認する機構を用意していません。値を確認したい方は、各自iostreamをインクルードし、std::coutで確かめてみて下さい。
このエントリーをはてなブックマークに追加

↑このページのトップヘ