なんかclassが宣言されているときに。。。
class cAnimal { protected: string mName; public: cAnimal() :mName("") { cout << "Animal誕生 "; } cAnimal(string _name) :mName(_name) { cout << "Animal:" << _name << " 誕生 "; }; void sayMyName() { cout << mName << endl; } };
動的にオブジェクト(インスタンス)を動的に生成するには
new class名;
で、1つオブジェクトを生成できる。
ここで、「class名」は、実は引数なしのコンストラクタである。
なので、引数付きのコンストラクタも呼べる。
new class名(コンストラクタ引数);
である。
つまり、上記のcAnimalクラスを動的に1つ生成すると
new cAnimal; //引数なし new cAnimal("ポチ");//引数あり
newは、生成されたオブジェクトのアドレスを返す演算子である。
newで生成されたオブジェクトには変数名が付いておらずアドレスしか返されないために、迷子になってしまわないようにアドレスを変数に保存する。
アドレスを保存するための変数=ポインタを使うには
cAnimal *pet0 = cAnimal; //cAnimalオブジェクトを動的に1つ作って、アドレスをpet0に保存 cAnimal *pet1 = cAnimal("ポチ"); //cAnimalオブジェクトを引数付きコンストラクタで動的に1つ作って、アドレスをpet1に保存
のように書くことができる。
今pet0, pet1の指すアドレスには、生成されたオブジェクトのメンバが収まっている。
それら(アドレスの先のメンバ)にアクセスするには、“→“アロー演算子を使う。んでしたね!
今publicにアクセスできるcAnimalのメンバはメンバ関数sayMyNameのみなので、
pet0->sayMyName();//名前指定してないので、""が表示される(何も表示されない) pet1->sayMyName();//ポチでmNameが初期化されているので、"ポチ"が表示される
となる。
以上が、動的なオブジェクト生成の基本である。
いろいろプログラムを作っていると、当然のようにオブジェクトの配列が欲しくなる時があります。
そんな時は、今までの「普通の型」の配列のようにオブジェクトの配列を作ることができます。
//doubleの要素数10個の配列+初期化 double darray[10] = {1.0, 2.1, 3.2, 4.3, 5.5, 6.6, 7.8, 9.9 10.2}; //同様に、オブジェクトの配列も作れる //通常の配列と同じように初期化もできる。 cDog pets[5] = { cDog("ポチ"), cDog("タマ"), cDog("モカ"), cDog("チョロ"), cDog("ジョン") }; //初期化子はコンストラクタを呼び出して値を確定させる //配列型アクセス pets[2].sayMyName(); //モカ と表示される //アドレス型アクセス (pets+3)->sayMyName(); //チビ と表示される
int num; cin >> num; //doubleの要素数num個の配列 double *darray = new double[num]; //初期化は同時にはできないので、値を必ず代入してから使う。 //同様に、オブジェクトの配列も作れる cDog *pets = new cDog[num]; //初期化子は引数なしのコンストラクタが呼ばれる //引数付きのコンストラクタは呼べぬ。→初期値を確定させる方法何か考えないとね。 //配列型アクセス pets[2].sayMyName(); //値入れないと、空文字列が表示=何も表示されない、よ。 //どうしてかはもうわかるよね? //アドレス型アクセス (pets+3)->sayMyName();
オブジェクト生成の時に、どのコンストラクタが呼ばれて、オブジェクトがどうなっているか想像して読んでね。
ってことで、動的に生成したオブジェクトの配列を、初期化するにはどうしたらいいかなぁと考えます。
値が入っていることは担保されるし、よくやるパターンだけど、厳密には初期化自体は初めに生成されたときのデフォルトコンストラクタがやってるから、初期化ではなく代入演算子による代入だよね。。。
#include <iostream> #include <string> #include <vector> //可変長配列 using std::cin; using std::cout; using std::endl; using std::string; using std::vector; class cAnimal { protected: string mName; public: cAnimal() :mName("") { cout << "Animal誕生 "; } cAnimal(string _name) :mName(_name) { cout << "Animal:" << _name << " 誕生 "; }; void sayMyName() { cout << mName << endl; } }; class cDog :public cAnimal { public: cDog() : cAnimal() {cout << "種類はDog" << endl;} cDog(string _name) :cAnimal(_name) {cout << "種類はDog" << endl;} void sayWan() { cout << "BowWow" << endl; } };
ここから先は、この2つのclassは定義済みとします。
まず、オブジェクトを必要数だけ動的に生成する処理。
これはいいよね?
前に書いたとおり、この書き方をすると引数なしコンストラクタしか呼び出せません。(不便だけど、数決まってないから初期化値並べらんないよね)
//cinから入力された頭数の犬のオブジェクトを確保し、 //cinからそれぞれの名前を入力して初期化する //そのあとに、それぞれの名前を表示し、一頭ずつ吠える cout << "頭数を入力" << endl; int num;//頭数 cin >> num; cDog* pets = new cDog[num];
cout << "頭数を入力" << endl; int num;//頭数 cin >> num; //本当の初期化は、ここでデフォルトコンストラクタがやってる cDog* pets = new cDog[num]; for (auto i = 0; i < num; i++) { string tmp; cin >> tmp; pets[i] = cDog(tmp); //同じ初期化された同じクラスのインスタンスの値を代入 }
これは、代入であり初期化じゃないことを確認してみよう。
つまり、代入演算子による代入で値のコピーが行われているのを確認してみよう。
同じ型のオブジェクトによる初期化が行われているのであればコピーコンストラクタが呼ばれるはずです。
ですが、今のところcAnimal, cDogともコピーコンストラクタは明示的に宣言されておらず、デフォルトコピーコンストラクタにより動作している状態です。
なので、上のcAnimalと、cDogのコピーコンストラクタを、以下のように書いてあげます。
class cAnimal { protected: string mName; public: cAnimal() :mName("") { cout << "Animal誕生 "; } cAnimal(string _name) :mName(_name) { cout << "Animal:" << _name << " 誕生 "; } //cAnimalのコピーコンストラクタをインライン定義 cAnimal(const cAnimal& obj) { mName = obj.mName; cout << "cAnimalコピーコンストラクタで構築" << endl; } void sayMyName() { cout << mName << endl; } }; class cDog :public cAnimal { public: cDog() : cAnimal() {cout << "種類はDog" << endl;} cDog(string _name) :cAnimal(_name) {cout << "種類はDog" << endl;} //cDogのコピーコンストラクタをインライン定義 cDog(const cDog& obj):cAnimal(obj.mName) {cout << "cDogコピーコンストラクタで:" << mName << "をコピー構築" << endl;} void sayWan() { cout << "BowWow" << endl; } };
コピーコンストラクタの動作確認をします。
コピーコンストラクタは、オブジェクトの初期化を、ほかの初期化されたオブジェクトのコピーをすることで行ったとき
すなわち「オブジェクトの生成と同時に、コピーが行われるとき」に呼ばれます。
cDog dog("犬"); //普通にオブジェクトを宣言 名前が犬のcDogオブジェクトができる => Animal:犬 誕生 種類はDog cDog pet0 = dog;//生成済みの”犬のオブジェクト”をコピーすることで新しく作ったpet0オブジェクトを初期化
結果を見てみます。
Animal: 誕生 種類はDog cDogコピーコンストラクタで:犬をコピー構築
コピーコンストラクタで初期化が行われてますね。
ちなみにcDogのコピーコンストラクタからは、cAnimalの引数付きコンストラクタ(stringが引数のやつ)が呼ばれてます。
ここで、実はcAnimalにもちゃんとコピーコンストラクタが定義されているので、
cDogの初期化に使ったオブジェクトを、そのままcAnimalに渡してみます。
すなわちcDogのコンストラクタで
cDog(const cDog &obj):cAnimal(obj){}
と変更してみます。
そうすると、cDogクラスのコピーコンストラクタから、cAnimalクラスのコピーコンストラクタが呼び出される仕様に変更になります。
Animal:犬 誕生 種類はDog cAnimalコピーコンストラクタで犬をコピー構築 cDogコピーコンストラクタで:犬をコピー構築
コピーコンストラクタが構築順に2回呼ばれるようになります。
コピーコンストラクタが、正しく呼ばれてる気がしてきたのでわき道から大通りに戻ります。
それでは、コピーコンストラクタを書いたところで、さっきの初期化(厳密には違う)法でどのコンストラクタがいつ呼ばれているか確認します。
cout << "頭数を入力" << endl; int num;//頭数 cin >> num; //本当の初期化は、ここでデフォルトコンストラクタがやってる cDog* pets = new cDog[num]; for (auto i = 0; i < num; i++) { string tmp; cin >> tmp; pets[i] = cDog(tmp); //同じ初期化された同じクラスのインスタンスの値を代入 }
頭数を入力 3 Animal誕生 種類はDog Animal誕生 種類はDog Animal誕生 種類はDog potch Animal:potch 誕生 種類はDog tama Animal:tama 誕生 種類はDog gon Animal:gon 誕生 種類はDog
通常の引数付きコンストラクタが呼ばれており、コピーコンストラクタは呼ばれていないのがわかります。
つまり、これは(厳密には)初期化ではなく、代入でしたってお話。
次は、おっそろしいことにチャレンジしてみます。
動的にオブジェクトを生成するときはnew演算子を使うんでしたね。
new オブジェクト名;
とすると、指定された型のコンストラクタにより新しいオブジェクトが生成されてその先頭アドレスが取得できます。
取得されるアドレスは、適切な方の変数(=オブジェクトのクラス名* 型)の変数に格納できます。したがって、最終的に
クラス名* 変数名 = new クラス名();
引数がある場合は
クラス名* 変数名 = new クラス名(引数リスト);
で、生成されたオブジェクトの位置を見失わないように保存できるので、アドレスの先に格納されているメンバを参照(=オブジェクトの値を参照できます)できます。
cDog* pets = new cDog; //または cDog* pets = new cDog(); //newによりcDogオブジェクトが、デフォルトコンストラクタ(引数なしのやつのこと)により生成されアドレスが返される //返されたアドレスはcDog型のアドレスを格納できる変数(= cDog *型)の変数に代入される。
次に
int num; cin >> num; cDog* pets = new cDog[num]; //num個のcDogオブジェクトが、デフォルトコンストラクタにより生成される。