オブジェクト向けの3つの特性の1つ:マルチステート(C++)
6202 ワード
目次
多態の定義と実現
抽象クラス
たじょうたいのげんり
単一継承とマルチ継承の関係の虚関数テーブル
多態の定義と実現
1、多態とは何ですか.
異なるオブジェクトが動作を完了すると、異なる結果が生成されます.マルチステートは、異なる継承関係のクラスオブジェクトが同じ関数を呼び出すと、異なる動作が発生します.
たとえば、StudentクラスはPersonクラスを継承します.Person対象のチケットは全額、Student対象のチケットは半額です.これが多態行為です.
2、多態を構成する二つの必要条件関数を呼び出すオブジェクトは、ポインタまたは参照でなければなりません.ポインタや参照を使用しない場合は、ベースクラスタイプ(スライス)にオブジェクトが渡され、親が自分の虚表を子クラスの虚表に変更するのは合理的ではありません. によって呼び出された関数は、虚関数である必要があり、虚関数の書き換えが完了している.
虚関数:クラスのメンバー関数の前にキーワードvirtualを追加します.
虚関数の書き換え:派生クラスにはベースクラスと完全に同じ虚関数があり、彼らの関数名、パラメータ、戻り値は同じで、私たちはサブクラスの虚関数と呼んでベースクラスの虚関数を書き換えた.また,虚関数の書き換えを虚関数の上書きと呼ぶ.
単純なマルチステートの例を実装します.
3、書き換え
虚関数の書き換えでは、派生クラスで書き換えられたメンバー関数にvirtualキーワードを付けなくてもよい.継承後、ベースクラスの虚関数が継承されたため、派生クラスでは虚関数属性が維持され、書き換えただけだ.しかし、これは非常に規範的ではありません.私たちは普段このように使用しないでください.
虚関数書き換えには例外があります.書き換えた虚関数の戻り値は異なりますが、ベースクラスポインタと派生クラスポインタ、またはベースクラス参照と派生クラス参照でなければなりません.このような行為を協変と呼ぶ.
構造関数の書き換え問題:ベースクラスの構造関数が虚関数と書かれている場合、継承された派生クラスには構造関数が書き換えられていますか?ここでは関数名が異なるように見え,書き換えのルールに反しているが,コンパイラが解析関数の書き換えを特殊に処理し,コンパイル後の解析関数の名前がdestructorに統一的に処理されていると理解できる.これは,ベースクラスの構造関数が虚関数と書くことが望ましいことも示している.
なぜ解析関数を虚関数と書くのですか?コンストラクション関数がvirtualを使用せず、ダイナミックバインドを使用する場合、コンストラクション時に派生クラスの部分は無視されます.派生クラスで空間の開拓を行い、派生クラスのプロファイルで解放すると、派生クラスのプロファイルを呼び出さないとメモリが漏洩します.
4、インタフェースの継承と実現
実装継承:一般関数の継承は実装継承であり、派生クラスはベースクラス関数を継承し、関数を使用することができ、継承は関数の実装である.
インタフェース継承:虚関数の継承はインタフェース継承であり、派生クラスはベースクラス虚関数のインタフェースを継承し、書き換えることを目的とし、多態を達成し、継承するのはインタフェースである.したがって,マルチステートを実装しない場合は,関数を虚関数として定義しない.
継承が達成した目的は継承を実現することであり,マルチステートはインタフェース継承である.
5、リロード、書き換え(上書き)、再定義(非表示)の対比
再ロード:の2つの関数は、同じ役割ドメインにあります. 関数名は同じで、パラメータの個数またはタイプは異なります.
書き換え(上書き):の2つの関数は、2つの異なる役割ドメインにあります. 関数名、パラメータ、戻り値は同じです(コヒーレントを除く). の両方の関数は虚関数でなければなりません.
再定義(非表示):の2つの関数は、2つの異なる役割ドメインにあります. 関数名は同じです. 上記条件が成立すると,書き換えでなければ必ず隠蔽を構成する.
抽象クラス
純虚関数:虚関数の後に=0と書くと、この関数は純虚関数です.関数のみを宣言し、実装しません.派生クラスでのみ関数が実装されます.
純虚関数の目的:虚関数を強制的に書き換える.
抽象クラス:純粋な虚関数を含むクラスを抽象クラス(インタフェースクラスとも呼ばれる)と呼ぶ.抽象クラスはオブジェクトをインスタンス化することができず、派生クラスが継承する後もオブジェクトをインスタンス化することができず、 派生クラスで継承された抽象クラスの純粋な虚関数を書き換えると、オブジェクトをインスタンス化できます. は、インタフェースの継承をさらに示している.
C++11はまた、overrideと荮nalを提供して虚関数を修飾する.
override:
虚関数の意味は多態を実現することであり,書き換えなければ虚関数には意味がない.したがって,C++11では純虚関数+override方式を用いて虚関数の書き換えを強制した.
override修飾の派生クラス虚関数が書き換えられていないと、コンパイルエラーが発生します.
final:
final修飾ベースクラスの虚関数は派生クラスによって書き換えられない.
たじょうたいのげんり
1、虚関数表(虚表)
虚関数テーブルの本質は、虚関数ポインタを格納するポインタ配列であり、この配列の一番後ろにnullptrが置かれている.虚関数ポインタはクラス内の虚関数のアドレスであり,これらの虚関数のアドレスはこのポインタ配列に格納される.
クラスオブジェクトには非表示のメンバーポインタがあります.vfptrは、虚関数テーブルポインタと呼ばれ、このポインタは虚関数テーブルを指します.
派生クラスのダミーテーブル生成:
a.ベースクラスの虚表内容を派生クラス虚表にコピーする
b.派生クラスがベースクラスの虚関数を書き換えた場合、派生クラス独自の虚関数で虚表のベースクラスの虚関数を上書きする
c.派生クラス自身が新たに増加した虚関数は、派生クラスにおける宣言順に派生クラス虚表の最後に増加する
虚関数はどこにありますか?
虚関数は普通の関数と同じように、コードセグメントが存在しますが、彼のポインタは虚表に保存されています.
虚表はどこにありますか?
vsで検証したところ、ダミーテーブルにコードセグメントが存在することが分かった.
虚関数テーブルポインタはどこにありますか?
虚関数ポインタはオブジェクトに格納されるので、虚関数ポインタの位置はオブジェクトの位置に従って歩くオブジェクトがスタック上に作成され、虚関数ポインタがスタック上に存在する.オブジェクトがスタックに作成され、虚関数ポインタがスタックに存在します.
2、多態の原理
実際のマルチステートは異なるオブジェクトであり、呼び出し時に虚関数テーブルを検索し、呼び出す関数を見つけます.
派生クラスでは,派生クラスの虚関数テーブルの書き換えが完了しているため,同じ関数が呼び出されているにもかかわらず虚テーブルは異なり,異なる動作が完了し,異なる形態を示している.
マルチステートを満たす関数呼び出しは、コンパイル時に決定されるものではなく、実行後にオブジェクトの中に探しに行きます.マルチステートを満たさない関数呼び出しは、コンパイル時に確認されます.
3、動的バインドと静的バインド
1)静的バインド(前期バインド/早期バインド):プログラムのコンパイル中にプログラムの動作が決定され、静的マルチステートとも呼ばれます.たとえば、関数のリロード
2)動的バインディング(後期バインディング/晩バインディング):プログラムの実行中に、具体的に入手したタイプに基づいてプログラムの具体的な動作を決定し、具体的な関数を呼び出し、動的マルチステートとも呼ばれる.
単一継承とマルチ継承の関係の虚関数テーブル
1、単一継承下の虚関数表派生クラスの虚関数テーブルは、ベースクラスからコピーされた虚関数テーブルを先に格納し、書き換えた関数でベースクラスに対応する関数を上書きします.テーブルの後ろに、派生クラス独自の宣言順に、独自の虚関数アドレスを追加します. vsのコンパイラの下で監視されているウィンドウには、派生クラス独自の虚関数が虚関数テーブルに表示されず、虚関数テーブルを印刷することで表示されます. 虚関数テーブルポインタは、nullptrで終わるオブジェクトの最初の4バイトに格納され、1つの関数ポインタ配列に相当します.
2、多継承下の虚関数表派生クラスを継承する書き換えられていない虚関数は、最初の継承ベースクラス部分の虚関数テーブルの に配置される.
多態の定義と実現
抽象クラス
たじょうたいのげんり
単一継承とマルチ継承の関係の虚関数テーブル
多態の定義と実現
1、多態とは何ですか.
異なるオブジェクトが動作を完了すると、異なる結果が生成されます.マルチステートは、異なる継承関係のクラスオブジェクトが同じ関数を呼び出すと、異なる動作が発生します.
たとえば、StudentクラスはPersonクラスを継承します.Person対象のチケットは全額、Student対象のチケットは半額です.これが多態行為です.
2、多態を構成する二つの必要条件
虚関数:クラスのメンバー関数の前にキーワードvirtualを追加します.
虚関数の書き換え:派生クラスにはベースクラスと完全に同じ虚関数があり、彼らの関数名、パラメータ、戻り値は同じで、私たちはサブクラスの虚関数と呼んでベースクラスの虚関数を書き換えた.また,虚関数の書き換えを虚関数の上書きと呼ぶ.
単純なマルチステートの例を実装します.
class Person
{
public:
virtual void BuyTicket()
{
cout << " " << endl;
}
};
class Student : public Person
{
public:
virtual void BuyTicket()
{
cout << " " << endl;
}
};
void Func(Person& people)
{
people.BuyTicket();
}
void Test()
{
Person p;
Func(p);
Student s;
Func(s);
}
3、書き換え
虚関数の書き換えでは、派生クラスで書き換えられたメンバー関数にvirtualキーワードを付けなくてもよい.継承後、ベースクラスの虚関数が継承されたため、派生クラスでは虚関数属性が維持され、書き換えただけだ.しかし、これは非常に規範的ではありません.私たちは普段このように使用しないでください.
虚関数書き換えには例外があります.書き換えた虚関数の戻り値は異なりますが、ベースクラスポインタと派生クラスポインタ、またはベースクラス参照と派生クラス参照でなければなりません.このような行為を協変と呼ぶ.
//
class A{};
class B : public A{};
class Person
{
public:
virtual A* f()
{
return new A;
}
};
class Student : public Person
{
public:
virtual B* f()
{
return new B;
}
};
構造関数の書き換え問題:ベースクラスの構造関数が虚関数と書かれている場合、継承された派生クラスには構造関数が書き換えられていますか?ここでは関数名が異なるように見え,書き換えのルールに反しているが,コンパイラが解析関数の書き換えを特殊に処理し,コンパイル後の解析関数の名前がdestructorに統一的に処理されていると理解できる.これは,ベースクラスの構造関数が虚関数と書くことが望ましいことも示している.
class Person
{
public:
virtual ~Person()
{
cout << "~Person()" << endl;
}
};
class Student : public Person
{
public:
virtual ~Student()
{
cout << "~Student()" << endl;
}
};
// Student Person , delete , , p1 p2 。
int main() {
Person* p1 = new Person;
Person* p2 = new Student;
delete p1; // ,
delete p2;
// :
// ~Person()
// ~Student()
return 0;
}
なぜ解析関数を虚関数と書くのですか?コンストラクション関数がvirtualを使用せず、ダイナミックバインドを使用する場合、コンストラクション時に派生クラスの部分は無視されます.派生クラスで空間の開拓を行い、派生クラスのプロファイルで解放すると、派生クラスのプロファイルを呼び出さないとメモリが漏洩します.
// , .
// , , , , 。
class Person
{
public:
~Person()
{
cout << "~Person()" << endl;
}
};
class Student : public Person
{
public:
~Student()
{
cout << "~Student()" << endl;
}
};
int main() {
Person* p1 = new Person;
Person* p2 = new Student;
delete p1;
delete p2;
// :
// ~Person()
// ~Person()
// ,
return 0;
}
4、インタフェースの継承と実現
実装継承:一般関数の継承は実装継承であり、派生クラスはベースクラス関数を継承し、関数を使用することができ、継承は関数の実装である.
インタフェース継承:虚関数の継承はインタフェース継承であり、派生クラスはベースクラス虚関数のインタフェースを継承し、書き換えることを目的とし、多態を達成し、継承するのはインタフェースである.したがって,マルチステートを実装しない場合は,関数を虚関数として定義しない.
継承が達成した目的は継承を実現することであり,マルチステートはインタフェース継承である.
5、リロード、書き換え(上書き)、再定義(非表示)の対比
再ロード:
書き換え(上書き):
再定義(非表示):
抽象クラス
純虚関数:虚関数の後に=0と書くと、この関数は純虚関数です.関数のみを宣言し、実装しません.派生クラスでのみ関数が実装されます.
純虚関数の目的:虚関数を強制的に書き換える.
抽象クラス:純粋な虚関数を含むクラスを抽象クラス(インタフェースクラスとも呼ばれる)と呼ぶ.
class A
{
public:
virtual void Func() = 0; // ,
};
class A1 : public A
{
public:
virtual void Func() //
{
cout << "A1" << endl;
}
};
class A2 : public A
{
public:
virtual void Func() //
{
cout << "A2" << endl;
}
};
int main()
{
A* a1 = new A1;
A* a2 = new A2;
a1->Func();
a2->Func();
return 0;
}
C++11はまた、overrideと荮nalを提供して虚関数を修飾する.
override:
虚関数の意味は多態を実現することであり,書き換えなければ虚関数には意味がない.したがって,C++11では純虚関数+override方式を用いて虚関数の書き換えを強制した.
override修飾の派生クラス虚関数が書き換えられていないと、コンパイルエラーが発生します.
final:
final修飾ベースクラスの虚関数は派生クラスによって書き換えられない.
たじょうたいのげんり
1、虚関数表(虚表)
虚関数テーブルの本質は、虚関数ポインタを格納するポインタ配列であり、この配列の一番後ろにnullptrが置かれている.虚関数ポインタはクラス内の虚関数のアドレスであり,これらの虚関数のアドレスはこのポインタ配列に格納される.
クラスオブジェクトには非表示のメンバーポインタがあります.vfptrは、虚関数テーブルポインタと呼ばれ、このポインタは虚関数テーブルを指します.
派生クラスのダミーテーブル生成:
a.ベースクラスの虚表内容を派生クラス虚表にコピーする
b.派生クラスがベースクラスの虚関数を書き換えた場合、派生クラス独自の虚関数で虚表のベースクラスの虚関数を上書きする
c.派生クラス自身が新たに増加した虚関数は、派生クラスにおける宣言順に派生クラス虚表の最後に増加する
虚関数はどこにありますか?
虚関数は普通の関数と同じように、コードセグメントが存在しますが、彼のポインタは虚表に保存されています.
虚表はどこにありますか?
vsで検証したところ、ダミーテーブルにコードセグメントが存在することが分かった.
虚関数テーブルポインタはどこにありますか?
虚関数ポインタはオブジェクトに格納されるので、虚関数ポインタの位置はオブジェクトの位置に従って歩くオブジェクトがスタック上に作成され、虚関数ポインタがスタック上に存在する.オブジェクトがスタックに作成され、虚関数ポインタがスタックに存在します.
2、多態の原理
実際のマルチステートは異なるオブジェクトであり、呼び出し時に虚関数テーブルを検索し、呼び出す関数を見つけます.
派生クラスでは,派生クラスの虚関数テーブルの書き換えが完了しているため,同じ関数が呼び出されているにもかかわらず虚テーブルは異なり,異なる動作が完了し,異なる形態を示している.
マルチステートを満たす関数呼び出しは、コンパイル時に決定されるものではなく、実行後にオブジェクトの中に探しに行きます.マルチステートを満たさない関数呼び出しは、コンパイル時に確認されます.
3、動的バインドと静的バインド
1)静的バインド(前期バインド/早期バインド):プログラムのコンパイル中にプログラムの動作が決定され、静的マルチステートとも呼ばれます.たとえば、関数のリロード
2)動的バインディング(後期バインディング/晩バインディング):プログラムの実行中に、具体的に入手したタイプに基づいてプログラムの具体的な動作を決定し、具体的な関数を呼び出し、動的マルチステートとも呼ばれる.
単一継承とマルチ継承の関係の虚関数テーブル
1、単一継承下の虚関数表
2、多継承下の虚関数表