C++ class とそれに関係してくるものを書いて覚えるための初心者自己中記事④
の続き。
書いてる途中にブラウザ戻っちゃった。
記事消えた。
やる気なくなる・・・
一からやり直し。
内容だけ書こう。
アップキャストについて。
アップキャストとは関数の仮引数に基底クラスの型で参照をする方法。
参照先は派生クラスのオブジェクト。
派生クラスのオブジェクトを基底クラスの型で参照するということは
派生クラスにしかないメンバに関しては扱えないということ。
しかし行いたい処理が基底クラスのメンバのみで構成されていれば有効。
利点は派生クラスが複数あってオブジェクトの型がそれぞれの派生クラスの型だから要はバラバラでも基底クラスの型での参照を仮引数にした関数を一つ用意すればOKという点。
この派生クラスのオブジェクトを基底クラスの型で参照する事柄をアップキャストという。
次にオーバーライド。
class を基底クラスと派生クラスに分けるのはそもそも、複数のクラスがありそのクラスどうしで共通するメンバがあった場合にまとめる為に行われる。
つまり基底クラスと派生クラスを作成した段階では基底クラスと派生クラスでは機銃されているメンバが違う。
しかし、派生クラス同士で見ると受け入れる型が違うが行なっている目的は同じというメンバ関数がある。
それが例えばSet関数というものだとして、基底クラスにはその名前のメンバ関数は無い。
オーバーライドとはそのような状態で基底クラスにも同じ名前のSet関数を作成する事。
しかしこのままだと別々には使用できるがアップキャストした状態で(基底クラスの型で参照している派生クラスオブジェクトに対して)それぞれに対応したメンバ関数として使用することができない。
そこで使用されるのが、仮想関数。
これによって先ほどのアップキャストで基底クラスの型で受け入れた派生クラスのオブジェクトに対してSet関数を使用すると元の派生クラスのメンバ関数で処理をしてくれるという優れもの。
使い方は基底クラスのSet関数プロトタイプの先頭に virtual とつけるだけ。
ただ、テキストだと派生クラスの同メンバ関数にを virtual をつけたほうがわかりやすいと書いてある。そこはどちらでも問題ないらしい。
ここまでがブラウザ戻るで消えた。
文章にすると短いけど、テキスト読んで実際やってみて試して書いて・・だと凄い時間かかったのに。つらい。
----------------------------------------------------
class Input{
public:
double Get(); //共通点
virtual bool Set(); //オーバーライド
protected:
double inputNum; //共通点
};
class InputCin : public Input{
public:
bool Set();
private:
};
class InputArray : public Input{ // class InputArrayは配列の管理
public:
InputArray(const double* array); //配列を受け取る引数付きコンストラクタ
bool Set(); //Set関数が呼び出されるたびにinputIndexとinputNumに次のインデックス番号と数値が記憶される
private:
const double* inputArray; //配列のアドレスポインタ
int inputIndex; //Set関数で現在のインデックスを記憶
};
~~~~~~~~~~~~~~~~~~~~
//Input class
double Input::Get(){
return inputNum;
}
bool Input::Set(){ //オーバーライド
inputNum = -1;
return false;
}
//InputCin class
bool InputCin::Set(){
cin >> inputNum;
if(inputNum > 0){
return true;
}else{
return false;
}
}
//InputArray class
InputArray::InputArray(const double* array){
inputArray = array;
inputIndex = 0;
}
bool InputArray::Set(){
inputNum = inputArray[inputIndex];
if(inputNum > 0){
++inputIndex;
return true;
}else{
return false;
}
}
~~~~~~~~~~~~~~~~~~~~
//Average関数
void Average(Input& input){
int count = 0;
double avr = 0.0;
while(input.Set()){
avr += input.Get();
++count;
}
avr /= count;
cout << avr << endl;
}
void Show(Input& input){ //基底クラスで派生クラスのオブジェクトを参照する
input.Set();
cout << input.Get() << endl; //派生クラスを参照している基底クラスがGet関数を使う
}
int main(){
Input input; //基底クラスのオブジェクト作成
Average(input);
InputCin inputcin; //派生クラス InputCin でオブジェクト作成
Average(inputcin);
double array[ ] = {1.1, 2.2, 3.3, 4.4, -1,};
InputArray inputarray(array);//派生クラス InputArray でオブジェクト作成
Average(inputarray);
}
---------------------
(結果)
nan
1.1
2.2
3.3
4.4
0
2.75
2.75
----------------------------------------------------
途中経過が全部消えたから結果だけ。
(ほぼテキストまま)
今の状態は、基底クラス型として仮想関数を呼び出して本来のクラスの関数へと行く流れだ。
次は間に1工程増えるだけ。
基底クラス型として基底クラスのメンバ関数を呼び出して、そのメンバ関数が仮想関数を呼び出す。
テキストだと、仮想関数は基底クラスのメンバ関数から呼び出した場合も本来の型に応じて呼び分けられる。とあった。
理解してから読むとはっきりわかるけど、わからない時に読むとさっぱりわからなかった。
読解力が低いなぁ・・・
----------------------------------------------------
class Input{
public:
double Get(); //共通点
bool Set(); // Set関数内ではSetBase関数を呼び出す その後0以上かをチェックする ここが変わった
protected:
virtual void SetBase(); //Set関数から値の設定部分を抜き出した SetBase関数が仮想関数になるので各派生クラスもそれに準じる ここが変わった
double inputNum; //共通点
};
class InputCin : public Input{
public:
virtual void SetBase(); ここが変わった
private:
};
class InputArray : public Input{ // class InputArrayは配列の管理
public:
InputArray(const double* array); //配列を受け取る引数付きコンストラクタ
virtual void SetBase(); //Set関数が呼び出されるたびにinputIndexとinputNumに次のインデックス番号と数値が記憶される ここが変わった
private:
const double* inputArray; //配列のアドレスポインタ
int inputIndex; //Set関数で現在のインデックスを記憶
};
----------------------------------------------------
基底クラスで仮想関数を作ってきたが、この関数が必要なのは今の所派生クラスのオブジェクトだけだ。基底クラスにこの関数があっても邪魔。
なので実装部分をなくそう!
としてもエラーになる。
そこで登場するのが純粋仮想関数なのだ。
やり方は簡単。
基底クラスの当該仮想関数のプロトタイプで = 0 をしてあげるだけ。
つまり、
----------------------------------------------------
class Input{
public:
double Get(); //共通点
bool Set(); // Set関数内ではSetBase関数を呼び出す その後0以上かをチェックする
protected:
virtual void SetBase() = 0; //Set関数から値の設定部分を抜き出した SetBase関数が仮想関数になるので各派生クラスもそれに準じる
double inputNum; //共通点
};
----------------------------------------------------
こんな感じ。
これで実装部分は削除できる。
この純粋仮想関数というやつは class そのもののあり方を変化させるらしい。
純粋仮想関数をメンバとしている class は抽象クラスと呼ばれる。
そして実体を作ることができなくなる。オブジェクトが作れないってことかな?
でも基底クラスの型で参照する関数は使えるなぁ。
実体とかオブジェクトとかの定義が全然わかってないからよくわからん。
うん、テキスト読んだら書いてあった。
やっぱりオブジェクトが作れなくなる。
でも参照やポインタはOKだと。
オブジェクトを作ろうとしても純粋仮想関数のメンバがいると実装がないからダメって感じでわかるけど、参照やポインタがOKなのはどんな理屈なんだ?
そもそも参照やポインタってのは先頭アドレスのことだよね?
あぁ、実体がないや・・・。
正しいかわからんけどそう理解しよう。
class の継承がある場合のコンストラクタ・デストラクタのタイミング。
基底クラスを上流、派生クラスを下流として
コンストラクタは上流から下流の順番で
デストラクタは逆に下流から上流の順番でやってくる。
つまり、派生クラスのオブジェクトを作成した場合はまず
基底クラスのコンストラクタ
派生クラスのコンストラクタ
で
派生クラスのデストラクタ
基底クラスのデストラクタ
だ。
まぁ、派生クラスには基底クラスがベースとしてあるわけだから
基底クラスのコンストラクタ・デストラクタが先陣としんがりじゃないと困るよね。
派生クラスのオブジェクトを作るときでも基底クラスのコンストラクタがまず動くということで、次は基底クラスと派生クラスの両方で引数のあるコンストラクタを書いてみるとどうなるか。
----------------------------------------------------
//Input class
Input::Input(double n){ //引数のあるコンストラクタ
inputNum = n;
}
double Input::Get(){
return inputNum;
}
bool Input::Set(){
SetBase();
return inputNum > 0;
}
//InputCin class
InputCin::InputCin(double n){ //引数のあるコンストラクタ エラーになる
inputNum = n;
}
void InputCin::SetBase(){
cin >> inputNum;
}
----------------------------------------------------
エラーになりました。
というか基底クラスは抽象クラスになったんだから引数のあるコンストラクタって必要なのかな・・?
引数付きのコンストラクタってことはオブジェクト作る時に使うからいらないんじゃないのかな?
テキストにあるからやってるけど。練習用?ってことかな?
んで、これを解決する方法は
派生クラスの実装部分で
コンストラクタ名(派生class名) ( 仮引数 ) : 基底クラス名 ( 実引数 ) { 処理内容}
とする。
実引数なのが要チェックしとこう。
----------------------------------------------------
//Input class
Input::Input(double n){
inputNum = n;
}
double Input::Get(){
return inputNum;
}
bool Input::Set(){
SetBase();
return inputNum > 0;
}
//InputCin class
InputCin::InputCin(double n):Input(n){
inputNum = n;
}
void InputCin::SetBase(){
cin >> inputNum;
}
----------------------------------------------------
エラーが消えました。
しかしInputArray の方はもとより引数付きのコンストラクタを作っていたはず。
さっきの InputCin は引数が基底クラスと同じ double型 だったからいけたけど
InputArray型 のコンストラクタの引数は const double* ポインタなんですけど・・。
テキストだと初めから居なかったかのように全く触れられないし。
どうすれば・・・。
----------------------------------------------------
//Input class
Input::Input(double n){
inputNum = n;
}
double Input::Get(){
return inputNum;
}
bool Input::Set(){
SetBase();
return inputNum > 0;
}
//InputCin class
InputCin::InputCin(double n):Input(n){
inputNum = n;
}
void InputCin::SetBase(){
cin >> inputNum;
}
//InputArray class
InputArray::InputArray(const double* array):Input(n){ //ここがエラーになる
inputArray = array;
inputIndex = 0;
}
void InputArray::SetBase(){
inputNum = inputArray[inputIndex];
}
----------------------------------------------------
できた・・。
なるほど。派生クラスのコンストラクタの引数が基底クラスのコンストラクタの引数に行くようにルートを通す感じなのか。
----------------------------------------------------
//InputArray class
InputArray::InputArray(const double* array, double n):Input (n){
inputNum = n;
inputArray = array;
inputIndex = 0;
}
----------------------------------------------------
奇跡的に俺にもできたけど、これは一歩間違えば詰む。
なんでここんとこちゃんと教えてくれないのよ・・。
読解力か・・・。
引数付きのコンストラクタを持っているクラスの派生クラスを作るときは
作った派生クラスのコンストラクタで同じように親のクラスへの引数をあげる。
また、派生クラスで新たにメンバ変数が親クラスの型の場合は同じやり方で引数を入れてあげる。
ややこしい。
----------------------------------------------------
// InputCin class の派生クラス
class Test : public InputCin{
public:
Test();
private:
InputCin test1;
InputCin test2;
};
~~~~~~~~~~~~~~~~~~~~
//Test class (InputCin class の派生クラス
Test::Test():InputCin (-1),test1(-1),test2(-1){
}
----------------------------------------------------
こんな感じ。
そして、この引数付きの型を ( ) 内に実引数を入れて初期化するというのは
自分で記述したコンストラクタがあるからというわけではなくて
通常型の初期化でも使える。
int i ( 0 ) ;
通常型もコンストラクタを持っているので可能らしい。
とりあえずここまで。