今日、私はC ++のポリモーフィズムを調べましたが、それが明確ではないことがわかりました。
ポリモーフィズムとは
ポリモーフィズムは文字通り複数の状態を意味し、継承されたクラスに表示されます。例を見てみましょう:
静的ポリモーフィズム
#include<iostream>
using namespace std;
class A {
public:
A():i(10){
}
void f() {
cout<< "A::f()"<<i<<endl;}
int i;
};
class B : public A{
public:
B():j(20) {
}
void f() {
cout << "B::f()" << j <<endl;}
int j;
};
class C : public A{
public:
C():q(30) {
}
void f() {
cout << "C::f()" << q <<endl;}
int q;
};
int main()
{
A a;
B b;
C c;
A *p = &b;
A *q = &c;
p->f();
q->f();
return 0;
}
この場合、プログラムは何を出力しますか?
結果はすべてであることがわかりますA::f()10
。
これは、コンパイル時に基本クラスの関数であるAが出力関数として使用されることが決定されているため、基本クラスのポインターを使用してサブクラスオブジェクトを指すためです。引き続き基本クラスの関数を呼び出します。これは静的リンクは静的ポリモーフィズムとも呼ばれます。
次の例を見てください。
動的ポリモーフィズム
同じプログラムで、基本クラスのf関数にvirtualを追加し、それを仮想関数として定義します。
#include<iostream>
using namespace std;
class A {
public:
A():i(10){
}
virtual void f() {
cout<< "A::f()"<<i<<endl;}
int i;
};
class B : public A{
public:
B():j(20) {
}
void f() {
cout << "B::f()" << j <<endl;}
int j;
};
class C : public A{
public:
C():q(30) {
}
void f() {
cout << "C::f()" << q <<endl;}
int q;
};
int main()
{
A a;
B b;
C c;
A *p = &b;
A *q = &c;
p->f();
q->f();
return 0;
}
操作の結果は次のとおりです。
B::f()20
C::f()30
このとき、ポインタはサブクラスの関数を指しています。これはあるに結合ダイナミック多型とも呼ばれる、ダイナミックな多型。
仮想機能
仮想関数を使って動的ポリモーフィズムを実現すると書いたので、次に記憶の中で何が起こっているのかを分析してみましょう。
通常のクラスサイズを出力する場合、次に例を示します。
class A{
A(){
}
void print(){
cout<<"1"<<endl;}
}
int main(){
cout<<sizeof(A)<<endl;
}
次に、印刷関数を仮想関数に変換し、そのサイズを出力します。
class A{
A(){
}
virtual void print(){
cout<<"1"<<endl;}
}
int main(){
cout<<sizeof(A)<<endl;
}
仮想関数のクラスが通常の関数のクラスよりも少し大きいことがわかります。なぜですか?
仮想関数を持つクラスの各オブジェクトには、仮想関数を格納するテーブルへのポインターが接頭辞として付けられるため、これをvtableと呼び、このテーブルはすべてのオブジェクトで共有されます。
vtable
前述のように、ポリモーフィズムを実現するために、C ++は動的バインディングメソッドを使用します。このメソッドのコアはvtable(仮想関数テーブル)です。仮想関数を持つ各クラスには、仮想関数テーブルがあります。このクラスのすべてのオブジェクトは、仮想関数テーブルを共有します。仮想関数テーブルは、実際には仮想関数へのポインターの配列です。
たとえば、次のコード:
class A {
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m_data1, m_data2;
};
その仮想テーブルは次のとおりです。
仮想テーブルポインタ
仮想関数テーブルはクラスに属しているため、すべてのオブジェクトが仮想関数テーブルを共有します。各オブジェクトは、どの仮想関数テーブルであるかをどのように認識しますか?*各オブジェクトは、仮想テーブルポインタ__vptrを介してその仮想関数テーブルを指します。
動的バインディング
これは、C ++が仮想関数テーブルと仮想テーブルポインターを使用して動的バインディングを実現する方法を示す例です。
class A {
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m_data1, m_data2;
};
class B : public A {
public:
virtual void vfunc1();
void func1();
private:
int m_data3;
};
class C: public B {
public:
virtual void vfunc2();
void func2();
private:
int m_data1, m_data4;
};
継承関係により、以下の結果が得られます。
仮想関数と純粋仮想関数
class A{
virtual void A(){
cout<<"A"<<endl;}
virtual void B()=0;
}
上記は、仮想関数と純粋仮想関数の共存の例です。それらの違いは、仮想関数が関数の実装を提供するため、サブクラスはこの関数をオーバーライドするか直接継承するかを選択できますが、純粋仮想関数はこの関数を実装するためにサブクラスを必要とします。
抽象クラス
純粋仮想関数を持つクラスは抽象クラスであり、抽象クラスは行動規範の定義と同等であり、基本クラスとしてのみ使用できます。たとえば、サブクラスがいくつかのアクションを完了しなければならないことを望む場合、これらのアクションを純粋仮想関数として記述します。
抽象クラスを継承するが純粋仮想関数を実装しないものは、依然として抽象クラスであることに注意してください。
巨人の肩の上:
1。仮想関数テーブルと仮想関数ポインターについて