継承(引用~析構~virtual)

55085 ワード

【1】プログラム1
 1 #include <iostream>
 2 using namespace std;
 3 
 4 class Base
 5 {
 6 private:
 7     int m_nBase;
 8 public:
 9     Base(int nValue = 100);
10     virtual ~Base();
11     virtual void print();
12 };
13 Base::Base(int nValue):m_nBase(nValue)
14 {
15 }
16 Base::~Base()
17 {
18     cout << "Base: " << "~Base" << endl;
19 }
20 void Base::print()
21 {
22     cout << "Base: " << m_nBase << endl;
23 }
24 class BA : public Base
25 {
26 private:
27     int m_nBA;
28 public:
29     BA(int nValue = 200);
30     ~BA();
31     void print();
32 };
33 BA::BA(int nValue):m_nBA(nValue)
34 {
35 }
36 BA::~BA()
37 {
38     cout << "BA: " << "~BA" << endl; 
39 }
40 void BA::print()
41 {
42     cout << "BA: " << m_nBA << endl;
43 }
44 
45 void main()
46 {
47     BA ba;
48     Base base = ba;
49     base.print();
50     Base& refBase = ba;
51     refBase.print();
52 }
53 /*54 Base: 100
55 BA: 200
56 Base: ~Base
57 BA: ~BA
58 Base: ~Base
59 */

main関数の2つのコードは油断が少ないように見えますが、違いは1つが参照であり、1つが参照ではなく、結果が遠いことです.なぜか分かりますか.
結果を見たときの第一反応は、同じように見える2つのオブジェクトが異なる方法(print()を呼び出しているに違いない.
では、この中で何が起こっているのでしょうか.
最初の文は、Baseタイプのオブジェクトを初期化するためにBAタイプのオブジェクトを使用しているように見えますが、その間に強制変換が発生しました.強制変換に問題はありますか?
私たちは強制変換をよく使うのではなく、データの正確さのためにintをdoubleに変換することが多いのですが、データの正確さのためにdoubleをintに変換したことはありますか?
明らかにありません.doubleデータをintに変換すると、小数点以下のものが捨てられることを潜在意識しているからです.
同様に,派生クラスをベースクラスに変換すると,サブクラスを親クラスに変換することも可能であるが,サブクラスはベースクラスを継承するすべての方法であるためである.
だから、サブクラスの中の方法はベースクラスより多いだけで少なくない.そうすれば、みんなは知っているはずだ.同様に、この変換は切断された.
簡単に言えば、このときbaseはbaオブジェクトで初期化されますが、実際にはbaseタイプのオブジェクトであるため、呼び出されるすべての方法はbaseクラスです.
では、引用はなぜ正しく表示されるのでしょうか.
これがポイントです.ここで皆さんにお話しします.引用するだけでなく、ポインタも同じようにできます.
したがって、親参照またはポインタを使用して子クラスを参照する場合、子クラスは上書きされたメソッドを正しく保持します.
上の点を覚えておいてください.次は新しい内容を続けて、まず次の内容を見てみましょう.
【2】プログラム2
 1 #include <iostream>
 2 using namespace std;
 3 
 4 class parent
 5 {
 6 public:
 7    parent()
 8    {
 9      cout<<"1"<<endl;
10    }
11 };
12  
13 class something
14 {
15 public:
16    something()
17    { 
18      cout<<"2"<<endl;
19    }
20 };
21  
22 class child : public parent
23 {
24 public:
25    child()
26    { 
27      cout<<"3"<<endl;
28     }
29 protected:
30    something Mysomething;
31 };
32  
33 void main()
34 {
35    child MyChild;
36 }
37 /*
38 1
39 2
40 3
41 */

今、私たちのプログラムは何を出力しますか?
主関数はchildのオブジェクトを構築しただけで、出力文は何もないと友人が聞くかもしれません.
ホームページ君は頭がドアに挟まれているのではないでしょうか.それとも水が入ったのでしょうか.
もちろん、このプログラムはchildオブジェクトを構築し、childの構造関数を呼び出すべきであるため、3を出力するべきだと考えるかもしれません.この考えは合理的です.
もしかすると、すごい友達はきっと見て、childの対象を構築します:
まず、childの親クラスのコンストラクション関数を呼び出すので、最初は1を出力し、次にchildがオブジェクトを構築する前にchildの関連データ(Mysomething)を初期化し、
このときsomethingのコンストラクション関数が呼び出され,また2が出力され,最後にchildのコンストラクション関数が出力されるので,1が出力される.
ええ、確かに123ですね.もしみんながこのレベルを考えることができるとしたら、継承については本当に何も言うことはありませんが、他の友达の世話をするために、やはり言うことにしました.
やはり上記の例では、構造関数を加えて何が起こるかを見てみましょう.
【3】手順3
 1 #include <iostream>
 2 using namespace std;
 3 
 4 class parent
 5 {
 6 public:
 7     parent()
 8     { cout<<"1"<<endl; }
 9     ~parent()
10     { cout<<"~1"<<endl; }
11 };
12  
13 class something
14 {
15 public:
16    something()
17    { cout<<"2"<<endl; }
18    ~something()
19    { cout<<"~2"<<endl; }
20 };
21  
22 class child : public parent
23 {
24 public:
25    child()
26    { cout<<"3"<<endl; }
27    virtual ~child()
28    { cout<<"~3"<<endl; }
29 protected:
30    something Mysometing;
31 };
32  
33 void main()
34 {
35   child * point = new child();
36   delete point;
37 }
38 /*
39 1
40 2
41 3
42 ~3
43 ~2
44 ~1
45 */

上記の例を通して、何が出力されるか考えられるはずです.
child * point = new child();オブジェクトを構築するので,プログラムがこのコードを実行すると,上記の例と同様に123が自然に出力される.
しかし,プログラムがdelete pointを実行すると,対応する構造関数が逐一呼び出されるが,どこから始まるのか.
もちろんchildから始め,その後関連データを解析し,最後に親クラスを解析する.
上の構造関数のvirtualを削除するとどうなるかを考えてみましょう.出力は同じです.
【4】プログラム4
 1 #include <iostream>
 2 using namespace std;
 3 
 4 class parent
 5 {
 6 public:
 7     parent()
 8     { cout<<"1"<<endl; }
 9     ~parent()
10     { cout<<"~1"<<endl; }
11 };
12  
13 class something
14 {
15 public:
16    something()
17    { cout<<"2"<<endl; }
18    ~something()
19    { cout<<"~2"<<endl; }
20 };
21  
22 class child : public parent
23 {
24 public:
25    child()
26    { cout<<"3"<<endl; }
27    ~child()
28    { cout<<"~3"<<endl; }
29 protected:
30    something Mysometing;
31 };
32  
33 void main()
34 {
35   child * point = new child();
36   delete point;
37 }
38 /*
39 1
40 2
41 3
42 ~3
43 ~2
44 ~1
45 */

実行フラグメントを変更します.
【5】手順5
 1 #include <iostream>
 2 using namespace std;
 3 
 4 class parent
 5 {
 6 public:
 7     parent()
 8     { cout<<"1"<<endl; }
 9     ~parent()
10     { cout<<"~1"<<endl; }
11 };
12  
13 class something
14 {
15 public:
16    something()
17    { cout<<"2"<<endl; }
18    ~something()
19    { cout<<"~2"<<endl; }
20 };
21  
22 class child : public parent
23 {
24 public:
25    child()
26    { cout<<"3"<<endl; }
27    ~child()
28    { cout<<"~3"<<endl; }
29 protected:
30    something Mysometing;
31 };
32  
33 void main()
34 {
35   parent * point = new child();
36   delete point;
37 }
38 /*
39 1
40 2
41 3
42 ~1
43 */

どこが違うの?どうしてこんなことになったの?
オブジェクトを構築するときに3つの構造関数を呼び出し、スタック上で構築しました.
だから、資源を回収する時、すべての資源を回収しなければならない.メモリが漏れる.
そして、上記の例では、メモリが1つしか回収されていません.これは自然に伝説のメモリが漏れてしまいます.
大きなプログラムに使えば、深刻な結果をもたらす!!!
もちろん,すべての解析関数の前にvirtualを厳密に加えると,出力は正常になる.
では、virtualの重要性が分かったのではないでしょうか.
別の形式を見てみましょう
【6】プログラム6
 1 #include <iostream>
 2 using namespace std;
 3 
 4 class parent
 5 {
 6 public:
 7     parent()
 8     { cout<<"1"<<endl; }
 9     void A()
10     { cout<<"this is parent"<<endl; }
11     ~parent()
12     { cout<<"~1"<<endl; }
13 };
14  
15 class something
16 {
17 public:
18     something()
19     { cout<<"2"<<endl; }
20     void B()
21     { cout<<"this is something"<<endl; }
22     ~something()
23     { cout<<"~2"<<endl; }
24 };
25  
26 class child : public parent, public something
27 {
28 public:
29     child()
30     { cout<<"3"<<endl; }
31     virtual ~child()
32     { cout<<"~3"<<endl; }
33 };
34  
35 void main()
36 {
37      child * point = new child();
38      point->A();
39      point->B();
40      delete point;
41 }
42 /*
43 1
44 2
45 3
46 this is parent
47 this is something
48 ~3
49 ~2
50 ~1
51 */

次のように変更します.
【7】手順7
 1 #include <iostream>
 2 using namespace std;
 3 
 4 class parent
 5 {
 6 public:
 7     parent()
 8     { cout<<"1"<<endl; }
 9     void A()
10     { cout<<"this is parent"<<endl; }
11     ~parent()
12     { cout<<"1"<<endl; }
13 };
14  
15 class something
16 {
17 public:
18     something()
19     { cout<<"2"<<endl; }
20     void A()
21     { cout<<"this is something"<<endl; }
22     ~something()
23     { cout<<"2"<<endl; }
24 };
25  
26 class child : public parent, public something
27 {
28 public:
29     child()
30     { cout<<"3"<<endl;}
31     virtual ~child()
32     { cout<<"3"<<endl;}
33 };
34  
35 void main()
36 {
37      child * point = new child();
38      // point->A(); // 
39      delete point;
40 }

このプログラムはコンパイルされますが、メソッド名の二義性が現れるので、なぜですか?
parentでメソッドAを定義し、somethingでメソッドAを定義したので、この2つのクラスはchildの親です.
childがAメソッドを呼び出すとコンパイラが隠れてしまいますが、いったいそれを使うのでしょうか.
もしかすると私たちはsomethingを継承して実はただ彼のA方法を呼び出したいだけなので、私たちはこのように解決することができます:
【8】プログラム8
1 void main()
2 {
3      child * point = new child();
4      point->something::A();// something 
5      point->parent::A();// parent 
6      delete point;
7 }

もちろん、上記のように、私たちはsomethingを継承して、実際には彼を呼び出してAメソッドを再利用したいだけです.だから、私たちはそんなに面倒ではありません.私たちはこのようにこの方法を書き直すことができます.
【9】プログラム9
 1 #include <iostream>
 2 using namespace std;
 3 
 4 class parent
 5 {
 6 public:
 7     parent()
 8     { cout<<"1"<<endl; }
 9     void A()
10     { cout<<"this is parent"<<endl; }
11     ~parent()
12     { cout<<"~1"<<endl; }
13 };
14  
15 class something
16 {
17 public:
18     something()
19     { cout<<"2"<<endl; }
20     void A()
21     { cout<<"this is something"<<endl; }
22     ~something()
23     { cout<<"~2"<<endl; }
24 };
25  
26 class child : public parent, public something
27 {
28 public:
29     child()
30     { cout<<"3"<<endl;}
31     virtual void A()
32     { something::A(); }
33     virtual ~child()
34     { cout<<"~3"<<endl;}
35 };
36  
37 void main()
38 {
39      child * point = new child();
40      point->A(); 
41      delete point;
42 }
43 /*
44 1
45 2
46 3
47 this is something
48 ~3
49 ~2
50 ~1
51 */

これで私たちの上の問題を完璧に解決することができます.では、皆さんはどのようにしてこのような現象があるのだろうか.
例えば、小鳥、猫、フクロウ、彼らは同じ動物に属しています.
彼らはすべて食べ物を食べて、すべて寝て、フクロウは猫と同じようにネズミを食べて、小鳥はできません.
しかし、フクロウや小鳥は鳥のようなもので、彼らの働き方はそれほど悪くないはずです(私も推測していますが、どうせここは例として挙げているだけです).
今、フクロウは小鳥の属性だけでなく、猫の特性も持っているので、猫と小鳥を受け継ぐことができます.私たちはどのように実現すればいいのでしょうか.
【10】プログラム10
 1 #include <iostream>
 2 using namespace std;
 3 
 4 class  
 5 {
 6 public:
 7     virtual  void  ()= 0;
 8     virtual  void  ()= 0;
 9 };
10  
11 class   : public  
12 {
13 public:
14     virtual void  ()
15     { 
16         // to do
17     }
18     virtual void  ()
19     { 
20         // to do 
21     }
22 };
23  
24 class   : public  
25 {
26 public:
27     virtual void  ()
28     { 
29         //to do 
30     }
31     virtual void   ()
32     { 
33         // to do
34     }
35 };
36  
37 class   : public  , public  
38 {
39 public:
40      virtual void  ()
41     { 
42         //  :: ()
43      }
44     virtual void  ()
45     { 
46         //  :: ()
47     }
48 };

これからは、それぞれ必要なものを取り、何も書かずに直接呼び出せばいいのですが、私たちの動物というclassは、すべて純虚関数であることに気づくはずです.
ああ、そうだ、純虚関数とは何ですか.虚関数を宣言しながら0に等しくすることで,純粋な虚関数を持つクラスが抽象クラスとなり,抽象クラスは生まれながらにしてベースクラスとなる.
名前の二義性の問題を解決するために、彼は普通のクラスのようにオブジェクトを定義することができなくて、彼の定義した方法はすべて派生クラスの中で実現して、異なる要求によって実現します. 
 
Good  Good  Study, Day  Day  Up.
順序選択ループのまとめ