C++ class とそれに関係してくるものを書いて覚えるための初心者自己中記事①
class は構造体のもっとどエライもの。
最初に構造体と同じようなメンバで作る。
---------------------------------------------------
class Data {
public:
char name[64];
int age;
double higth;
};
---------------------------------------------------
class を作成するときは
class class名 {public: メンバ };
となる。とりあえずね。
この public はそのまんま class の外部からでも操作できますよ〜っていう意味。
public の逆バージョンがあるけど今は放置。
この calss がある状態でデータを入れてみる。
---------------------------------------------------
int main(){
Data data = {
{"河合いよな" , 21 , 158.5,},
{"蛇場じゃば夫" , 20 , 178.3,},
{"江戸城本丸" , 22 , 169.7,},
};
cout << data[0].name << endl;
strcpy(data[0].name,"河合いよ");
cout << data[0].name << endl;
data[0] = {"江戸城本丸" , 22 , 169.7,};
cout << data[0].name << endl;
}
(結果)
河合いよな
江戸城本丸
---------------------------------------------------
入れ方は構造体と一緒かな?
んで、ここからが class で初めて学ぶこと。
class は関数を入れられる。
関数を class に入れるときはclass内にはプロトタイプを書き、
実装は class の外に書くこと。
やってみよう。
---------------------------------------------------
//class
class Data {
public:
char name[64];
int age;
double hight;
void Show(); //classの中にプロトタイプ
};
//classの外に実装
void Data::Show(){
cout << name << " : " << age << " : " << hight << endl;
}
int main(){
Data data = {
{"河合いよな" , 21 , 158.5,},
{"蛇場じゃば夫" , 20 , 178.3,},
{"江戸城本丸" , 22 , 169.7,},
};
data[0].Show();//classの中にある関数を呼び出してる
}
(結果)
河合いよな : 21 : 158.5
---------------------------------------------------
class内でプロトタイプを書く。これは大丈夫。ただのプロトタイプ。
class外で関数実装。ここでちょっと違う、 戻り型 class名 :: 関数名 になっている。
関数名の前に class名 :: をつけなければいけないようだ。
これで class と実装部分が紐付くのだろうか。
関数呼び出しの方も class仕様だね。
class型で作られた変数のメンバとして関数を呼び出している。
data[0].Show(); で呼び出しているから同じインデックスのメンバが使われて表示されている。
data[2].Show(); ならインデックスが2のメンバが出てくるわけね。
class のメンバは変数と関数でそれぞれ
メンバ変数
と呼ぶ。
次に、class の型で変数を宣言するけど初期化はしないとどうなるか。
---------------------------------------------------
class Data {
public:
char name[64];
int age;
double hight;
void Show();
};
void Data::Show(){
cout << name << " : " << age << " : " << hight << endl;
}
int main(){
Data data;
data.Show();
}
(結果)
: 0 : 6.95322e-310
---------------------------------------------------
変な結果になった。初期化されていないのだから仕方がない。
そこで、自動で初期化してくれるようにする為に使うのがコンストラクタだ。
---------------------------------------------------
class Data {
public:
char name[64];
int age;
double hight;
void Show();
Data(); //コンストラクタのプロトタイプ
};
void Data::Show(){
cout << name << " : " << age << " : " << hight << endl;
}
Data::Data(){ //コンストラクタの実装
strcpy(name,"未入力");
age = 0;
hight = 0.0;
}
int main(){
Data data;
data.Show();
}
(結果)
未入力 : 0 : 0
---------------------------------------------------
初期化してくれている。
プロトタイプの記述では戻り値の型が記述されない。
無いなら void で良くない?と思いそうだけど書いてはいけないそうだ。
そしてコンストラクタも関数なので(なのか?)、関数名を書くけど関数名は class名と同じにする。
そしたら実装部分で他のメンバ関数と同じようにやればOK
class型で変数を宣言した時にコンストラクタが初期化を勝手にやってくれるのはわかった。
じゃあそのあとにデータを入れていけば良い?
と思ったらもっと簡単にできそう。
コンストラクタに引数を持たせることが出来る。
この場合引数無しの普通のコンストラクタとは別に作る必要があるので
多分これって関数オーバーロードみたいな感じなのではないかと思った。
---------------------------------------------------
class Data {
public:
char name[64];
int age;
double hight;
void Show();
Data(); //コンストラクタのプロトタイプ
Data(char name[ ]); //引数付きコンストラクタのプロトタイプ
};
void Data::Show(){
cout << name << " : " << age << " : " << hight << endl;
}
Data::Data(){ //コンストラクタの実装
strcpy(name,"未入力");
age = 0;
hight = 0.0;
}
Data::Data(char str[ ]){ //引数付きコンストラクタの実装
strcpy(name,str);
age = 0;
hight = 0.0;
}
int main(){
char str[64];
strcpy(str,"引数入れたよ");
Data data(str);
data.Show();
}
(結果)
引数入れたよ : 0 : 0
---------------------------------------------------
できました。文字列リテラルが手こずる・・
次に public について。
public はアクセス指定子と言われる。
class外からのアクセスに関しての許可不許可をこちらで指定することが出来る。
public はclass外からのアクセスが許可される。
逆に、 class外からのアクセスを禁止するために使うのが private だ。
でも、class外からのアクセスが出来ないと何も出来ないじゃんと思ってしまうがそうではなくて、 public なメンバ関数からなら private なメンバが操作できるということ。
この場合 private なメンバはメンバ変数でもメンバ関数でもOK
なので private なメンバ変数に値を入れる為の public なメンバ関数を作ってしまえば、値を入力する際はその public なメンバ関数から行えばよくて、そのメンバ関数の中で値のチェックなどをさせるととても良くなる。
---------------------------------------------------
class Data {
public:
void Set(const char* name,int age,double hight);
void Show();
Data(); //コンストラクタのプロトタイプ
Data(const char* name); //引数付きコンストラクタのプロトタイプ
private:
const char* c_name;
int c_age;
double c_hight;
};
void Data::Set(const char* name,int age,double hight){
c_name = name;
c_age = age;
c_hight = hight;
}
void Data::Show(){
cout << c_name << " : " << c_age << " : " << c_hight << endl;
}
Data::Data(){ //コンストラクタの実装
c_name = "未入力";
c_age = 0;
c_hight = 0.0;
}
Data::Data(const char* str){ //引数付きコンストラクタの実装
c_name = str;
c_age = 0;
c_hight = 0.0;
}
int main(){
Data data[4];
data[0].Set("河合いよな" , 21 , 158.5);
// data[1].c_age = 5; //ここのコメントアウト外すとエラーになる(直接アクセスしようとしているから)
data[0].Show();
data[1].Show();
}
(結果)
河合いよな : 21 : 158.5
未入力 : 0 : 0
---------------------------------------------------
(注)ごちゃごちゃしたので文字リテラルままのアドレスを class に保存するように変えました。
public と private のアクセス指定子を使ってメンバ変数にはメンバ関数からしかアクセス出来ないようにしました。
コンストラクタはどっちなんだろうと思ったけど、 private にしたらエラー出た。
class型の変数宣言した時に呼び出せなくなっちゃったんだろうか。
Set関数にチェック機能入れようと思ったんだけど、文字リテラルのアドレス送る場合のチェックって何をしたら良いんだろう・・・
ちょっとそれはまた後で。そのうちわかるだろう。
次、ガラッと変わって
class のメンバ変数に配列があってその配列を new を使って動的に作りたい時。
new で確保したアドレスは、そのアドレスが渡されたポインタが消滅する前に delete で解放してあげないとずっと確保されっぱなしになってしまうというのをこないだ勉強した。
では class のメンバ変数(ポインタ)にそのアドレスが入っていた場合には、一体どこで delete すれば良いのか。
そして、そもそも new はどこでやるのか?
---------------------------------------------------
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(); //デストラクタ!!
private:
const char* c_name;
int* c_age; //newで作る配列のアドレスを入れるポインタ このアドレスを解放しなければならない
double* c_hight; //newで作る配列のアドレスを入れるポインタ このアドレスを解放しなければならない
int c_past; //要素数を記憶しておく変数
};
void Data::Set(const char* name,int age,double hight,int past){ //past = 配列の要素番号
c_name = name;
c_age[past] = age;//年齢の履歴
c_hight[past] = hight;//身長の履歴
}
void Data::Show(){
cout << c_name << " さん" << endl;
for ( int i = 0 ; i < c_past ; ++i){
cout << " " << c_age[i] << " 才 : " << c_hight[i] << " cm"<< endl;
}
}
Data::Data(int past){ //コンストラクタの実装
c_name = "未入力";
c_age = new(nothrow) int[past];
c_hight = new(nothrow) double[past];
if(c_age != NULL && c_hight != NULL){
for(int i = 0 ; i < past ; ++i){
c_age[i] = 0;
c_hight[i] = 0;
}
c_past = past;
}else{
cout << "初期化に失敗しました" << endl;
exit(EXIT_FAILURE);
}
}
Data::Data(const char* str,int past){ //引数付きコンストラクタの実装
c_name = str;
c_age = new(nothrow) int[past];
c_hight = new(nothrow) double[past];
if(c_age != NULL && c_hight != NULL){
for(int i = 0 ; i < past ; ++i){
c_age[i] = 0;
c_hight[i] = 0;
}
c_past = past;
}else{
cout << "初期化に失敗しました" << endl;
exit(EXIT_FAILURE);
}
}
Data::~Data(){ //デストラクタで delete を行うのは new で確保したアドレスが入っているポインタ
delete c_age;
delete c_hight;
}
int main(){
Data data(3);
data.Set("河合いよな",21,158.5,0);
data.Set("河合いよな",22,158.7,1);
data.Set("河合いよな",23,159.2,2);
data.Show();
}
(結果)
河合いよな さん
21 才 : 158.5 cm
22 才 : 158.7 cm
23 才 : 159.2 cm
---------------------------------------------------
class のメンバとしてnew が確保したアドレスを入れるためのポインタ変数を作る。メンバポインタ変数っていうのか?
そして new がアドレスを確保するときに指定したい配列数はコンストラクタに引数で渡す。
これは引数付きコンストラクタと一緒。というか引数つけたから引数付きコンストラクタになったのか。
コンストラクタでは new が失敗した時の為に nothrow を使った処理を入れてみる。習ったことだから使ってみなければ。
Set関数がすごく変だけど、仕方ない(初心者だもん)
最後にデストラクタ内の処理でポインタ変数を delete する。
デストラクタの作り方はコンストラクタとほとんど同じで、唯一違うのが関数名?デストラクタ名?の部分で ~ をつける事。それだけ。
なんか、コンストラクタは初期化する為のもの
デストラクタは delete する為のもの
って考えていたけど、それで良いんだと思うけど、こうやって処理をいじれるんであればそれはもう宣言時に自動で何かを処理させる機能と、関数抜ける時に自動で何かを処理させる機能って事で、何かすごい便利に使えるのではないだろうか・・?
上記の処理でコンストラクタが二つあって今はどっちも引数付きのコンストラクタになっちゃっているわけだが、引数なしの純粋なコンストラクタのことをデフォルトコンストラクタと呼ぶらしい。
んで上記の処理中で二つのコンストラクタでの処理がほとんど同じなのでその部分だけ別に作ったメンバ関数に移動させて、コンストラクタ内でその関数を呼び出したらちゃんと動いた。コンストラクタでも他のメンバ関数は呼び出せるのね〜。常識?
---------------------------------------------------
Data::Data(int past){ //コンストラクタの実装
c_name = "未入力";
C_Data(past);
}
Data::Data(const char* str,int past){ //引数付きコンストラクタの実装
c_name = str;
C_Data(past);
}
void Data::C_Data(int past){
c_age = new(nothrow) int[past];
c_hight = new(nothrow) double[past];
if(c_age != NULL && c_hight != NULL){
for(int i = 0 ; i < past ; ++i){
c_age[i] = 0;
c_hight[i] = 0;
}
c_past = past;
}else{
cout << "初期化に失敗しました" << endl;
exit(EXIT_FAILURE);
}
}
---------------------------------------------------
class はまだまだ長くなりそうなのでいったんここまで