C++ class とそれに関係してくるものを書いて覚えるための初心者自己中記事②
の続きです。
配列のメンバ変数を持っている class のコンストラクタを作ったりしました。
んで、配列のメンバ関数はポインタで作成するのですがその class をそのまま引数にした関数を作ってしまうとやばいらしい。
なんでも、class を引数にするということは中身のポインタを渡すということで
つまりはアドレスを渡すということ。
通常の値の場合は別の変数として値が写るけどポインタのアドレスは写っても本体のアドレスのまま。
そしてその写ったアドレスを持っているポインタはその関数を抜ける時にデストラクタで delete される。
つまり本体が delete されているのと同じこと。
そしてさらにその本家の本体のポインタはプログラム終了時に delete されようとするが、すでに関数内で delete されてしまっている為に出来ない。で、エラーになる。
そこで、ポインタを持っている class を引数にする場合には引数に自身の class型 を参照する形のコンストラクタを新たに作製する必要がある。
このコンストラクタの中身で新しく作ったポインタのアドレスの場所に、渡ってきたアドレスに置いてあるデータをコピーするという流れだ。
でもなぜ参照にしなければならないのか?
参照じゃなくてもアドレスは渡ってくるよね?
と思って調べたけど、なんかそもそも class とかのひと固まりのデータをそのまま引数にするとかありえん。ということのようだ。
なるほど。そう言われてみれば。よしそう覚えよう。
んで、このコンストラクタのことをコピーコンストラクタという。
classとコピーコンストラクタとデストラクタだけメモ
-------------------------------------------------------
class Data {
public:
void Set(const char* name,int age,double hight,int past);
void Show();
Data(int past); //コンストラクタのプロトタイプ
Data(const char* name,int past); //引数付きコンストラクタのプロトタイプ
Data(const Data& c_data); //コピーコンストラクタのプロトタイプココだよ!!
~Data(); //デストラクタ!!
private:
const char* c_name;
int* c_age; //newで作る配列のアドレスを入れるポインタ このアドレスを解放しなければならない
double* c_hight; //newで作る配列のアドレスを入れるポインタ このアドレスを解放しなければならない
int c_past; //要素数を記憶しておく変数
void C_Data(int past);
};
~~~~~~~~~~~~~~~~~~~~
Data::Data(const Data& c_data){ //コピーコンストラクタココだよ!!
c_name = c_data.c_name; //文字リテラルのポインタだけどデストラクタで消されないのでこのままアドレスコピー
c_past = c_data.c_past; //配列の数を渡す
c_age = new int[c_past]; //new で動的なアドレス範囲確保
c_hight = new double[c_past];//new で動的なアドレス範囲確保
copy(c_data.c_age,c_data.c_age + c_past,c_age); //1.ここから 2.ここまでの範囲のデータを 3.このアドレスから先にコピー という関数(copy)
copy(c_data.c_hight,c_data.c_hight + c_past,c_hight); //同上
}
~~~~~~~~~~~~~~~~~~~~
Data::~Data(){ //デストラクタで行うのは new で確保したアドレスが入っているポインタ
delete c_age;
delete c_hight;
}
-------------------------------------------------------
copy関数を使う場合は #include <algorithm> が必要らしい。
これで関数の引数に class型を使用するのは大丈夫。
次に、同じ class型 の変数AとBがある場合の話で
Aにはデータを入力してあってBは宣言だけ。もちろんコンストラクタで初期化はされている。
ここで
B = A;
をすると、コピーコンストラクタを作らなければならなかった理由と同じことが起こる。
代入ではコピーコンストラクタは何もしてくれないのだ。
その為代入の場合は代入演算子 ( = ) の動作を自分で上書きしなければならない。
代入以外の演算子でもできるらしいから、今回のは「代入の」演算子オーバーロード。
-------------------------------------------------------
class Data {
public:
void Set(const char* name,int age,double hight,int past);
void Show();
Data(int past); //コンストラクタのプロトタイプ
Data(const char* name,int past); //引数付きコンストラクタのプロトタイプ
Data(const Data& c_data); //コピーコンストラクタ
void operator=(const Data& c_data); //代入演算子オーバーロードココだよ!!
~Data(); //デストラクタ!!
private:
const char* c_name;
int* c_age; //newで作る配列のアドレスを入れるポインタ このアドレスを解放しなければならない
double* c_hight; //newで作る配列のアドレスを入れるポインタ このアドレスを解放しなければならない
int c_past; //要素数を記憶しておく変数
void C_Data(int past);
};
~~~~~~~~~~~~~~~~~~~~
Data::Data(const Data& c_data){ //コピーコンストラクタ
c_name = c_data.c_name; //文字リテラルのポインタだけどデストラクタで消されないのでこのままアドレスコピー
c_past = c_data.c_past; //配列の数を渡す
c_age = new int[c_past]; //new で動的なアドレス範囲確保
c_hight = new double[c_past];//new で動的なアドレス範囲確保
copy(c_data.c_age,c_data.c_age + c_past,c_age); //1.ここから 2.ここまでの範囲のデータを 3.このアドレスから先にコピー という関数(copy)
copy(c_data.c_hight,c_data.c_hight + c_past,c_hight); //同上
}
~~~~~~~~~~~~~~~~~~~~
void Data::operator=(const Data& c_data){ //代入演算子オーバーロードココだよ!!
c_name = c_data.c_name; //文字リテラルのポインタだけどデストラクタで消されないのでこのままアドレスコピー
c_past = c_data.c_past; //配列の数を渡す
int* age = new int [c_past]; //新たにメモリを確保
double* hight = new double [c_past]; //同上
delete c_age; //代入先のclass型変数はすでに作られているからまず解放しないといけない(newしてたらdeleteということ)
delete c_hight; //同上
c_age = age; // 確保したメモリのアドレスを、解放した代入先のメンバ変数に渡す
c_hight = hight; //同上
copy(c_data.c_age,c_data.c_age + c_past,c_age); //1.ここから 2.ここまでの範囲のデータを 3.このアドレスから先にコピー という関数(copy)
copy(c_data.c_hight,c_data.c_hight + c_past,c_hight); //同上
}
-------------------------------------------------------
比較するためにコピーコンストラクタも記載。
演算子オーバーロードでやっていることはコピーコンストラクタとだいたい同じ。
ただし、代入先の変数が初期化済みなのですでに動的なアドレスを確保してしまっているから、それを delete してあげなければいけないところが違い。
次
constメンバ関数
えっと、
class型の変数があって
その変数を const参照で引数とする関数があって
その関数内で メンバ関数(メンバを変更しない・実質const状態?)を使おうとするとダメらしい。エラーが出る。
ちなみに cons参照で~ の部分で const つけなければエラーにならない。
constオブジェクトからは constメンバ関数しか呼び出せないのだそうだ。
constメンバ関数って?
関数のプロトタイプと実装部分と両方で、仮引数入れるカッコの後に const って書いたらそれは constメンバ関数なのだ!
-------------------------------------------------------
class Data {
public:
void Set(const char* name,int age,double hight,int past);
void Get(const Data& c_data)const; //ココだよ!!
// void Show();
Data(int past); //コンストラクタのプロトタイプ
Data(const char* name,int past); //引数付きコンストラクタのプロトタイプ
Data(const Data& c_data); //コピーコンストラクタ
void operator=(const Data& c_data); //代入演算子オーバーロード
~Data(); //デストラクタ!!
private:
const char* c_name;
int* c_age; //newで作る配列のアドレスを入れるポインタ このアドレスを解放しなければならない
double* c_hight; //newで作る配列のアドレスを入れるポインタ このアドレスを解放しなければならない
int c_past; //要素数を記憶しておく変数
void C_Data(int past);
int Size();
};
~~~~~~~~~~~~~~~~~~~~
void Data::Get(const Data& c_data)const{ //メンバを表示させる関数ココだよ!!
cout << c_data.c_name << " さん" << endl;
for ( int i = 0 ; i < c_past ; ++i){
cout << " " << c_age[i] << " 才 : " << c_hight[i] << " cm"<< endl;
}
}
~~~~~~~~~~~~~~~~~~~~
void Show(const Data& c_data){ //Get関数を呼び出す関数
c_data.Get(c_data);
}
-------------------------------------------------------
いつかちゃんとまともで有効な使い方ができるだろう・・
次は継承について勉強しよう。
ここまで