記事ディレクトリ
ダイヤモンド継承: ダイヤモンド継承は、多重継承の特殊なケースです。
1. 継承の概念と定義
継承メカニズムは、コードを再利用可能にするオブジェクト指向プログラミングの最も重要な手段であり、プログラマが元の特性を維持したまま機能を拡張および追加し、派生クラスと呼ばれる新しいクラスを生成できるようにします。継承はオブジェクト指向プログラミングの階層構造を表し、単純なものから複雑なものまでの認知プロセスを反映します。継承はクラス設計レベルの再利用です。
//基类 person
class Person
{
public:
void Print()
{
cout<<"name:" <<_name<<endl;
cout<<"age:"<<_age<<endl;
}
protected:
string _name = "peter";
int _age;
};
class Student::Person
{
protectd:
int _stuid; //学号;
};
int main()
{
Student s;
s.print();
return 0;
}
1. 継承の定義
class Student::public Person
2. 継承関係とアクセス修飾子
継承方法はパブリック継承、保護継承、プライベート継承に分かれます。
アクセス修飾子: パブリック アクセス、保護されたアクセス、プライベート アクセス
クラスメンバー/継承メソッド | パブリック継承 | 保護された継承 | 私的継承 |
基本クラスのパブリックメンバー | 派生クラスのパブリック メンバー | 派生クラスの保護されたメンバー | 派生クラスのプライベート メンバー |
基本クラスの保護されたメンバー | 派生クラスの保護されたメンバー | 派生クラスの保護されたメンバー | 派生クラスのプライベート メンバー |
基本クラスのプライベートメンバー | 派生クラスでは表示されません | 派生クラスでは表示されません | 派生クラスでは表示されません |
要約:
- 基本クラスのプライベート メンバーは、継承方法に関係なく、派生クラスでは非表示になります。ここで非表示とは、基本クラスのプライベート メンバーは引き続き派生クラス オブジェクトに継承されますが、構文により派生クラス オブジェクトがその範囲内に制限されることを意味します。またはクラス外からはアクセスできません
- 基本クラスのプライベート メンバーには、派生クラスではアクセスできません。基本クラスのメンバーがクラス外から直接アクセスされることを望まないが、派生クラスでアクセスする必要がある場合、それは protected として定義されます。継承により protected メンバー修飾子が表示されることがわかります。
- キーワードclass を使用する場合、デフォルトの継承メソッドは private、struct を使用する場合、デフォルトの継承メソッドは Public になります。継承メソッドは明示的に記述することをお勧めします。
- 実際に最も一般的に使用される方法はパブリック継承です。
2. 基本クラスと派生クラスのオブジェクトの代入と変換
- 派生クラス オブジェクトは、基本クラス オブジェクト/基本クラス ポインター/基本クラス参照に割り当てることができます。これをスライスまたはカットと呼びます。これは、派生クラス内の親クラスの一部に値が割り当てられることを意味します。
- 基本クラスのオブジェクトを派生クラスのオブジェクトに割り当てることはできません
- 基本クラスのポインターまたは参照は、強制的な型変換によって派生クラスのポインターまたは参照に割り当てることができますが、安全なのは、基本クラスのポインターが派生クラスのオブジェクトを指している場合に限られます。基本クラスがポリモーフィック型の場合、RTTI の Dynamic_cast を使用してそれを識別し、安全な変換を実行できます。
3. 継承の範囲
- 継承システムでは、基底クラスと派生クラスは独立したスコープを持ちます。
- サブクラスと親クラスに同じ名前のメンバーが存在する場合、サブクラスのメンバーは、親クラスが同じ名前のメンバーに直接アクセスできないようにブロックします。この状況は非表示と呼ばれ、再定義とも呼ばれます)。
- メンバー関数の非表示の場合、非表示を構成するには関数名が同じである必要があるだけです。
- 実際には、同じ名前のメンバーを使用しない方がよいでしょう。
4. 派生クラスのデフォルトのメンバー関数
デフォルトのメンバー関数はプログラマによって記述されるのではなく、コンパイラによって自動的に生成されます。6 つのデフォルトのメンバー関数は次のとおりです。
初期化とクリーンアップ: コンストラクターは初期化作業を完了し、デストラクターはクリーンアップ作業を完了します。
コピー割り当て: コピー構築では、同様のオブジェクトを使用してオブジェクトの初期化と作成を行います。割り当てオーバーロードは主に、あるオブジェクトを別のオブジェクトに割り当てます。
アドレスのオーバーロード: 主に通常のオブジェクトと const オブジェクトがアドレスを取得するためのものですが、自分で実装することはほとんどありません。
- 派生クラスのコンストラクターは、基本クラスのコンストラクターを呼び出して、基本クラスのメンバーを初期化する必要があります。基本クラスに既定のコンストラクターがない場合は、派生クラス コンストラクターの初期化リストの段階で明示的に呼び出す必要があります。
- 派生クラスのコピー コンストラクターは、基本クラスのコピーの初期化を完了するために、基本クラスのコピー コンストラクターを呼び出す必要があります。
- 派生クラスの演算子 = は、基本クラスの代入を完了するために、基本クラスの演算子 = を呼び出す必要があります。
- 派生クラスのデストラクターが、呼び出された後に基本クラスのメンバーをクリーンアップするために基本クラスのデストラクターを自動的に呼び出すのではないかと心配しています。派生クラスのオブジェクトが最初に派生クラスのメンバーをクリーンアップしてから、基本クラスをクリーンアップするようにしてください。メンバー。
- 派生クラス オブジェクトを初期化するときは、最初に基本クラスのコンストラクターを呼び出し、次に派生クラスのコンストラクターを呼び出します。
- 派生クラス オブジェクトを破棄する場合は、最初に派生クラスのデストラクターを呼び出してから、基本クラスのデストラクターを呼び出します。
- デストラクタの書き換えが必要ですが、関数名が同じであることが書き換えの条件となります。コンパイラはデストラクタ名に対して特殊な処理を行ってデストラクタとして扱うため、親クラスのデストラクタがvirtualを付加していない場合、子クラスのデストラクタと親クラスのデストラクタは隠蔽関係を形成します。
5. 相続と友情
フレンド関係は継承できません。つまり、基本クラスのフレンドは、サブクラスのプライベートおよび保護されたメンバーにアクセスできません。
6. 継承と静的メンバー
基本クラスで静的メンバーが定義されている場合、そのようなメンバーは継承システム全体で 1 つだけ存在し、サブクラスがいくつ派生しても、静的メンバーのインスタンスは 1 つだけ存在します。
class Person
{
public :
Person () {++ _count ;}
protected :
string _name ; // 姓名
public :
static int _count; // 统计人的个数。
};
int Person :: _count = 0;
class Student : public Person
{
protected :
int _stuNum ; // 学号
};
class Graduate : public Student
{
protected :
string _seminarCourse ; // 研究科目
};
void TestPerson()
{
Student s1 ;
Student s2 ;
Student s3 ;
Graduate s4 ;
cout <<" 人数 :"<< Person ::_count << endl; //4
}
7. 複雑なダイヤモンド継承とダイヤモンド仮想継承
単一継承: サブクラスには直接の親クラスが 1 つだけあり、この継承関係は単一継承と呼ばれます。
多重継承: サブクラスに複数の親クラスがあるこの継承関係を多重継承といいます。
ダイヤモンド継承: ダイヤモンド継承は、多重継承の特殊なケースです。
ダイヤモンド継承の問題: 以下のオブジェクト メンバー モデルの構築から、ダイヤモンド継承にはデータの冗長性とあいまいさの問題があることがわかります。アシスタント オブジェクトには person メンバー変数のコピーが 2 つあります。
class person
{
public:
string _name;
};
class Student:public person
{
protected:
int _num;
};
class Teacher:public person
{
protectd:
int _id;
};
class Assisstant:public Stduent,public Teacher
{
protected:
string _majorCourse;
};
void Test()
{
a._name = "peer" ; //数据的二义性,不知是student还是teacher的_name
//解决数据的二义性
a.Student::_name = "p1";
a._Teacher::_name = "p2";
//虽然解决了数据的二义性,使用作用域限定符,但是这样带来了数据冗余的问题,数据冗余的本质是空间浪费
}
class A
{
public:
int _a;
};
// class B : public A
class B : public A
{
public:
int _b;
};
// class C : public A
class C : public A
{
public:
int _c;
};
class D : public B, public C
{
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
メモリ ウィンドウを使用して次のことを確認します。
ダイヤモンド継承の問題を解決する: 仮想継承
仮想継承はデータの冗長性と曖昧性の問題を解決するもので、ポインタを使用してアドレスを格納し、オフセットはこのアドレスに格納されます。
class A
{
public:
int _a;
};
// class B : public A
class B : virtual public A
{
public:
int _b;
};
// class C : public A
class C : virtual public A
{
public:
int _c;
};
class D : public B, public C
{
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
Dオブジェクト では、 A が オブジェクト構成の 一番下。この A は BとC の両方に属します。 では、BとCはどのようにして共通のAを見つけるのでしょうか? ここに、 2つのポインターBとCが指すテーブルがあります。この 2 つのポインタを仮想実表ポインタと呼び、これら 2 つのテーブルを仮想実表と呼びます。仮想ベーステーブルに格納されているオフセット。オフセットにより以下のものが見つかります。
8. 継承と結合
1. 継承と合成
//A和B 继承
class A {};
class B:public A
{};
//C和D 组合
class C
{
public:
void fun()
{}
protected:
int _a1;
int _a2;
};
class D
{
private:
C _cc;
}
- パブリック継承は is-a 関係です。これは、各派生クラス オブジェクトが基本クラス オブジェクトであることを意味します。
- 組み合わせは has-a 関係です。B が A を組み合わせたとします。各 B オブジェクトには A オブジェクトがあります。
- クラスの継承よりもオブジェクトの合成を優先します。
- 継承の結合度は高くなります。B は A のメンバーを直接使用でき、D は C のメンバー関数を直接使用し、他の 2 つのメンバーを間接的に使用できます。A のメンバーが変更されると、B のすべてのメンバーが変更されます。C はメンバーを変更しますが、D は使用するメンバーのみを変更する必要があります。
9. 相続に関する筆記面接の質問
1. ダイヤモンド相続とは何ですか?また、ダイヤモンド相続の問題点は何ですか?
相続には単一継承と多重継承があり、ダイヤモンド継承は多重継承の一種であり、ダイヤモンド継承の問題はデータの曖昧さや冗長化につながります。
2. ダイヤモンド仮想継承とは何ですか?また、冗長性と曖昧性を解決する方法は何ですか?
ダイヤモンド仮想継承では、継承時に構文に仮想を追加します。仮想継承により、ポインタが追加されます。このポインタは、仮想ベース テーブルのアドレスです。アドレスを通じてオフセットを見つけて、パブリック メンバを見つけます。
3. 継承と結合の違い、継承を使用する場合と結合を使用する場合