多型は何ですか?仮想関数の原則の実現は何ですか?
多型多型は、静的および動的な多形に分割されています
- 静的ポリモーフィズム:コンパイル時に、メイン関数のオーバーロード、演算子のオーバーロード
- ダイナミックな多型:実行時に、仮想関数の形で主な成果起こる
多型「とは、一個のインターフェース、複数の方法」のようにまとめることができ、プログラムは実行時の関数呼び出しで決定し、多型は、コアオブジェクト指向プログラミングであります実装多型C ++仮想関数は、基底クラスの仮想関数で定義され、サブクラスが覆われて実現される機能を、再定義継承可能にすることができます。ポリモーフィズムの目的は、再利用をインタフェースすることです。換言すれば、クラスがオブジェクト上を通過さに関係なく、関数はインタフェースを介して、各オブジェクトのメソッドを実装するために呼び出すことができます。
最も一般的な用途は、オブジェクトは、バインディングの派生クラスへのポインタまたは参照であり、基本クラスへのポインタまたは参照を宣言することで、対応する仮想関数が向け派生クラスに応じて異なる方法で実施することができる呼び出します。
仮想関数の原則:クラス内の仮想関数が定義されている場合、このクラスの格納場所が仮想関数テーブルを指す複数のvfptrポインタ、仮想関数に格納された仮想関数テーブル内のオブジェクトをインスタンス化しますエントリアドレス
コンストラクタは、仮想関数に設定することができますか?なぜ?
コンストラクタは、仮想関数に設定することができない仮想関数は、多型のC ++オブジェクト指向プログラミング・セット、および動的起動条件多型に実装されています。
- 関数の関数定義をカバーしながら、ベースクラスの仮想関数、派生クラスで定義されています。
- 派生クラスのオブジェクトを作成します。
- 基本クラスへのポインタまたは参照を定義し、その派生クラスのオブジェクトに結合し、基底クラスによって定義され、対応する仮想関数呼び出しへのポインタまたは参照を有します
==ダイナミックな多型は、その基底クラスのコンストラクタを呼び出すと、場合でしょう、あなたが最初の派生クラスのオブジェクトを作成する必要があり、派生クラスのオブジェクトはプロセスで作成された仮想有理関数を使用するために、上記の活性化条件によって知っているかもしれません==基底クラスのコンストラクタは、仮想関数ですが、今回は彼らが競合状況に巻き込ま派生クラスのオブジェクトを、作成していないので、コンストラクタは、仮想関数として設定することはできませんが、また、いかなる意味のない問題
どのような状況下ではデストラクタは仮想関数に提供されますか?なぜ?
ときにもスペースおよび破壊のためのデストラクタの基底クラスの仮想関数を定義する基底クラスのデータ・メンバー・アプリケーション・ヒープ領域、。派生クラスは、ベースクラスを持っている場合、派生クラスはまた、ヒープに適用されます。私たちは、新しいクラスのスペースを導き出す場合次に、新しい表現は、派生クラスへのポインタは、基本クラスのポインタに割り当てられて返されます。私達はちょうど適用するために自由な表現派生クラスのスペースに削除使用した場合、我々はそれが導出されているように、クラスのポインタが基底クラスのポインタに代入され、派生クラスのデストラクタを呼び出すことはありません。基底クラスのデストラクタを呼び出します。 。この時点では、派生クラスのデストラクタを呼び出し中に、デストラクタ仮想関数を設定する必要がありますが、また、すべてのアプリケーションのリソースが解放された、基底クラスのデストラクタを呼び出します。コードは以下の通りであります:
#include <string.h>
#include <iostream>
using std::cout;
using std::endl;
class Base
{
public:
Base(const char * base)
: _base(new char[strlen(base) + 1]())
{
strcpy(_base, base);
cout << "Base(const char * base)" << endl;
}
virtual void print(void) const
{
cout << "base = " << _base << endl;
}
~Base()
{
if (_base)
delete [] _base;
cout << "~Base()" << endl;
}
private:
char * _base;
};
class Derived
: public Base
{
public:
Derived(const char * base, const char * derived)
: Base(base)
, _derived(new char[strlen(base) + 1]())
{
strcpy(_derived, derived);
cout << "Derived(const char * base, const char * derived)" << endl;
}
void print(void) const
{
cout << "derived = " << _derived << endl;
}
~Derived()
{
if (_derived)
delete [] _derived;
cout << "~Derived()" << endl;
}
private:
char * _derived;
};
int main(void)
{
Base * base = new Derived("hello", "world");
base->print();
delete base;
return 0;
}
業績
ベース(のconstのchar *ベース)
派生(のconst char型*ベース、constのcharは*派生)
派生=世界
〜ベース()
分析:ここでは、もはや分析されていない仮想関数呼び出しのために、派生クラスがカバーに基底クラスの仮想関数であり、その後、基底クラスの仮想関数から基底クラスのオブジェクトを呼び出して、クラスの仮想関数呼び出しを導出されます。削除ベース、ベースクラスのデストラクタが呼び出されるが、派生クラスのデストラクタが呼び出されていない、すなわち、スタックベースクラスリソース要求がされた場合、結果を実行して、ここで分析デストラクタ、それは私たちが望む結果ではありません、リリース、および派生クラスのリソースが解放されていないが、上記の理由が分析されています。この問題を解決するために、あなたはあなたが派生デストラクタを呼び出すときに、基底クラスのデストラクタが自動的に呼び出されます、と考えてください、私たちは、派生クラスのデストラクタを呼び出すために試すことができ、それの派生クラスのデストラクタを呼び出す方法?ここでは仮想関数の特性をカバーするために利用することができる、我々は仮想に、クラスのデストラクタをベースに次のように、この場合は、コードの変更、我々の目的を達成するために、削除ベースの間に、派生クラスのデストラクタと呼ばれます。
virtual
~Base()
{
if (_base)
delete [] _base;
cout << "~Base()" << endl;
}
結果:
ベース(のconstのchar *ベース)
派生(のconstのchar *ベース、constのchar型*派生)
派生=世界
〜派生()
〜ベース()
デストラクタは、次呼び出される場合は、仮想関数に基底クラスのデストラクタ、派生クラスのデストラクタが自動的に、でも仮想キーワードを追加することなく、仮想デストラクタになるだろうと、仮想関数呼び出しが行きますメカニズム
ケース場合、共通の基本クラスのメンバ関数内の仮想関数呼び出し
#include <iostream>
using std::cout;
using std::endl;
class Base
{
public:
Base() = default;
Base(int base_val)
: _base_val(base_val)
{
cout << "Base(int base_val)" << endl;
}
void func1(void)
{
//this->display();
display();
}
void func2(void)
{
Base::display();
}
virtual void display(void) const
{
cout << "base_val = " << _base_val << endl;
}
private:
int _base_val;
};
class Derived
: public Base
{
public:
Derived(int base_val, int derived_val)
: Base(base_val)
, _derived_val(derived_val)
{
cout << "Derived(int base_val, int derived_val)" << endl;
}
void display(void) const
{
cout << "derived_val = " << _derived_val << endl;
}
private:
int _derived_val;
};
int main(void)
{
Base base(12);
base.func1();
Derived derived(34, 56);
derived.func1();
cout << "-----------" << endl;
base.func2();
derived.func2();
return 0;
}
結果:
塩基(INT base_val)
base_val = 12
塩基(int型base_val)
由来(INT base_val、INT derived_val)
derived_val = 56
-----------
base_val = 12 base_val = 34
分析:基本クラスの関数関数func1とfunc2の2つの方法で仮想関数にアクセスするためながら上記のコードから明らかなように、ディスプレイ()関数定義の仮想関数、前記ベース基材(12); base.func1()。このコードはよく理解されているため、それだけでメンバー内のオブジェクトの仮想関数を訪問し、オブジェクトを作成し、メンバオブジェクトにアクセスすることが不可欠ですが、何の継承はありません、ので、通常の訪問の間に差がないからです。
Derived derived(34, 56);
derived.func1();
基底クラスは関数func1、関数func2、および仮想関数を継承し、このコードは、基本ため派生クラスは、派生クラスのサブクラスは、基本クラスをオーバーライドし、この時点で、仮想関数、同じ名前の関数を再定義するため、クラスから継承します仮想関数、それは再定義仮想関数を呼び出す関数func1時間のサブクラスです。
base.func2();
derived.func2();
関数func2の派生オブジェクトと呼ばれるが、オペレータによって内部機能の範囲は、次に、コールが直接基底クラスの仮想関数を呼び出す、仮想関数を呼び出して、それが起動していないが、上記のコードの結果を実行して派生クラスの仮想関数
ケース際に、基本クラスのコンストラクタとデストラクタ内で仮想関数呼び出し
#include <iostream>
using std::cout;
using std::endl;
class Grandpa
{
public:
Grandpa(int grandpa_val)
: _grandpa_val(grandpa_val)
{
cout << "Grandpa(int grandpa_val)" << endl;
}
virtual
void func1(void)
{
cout << "Grandpa: func1()" << endl;
}
virtual
void func2(void)
{
cout << "Grandpa: func2()" << endl;
}
~Grandpa()
{
cout << "~Grandpa()" << endl;
}
private:
int _grandpa_val;
};
class Son
: public Grandpa
{
public:
Son(int grandpa_val, int son_val)
: Grandpa(grandpa_val)
, _son_val(son_val)
{
cout << "Son(int grandpa_val, int son_val)" << endl;
func1();
}
#if 1
void func1(void)
{
cout << "Son: func1()" << endl;
}
void func2(void)
{
cout << "Son: func2()" << endl;
}
#endif
~Son()
{
cout << "~Son()" << endl;
func2();
}
private:
int _son_val;
};
class Grandson
: public Son
{
public:
Grandson(int grandpa_val, int son_val, int grandson_val)
: Son(grandpa_val, son_val)
, _grandson_val(grandpa_val)
{
cout << "Grandson(int grandpa_val, int son_val, int grandson_val)" << endl;
}
void func1(void)
{
cout << "Grandson: func1()" << endl;
}
void func2(void)
{
cout << "Grandson: func2()" << endl;
}
~Grandson()
{
cout << "~Grandson()" << endl;
}
private:
int _grandson_val;
};
int main(void)
{
Grandson test(1, 2, 3);
//cout << "-------------/\n";
Grandpa & tt = test;
cout << "-------------/\n";
tt.func1();
return 0;
}
業績
おじいちゃん(int型grandpa_val)
息子(int型grandpa_val、int型son_val)
息子:func1の()
孫(int型grandpa_val、int型son_val、int型grandson_val)
------------- /
孫:関数func1()
〜孫()
〜息子()
息子:関数func2()
〜おじいちゃん()
表面と動的バインディング区別は、言いませんでしたが、我々はコンストラクタやデストラクタ内部仮想関数を呼び出すコード及び業績で知られているが、仮想関数呼び出し内のこれら二つの機能、基本的に実際にコールそれ自体がクラス(仮想ではない)、または仮想関数機構からの呼び出しが、通常の通話機能に応じて機構(静的結合)が存在しないことの関数です。
プレゼンス確認のvtable
我々は、基底クラス、仮想関数が定義されている場合、派生クラスの仮想関数をカバーしながら、次に由来ストレージインスタンス派生クラスのオブジェクト・クラスが作成されることを知っているときに、仮想関数テーブルポインタに複数の点、以下のように、多型、ここでは検証のための仮想関数テーブルの存在の動的な性質のパフォーマンスを可能にします:
#include <iostream>
using std::cout;
using std::endl;
class Base
{
public:
Base(long base_val)
: _base_val(base_val)
{
cout << "Base(int base_val)" << endl;
}
virtual
void func1(void) { cout << "Base::func1()" << endl; }
virtual
void func2(void) { cout << "Base::func2()" << endl; }
virtual
void func3(void) { cout << "Base::func3()" << endl; }
private:
long _base_val;
};
class Derived
: public Base
{
public:
Derived(long base_val, long derived_val)
: Base(base_val)
, _derived_val(derived_val)
{
cout << "Derived(long base_val, long derived_val)" << endl;
}
virtual
void func1(void) { cout << "Derived::func1()" << endl; }
virtual
void func2(void) { cout << "Derived::func2()" << endl; }
virtual
void func3(void) { cout << "Derived::func3()" << endl; }
private:
long _derived_val;
};
int main(void)
{
Base base(10);
cout << "sizeof(Base) = " << sizeof(Base) << endl;
long * p = (long *)&base;
cout << p[0] << endl;
cout << p[1] << endl;
typedef void (*Function)(void);
long * p2 = (long *)p[0];
Function f = (Function)p2[0];
//Function f = (Function)p[0][0];
f();
f = (Function)p2[1];
f();
f = (Function)p2[2];
f();
return 0;
}
業績
塩基(INT base_val)
はsizeof(ベース)= 16
94272637283664
10
塩基::関数func1()
ベース::関数func2()
ベース:: FUNC3()
コンパイルと実行は、GCCバージョン7.4.0(Ubuntuの〜18.04.1 7.4.0-1ubuntu1)であるので、システムは64ビットであるので、順番に検証を容易にするように、ポインタのサイズは、8バイト(64ビット)であります、longデータ型は、格納する場所。我々は、スタックのベース内のオブジェクトのサイズが16バイト、8バイトのデータ長型、8バイトであり、ポインタが仮想関数テーブルへのポインタであるvfptr、これは偽の検証であることを知っています本当に関数テーブルがあります。ポインタのサイズであるため、オブジェクトベースは、我々はベースオブジェクトに割り当てられた初期値を開始[1]データ、P [0]が仮想関数テーブルへのポインタであるPを参照して、long型にキャストアドレスをフェッチ8つのバイトは、私たちは再びP [0]長い*キャストタイプは、我々はプログラムから以来、P [0] 3つのポインタがありますが、つまり、データP2、P2を外を見ることができますデータは、8バイトのポインタですが、あなたは仮想関数を呼び出すことができるように、アドレスデータを認識しないコンパイラは、データには、我々は、関数ポインタをキャスト。オハイオ州の存在を確認するために、仮想関数テーブル
純粋仮想関数とは何ですか?抽象クラスとは何ですか?抽象クラスの役割は何ですか?
- 基底クラスの仮想関数に純粋仮想関数を指すが定義され、しかし= 0の後ろに追加したとき純粋仮想関数が宣言されていない。、次いで基底クラスの仮想関数は、純粋仮想関数であることができます
- 純粋仮想関数が基本クラスで定義されている場合、基底クラスは抽象基底クラス、抽象基底クラスだけ外側にインターフェースであり、グループ派生クラスのオブジェクトをインスタンス化することができるオブジェクトをインスタンス化しないが、前提は、これらのことです派生クラスは、ここで私たちは抽象基底クラスざっと目を通して、これらの純粋仮想関数をカバーする機能を定義するインタフェースの実装は、派生クラスの基底クラスで行われ、インタフェース定義を提供するための唯一の責任があるインターフェイスを実装するための責任を負いません。コードは以下の通りであります:
#include <math.h>
#include <iostream>
using std::cout;
using std::endl;
class Figure //定义一个抽象基类
{
public:
virtual void display(void) const = 0; //定义纯虚函数
virtual double area(void) const = 0;
};
void display(Figure & figure)
{
figure.display();
cout << ", the area is " << figure.area() << endl;
}
class Circle
: public Figure
{
public:
Circle(double radius)
: _radius(radius)
{}
void display(void) const
{
cout << "I am Circle";
}
double area(void) const
{
return 3.14 * _radius * _radius;
}
private:
double _radius;
};
class Rectangle
: public Figure
{
public:
Rectangle(double length, double width)
: _length(length)
, _width(width)
{}
void display(void) const
{
cout << "I am Rectangle";
}
double area(void) const
{
return _length * _width;
}
private:
double _length;
double _width;
};
class Triangle
: public Figure
{
public:
Triangle(double a, double b, double c)
: _a(a)
, _b(b)
, _c(c)
{}
void display(void) const
{
cout << "I am Triangle";
}
double area(void) const
{
double p = (_a + _b + _c)/2;
return sqrt(p * (p - _a) * (p - _b) * (p - _c));
}
private:
double _a;
double _b;
double _c;
};
int main(void)
{
Circle circle(10);
Rectangle rec(10, 12);
Triangle triangle(3, 4, 5);
display(circle);
display(rec);
display(triangle);
return 0;
}
結果:
私はサークル午前、面積が314である
Iが長方形午前、面積が120である
I三角形午前、面積は6
分析:だけ外側に設けられた様々なインタフェースを定義し、図抽象基底クラスは、呼び出しで、我々が見ることができる主な機能を完了するために、基本クラスの派生クラスによって達成されるように上記のコードから、知ることができます機能が、結果は同じではありませんでした。(元のコードを変更せずに)閉じる修正、拡張するオープン:この原則は、抽象基本クラスのフォローです。
もう一つ:限り純粋仮想関数を持つ基本クラスが定義されているとして、それは限り達成するための純粋仮想関数がないとして、基底クラスで実装されるすべての純粋仮想関数のために、派生クラスに与えられなければならない、ということに留意すべきで、このための抽象基底クラス、派生クラスは、オブジェクトを作成することはできません。
上記で定義された抽象クラスを達成するための純粋仮想関数、抽象、別のクラスが存在しているように、基本クラスのコンストラクタが保護、このときに設定されている基本クラスは、ベースクラスを呼び出すように構成されたオブジェクトをインスタンス化していません関数は、サブクラスの基底クラスそこを通って、サブクラスは、以下のように基底クラスのメンバは、オブジェクトを介してアクセスすることができる呼び出すためにインスタンス化されます。
#include <iostream>
using std::cout;
using std::endl;
class Base
{
public:
virtual void print(void)
{
cout << "I am Base_class" << endl;
cout << "base_val = " << _base_val << endl;
}
protected: //把基类的构造函数定义为protected性质,这时外部非派生类将不能对该类进行实例化,只能通过该类的派生来调用该基类的构造函数
Base(int base_val)
: _base_val(base_val)
{
cout << "Base(int base_val)" << endl;
}
private:
int _base_val;
};
class Inherit_cls
: public Base
{
public:
Inherit_cls(int base, int inherit_val) //通过派生类中的构造函数来调用基类中的构造函数
: Base(base)
, _inherit_val(inherit_val)
{
cout << "Inherit_cls(int base, int inherit_val) " << endl;
}
void print(void)
{
cout << "I am Inherit_cls" << endl;
cout << "inherit_val = " << _inherit_val << endl;
}
private:
int _inherit_val;
};
int main(void)
{
Inherit_cls test(12, 34);
Base & base = test;
base.print();
return 0;
}
分析:上記のコードは動的多型C ++言語であるが、基底クラスのコンストラクタが保護提供され、この場合は、基本クラスによってベース・クラス・オブジェクトを作成しないで、ベースクラスのみ派生クラスによって作成することができますオブジェクトは、抽象クラスのためのはるかに弱いと比較して、機能の一種という点で、このような抽象化の前に、純粋仮想関数を達成するために使用する必要があります。
何が過負荷になっていますか?隠しは何ですか?何が覆われていますか?両者の差が前ということでしょうか?
- 過負荷:同じクラスのオーバーロード、および関数名限り通常の関数であってもよいが、関数パラメータの同じ数、パラメータの型、戻り値が異なるが、リロードを達成することができます
- 隠す:関数functionが基本クラスに存在する場合、サブクラスは、関数機能を持って、この時間は基底クラスの関数機能に非表示になり、あなたがベースを呼び出したい、クラスの継承における親子関係の機能を指し同じ名前を隠します唯一のスコープ修飾子を追加する関数functionクラス、
- カバーは、ベースクラスは、派生クラスは、関数パラメータリスト(非仮想変更がなくてもよい)と、戻り値と、基本クラスで同じ名前を定義している間に、ベースクラスは、仮想関数を定義している場合、仮想関数を指し、この場合、派生クラスのオブジェクトは、派生クラスの仮想関数は、オーバーベースクラス仮想関数から継承された上書きされ、インスタンス化
多重継承、仮想関数の状況
#include <iostream>
using std::cout;
using std::endl;
class A
{
public:
virtual
void f(void) { cout << "A::f()" << endl; }
virtual
void g(void) { cout << "A::g()" << endl; }
virtual
void h(void) { cout << "A::h()" << endl; }
};
class B
{
public:
virtual
void f(void) { cout << "B::f()" << endl; }
virtual
void g(void) { cout << "B::g()" << endl; }
void h(void) { cout << "B::h()" << endl; }
void j(void) { cout << "B::j()" << endl; }
};
class C
: public A
, public B
{
public:
virtual
void f(void) { cout << "C::f()" << endl; }
void h(void) { cout << "C::g()" << endl; }
void j(void) { cout << "C::j()" << endl; }
};
int main(void)
{
C c;
cout << "sizeof(A) = " << sizeof(A) << endl;
A & a = c;
a.f();
a.g();
a.h();
cout << "-----------------" << endl;
B & b = c;
b.f();
b.g();
b.h();
b.j();
c.f();
//c.g();
c.h();
c.j();
return 0;
}
業績
sizeof(A)= 8
C :: F()
A :: G()
C :: G()
/ -----------------
C :: F()
B: :G()
B :: H()
B :: J()
/ ---------------
C :: F()
C :: G()
C :: J()
多重継承仮想関数がAからC多重継承、B二つのクラスを意味する上記のコードから明らか; C同時にまた、仮想関数テーブルBを継承し、Cはクラスで定義されたカバーA、Bの場合仮想関数は、仮想テーブルは、このように多型動的特性を示し、仮想関数、元のベースクラスの仮想関数カバレッジ派生クラスの仮想関数を変更します。このような場合のために、仮想関数の様々な種類が一目で、描かれて継承します。