継承の定義
継承メカニズムは、コードを再利用可能にするためのオブジェクト指向プログラミングの最も重要な手段です。これにより、プログラマーは、元のクラスの特性を維持することに基づいて関数を拡張および追加し、派生クラスと呼ばれる新しいクラスを生成できます。
継承はの関係であり、サブクラス(派生クラス)は親クラス(基本クラス)を継承し、サブクラスは親クラスと同じプロパティを持ちます。
所以子类可以使用父类的方法,父类的成员变量也拷贝到了子类!
フォーマットを定義する
派生类 继承方式 基类
クラスサブクラス:public parentclass {}
継承
パーミッション:public> protected> private
親クラスのパーミッションと継承メソッドのパーミッションは、サブクラスのメンバーパーミッションである最小限のパーミッションを取ります。
パブリック
は保護されたクラスにアクセスでき、アクセスできませんが、サブクラス
はプライベートクラスにアクセスでき、アクセスできず、サブクラスはそれを見ることができません。
非表示:
親クラスの「メンバー」は引き続きサブクラスにコピーされ、サブクラスはそれにアクセスできませんが、サブクラスにメモリがあります
基本クラスと派生クラスのオブジェクト代入変換
この部分は継承の最も重要な部分です。この部分を完全に理解していないと、継承に問題があり、ポリモーフィズムが混乱し
ているはずです。個人的な理解を説明させてください。問題がある場合は、修正してください。
1.基本クラスを派生クラスに割り当てることはできません
2.派生クラスオブジェクトを基本クラスのオブジェクト/ポインタ/参照に割り当てることができます。スライスまたはカットと呼ばれる画像があります
1:
基本クラスに含まれるメンバーは、派生クラス以下である必要があります。これは、クラスとオブジェクトのthisポインターの割り当てに似ています。権限の削減のみが許可され、権限は次のようになります。拡張することはできません。
2:
①派生クラスは基本クラスのオブジェクトを割り当てます
#include<iostream>
using namespace std;
class A
{
public:
void show()//切片
{
cout << _age << endl;
}
protected:
int _age=10;
};
class B :public A
{
public:
int _num;
};
int main()
{
B b;//派生类
A a=b;//基类
}
派生クラスはAを継承し
、派生クラスが一意のメンバー_numを使用して基本クラスに割り当てられると、切り捨て(スライス)が発生することがわかります。
aは特定のクラスであり、親クラスオブジェクト用に別のスペースを開き(これはポリモーフィズムの原則で使用されます)、派生クラスの親クラスのメンバーをコピーします。仮想関数ポインターはコピーされません。 aは、Aに固有の仮想関数ポインタを持つ具象クラスです。
②派生クラスから基本クラスに割り当てられたポインタ/参照
この場合、ポインタは、親クラスに共通の派生クラスの部分を指します。
このように:
ポインタと参照は親クラスのパブリック部分にのみアクセスでき、継承された親クラスのメンバーにのみアクセスできます
#include<iostream>
using namespace std;
class A
{
public:
//A:this *= B:this*;
void show()//切片
{
cout << _age << endl;
}
protected:
int _age=10;
};
class B :public A
{
public:
int _age = 20;
int _num;
};
int main()
{
//1
B b1;
b1.show();
//2
A* a1 = &b1;
a1->show();
//3
A& a2 = b1;
a2.show();
return 0;
}
b1、a1の内部構造
b1がインスタンス化された後、b1はshow関数を呼び出し、派生クラスは親クラスの関数を呼び出すことができます。このとき、親クラスのthisポインター=子クラスのthisポインターであり、スライス動作が発生します。このポインタには、親クラスからのみアクセスできます。ダウンメンバーは、サブクラスの一意のメンバーにはアクセスできません。
さらに、サブクラスとスーパークラスには固有のスコープがあります。サブクラスとスーパークラスには同じ名前のメンバーがあります。サブクラスのメンバーは、スーパークラスによる同じ名前のメンバーへの直接アクセスをブロックします。この状況は非表示と呼ばれ、再定義とも呼ばれます。
したがって、上の図から、b1には2つの_ageがあり、Aから継承されたものは10に等しく、再定義されたものは20に等しいため、親クラスから継承された10が出力されます。
同様に、2と3もスライスが発生し、関数を呼び出すためのポインターは、再定義された20ではなく、基本クラスから継承されたメンバーにのみアクセスできます。
演算結果:
次に、クラスBを変更すると、最初のステップで20が出力されます。
親クラスは10を継承するため、コンストラクターで、親クラスを変更して20を継承します。
ダイヤモンドの継承
単一継承と多重継承:
単一継承:サブクラスに直接の親クラスが1つしかない場合、継承関係は単一継承と呼ばれます。
多重継承:サブクラスに2つ以上の直接の親クラスがある場合、継承関係は多重継承と呼ばれます
。ダイヤモンド継承:ダイヤモンド継承は多重継承の特殊なケースです。
ダイヤモンドの継承に関する問題(ピット)
#include<iostream>
using namespace std;
class A
{
public:
int _a;
};
class B :public A
{
public:
int _b;
};
class C :public A
{
public:
int _c;
};
class D :public B, public C
{
int _d;
};
int main()
{
D d;
//d._a = 10; 二义性编译器不知道调用从B还是C中继承而来的_a
return 0;
}
dのクラスメンバーシップモデル:
DはBから_aを継承し、Cから_aを継承するため、データの冗長性が得られることがわかります
。d._aにアクセスすると、コンパイラはアクセスする_aを認識しないため、あいまいさが発生します。
次のように解決
すると、スコープを指定することで_aにアクセスできますが、あいまいさは解決できますが、冗長性は解決できません。クラスに同じメンバー変数のコピーが2つあるため、許可されていません。
この問題を解決するために
、仮想継承が提案されています(仮想関数とは関係ありません)
#include<iostream>
using namespace std;
class A
{
public:
int _a;
};
class B :virtual public A
{
public:
int _b;
};
class C :virtual public A
{
public:
int _c;
};
class D :public B, public C
{
int _d;
};
int main()
{
D d;
d._a = 20;
cout << d._a << endl;
d.B::_a = 10;
cout << d.B::_a << endl;
return 0;
}
プログラムがd._a=20を実行した後
、監視ウィンドウから:仮想継承後、dの_aが同時に割り当てられ、あいまいさがなく、冗長性がない
ことがわかります。これは1つだけに相当します。データの一部
C++コンパイラが仮想継承のあいまいさと冗長性をどのように解決するか
モニターウィンドウからは実際の状況を見ることができず、コンパイラーが処理されているので
、メモリーウィンドウから見ることができます。
①:仮想継承の場合ではありません。
通常のダイヤモンド継承には_aのアドレスが2つ
あります。②仮想継承の状況
_aは下部に配置されます
仮想継承では、2つの_aがアドレスを共有し、データの冗長性とあいまいさを解決します
ただし、B領域とC領域にはさらに2行のデータがあり、この行は正確に4バイトです。
ポインタを考えるのは簡単ですが、それは何を指しているのでしょうか?
研究に連れて行ってあげましょう
仮想ベーステーブル:
ポイントされる次の位置は20、12であることがわかります。これは、
BおよびCのポインター(仮想ベーステーブルポインター)の、それぞれ共通ベースクラス_aへのオフセットに対応します。
では、これの用途は何ですか?
D d;
B b=d;
C c=d;
このように、b、cはスライスされ、このオフセットは共通の_aを見つけるために必要です。
最初の行はすべてゼロです。これは、ポリモーフィック仮想テーブル用に予約されているオフセットです(ポリモーフィズムが再び導入されます)