C++ ⇒ VBA 書いて覚えるための初心者自己中記事

C++ ⇒ VBA 勉強の履歴を付けるというかノート代わりに使ってます

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外からのアクセスに関しての許可不許可をこちらで指定することが出来る。

 

publicclass外からのアクセス許可される。

逆に、 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 に保存するように変えました。

 

publicprivate のアクセス指定子を使ってメンバ変数にはメンバ関数からしかアクセス出来ないようにしました。

コンストラクタはどっちなんだろうと思ったけど、 private にしたらエラー出た。

class型の変数宣言した時に呼び出せなくなっちゃったんだろうか。

 

Set関数にチェック機能入れようと思ったんだけど、文字リテラルのアドレス送る場合のチェックって何をしたら良いんだろう・・・

ちょっとそれはまた後で。そのうちわかるだろう。

 

次、ガラッと変わって

classメンバ変数配列があってその配列を new を使って動的に作りたい時。

new確保したアドレスは、そのアドレスが渡されたポインタが消滅する前に delete で解放してあげないとずっと確保されっぱなしになってしまうというのをこないだ勉強した。

 

 

nenechi.hatenablog.com

 

では 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 はまだまだ長くなりそうなのでいったんここまで