C++中多態の定義と詳細な説明を実現する。

10053 ワード

1.多形概念
1.1概念
  • 多形の概念:通俗的には、複数の形態であり、具体的には、ある行為を完了することで、異なる対象が完成すると、異なる状態が生じる。
  • 栗を挙げます。切符を買う時、普通の人が買う時、全部の値段で買います。学生は切符を買う時、半額で切符を買います。軍人が切符を買う時は優先的に切符を買う。同じ事柄は異なる人や状況に対して異なる結果や形態がある。
  • 2.多形の定義と実現
    2.1多形の構成条件
    多形は、異なる継承関係にあるクラスのオブジェクトで、同じ関数を呼び出して、異なる挙動を生み出します。例えばStudentがPersonを継承した。
    Personの対象者は全価格で、Studentの対象者は半額で切符を買います。
    注意:継承の中で多形を構成するには二つの条件があります。
  • は、ベースクラスのポインタまたは参照によって虚数関数を呼び出す必要があります。
  • で呼び出された関数は、**虚関数でなければならず、派生クラスは、ベースクラスの虚関数を書き換えなければならない。
  • 2.2虚関数
    虚関数:すなわちvirtualで修飾されたクラスのメンバー関数を虚関数と呼びます。
    
    class Person {
    public:
    	virtual void BuyTicket() { cout << "  -  " << endl;}
    };
    
    2.3虚関数の書き換え
  • 虚関数の書き換え(上書き):派生クラスには、基本種類と全く同じ虚関数(すなわち、派生種虚関数とベース種類虚関数の戻り値タイプ、関数名、パラメータリストが完全に同じ)があり、サブクラスの虚関数は、ベースクラスの虚関数を書き換えた。
  • 注意:
  • は、基質虚関数の書き換えにおいて、派生種の虚関数がvirtualキーワードを付加しない場合には、書き換えを構成することもできるが(引き継ぎ基質の虚関数が引き継がれたため、派生クラスでは虚関数属性が維持されている)、このような表記はあまり規範化されていないので、このように使用することは推奨されない。
  • 
    class Person {
    public:
    	virtual void BuyTicket() { cout << "  -  " << endl; }
    };
    class Student : public Person {
    public:
    	void BuyTicket() { cout << "  -  " << endl; }
    };
    
    2.4コード例
    2.4.1書き換えの構成がない

    2.4.2構成書き換え

    2.5虚関数の書き換えの2つの例外
     2.5.1協働
    派生クラスがベース種類の虚関数を書き換えるときは、ベース種類の虚関数の戻り値タイプとは異なります。基質虚関数は基質オブジェクトのポインタまたは参照を返し、派生種虚関数は派生類オブジェクトのポインタまたは参照を返した場合、協働と呼びます。

     2.5.2構文関数の書き換え
    マトリックスのコンストラクタが虚関数である場合、派生型のコンストラクタは定義されている限り、ビゲートキーの有無にかかわらず、ベースクラスのコンストラクタと書き換えられます。関数名は違っていますが、書き換えのルールに違反しているように見えますが、実はそうではないです。ここではコンパイラは、コンストラクタの名前を特殊処理し、コンパイル後のコンストラクタの名前をデストローとしてまとめて処理していると理解できます。

    注意:コンストラクタはコンパイル後、関数名がdestructorに統一されます。virtualをプラスしないと再定義(非表示)されます。上述のようにコードがコンストラクションの書き換えを構成しないと、解析構p 2の時にコンストラクションStudentだけができます。この時は再定義になりますので、Personの中の分析を自動的に呼び出さず、リソースが漏れてしまいます。
    2.6 C+11 overrideとfinal
  • C++は関数の書き換えに対して厳しい要求がありますが、場合によってはうっかりして、関数名のアルファベット順の書き込みができなくなることがあります。このようなエラーはコンパイルの間には報告されません。プログラムの実行時に予想された結果が得られなかっただけで、デバッグができます。
  • したがって、C+11はoverrideとfinalの2つのキーワードを提供しており、書き換えるかどうかをユーザに確認することができる。
  • 1.final:虚関数を修飾し、虚関数はこれ以上継承されないことを表します。

    2.override:派生種類の虚関数が基質の虚関数を書き換えたかどうかを確認し、もし書き換えコンパイルがなかったらエラーを報告します。

    2.7リロード、上書き(書き換え)、隠し(再定義)の比較


    3.抽象類
    3.1概念
    虚数関数の後に0を書くと、この関数は純粋な虚数関数です。純粋な虚数関数を含むクラスは抽象類(インターフェース類ともいう)と呼ばれ、抽象類は対象をインスタンス化できない。派生クラスを引き継いだ後も例を挙げてオブジェクトを現像することはできません。純粋な虚数関数を書き換えてこそ、派生クラスはオブジェクトをインスタンス化することができます。純粋な虚数関数は,派生種を規定するためには書き換えなければならず,また純粋な虚数関数は,界面継承をより具現化している。
     
    3.2インターフェースの継承と継承の実現
  • 通常関数の継承は、継承を実現するものであり、派生クラスは基底関数を継承し、関数を使用してもよく、継承は関数の実現である。
  • 虚関数の継承はインタフェースの継承であり、派生クラスは基底系虚関数のインターフェースを継承しています。目的は書き換えのため、多状態を達成するため、継承はインターフェースです。したがって、多状態を実現しない場合は、関数を虚関数として定義しないでください。
  • 4.多形の原理
    4.1虚関数表

    観察テストによって、bオブジェクトは8 bytesであることが分かりました。bメンバー、もう一つ。vfptrは対象の前に置く(一部のプラットフォームは対象の一番後ろに置くことがあります。これはプラットフォームと関係があります。)オブジェクトの中のこのポインタは虚関数表ポインタ(vはvirtual、fはfunctionを表します。)といいます。虚関数が含まれているクラスの中には少なくとも虚関数の表ポインタがあります。虚関数の住所は虚函数表に入れられます。虚関数の表も虚表と略称します。

    虚関数表の針(虚表の針と略称する)
    虚関数表は本質的には、ポインタ配列(ポインタは虚関数ポインタ)であり、(虚基本テーブル−>菱形継電器−保存のオフセット量)である。

    基本類と派生類における虚関数表。

    まとめ:
  • ベースのbオブジェクトと派生タイプのdオブジェクトの虚数表は違っています。ここでFnc 1は書き換えが完了していることが分かりました。ですから、dの虚数表には書き換えのDeriveが保存されています。書き直しは文法の呼び方で、覆っては原理層の呼び方です。
  • はまたFnc 2が継承されて虚関数となりますので、虚表に入れてFnc 3も継承しましたが、虚関数ではないので虚表には入れません。
  • 虚関数テーブルは、本質的には虚関数ポインタを持つポインタ配列であり、この配列の一番後ろにnullptrが置かれている。
  • 派生種の虚表生成:
    a.まず基本類の虚表の内容を派生類虚表にコピーする。
    b.派生クラスが基質の虚関数を書き換えた場合、派生種自身の虚関数で虚表の基種の虚関数をカバーする。
    c.派生種自体が新たに増加した虚関数は、派生種における声明の順序に従って、派生種虚表の最後に追加される。
  • 虚関数はどこにありますか?虚表はどこにありますか?
  • 虚関数はコードセグメントにあり、虚関数テーブルもコードセグメントにあります。虚函数表に保存されているのは虚関数の住所です。オブジェクトの中には、虚関数テーブルのポインタがあります。
  • 4.2多形の原理


    まとめ:
  • は、このようにして、異なるオブジェクトが同じ行動を完了することを実現した場合、異なる形態を示す。
  • 逆に考えてみると、多状態に到達したいです。二つの条件があります。一つは虚関数で覆われています。一つは対象のポインタですか?それとも参照で虚関数を呼び出しますか?
    虚数関数カバーは、多状態を構成する場合、異なるオブジェクトが対応するクラスの虚数関数テーブルを呼び出すときに、虚数関数のアドレスで対応する虚数関数を見つけるためです。ポインタまたは参照は、対応する虚数関数を探す際に、虚数表で住所を検索します。
  • は、コンパイル時に決定されたのではなく、実行後にオブジェクトの中に探しています。多形の関数が満たされない場合はコンパイルして確認します。すなわち、多形の関数アドレスのコンパイルが満たされていない場合にはすでに決定されていますが、多形を構成する虚数関数は、実行時にアドレスによってコールされます。
  • 4.3動的バインディングと静的バインディング
  • 静的バインディングは、初期バインディング(早期バインディング)とも呼ばれ、プログラムコンパイル中にプログラムの動作を決定し、静的多状態とも呼ばれ、例えば、関数再負荷とも呼ばれる。
  • 動的バインディングは、後期バインディング(後バインディング)とも呼ばれ、プログラムの実行中に、特定の種類に基づいてプログラムの特定の挙動を決定し、特定の関数を呼び出し、動的多状態とも呼ばれる。
  • 5.単一継承と複数継承の関係の虚関数表
    5.1単承継中の虚関数表

    コードで虚表の関数を印刷します。
    
    #include<iostream>
    using namespace std;
    
    class Base {
    public:
    	virtual void func1() { cout << "Base::func1" << endl; }
    	virtual void func2() { cout << "Base::func2" << endl; }
    private:
    	int a;
    };
    class Derive :public Base {
    public:
    	virtual void func1() { cout << "Derive::func1" << endl; }
    	virtual void func3() { cout << "Derive::func3" << endl; }
    	virtual void func4() { cout << "Derive::func4" << endl; }
    private:
    	int b;
    };
    typedef void(*VFPTR) ();
    void PrintVTable(VFPTR vTable[])
    {
    	//                  。              
    	cout << "     >" << vTable << endl;
    	for (int i = 0; vTable[i] != nullptr; ++i)
    	{
    		printf("  %d       :0X%x,->", i, vTable[i]);
    		VFPTR f = vTable[i];
    		f();
    	}
    	cout << endl;
    }
    
    int main()
    {
    	Base b;
    	Derive d;
    	//   :  b、d    4bytes,       ,                          ,           nullptr
    	// 1.  b   ,     int*   
    	// 2.      ,    b   4bytes  ,            
    	// 3.    VFPTR*,         VFPTR  (       )   。
    	// 4.       PrintVTable      
    	// 5.                    ,                ,        nullptr,    ,        。           -    -       ,      。
    	VFPTR * vTableb = (VFPTR*)(*(int*)&b);
    	PrintVTable(vTableb);
    	VFPTR* vTabled = (VFPTR*)(*(int*)&d);
    	PrintVTable(vTabled);
    	return 0;
    }
    
    

    5.2相続中の虚関数表
    
    #include<iostream>
    using namespace std;
    
    class Base1 {
    public:
    	virtual void func1() { cout << "Base1::func1" << endl; }
    	virtual void func2() { cout << "Base1::func2" << endl; }
    private:
    	int b1;
    };
    class Base2 {
    public:
    	virtual void func1() { cout << "Base2::func1" << endl; }
    	virtual void func2() { cout << "Base2::func2" << endl; }
    private:
    	int b2;
    };
    class Derive : public Base1, public Base2 {
    public:
    	virtual void func1() { cout << "Derive::func1" << endl; }
    	virtual void func3() { cout << "Derive::func3" << endl; }
    private:
    	int d1;
    };
    typedef void(*VFPTR) ();
    void PrintVTable(VFPTR vTable[])
    {
    	cout << "     >" << vTable << endl;
    	for (int i = 0; vTable[i] != nullptr; ++i)
    	{
    		printf("  %d       :0X%x,->", i, vTable[i]);
    		VFPTR f = vTable[i];
    		f();
    	}
    	cout << endl;
    }
    int main()
    {
    	Derive d;
    	VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
    	PrintVTable(vTableb1);
    	VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));
    	PrintVTable(vTableb2);
    	return 0;
    }
    
    

    6.関連テーマ
    1.inline関数は虚関数でもいいですか?
    答え:できます。virtual関数はinline関数として書いてもいいです。文法の間違いがありません。虚数関数は、実行時にベースまたはサブクラスの対応関数を呼び出すことを決定します。inline関数はコンパイル中に展開を決定します。虚数関数は、実行時にベースまたはサブクラスの対応関数を呼び出すことを決定します。inline関数はコンパイル中に展開を決定します。虚数関数は、実行時にベースまたはサブクラスの対応関数を呼び出すことを決定します。inline関数はコンパイル中に展開を決定します。
    2.スタティックメンバーは虚数関数でもいいですか?
    答:できません。スタティックメンバー関数にはthisポインタがありませんので、タイプ:メンバー関数の呼び出しが虚関数テーブルにアクセスできません。したがって、スタティックメンバー関数は虚関数テーブルに入れられません。
    3.コンストラクタは虚関数でもいいですか?
    オブジェクト内の虚関数テーブルポインタは構造関数初期化リスト段階で初期化されたものです。
    4.構文関数は虚関数でもいいですか?どのような場面でのコンストラクションは虚関数ですか?
    答え:できます。マトリックスのコンストラクションを虚関数として定義したほうがいいです。
    5.一般関数へのオブジェクトのアクセスが早いですか?それとも虚数関数が早いですか?
    まず普通の相手なら同じくらい早いです。ポインタオブジェクトまたは参照オブジェクトであれば、呼び出しの通常関数は、多状態を構成するので、実行時に虚関数を呼び出して虚函数表を検索する必要があります。
    6.虚函数表はどの段階で生成されましたか?どこにありますか?
    虚函数表はコンパイル段階で生成され、一般的にコードセグメント(定数エリア)が存在します。
    締め括りをつける
    ここでC++中多态の定义と详しい解决を実现する文章をここに绍介します。C++に関する详细な内容は以前の文章を検索してください。また、下の関连する文章を引き続きご覧ください。これからもよろしくお愿いします。