C++でのスコープ、グローバルスコープ、クラスのポインタ、配列、new演算子、delete演算子 —勉強しなおしの記録というか学習ノート-12日目-
Unreal Engin4すごい!(語彙力)ってなったので、少しいじりつつ、むかーしかじった事あるC,C++の学びなおしの学習ノート。この連載記事の詳しい趣旨と注意事項は1日目をご覧ください。
目次
クラスのスコープ
変数や関数についての『スコープ』は、Cの方の以前の記事で、『メモリ領域(記憶領域)』と、『記憶クラス指定子』と共にご紹介したと思います。
クラスも、変数や関数と同じく、「呼び出されたその関数なりクラスなりが終了した時に、消滅する。」というのが、基本となります。
クラスには生成と消滅を確かめる方法がありましたね。 『コンストラクタ』、『デストラクタ』 です。
前回の記事のソースコードを、生成と消滅のタイミングを確かめるために少し改変したものを使って、クラスのスコープを確認してみようと思います。
#include <iostream>
using namespace std;
// 定数
const double PI = 3.14;
// プロトタイプ宣言
void func1();
/**************************
* 円柱クラス
*/
class Column {
private:
/* メンバ変数の宣言 */
int x; // x座標
int y; // y座標
int z; // z座標
double radius; // 半径
double height; // 高さ
string color; // 色
double volume; // 体積
void set_volume();//勝手に弄られたら困るのでprivateに
public:
/* メンバ関数の宣言 */
//コンストラクタ
Column(int _x, int _y, int _z, double _radius, double _height, string _color);
// デストラクタ
~Column();
void set_position(int _x, int _y, int _z);
void set_radius(double _radius);
void set_height(double _height);
void set_color(string _color);
int get_x();
int get_y();
int get_z();
double get_radius();
double get_height();
string get_color();
void info_status();
};
int main() {
cout << "main関数です//////////////" << endl;
func1();
cout << "main関数です//////////////" << endl;
return 0;
}
void func1() {
cout << "func1関数が呼び出されました******************" << endl;
Column clm(10, 2, 0, 5.4, 25.6, "red");
cout << "func1関数を終わります************************" << endl;
}
/*Columnクラスのメンバ関数の定義********/
// コンストラクタの定義
Column::Column(int _x, int _y, int _z, double _radius, double _height, string _color) {
cout << "円柱を一つ建設しました。-----------------------" << endl;
// インスタンスの初期化
x = _x; // x座標
y = _y; // y座標
z = _z; // z座標
radius = _radius; // 半径
height = _height; // 高さ
color = _color; // 色
volume = radius * radius * PI * height;// 体積
}
// デストラクタの定義
Column::~Column() {
cout << "円柱を一つ破壊しました。--------------------------" << endl;
}
// X,Y,Z座標を変更するセッター
void Column::set_position(int _x, int _y, int _z) {
x = _x;
y = _y;
z = _z;
}
// 半径を変更するセッター
void Column::set_radius(double _radius) {
radius = _radius;
set_volume();//一緒に体積も変更
}
// 高さを変更するセッター
void Column::set_height(double _height) {
height = _height;
set_volume();//一緒に体積も変更
}
// 色を変更するセッター
void Column::set_color(string _color) {
color = _color;
}
// Xのゲッター
int Column::get_x() {
return x;
}
// Yのゲッター
int Column::get_y() {
return y;
}
// Zのゲッター
int Column::get_z() {
return z;
}
// 半径の値のゲッター
double Column::get_radius() {
return radius;
}
// 高さの値のゲッター
double Column::get_height() {
return height;
}
// 色の値のゲッター
string Column::get_color() {
return color;
}
// 半径か高さが変更された時に呼び出される体積のセッター
void Column::set_volume() {
volume = radius * radius * PI * height;// 体積
}
void Column::info_status() {
cout << "この" << color << "の円柱は(" << x << ", " << y << ", " << z << ")の位置にあり、\n"
<< "半径:" << radius << ", 高さ:" << height << ", なので、体積:" << volume << endl;
}
実行結果↓
main関数です////////////// // 1
func1関数が呼び出されました****************** // 2
円柱を一つ建設しました。----------------------- // 3
func1関数を終わります************************ // 4
円柱を一つ破壊しました。--------------------------
main関数です////////////// // 5
実行結果に、番号をふってみました。それに対応する該当の箇所だけソースコードから抜粋します。
int main() {
cout << "main関数です//////////////" << endl; // 1
func1();
cout << "main関数です//////////////" << endl; // 5
return 0;
}
void func1() {
cout << "func1関数が呼び出されました******************" << endl; // 2
Column clm(10, 2, 0, 5.4, 25.6, "red"); // 3
cout << "func1関数を終わります************************" << endl; // 4
}
/*Columnクラスのメンバ関数の定義********/
// コンストラクタの定義
Column::Column(int _x, int _y, int _z, double _radius, double _height, string _color) {
cout << "円柱を一つ建設しました。-----------------------" << endl; // 3
// インスタンスの初期化
/*
略
*/
}
// デストラクタの定義
Column::~Column() {
cout << "円柱を一つ破壊しました。--------------------------" << endl; // 4
}
- main関数が始まりました。
- main関数内で呼び出されたfunc1関数が始まりました。
- func1関数で呼び出された新しくColumnクラスのインスタンス”clm”が生成されたので、自動的にコンストラクタが呼び出されました。
- func1関数が終わりました。Columnクラスのインスタンス”clm”も、呼び出されたfunc1関数を抜けるので同時に消滅します。また、それに伴い自動的にデストラクタが呼び出されました。
- 再びmain関数に戻ってきました。
という感じになります。
おまけ:C++での変数のスコープ操作 –同名のグローバル変数・ローカル変数がぶつかった場合の、”::”を利用した参照方法
Cでもあった概念ですが、「その関数の場所にあるローカル変数と、その同名のグローバル変数がある場合に、変数の有効範囲(スコープ)によって、どちらの方が参照されるのか決定される。」ということがあったのを覚えておいででしょうか?
関連した以前の記事での説明箇所:変数の有効範囲(スコープ)
実は、C++では、名前空間内で定義済みのオブジェクトへのアクセスや、クラスのメンバにアクセスする時に使う、『”::”』を使って、「その関数内に同名のローカル・グローバル変数がある場合、そのどちらを選択するのか指定し参照する」ということが可能になります!
#include <iostream>
using namespace std;
// 定数
const double PI = 3.14;
// プロトタイプ宣言
void func1();
int main() {
int PI = 3;
func1();
cout << "main関数内の「PI」の値: " << PI << endl;
cout << "main関数内の「::PI」の値: " << ::PI << endl;
return 0;
}
void func1() {
cout << "func1関数内の「PI」の値: " << PI << endl;
}
実行結果↓
func1関数内の「PI」の値: 3.14
main関数内の「PI」の値: 3
main関数内の「::PI」の値: 3.14
グローバル変数なのでfunc1関数からアクセスできたのには疑問は特に持たれない方思われます。
問題は、main関数ですね。グローバル変数と同名の、関数内で宣言されているローカル変数”PI”。これがあるので、何もつけない”PI”は、ローカル変数の方にアクセスするのですが、『”::”』を付けた”::PI”の方は、グローバル変数の方のPIにアクセスしています。
実はこれ、名称がついていて、『グローバルスコープ解決演算子』と言います。普通の『スコープ解決演算子』と違う点は、『”::”』の前に何もクラスも何も指定しないという点です。
::変数
クラスの配列
クラスの配列を作れるので(正確にはインスタンスの配列ですが)、作り方を書いてみます。
前項の『円柱クラス』が記述してあるプログラムのmain関数部分だけを書き換えたものを用意します。
int main() {
// まとめてインスタンスを3つ生成
Column obj[3] = {
{8, 0, 0, 15.3, 34.6, "blue"},
{10, 30, 6, 44.3, 24.3, "red"},
{20, 2, 10, 78.3, 14.1, "green"}
};
for (int i = 0; i < 3; i++) {
obj[i].info_status();
}
return 0;
}
実行結果↓
円柱を一つ建設しました。-----------------------
円柱を一つ建設しました。-----------------------
円柱を一つ建設しました。-----------------------
このblueの円柱は(8, 0, 0)の位置にあり、
半径:15.3, 高さ:34.6, なので、体積:25432.5
このredの円柱は(10, 30, 6)の位置にあり、
半径:44.3, 高さ:24.3, なので、体積:149742
このgreenの円柱は(20, 2, 10)の位置にあり、
半径:78.3, 高さ:14.1, なので、体積:271439
円柱を一つ破壊しました。--------------------------
円柱を一つ破壊しました。--------------------------
円柱を一つ破壊しました。--------------------------
初期化時にまとめて引数を指定するやり方は、構造体の時と似ていますね。
クラスのポインタ
前回もちらっとご紹介しましたが、クラスのポインタを扱うことも出来るんです。その場合、メンバにアクセスする時に『アロー演算子(“->”)』を使います。
こちらもmain関数内だけ抜粋して書いてみます。
int main() {
Column* pC = 0; // Columnクラス用ポインタの初期化
Column obj(8, 0, 0, 15.3, 34.6, "blue");
pC = &obj;
pC->info_status();
return 0;
}
実行結果↓
円柱を一つ建設しました。-----------------------
このblueの円柱は(8, 0, 0)の位置にあり、
半径:15.3, 高さ:34.6, なので、体積:25432.5
円柱を一つ破壊しました。--------------------------
おまけ:配列とポインタと一緒に共に使ったもの
int main() {
Column* pC = 0; // Columnクラス用ポインタの初期化
Column obj[3] = {
{8, 0, 0, 15.3, 34.6, "blue"},
{10, 30, 6, 44.3, 24.3, "red"},
{20, 2, 10, 78.3, 14.1, "green"}
};
pC = &obj[0];
for (int i = 0; i < 3; i++) {
pC[i].info_status();
}
return 0;
}
実行結果↓
円柱を一つ建設しました。-----------------------
円柱を一つ建設しました。-----------------------
円柱を一つ建設しました。-----------------------
このblueの円柱は(8, 0, 0)の位置にあり、
半径:15.3, 高さ:34.6, なので、体積:25432.5
このredの円柱は(10, 30, 6)の位置にあり、
半径:44.3, 高さ:24.3, なので、体積:149742
このgreenの円柱は(20, 2, 10)の位置にあり、
半径:78.3, 高さ:14.1, なので、体積:271439
円柱を一つ破壊しました。--------------------------
円柱を一つ破壊しました。--------------------------
円柱を一つ破壊しました。--------------------------
C++でのメモリの動的確保、解放
Cでは、『スコープ』と言えば、「メモリの動的確保、解放などによって、その変数の寿命を操作できた」ということも思い出されませんか?(少し無理やり感)
“malloc()”や”lealloc()”など、「メモリ領域を動的に確保・再確保」したり、”free()”の「動的に確保したメモリ領域を解放」というような役割を持つ関数なども、以前の記事でご紹介したと思います。
C++の場合、もちろんCの上位互換なので”malloc()”、”free()”を利用することも出来るのですが、あまり推奨されません。
クラスから生成されたインスタンスでそれを使うとなると、コンストラクタやデストラクタが呼び出されないのです。
ですから基本的にC++では、CにはなかったC++独自の演算子によって動的にメモリ確保、解放を行います。
その為の演算子、『new演算子』と、『delete演算子』についてご紹介します。
new演算子 –メモリの動的確保をする
『new演算子』を扱う時の書式は↓のようになります。
ポインタ変数 = new 型;
この『new演算子』の場合、malloc()のようにsizeof()でサイズを指定する必要はありません。(便利ですよね)
そして、上の書式を見て気付かれたかと思いますが、『new演算子』を使った場合、確保したメモリのアドレスが渡されるので、格納する変数はポインタ(*)変数になります。
delete演算子 –動的確保したメモリを開放する
delete演算子を扱う際の書式↓
delete 変数;
また、配列であった場合には↓のようにします。
delete[] 変数;
Cの”malloc()”などを使った時も”free()”で必ず解放したように、動的確保したメモリは、必ず動的に開放しなければなりません。
確保したメモリを不要になっても解放せずにいるとメモリ不足となり、新たにメモリが確保できなくなるという問題が発生します。これを『メモリリーク』と言います。
コラム:ガベージコレクション(GC)
『ガベージコレクション』とは、プログラムを実行する際に、必要な情報を一時保管するためメモリを確保し、実行後、不要になった情報を、確保していたメモリと共に自動的に解放してくれる機能の事。C, C++にはありませんが、JavaやPHP、C#などには言語レベルでこれが標準機能として備えられているため、上記の『メモリリーク』と言った事態には陥りません。
それでは例のごとく使いまわしですが、上の項でも使った、円柱クラスを使って、クラスのメモリ動的確保、解放をしてみます。
int main() {
Column* pC = 0; // Columnクラス用ポインタの初期化
pC = new Column(8, 0, 0, 15.3, 34.6, "blue");// 動的確保したインスタンスのメモリアドレスをポインタ変数pCに格納
pC->info_status(); // ポインタからメンバ関数にアクセス
delete pC; // インスタンス消去
return 0;
}
実行結果↓
円柱を一つ建設しました。-----------------------
このblueの円柱は(8, 0, 0)の位置にあり、
半径:15.3, 高さ:34.6, なので、体積:25432.5
円柱を一つ破壊しました。--------------------------
おまけ:ポインタを活用したクラスの配列の場合
int main() {
Column* pC = 0; // Columnクラス用ポインタの初期化
pC = new Column[3]{
{0, 0, 0, 15.3, 34.6, "blue"},
{0, 0, 0, 15.3, 34.6, "red"},
{0, 0, 0, 15.3, 34.6, "green"}
};
for (int i = 0; i < 3; i++) {
pC[i].info_status();
}
delete[] pC; // インスタンス消去
return 0;
}
実行結果↓
円柱を一つ建設しました。-----------------------
円柱を一つ建設しました。-----------------------
円柱を一つ建設しました。-----------------------
このblueの円柱は(0, 0, 0)の位置にあり、
半径:15.3, 高さ:34.6, なので、体積:25432.5
このredの円柱は(0, 0, 0)の位置にあり、
半径:15.3, 高さ:34.6, なので、体積:25432.5
このgreenの円柱は(0, 0, 0)の位置にあり、
半径:15.3, 高さ:34.6, なので、体積:25432.5
円柱を一つ破壊しました。--------------------------
円柱を一つ破壊しました。--------------------------
円柱を一つ破壊しました。--------------------------
あとがき
ご意見・ご感想・ご質問、また誤字脱字や、もっといい方法あるよといったご指摘などございましたら、お手数ですがコメント欄やtwitterアカウントほろほろり(@_horo_horori)へお願いしますm(_ _)m