ホームページ  >  記事  >  バックエンド開発  >  学ばなければなりません! C++ でポリモーフィック メカニズムを実装するための基本条件

学ばなければなりません! C++ でポリモーフィック メカニズムを実装するための基本条件

php是最好的语言
php是最好的语言オリジナル
2018-07-24 14:11:022157ブラウズ

C のポリモーフィック メカニズムを実装する方法。実行ポリモーフィズムを実装するメカニズムは、基本クラスの関数の前に virtual キーワードを追加し、派生クラスで関数を書き換えることです。これにより、ランタイムが実際の関数に従って呼び出されます。オブジェクトのタイプと対応する関数。

C ポリモーフィズムの実装と原理

C のポリモーフィズムは 1 つの文で要約できます。基本クラスの関数の前に virtual キーワードを追加し、次のコードを追加します。派生関数の前に仮想キーワード。この関数がクラスでオーバーライドされると、実行時にオブジェクトの実際の型に従って対応する関数が呼び出されます。オブジェクト型が派生クラスの場合は派生クラスの関数が呼び出され、オブジェクト型が基底クラスの場合は基底クラスの関数が呼び出されます。

1: で宣言された関数virtual キーワードは仮想関数と呼ばれ、この関数はクラスのメンバー関数である必要があります。

2: 仮想関数を持つクラスは、仮想テーブルと呼ばれる 1 次元の仮想関数テーブルを持ち、クラスのオブジェクトには、仮想テーブルの先頭を指す仮想ポインタがあります。仮想テーブルはクラスに対応し、仮想テーブルポインタはオブジェクトに対応する。

3: ポリモーフィズムは、インターフェイスの複数の実装であり、オブジェクト指向の中核であり、クラス ポリモーフィズムと関数ポリモーフィズムに分けられます。

4: ポリモーフィズムは、動的バインディングと組み合わせた仮想関数を使用して実装されます。

5: 純粋な仮想関数は、仮想関数に = 0 を加えたものです;

6: 要約 A class は、少なくとも 1 つの純粋仮想関数を含むクラスです。

純粋な仮想関数: virtual void fun()=0; つまり、抽象クラス!この関数はサブクラスに実装する必要があります。つまり、名前が最初にあり、内容はなく、内容は派生クラスに実装されます。

最初に例を見てみましょう

#include <iostream> #include <stdlib.h>using namespace std; 

class Father
{public:    void Face()
    {        cout << "Father&#39;s face" << endl;
    }    void Say()
    {        cout << "Father say hello" << endl;
    }
};class Son:public Father
{public:     
    void Say()
    {        cout << "Son say hello" << endl;
    }
};int main()
{
    Son son;
    Father *pFather=&son; // 隐式类型转换
    pFather->Say();    return 0;
}

出力結果は次のとおりです:

Father say hello

まず main() 関数で Son クラスのオブジェクト Son を定義します。ファーザークラスを指すポインタ変数 pFather を定義し、この変数を使用して pFather->Say() を呼び出します。多くの人は、son が実際にはオブジェクトであると考えて、この状況を c の多態性と混同する傾向があると推定されますSon クラスの Say を呼び出し、「Son Say hello」と出力されますが、結果はそうではありません。

コンパイルの観点から:

When the cコンパイラはコンパイル中に、オブジェクトによって呼び出される関数 (非仮想関数) のアドレスをそれぞれ決定する必要があります。これは早期バインディングと呼ばれます。Son クラスのオブジェクトの息子のアドレスを pFather に割り当てると、C コンパイラは次のことを実行します。このとき、Cコンパイラは変数pFatherを考慮します 保存されるのはFatherオブジェクトのアドレスです main関数でpFather->Say()を実行すると呼び出されるのは当然のことながらSay関数ですFather オブジェクト

# メモリの観点から

学ばなければなりません! C++ でポリモーフィック メカニズムを実装するための基本条件

#Son クラス オブジェクトのメモリ モデルは上に示したとおりです

オブジェクトを構築するときSon クラスの場合、最初に Father クラスのコンストラクターを呼び出して Father クラスのオブジェクトを構築し、次に Son クラスのコンストラクターを呼び出す必要があります。この関数は独自の部分の構築を完了し、完全な Son クラス オブジェクトを分割します。 Son クラスのオブジェクトを Father 型に変換すると、そのオブジェクトは元のオブジェクトのメモリ モデル全体の上半分、つまり上図の「Father オブジェクトが占有するメモリ」とみなされます。型変換を使用する オブジェクト ポインタがそのメソッドを呼び出すときは、当然、それが配置されているメモリ内のメソッドを呼び出すため、「Father Say hello」と出力するのが論理的です。

多くの人が考えているように、上記のコードでは、pFather が実際には Son クラスのオブジェクトを指していることがわかっており、出力結果が Son クラスの Say メソッドであることを期待しています。この結果を得るには、仮想関数を使用する必要があります。

上記の出力結果は、コンパイラがコンパイル時にオブジェクトによって呼び出される関数のアドレスをすでに決定しているためです。この問題を解決するには、遅延バインディングを使用する必要があります。コンパイラが遅延バインディングを使用すると、オブジェクトのタイプと実行時の正しい呼び出し関数。コンパイラに遅延バインディングを採用させるには、基本クラスで関数を宣言するときに virtual キーワードを使用する必要があります。関数が定義されたら、そのような関数を virtual Function と呼びます。基本クラスで virtual として宣言された場合、関数はすべての派生クラスで virtual となり、明示的に virtual として宣言する必要はありません。

コードを少し変更して、実行結果を確認してください。

#include <iostream> #include <stdlib.h>using namespace std; 

class Father
{public:    void Face()
    {        cout << "Father&#39;s face" << endl;
    }    virtual void Say()
    {        cout << "Father say hello" << endl;
    }
};class Son:public Father
{public:     
    void Say()
    {        cout << "Son say hello" << endl;
    }
};int main()
{
    Son son;
    Father *pFather=&son; // 隐式类型转换
    pFather->Say();    return 0;
}

実行結果:

Son say hello

結果は「Son Say hello」であることがわかりました。これは、次のことを意味します。正しい呼び出しは object. 関数の型に基づいて行われ、次に Say() を virtual として宣言すると舞台裏で何が起こるかということです。

コンパイラはコンパイル中に、ファーザー クラスに仮想関数があることを検出します。このとき、コンパイラは、仮想関数を含む各クラスの仮想テーブル (vtable) を作成します。このテーブルは 1 次元配列です。この配列に各仮想関数のアドレスを格納します。

那么如何定位虚表呢?编译器另外还为每个对象提供了一个虚表指针(即vptr),这个指针指向了对象所属类的虚表,在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向了所属类的虚表,从而在调用虚函数的时候,能够找到正确的函数,对于第二段代码程序,由于pFather实际指向的对象类型是Son,因此vptr指向的Son类的vtable,当调用pFather->Son()时,根据虚表中的函数地址找到的就是Son类的Say()函数.

  正是由于每个对象调用的虚函数都是通过虚表指针来索引的,也就决定了虚表指针的正确初始化是非常重要的,换句话说,在虚表指针没有正确初始化之前,我们不能够去调用虚函数,那么虚表指针是在什么时候,或者什么地方初始化呢?

  答案是在构造函数中进行虚表的创建和虚表指针的初始化,在构造子类对象时,要先调用父类的构造函数,此时编译器只“看到了”父类,并不知道后面是否还有继承者,它初始化父类对象的虚表指针,该虚表指针指向父类的虚表,当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。

  总结(基类有虚函数的):

  1:每一个类都有虚表

  2:虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现,如果基类有3个虚函数,那么基类的虚表中就有三项(虚函数地址),派生类也会虚表,至少有三项,如果重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现,如果派生类有自己的虚函数,那么虚表中就会添加该项。

  3:派生类的虚表中虚地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。

  这就是c++中的多态性,当c++编译器在编译的时候,发现Father类的Say()函数是虚函数,这个时候c++就会采用晚绑定技术,也就是编译时并不确定具体调用的函数,而是在运行时,依据对象的类型来确认调用的是哪一个函数,这种能力就叫做c++的多态性,我们没有在Say()函数前加virtual关键字时,c++编译器就确定了哪个函数被调用,这叫做早期绑定。

  c++的多态性就是通过晚绑定技术来实现的。

  c++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数,如果对象类型是派生类,就调用派生类的函数,如果对象类型是基类,就调用基类的函数。

  虚函数是在基类中定义的,目的是不确定它的派生类的具体行为,例如:

  定义一个基类:class Animal //动物,它的函数为breathe()

  再定义一个类class Fish //鱼。它的函数也为breathe()

  再定义一个类class Sheep //羊,它的函数也为breathe()

将Fish,Sheep定义成Animal的派生类,然而Fish与Sheep的breathe不一样,一个是在水中通过水来呼吸,一个是直接呼吸,所以基类不能确定该如何定义breathe,所以在基类中只定义了一个virtual breathe,它是一个空的虚函数,具体的函数在子类中分别定义,程序一般运行时,找到类,如果它有基类,再找到它的基类,最后运行的是基类中的函数,这时,它在基类中找到的是virtual标识的函数,它就会再回到子类中找同名函数,派生类也叫子类,基类也叫父类,这就是虚函数的产生,和类的多态性的体现。

  这里的多态性是指类的多态性。

  函数的多态性是指一个函数被定义成多个不同参数的函数。当你调用这个函数时,就会调用不同的同名函数。

一般情况下(不涉及虚函数),当我们用一个指针/引用调用一个函数的时候,被调用的函数是取决于这个指针/引用的类型。

当设计到多态性的时候,采用了虚函数和动态绑定,此时的调用就不会在编译时候确定而是在运行时确定。不在单独考虑指针/引用的类型而是看指针/引用的对象的类型来判断函数的调用,根据对象中虚指针指向的虚表中的函数的地址来确定调用哪个函数

现在我们看一个体现c++多态性的例子,看看输出结果:

#include <iostream> #include <stdlib.h>using namespace std; 

class CA 
{ 
public: 
    void f() 
    { 
        cout << "CA f()" << endl; 
    } 
    virtual void ff() 
    { 
        cout << "CA ff()" << endl; 
        f(); 
    } 
}; 

class CB : public CA 
{ 
public : 
    virtual void f() 
    { 
        cout << "CB f()" << endl; 
    } 
    void ff() 
    { 
        cout << "CB ff()" << endl; 
        f(); 
        CA::ff(); 
    } 
}; 
class CC : public CB 
{ 
public: 
    virtual void f() 
    { 
        cout << "C f()" << endl; 
    } 
}; 

int main() 
{ 
    CB b; 
    CA *ap = &b; 
    CC c; 
    CB &br = c; 
    CB *bp = &c; 

    ap->f(); 
    cout << endl;

    b.f(); 
    cout << endl;

    br.f(); 
    cout << endl;

    bp->f(); 
    cout << endl;

    ap->ff(); 
    cout << endl;

    bp->ff(); 
    cout << endl;    return 0; 
}

输出结果:

CA f()CB f()C f()C f()CB ff()CB f()CA ff()CA f()CB ff()C f()CA ff()CA f()

 相关推荐:

【c#教程】C# 多态性       

C# 多态性

C ビデオ チュートリアル_無料 C チュートリアル オンライン学習

以上が学ばなければなりません! C++ でポリモーフィック メカニズムを実装するための基本条件の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。