[C++] 仮想関数とポリモーフィズム

多態性

ポリモーフィズムはオブジェクト指向プログラミングの重要な機能の 1 つであり、異なる種類のオブジェクトによって同じメッセージが送受信されると、まったく異なる動作が生じる可能性があることを意味します。ポリモーフィックな実装: 関数のオーバーロード、演算子のオーバーロード、テンプレート、仮想関数。

静的バインディングと動的バインディング

静的バインディング: バインド プロセスはコンパイル フェーズで発生し、呼び出される関数はコンパイル フェーズ中に決定されます。
動的バインディング: プログラムの実行中にバインド処理が実行され、呼び出される関数はプログラムの実行中に決定されます。

仮想関数

仮想関数の概念: 基本クラス内のキーワード virtual でラベル付けされたメンバー関数。
仮想関数の定義:

virtual 函数类型 函数名称(参数列表);

関数が基本クラスで仮想と宣言されている場合、その関数はすべての派生クラスでも仮想になります。動的バインディングは、基本クラス ポインターまたは仮想関数の呼び出しを通じてのみ誘発できます。仮想関数は静的に宣言できません。

#include <iostream>
using namespace std;

class Base
{
    
    
public:
    virtual void Fun1()
    {
    
    
        cout << "Base::Fun1 ..." << endl;
    }
    virtual void Fun2()
    {
    
    
        cout << "Base::Fun2 ..." << endl;
    }
    void Fun3()
    {
    
    
        cout << "Base::Fun3 ..." << endl;
    }
};

class Derived :public Base
{
    
    
public:
    virtual void Fun1()

    {
    
    
        cout << "Derived::Fun1 ,,," << endl;
    }
    virtual void Fun2()
    {
    
    
        cout << "Derived::Fun2 ,,," << endl;
    }
    void Fun3()
    {
    
    
        cout << "Derived::Fun3 ..." << endl;
    }
};

int main()
{
    
    
    Base* p;
    Derived d;

    p = &d;
    p->Fun1();//Fun1是虚函数,基类指针指向派生类对象,调用的是派生类对象的虚函数
    p->Fun2();
    p->Fun3();//Fun3非虚函数,根据p指针实际类型来调用相应类的成员函数
    return 0;
}

仮想デストラクタ

仮想デストラクターが必要になるのはどのような場合ですか? 基本クラス ポインターを介して派生クラス オブジェクトを削除することはできますが、他のユーザーが基本クラス ポインターを介してオブジェクトのデストラクターを呼び出すことを許可する予定であり (削除によってそうするのが通常です)、破棄されたオブジェクトに重要な分析オブジェクトがある場合は、コンストラクターの派生クラスは、基本クラスのデストラクターを仮想関数にする必要があります。

#include <iostream>
using namespace std;

class Base
{
    
    
public:
    virtual void Fun1()
    {
    
    
        cout << "Base::Fun1 ..." << endl;
    }
    virtual void Fun2()
    {
    
    
        cout << "Base::Fun2 ..." << endl;
    }
    void Fun3()
    {
    
    
        cout << "Base::Fun3 ..." << endl;
    }
    Base()
    {
    
    
        cout << "Base ..." << endl;
    }
    virtual ~Base()
    {
    
    
        cout << "~Base ..." << endl;
    }
};

class Derived :public Base
{
    
    
public:
    virtual void Fun1()

    {
    
    
        cout << "Derived::Fun1 ,,," << endl;
    }
    virtual void Fun2()
    {
    
    
        cout << "Derived::Fun2 ,,," << endl;
    }
    void Fun3()
    {
    
    
        cout << "Derived::Fun3 ..." << endl;
    }
    Derived()
    {
    
    
        cout << "Derived ..." << endl;
    }
    //基类析构函数为虚函数   派生类不加virtual也为虚函数
    //如果一个类要作为多态基类,要将析构函数定义成虚函数
    virtual ~Derived()
    {
    
    
        cout << "~Derived ..." << endl;
    }
};

int main()
{
    
    
    Base* p;
    p = new Derived;

    p->Fun1();
    delete p;
    return 0;
}

vtable ポインタ

仮想関数の動的バインディングは、仮想テーブルを通じて実現されます。仮想関数を含むクラスの最初の 4 バイトには、仮想テーブルへのポインタが格納されます。

オブジェクトのスライスと仮想関数

#include <iostream>
using namespace std;

class CObject
{
    
    
public:
    virtual void Serialize()
    {
    
    
        cout << "CObject::Serialize ..." << endl;
    }
};

class CDocument : public CObject
{
    
    
public:
    int data1_;
    void func()
    {
    
    
        cout << "CDocument::func ..." << endl;
        Serialize();
    }
    void Serialize()
    {
    
    
        cout << "CDocument::Serialize ..." << endl;
    }
    CDocument()
    {
    
    
        cout << "CDocument()" << endl;
    }
    CDocument(const CDocument& other)
    {
    
    
        cout << "CDocument(const CDocument& other)" << endl;
    }
};

class CMyDoc : public CDocument
{
    
    
public:
    int data2_;
    void Serialize()
    {
    
    
        cout << "CMyDoc::Serialize ..." << endl;
    }
};

int main()
{
    
    
    CMyDoc mydoc;
    CMyDoc* pmydoc = new CMyDoc;
    cout << "#1 testing" << endl;
    mydoc.func();

    cout << "#2 testing" << endl;
    ((CDocument*)(&mydoc))->func();  

    cout << "#3 testing" << endl;
    pmydoc->func();

    cout << "#4 testing" << endl;
    ((CDocument)mydoc).func();//mydoc对象强制转换为CDocument对象  向上转型  完全将派生类对象转化成了基类对象
    return 0;
}

オーバーロード、上書き、オーバーライド

オーバーロードされるメンバー関数の特徴:

  • 同じスコープ (同じクラス内)。
  • 関数名は同じです。
  • パラメータが異なります。
  • virtual キーワードはオプションです。

オーバーライドとは、派生クラス関数が基本クラス関数をカバーすることを意味し、その特性は次のとおりです。

  • 異なるスコープ (それぞれ派生クラスと基本クラス)。
  • 関数名は同じです。
  • パラメータは同じです。
  • 基本クラス関数には virtual キーワードが必要です。

再定義 (派生クラスと基本クラス)

  • 異なるスコープ (それぞれ派生クラスと基本クラス)。
  • 関数名とパラメーターは、virtual キーワードを除いて同じです。
  • 関数名は同じですが、パラメータが異なります。virtual はオプションです。·

純粋仮想関数

ポリモーフィズムを実現するための仮想関数の前提。共通インターフェイスは基本クラスで定義する必要があり、そのインターフェイスは仮想関数として定義する必要があります。基本クラスのインターフェイスを実装できない場合は、これらのインターフェイスを純粋仮想関数として定義します。意味のある仮想関数定義を基底クラス内で与えることはできず、現時点では純粋仮想関数として記述することができ、その定義は派生クラスに委ねられます。純粋な仮想関数を実装する必要はありません。純粋仮想関数を定義します。

class 类名{
    
    
	virtual 返回值类型 函数名(参数表) = 0}

抽象クラス

抽象クラスは、抽象化と設計の目的で宣言され、関連するデータと動作を継承階層で編成し、派生クラスが必要な動作を確実に持つようにします。一時的に実装できない関数については、純粋仮想関数として宣言し、派生クラスに実装を任せることができます。抽象クラスは基本クラスとしてのみ使用でき、抽象クラスのオブジェクトは宣言できず、コンストラクターを仮想関数にすることはできず、デストラクターを仮想関数にすることはできません。抽象クラスを使用してオブジェクト インスタンスを直接作成することはできませんが、抽象クラスへのポインタと参照を宣言できます。実行時ポリモーフィズムは、抽象クラスへのポインターを使用してサポートされます。基本クラスの純粋仮想関数は派生クラスで実装する必要があります。実装しない場合は、抽象クラスとみなされます。

ポリモーフィズムの利点

  • ポリモーフィズムは、より優れた抽象プログラムに役立ちます。制御モジュールは一般的な問題の処理に集中できます。特定の操作は、実行する特定のオブジェクトに渡されます。

  • ポリモーフィズムはプログラムのスケーラビリティの向上に役立ちます。制御モジュールと操作対象を分離することが可能です。定義したクラスの新しいオブジェクトを追加し、そのオブジェクトを管理できます。新規クラス(既存のクラスから派生したクラス)の新規オブジェクトを追加して管理することができます。

仮想デストラクタ

デストラクタは仮想関数として宣言でき、基本クラス ポインタ削除プログラムは、基本クラス ポインタが指すオブジェクトの型に応じて呼び出されるデストラクタを決定します。基本クラスのデストラクターは仮想関数であり、すべての派生クラスのデストラクターは仮想関数です。コンストラクターは仮想であってはなりません。継承関係を持つクラスの動的オブジェクトを操作する場合は、仮想デストラクターを使用するのが最善です。特に、デストラクターがメモリの解放などの意味のある操作を完了する必要がある場合、デストラクターを純粋な仮想にすることもできます。

インターフェイスのないクラスの場合、抽象クラスとして定義したい場合は、仮想デストラクターを純粋仮想として宣言するだけです。通常、純粋仮想関数を除いて、純粋仮想関数は基本クラスに実装する必要はありません。コンストラクターには実装を与える必要があります (空の実装を与えるだけです)。

#include <iostream>
#include <vector>
using namespace std;

class Shape
{
    
    
public:
    virtual void Draw() = 0;
    virtual ~Shape() {
    
    };
};

class Circle :public Shape
{
    
    
public:
    void Draw()
    {
    
    
        cout << "Circle::Draw() ..." << endl;
    }
    ~Circle()
    {
    
    
        cout << "~Circle ..." << endl;
    }
};


class Square :public Shape
{
    
    
public:
    void Draw()
    {
    
    
        cout << "Square::Draw() ..." << endl;
    }
    ~Square()
    {
    
    
        cout << "~Square ..." << endl;
    }
};

class Rectangle :public Shape
{
    
    
public:
    void Draw()
    {
    
    
        cout << "Rectangle::Draw() ..." << endl;
    }
    ~Rectangle()
    {
    
    
        cout << "~Rectangle ..." << endl;
    }
};

void DrawAllShapes(const vector<Shape*>& v)
{
    
    
    vector<Shape*>::const_iterator it;
    for (it = v.begin(); it != v.end(); ++it)
    {
    
    
        (*it)->Draw();
    }
}

void DeleteAllShapes(const vector<Shape*>& v)
{
    
    
    vector<Shape*>::const_iterator it;
    for (it = v.begin(); it != v.end(); ++it)
    {
    
    
        delete (*it);
    }
}

class ShapeFactory
{
    
    
public:
    static Shape* CreateShape(const string& name)
    {
    
    
        Shape* ps = 0;
        if (name == "Circle")
        {
    
    
            ps = new Circle;
        }
        else if (name == "Square")
        {
    
    
            ps = new Square;
        }
        else if (name == "Rectangle")
        {
    
    
            ps = new Rectangle;
        }
        return ps;
    }
};
int main()
{
    
    
    //Shape s; ERROR,不能实例化抽象类
    vector<Shape*> v;
    Shape* ps;
    /*ps = new Circle;
    v.push_back(ps);
    ps = new Square;
    v.push_back(ps);
    ps = new Rectangle;
    v.push_back(ps);*/

    ps = ShapeFactory::CreateShape("Circle");
    v.push_back(ps);
    ps = ShapeFactory::CreateShape("Square");
    v.push_back(ps);
    ps = ShapeFactory::CreateShape("Rectangle");
    v.push_back(ps);

    DrawAllShapes(v);
    DeleteAllShapes(v);
    return 0;
}

おすすめ

転載: blog.csdn.net/weixin_43912621/article/details/131203755